From c7bdd600eab62607ac26e53cfabcd3785db9c29e Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Tue, 10 May 2016 22:43:42 +1000 Subject: [PATCH 0001/1012] Add new item descriptions for DF 43.01 --- scripts/item-descriptions.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/item-descriptions.lua b/scripts/item-descriptions.lua index fe7e7ac00..b2c57d2e4 100644 --- a/scripts/item-descriptions.lua +++ b/scripts/item-descriptions.lua @@ -360,8 +360,12 @@ descriptions = { ITEM_TOOL_FORK_CARVING = { "A carving fork typically has only two prongs and is exceptionally long.", "It is used to hold down a piece of cooked meat while using a knife."}, + ITEM_TOOL_HELVE = { + "A helve is the handle of a tool such as an axe.", + "It is not useful in this state - but adding a rock makes a stone axe,", + "which can be used for woodcutting in Adventure mode."}, ITEM_TOOL_HIVE = { - " Hives are structures that house colonies of honey bees. To be", + "Hives are structures that house colonies of honey bees. To be", "productive, they need to be constructed on an above-ground tile with", "an accessible honey bee colony somewhere on the map. Some time after", "bees are 'installed' by a beekeeper, the hive will be ready to harvest", @@ -417,6 +421,9 @@ descriptions = { ITEM_TOOL_STEPLADDER = { "A small stepladder. If you have one of these, you can use zones to", "assign your dwarves to pick fruit from certain trees."}, + ITEM_TOOL_STONE_AXE = { + "This tool can be made in Adventure mode, and is used to cut", + "trees for building or carpentry."}, ITEM_TOOL_WHEELBARROW = { "A small hand-cart with long handles and a single wheel, this", "wheelbarrow makes heavy hauling jobs much more manageable."}, From a6c10307b0debc1e3efd9a389b17504805027de6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 14 May 2016 19:39:10 -0400 Subject: [PATCH 0002/1012] Reset terminal color on exit --- package/darwin/dfhack | 1 + package/linux/dfhack | 1 + 2 files changed, 2 insertions(+) diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 5a04bd591..66b574cf8 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -14,4 +14,5 @@ fi old_tty_settings=$(stty -g) DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" stty "$old_tty_settings" +tput sgr0 echo "" diff --git a/package/linux/dfhack b/package/linux/dfhack index 2b2d04465..b7ce1d812 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -95,6 +95,7 @@ esac # Restore previous terminal settings stty "$old_tty_settings" +tput sgr0 echo if [ -n "$DF_POST_CMD" ]; then From 1832e563b746632dfcb1a8c5a7f8058003ba902b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 14 May 2016 19:41:41 -0400 Subject: [PATCH 0003/1012] Update xml --- docs/Authors.rst | 1 + library/xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index 29521320f..c4462a987 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -33,6 +33,7 @@ Erik Youngren Artanis Espen Wiborg expwnent expwnent Feng +gchristopher gchristopher Harlan Playford playfordh IndigoFenix James Logsdon jlogsdon diff --git a/library/xml b/library/xml index c2b1a540b..ab8d62fb7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c2b1a540b026555944f3ab60cef37dd6a4b2fe5c +Subproject commit ab8d62fb7fc89f5c100439475f7101b010b0bd95 From 3af24507a32e18d875a430807897e1675080438e Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 14 May 2016 20:15:49 -0500 Subject: [PATCH 0004/1012] adjust autogems to work with the workshop profile changes --- plugins/autogems.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index 9bdfcb02c..013038834 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -134,7 +134,7 @@ void create_jobs() { for (auto w = workshops->begin(); w != workshops->end(); ++w) { auto workshop = virtual_cast(*w); - auto links = workshop->links.take_from_pile; + auto links = workshop->profile.links.take_from_pile; if (workshop->construction_stage < 3) { // Construction in progress. From b7aa8aca260253854d986338c5b4de9ed5e8c313 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 14 May 2016 22:27:21 -0400 Subject: [PATCH 0005/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index ab8d62fb7..82e00df85 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ab8d62fb7fc89f5c100439475f7101b010b0bd95 +Subproject commit 82e00df85ec147a4a6263f93b03421d76eb24fe0 From 02e0b9e72197ffdf5949c594f24f31b05893db8a Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 14 May 2016 22:42:49 -0600 Subject: [PATCH 0006/1012] Manager interface now uses the full height. Its navigation behavior has also changed, and it now has key hints for navigation. --- plugins/lua/stockflow.lua | 89 +++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 7b038d34c..9bf254c67 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -24,9 +24,9 @@ entry_ints = { trigger_number = 3, } -PageSize = 16 -FirstRow = 4 +FirstRow = 3 CenterCol = 38 +ExtraLines = 9 -- Populate the reaction and stockpile order lists. -- To be called whenever a world is loaded. @@ -797,10 +797,26 @@ screen = gui.FramedScreen { function screen:onRenderBody(dc) -- Emulates the built-in manager screen. + + -- Top instruction line. dc:seek(1, 1):string("Type in parts of the name to narrow your search. ", COLOR_WHITE) dc:string(gui.getKeyDisplay("LEAVESCREEN"), COLOR_LIGHTGREEN) dc:string(" to abort.", COLOR_WHITE) - dc:seek(1, PageSize + 5):string(self.search_string, COLOR_LIGHTCYAN) + + -- Search term, if any. + dc:seek(1, FirstRow + self.page_size + 1):string(self.search_string, COLOR_LIGHTCYAN) + + -- Bottom instruction line. + dc:seek(1, FirstRow + self.page_size + 2) + dc:string(gui.getKeyDisplay("STANDARDSCROLL_UP"), COLOR_LIGHTGREEN) + dc:string(gui.getKeyDisplay("STANDARDSCROLL_DOWN"), COLOR_LIGHTGREEN) + dc:string(gui.getKeyDisplay("STANDARDSCROLL_PAGEUP"), COLOR_LIGHTGREEN) + dc:string(gui.getKeyDisplay("STANDARDSCROLL_PAGEDOWN"), COLOR_LIGHTGREEN) + dc:string(gui.getKeyDisplay("STANDARDSCROLL_LEFT"), COLOR_LIGHTGREEN) + dc:string(gui.getKeyDisplay("STANDARDSCROLL_RIGHT"), COLOR_LIGHTGREEN) + dc:string(": Select", COLOR_WHITE) + + -- Reaction lines. for _, item in ipairs(self.displayed) do dc:seek(item.x, item.y):string(item.name, item.color) end @@ -820,23 +836,61 @@ function screen:onInput(keys) elseif keys.STANDARDSCROLL_DOWN then self.position = self.position + 1 elseif keys.STANDARDSCROLL_LEFT then - self.position = self.position - PageSize + if self.position == 1 then + -- Move from the very first item to the very last item. + self.position = #self.reactions + elseif self.position < self.page_size then + -- On the first column, move to the very first item. + self.position = 1 + else + -- Move to the same position on the previous column. + self.position = self.position - self.page_size + end elseif keys.STANDARDSCROLL_RIGHT then - self.position = self.position + PageSize + if self.position == #self.reactions then + -- Move from the very last item to the very first item. + self.position = 1 + else + -- Move to the same position on the next column. + self.position = self.position + self.page_size + if self.position > #self.reactions then + -- If that's past the end, move to the very last item. + self.position = #self.reactions + end + end elseif keys.STANDARDSCROLL_PAGEUP then - -- Moves to the first item displayed on the new page, for some reason. - self.position = self.position - PageSize*2 - ((self.position-1) % (PageSize*2)) + if self.position == 1 then + -- Move from the very first item to the very last item. + self.position = #self.reactions + elseif self.position < self.page_size*2 then + -- On the first page, move to the very first item. + self.position = 1 + else + -- Move to the same position on the previous page. + self.position = self.position - self.page_size*2 + end elseif keys.STANDARDSCROLL_PAGEDOWN then - -- Moves to the first item displayed on the new page, for some reason. - self.position = self.position + PageSize*2 - ((self.position-1) % (PageSize*2)) + if self.position == #self.reactions then + -- Move from the very last item to the very first item. + self.position = 1 + else + -- Move to the same position on the next page. + self.position = self.position + self.page_size*2 + if self.position > #self.reactions then + -- If that's past the end, move to the very last item. + self.position = #self.reactions + end + end elseif keys.STRING_A000 then -- This seems like an odd way to check for Backspace. self.search_string = string.sub(self.search_string, 1, -2) + self.position = 1 elseif keys._STRING and keys._STRING >= 32 then -- This interface only accepts letters and spaces. local char = string.char(keys._STRING) if char == " " or string.find(char, "^%a") then self.search_string = self.search_string .. string.upper(char) + self.position = 1 end end @@ -886,6 +940,13 @@ function splitstring(full, pattern) end function screen:refilter() + -- Determine which rows to show, and in which colors. + -- Todo: The official one now has three categories of search results: + -- * Cyan: Contains at least one exact word from the search terms + -- * Yellow: At least one word starts with at least one search term + -- * Grey: Each search term is found in the middle of a word + self.page_size = self.frame_rect.height - ExtraLines + local filtered = {} local needles = splitstring(self.search_string, " ") for key, value in ipairs(reaction_list) do @@ -904,12 +965,12 @@ function screen:refilter() end local start = 1 - while self.position >= start + PageSize*2 do - start = start + PageSize*2 + while self.position >= start + self.page_size*2 do + start = start + self.page_size*2 end local displayed = {} - for n = 0, PageSize*2 - 1 do + for n = 0, self.page_size*2 - 1 do local item = filtered[start + n] if not item then break @@ -918,9 +979,9 @@ function screen:refilter() local x = 1 local y = FirstRow + n - if n >= PageSize then + if n >= self.page_size then x = CenterCol - y = y - PageSize + y = y - self.page_size name = " "..name end From 8c7d5366c95edc398ef597a080843e1536f7a11c Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 14 May 2016 23:33:38 -0600 Subject: [PATCH 0007/1012] Switching to the new manager order flags --- plugins/lua/stockflow.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 9bf254c67..3422425ad 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1087,7 +1087,8 @@ function create_orders(order, amount) new_order.amount_left = amount new_order.amount_total = amount -- Todo: Create in a validated state if the fortress is small enough? - new_order.is_validated = 0 + new_order.status.validated = false + new_order.status.active = true df.global.world.manager_orders:insert('#', new_order) end From 3e8d4f925753e35465bb6b3076ddd03ff3ca8d67 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 14 May 2016 23:35:07 -0600 Subject: [PATCH 0008/1012] New command for clearing the stockpile order. Shift-Space by default, using the SETUPGAME_SAVE_PROFILE_ABORT key. --- plugins/lua/stockflow.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 3422425ad..1590bf4c4 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -816,6 +816,10 @@ function screen:onRenderBody(dc) dc:string(gui.getKeyDisplay("STANDARDSCROLL_RIGHT"), COLOR_LIGHTGREEN) dc:string(": Select", COLOR_WHITE) + dc:seek(CenterCol, FirstRow + self.page_size + 2) + dc:string(gui.getKeyDisplay("SETUPGAME_SAVE_PROFILE_ABORT"), COLOR_LIGHTGREEN) + dc:string(": No order", COLOR_WHITE) + -- Reaction lines. for _, item in ipairs(self.displayed) do dc:seek(item.x, item.y):string(item.name, item.color) @@ -831,6 +835,9 @@ function screen:onInput(keys) if selected then store_order(self.stockpile, selected.index) end + elseif keys.SETUPGAME_SAVE_PROFILE_ABORT then + self:dismiss() + clear_order(self.stockpile) elseif keys.STANDARDSCROLL_UP then self.position = self.position - 1 elseif keys.STANDARDSCROLL_DOWN then @@ -1002,6 +1009,14 @@ function screen:refilter() self.displayed = displayed end +function clear_order(stockpile) + local saved = saved_orders[stockpile.id] + if saved then + saved.entry:delete() + saved_orders[stockpile.id] = nil + end +end + function store_order(stockpile, order_number) local name = reaction_list[order_number].name -- print("Setting stockpile #"..stockpile.stockpile_number.." to "..name.." (#"..order_number..")") From 6575f5f4126a71552875a8b5725c6bcbc6949400 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 14 May 2016 23:40:01 -0600 Subject: [PATCH 0009/1012] Removing the quantity limit on stockflow orders. --- plugins/lua/stockflow.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 1590bf4c4..394d3f1b5 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1087,12 +1087,6 @@ function order_quantity(order, quantity) end end - if amount > 30 then - -- Respect the quantity limit. - -- With this many in the queue, we can wait for the next cycle. - return 30 - end - return amount end From b68cff0384faa936984206c34563be9a9daeb0f7 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 14 May 2016 23:44:25 -0600 Subject: [PATCH 0010/1012] Removing stockflow and workflow from the example dfhack.init They're no longer necessary, now that the default manager interface has been improved. --- dfhack.init-example | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index a367d3441..4d2a1b2f3 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -128,10 +128,6 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job -# workflow front-end -keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow -keybinding add Alt-W@overallstatus "gui/workflow status" - # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" @@ -220,7 +216,6 @@ enable \ zone \ stocks \ autochop \ - stockflow \ stockpiles #end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. # Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. From 6add87b3582bd80ef9d935ff3e0b62b1e22288b1 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 15 May 2016 08:58:45 -0600 Subject: [PATCH 0011/1012] Simplifying key strings --- plugins/lua/stockflow.lua | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 394d3f1b5..0728b8db8 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -800,25 +800,20 @@ function screen:onRenderBody(dc) -- Top instruction line. dc:seek(1, 1):string("Type in parts of the name to narrow your search. ", COLOR_WHITE) - dc:string(gui.getKeyDisplay("LEAVESCREEN"), COLOR_LIGHTGREEN) - dc:string(" to abort.", COLOR_WHITE) + dc:key("LEAVESCREEN"):string(" to abort.", COLOR_WHITE) -- Search term, if any. dc:seek(1, FirstRow + self.page_size + 1):string(self.search_string, COLOR_LIGHTCYAN) -- Bottom instruction line. dc:seek(1, FirstRow + self.page_size + 2) - dc:string(gui.getKeyDisplay("STANDARDSCROLL_UP"), COLOR_LIGHTGREEN) - dc:string(gui.getKeyDisplay("STANDARDSCROLL_DOWN"), COLOR_LIGHTGREEN) - dc:string(gui.getKeyDisplay("STANDARDSCROLL_PAGEUP"), COLOR_LIGHTGREEN) - dc:string(gui.getKeyDisplay("STANDARDSCROLL_PAGEDOWN"), COLOR_LIGHTGREEN) - dc:string(gui.getKeyDisplay("STANDARDSCROLL_LEFT"), COLOR_LIGHTGREEN) - dc:string(gui.getKeyDisplay("STANDARDSCROLL_RIGHT"), COLOR_LIGHTGREEN) + dc:key("STANDARDSCROLL_UP"):key("STANDARDSCROLL_DOWN") + dc:key("STANDARDSCROLL_PAGEUP"):key("STANDARDSCROLL_PAGEDOWN") + dc:key("STANDARDSCROLL_LEFT"):key("STANDARDSCROLL_RIGHT") dc:string(": Select", COLOR_WHITE) dc:seek(CenterCol, FirstRow + self.page_size + 2) - dc:string(gui.getKeyDisplay("SETUPGAME_SAVE_PROFILE_ABORT"), COLOR_LIGHTGREEN) - dc:string(": No order", COLOR_WHITE) + dc:key("SETUPGAME_SAVE_PROFILE_ABORT"):string(": No order", COLOR_WHITE) -- Reaction lines. for _, item in ipairs(self.displayed) do From 0b1004f2b8910589000af52663e2bb805cd1d28d Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 15 May 2016 09:14:30 -0600 Subject: [PATCH 0012/1012] Notice resizing immediately, instead of on next key press --- plugins/lua/stockflow.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 0728b8db8..b5fd186cc 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -798,6 +798,11 @@ screen = gui.FramedScreen { function screen:onRenderBody(dc) -- Emulates the built-in manager screen. + if not (self.page_size == self.frame_rect.height - ExtraLines) then + -- The screen size has changed. + self:refilter() + end + -- Top instruction line. dc:seek(1, 1):string("Type in parts of the name to narrow your search. ", COLOR_WHITE) dc:key("LEAVESCREEN"):string(" to abort.", COLOR_WHITE) From 2df41e6bade1ce956bc7310b9c438a3e4e13945b Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 15 May 2016 22:03:36 -0600 Subject: [PATCH 0013/1012] Clearing the active flag for new stockflow orders. It's not entirely clear what this flag does when there aren't any conditions on the order, but new manual orders start as Ready instead of Active. --- plugins/lua/stockflow.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index b5fd186cc..9a5a4f080 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1097,7 +1097,7 @@ function create_orders(order, amount) new_order.amount_total = amount -- Todo: Create in a validated state if the fortress is small enough? new_order.status.validated = false - new_order.status.active = true + new_order.status.active = false df.global.world.manager_orders:insert('#', new_order) end From 11a0bea00279b56add1a1a3b8bac351cff5cc083 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 15 May 2016 22:49:35 -0600 Subject: [PATCH 0014/1012] Moving a search option to the bottom of the screen. The list of workers in the workshop profile page can take nearly the full height of the screen. --- plugins/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 0b7556552..a177665ad 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1671,7 +1671,7 @@ public: void render() const { - print_search_option(2, 23); + print_search_option(2, gps->dimy - 5); } vector *get_primary_list() From 3f0857bff3eea450adce702973e850fc97e7c848 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 15 May 2016 22:47:43 -0600 Subject: [PATCH 0015/1012] Incrementing the manager order id properly. There's a chance that not doing so could lead to a crash, somehow. --- library/xml | 2 +- plugins/lua/stockflow.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 82e00df85..23edfbdfa 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 82e00df85ec147a4a6263f93b03421d76eb24fe0 +Subproject commit 23edfbdfadd5f5599c0ce4a5e06ee68e9472d30b diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 9a5a4f080..4d7889843 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1098,6 +1098,8 @@ function create_orders(order, amount) -- Todo: Create in a validated state if the fortress is small enough? new_order.status.validated = false new_order.status.active = false + new_order.id = df.global.world.manager_order_next_id + df.global.world.manager_order_next_id = df.global.world.manager_order_next_id + 1 df.global.world.manager_orders:insert('#', new_order) end From d198bf547a2f29fbee54bb4fd18fa030c973a0cc Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 17 May 2016 23:23:54 -0400 Subject: [PATCH 0016/1012] Unit list search: reset cursor position to 0 before switching pages On empty lists, pressing "up" sets the cursor position to -1. This results in a crash if the list is populated, which the search plugin does before switching pages. Fixes #928 --- plugins/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/search.cpp b/plugins/search.cpp index a177665ad..80dcc0f61 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1160,6 +1160,9 @@ private: if (!in_entry_mode()) { // Changing screens, reset search + int32_t *cursor_pos = get_viewscreen_cursor(); + if (cursor_pos && *cursor_pos < 0) + *cursor_pos = 0; clear_search(); reset_all(); } From d93cf2cd2e2933852be3121df13a1a1a08192493 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 17 May 2016 23:26:21 -0400 Subject: [PATCH 0017/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 82e00df85..af8548bb1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 82e00df85ec147a4a6263f93b03421d76eb24fe0 +Subproject commit af8548bb118851f3da2618ab6055066774614cb2 From 6c64b3441d6f340a46e1508f3058da1a24ccadbb Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 20 May 2016 13:55:59 +0300 Subject: [PATCH 0018/1012] Fixes to gui/advfort.lua --- library/lua/dfhack/workshops.lua | 21 +---- scripts/gui/advfort.lua | 156 +++++++++++++++++-------------- 2 files changed, 91 insertions(+), 86 deletions(-) diff --git a/library/lua/dfhack/workshops.lua b/library/lua/dfhack/workshops.lua index 9fbe4c08a..09268ff38 100644 --- a/library/lua/dfhack/workshops.lua +++ b/library/lua/dfhack/workshops.lua @@ -126,19 +126,6 @@ jobs_workshop={ job_fields={job_type=df.job_type.CatchLiveFish} }, -- no items? }, - [df.workshop_type.Still]={ - { - name="brew drink", - items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}}, - job_fields={job_type=df.job_type.BrewDrink} - }, - { - name="extract from plants", - items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}}, - job_fields={job_type=df.job_type.ExtractFromPlants} - }, - --mead from raws? - }, [df.workshop_type.Masons]={ defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true}, { @@ -336,20 +323,20 @@ jobs_workshop={ }, [df.workshop_type.Kitchen]={ --mat_type=2,3,4 - defaults={flags1={unrotten=true,cookable=true}}, + defaults={flags1={unrotten=true}}, { name="prepare easy meal", - items={{flags1={solid=true}},{}}, + items={{flags1={solid=true,cookable=true}},{flags1={cookable=true}}}, job_fields={job_type=df.job_type.PrepareMeal,mat_type=2} }, { name="prepare fine meal", - items={{flags1={solid=true}},{},{}}, + items={{flags1={solid=true,cookable=true}},{flags1={cookable=true}},{flags1={cookable=true}}}, job_fields={job_type=df.job_type.PrepareMeal,mat_type=3} }, { name="prepare lavish meal", - items={{flags1={solid=true}},{},{},{}}, + items={{flags1={solid=true,cookable=true}},{flags1={cookable=true}},{flags1={cookable=true}},{flags1={cookable=true}}}, job_fields={job_type=df.job_type.PrepareMeal,mat_type=4} }, }, diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 70d4bc083..0733e88a2 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -11,6 +11,9 @@ keybinding. (e.g. ``keybinding set Ctrl-T gui/advfort``). Possible arguments: :-a, --nodfassign: uses different method to assign items. :-i, --inventory: checks inventory for possible items to use in the job. :-c, --cheat: relaxes item requirements for buildings (e.g. walls from bones). Implies -a +:-u, --unsafe: ignores dangerous conditions. +:-s, --safe: only allow building and etc. only if in site +:-q, --quick: quick item select mode :job: selects that job (e.g. Dig or FellTree) An example of player digging in adventure mode: @@ -22,8 +25,12 @@ An example of player digging in adventure mode: =end]] --[==[ - version: 0.044 + version: 0.05 changelog: + *0.05 + - fixed some reactions not showing. Now there are duplicated reactions :/ + - fixed brewing accepting too many items instead of barrel + - fixed tallow making to accept fat *0.044 - added output to clear_jobs of number of cleared jobs - another failed attempt at gather plants fix @@ -663,7 +670,7 @@ function RemoveBuilding(args) end function isSuitableItem(job_item,item) - --todo butcher test + if job_item.item_type~=-1 then if item:getType()~= job_item.item_type then return false, "type" @@ -696,29 +703,31 @@ function isSuitableItem(job_item,item) return false,"already cooked" end - if type(job_item) ~= "table" and not matinfo:matches(job_item) then - --[[ - local true_flags={} - for k,v in pairs(job_item.flags1) do - if v then - table.insert(true_flags,k) + if job_item.has_material_reaction_product~="" then + local ok=false + for k,v in pairs(matinfo.material.reaction_product.id) do + if v.value==job_item.has_material_reaction_product then + ok=true + break end end - for k,v in pairs(job_item.flags2) do - if v then - table.insert(true_flags,k) - end + if not ok then + return false, "no material reaction product" end - for k,v in pairs(job_item.flags3) do - if v then - table.insert(true_flags,k) + end + if job_item.reaction_class~="" then + local ok=false + for k,v in pairs(matinfo.material.reaction_class) do + if v.value==job_item.reaction_class then + ok=true + break end end - for k,v in pairs(true_flags) do - print(v) + if not ok then + return false, "no material reaction class" end - --]] - + end + if type(job_item) ~= "table" and not matinfo:matches(job_item) then return false,"matinfo" end -- some bonus checks: @@ -732,6 +741,7 @@ function isSuitableItem(job_item,item) return false,"metal ore" end if job_item.min_dimension~=-1 then + end -- if #job_item.contains~=0 then -- end @@ -740,30 +750,35 @@ function isSuitableItem(job_item,item) return false,"tool use" end end - if job_item.has_material_reaction_product~="" then - local ok=false - for k,v in pairs(matinfo.material.reaction_product.id) do - if v.value==job_item.has_material_reaction_product then - ok=true - break - end - end - if not ok then - return false, "no material reaction product" + + if job_item.flags3.food_storage and not item:isFoodStorage() then + return false,"not food storage" + end + + if job_item.flags1.empty and dfhack.items.getGeneralRef(item,df.general_ref_type.CONTAINS_ITEM) then + return false,"not empty" + end +--[[ + local true_flags={} + for k,v in pairs(job_item.flags1) do + if v then + table.insert(true_flags,k.." f1") end end - if job_item.reaction_class~="" then - local ok=false - for k,v in pairs(matinfo.material.reaction_class) do - if v.value==job_item.reaction_class then - ok=true - break - end + for k,v in pairs(job_item.flags2) do + if v then + table.insert(true_flags,k.." f2") end - if not ok then - return false, "no material reaction class" + end + for k,v in pairs(job_item.flags3) do + if v then + table.insert(true_flags,k.." f3") end end + for k,v in pairs(true_flags) do + print(v) + end + --]] return true end function getItemsUncollected(job) @@ -1396,45 +1411,48 @@ function usetool:openShopWindowButtoned(building,no_reset) for k,v in pairs(wui.material_category) do wui.material_category[k]=false end - --]] - --[[building:fillSidebarMenu() - if #wui.choices_all>0 then - wui.choices_all[#wui.choices_all-1]:click() - end - --]] end building:fillSidebarMenu() - + local list={} + local names_already_in={} for id,choice in pairs(wui.choices_visible) do - table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice}) - end - if #list ==0 and not no_reset then - print("Fallback") - self:openShopWindow(building) - return - --qerror("No jobs for this workshop") + local label=string.lower(utils.call_with_string(choice,"getLabel")) + table.insert(list,{text=label,button=choice,is_button=true}) + names_already_in[label]=true end - dialog.showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,list,self:callback("onWorkShopButtonClicked",building) - ,nil, nil,true) -end -function usetool:openShopWindow(building) - local adv=df.global.world.units.active[0] - + --add fallback list if for some reason df does not make the buttons local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) - if filter_pile then - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building - ,screen=self,bld=building,common=filter_pile.common} - choices={} - for k,v in pairs(filter_pile) do - table.insert(choices,{job_id=0,text=v.name:lower(),filter=v}) - end - dialog.showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) - ,nil, nil,true) - else - qerror("No jobs for this workshop") + local adv=df.global.world.units.active[0] + local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building + ,screen=self,bld=building} + if filter_pile and not no_reset then + local count=0 + state.common=filter_pile.common + for i,v in ipairs(filter_pile) do + local label=v.name:lower() + if not names_already_in[label] then + table.insert(list,{job_id=0,text=label,filter=v}) + count=count+1 + end + end + print("Added:",count," non-button jobs") + end + if #list==0 then + qerror("no jobs for this shop") end + dialog.showListPrompt("Workshop job choice", "Choose what to make", + COLOR_WHITE,list, + function (index,choice) + if choice.is_button then + self:onWorkShopButtonClicked(building,index,choice) + else + onWorkShopJobChosen(state,index,choice) + end + end + ,nil, nil,true) end + function track_stop_configure(bld) --TODO: dedicated widget with nice interface and current setting display local dump_choices={ {text="no dumping"}, From 700392ba55b68759e3a1dbdca8bd21470574ecbd Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 21 May 2016 19:17:08 -0500 Subject: [PATCH 0019/1012] Fix tinythread memory leak in two ways: 1. update to tinythread version 1.1, which provides a detach method 2. move the thread disassociation to the join function, which allows attached threads to be joined even after they finish (this was the main leak) Also update RemoteServer.cpp to detach threads and delete the thread objects instead of leaking them (although they are much smaller than the actual threads). --- depends/tthread/fast_mutex.h | 13 ++++- depends/tthread/tinythread.cpp | 32 ++++++++++-- depends/tthread/tinythread.h | 91 ++++++++++++++++++++-------------- library/RemoteServer.cpp | 4 ++ 4 files changed, 98 insertions(+), 42 deletions(-) diff --git a/depends/tthread/fast_mutex.h b/depends/tthread/fast_mutex.h index b4e712f44..4d4b7cc43 100644 --- a/depends/tthread/fast_mutex.h +++ b/depends/tthread/fast_mutex.h @@ -1,5 +1,5 @@ -/* -Copyright (c) 2010 Marcus Geelnard +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -47,7 +47,15 @@ freely, subject to the following restrictions: #endif #if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif #else #ifdef _FAST_MUTEX_ASM_ #include @@ -237,3 +245,4 @@ class fast_mutex { } #endif // _FAST_MUTEX_H_ + diff --git a/depends/tthread/tinythread.cpp b/depends/tthread/tinythread.cpp index eb2dce0e6..176bc2ac8 100644 --- a/depends/tthread/tinythread.cpp +++ b/depends/tthread/tinythread.cpp @@ -1,5 +1,5 @@ -/* -Copyright (c) 2010 Marcus Geelnard +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -171,13 +171,17 @@ void * thread::wrapper_function(void * aArg) catch(...) { // Uncaught exceptions will terminate the application (default behavior - // according to the C++0x draft) + // according to C++11) std::terminate(); } +#if 0 + // DFHack fix: this code prevents join from freeing thread resources. + // The thread is no longer executing lock_guard guard(ti->mThread->mDataMutex); ti->mThread->mNotAThread = true; +#endif // The thread is responsible for freeing the startup information delete ti; @@ -228,9 +232,16 @@ void thread::join() { #if defined(_TTHREAD_WIN32_) WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); #elif defined(_TTHREAD_POSIX_) pthread_join(mHandle, NULL); #endif + +#if 1 + // DFHack patch: moved here from the wrapper function + lock_guard guard(mDataMutex); + mNotAThread = true; +#endif } } @@ -242,6 +253,21 @@ bool thread::joinable() const return result; } +void thread::detach() +{ + mDataMutex.lock(); + if(!mNotAThread) + { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); +} + thread::id thread::get_id() const { if(!joinable()) diff --git a/depends/tthread/tinythread.h b/depends/tthread/tinythread.h index f4f8c5b22..aed7b5856 100644 --- a/depends/tthread/tinythread.h +++ b/depends/tthread/tinythread.h @@ -1,5 +1,5 @@ -/* -Copyright (c) 2010 Marcus Geelnard +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,7 +31,7 @@ freely, subject to the following restrictions: /// TinyThread++ is a minimal, portable implementation of basic threading /// classes for C++. /// -/// They closely mimic the functionality and naming of the C++0x standard, and +/// They closely mimic the functionality and naming of the C++11 standard, and /// should be easily replaceable with the corresponding std:: variants. /// /// @section port_sec Portability @@ -39,7 +39,7 @@ freely, subject to the following restrictions: /// classes, while for other systems, the POSIX threads API (pthread) is used. /// /// @section class_sec Classes -/// In order to mimic the threading API of the C++0x standard, subsets of +/// In order to mimic the threading API of the C++11 standard, subsets of /// several classes are provided. The fundamental classes are: /// @li tthread::thread /// @li tthread::mutex @@ -67,8 +67,15 @@ freely, subject to the following restrictions: // Platform specific includes #if defined(_TTHREAD_WIN32_) - #define NOMINMAX + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif #else #include #include @@ -82,22 +89,22 @@ freely, subject to the following restrictions: /// TinyThread++ version (major number). #define TINYTHREAD_VERSION_MAJOR 1 /// TinyThread++ version (minor number). -#define TINYTHREAD_VERSION_MINOR 0 +#define TINYTHREAD_VERSION_MINOR 1 /// TinyThread++ version (full version). #define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) -// Do we have a fully featured C++0x compiler? +// Do we have a fully featured C++11 compiler? #if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) - #define _TTHREAD_CPP0X_ + #define _TTHREAD_CPP11_ #endif -// ...at least partial C++0x? -#if defined(_TTHREAD_CPP0X_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) - #define _TTHREAD_CPP0X_PARTIAL_ +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP11_PARTIAL_ #endif // Macro for disabling assignments of objects. -#ifdef _TTHREAD_CPP0X_PARTIAL_ +#ifdef _TTHREAD_CPP11_PARTIAL_ #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ name(const name&) = delete; \ name& operator=(const name&) = delete; @@ -109,18 +116,18 @@ freely, subject to the following restrictions: /// @def thread_local /// Thread local storage keyword. -/// A variable that is declared with the \c thread_local keyword makes the +/// A variable that is declared with the @c thread_local keyword makes the /// value of the variable local to each thread (known as thread-local storage, /// or TLS). Example usage: /// @code /// // This variable is local to each thread. /// thread_local int variable; /// @endcode -/// @note The \c thread_local keyword is a macro that maps to the corresponding -/// compiler directive (e.g. \c __declspec(thread)). While the C++0x standard +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard /// allows for non-trivial types (e.g. classes with constructors and -/// destructors) to be declared with the \c thread_local keyword, most pre-C++0x -/// compilers only allow for trivial types (e.g. \c int). So, to guarantee +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee /// portable code, only use trivial types for thread local storage. /// @note This directive is currently not supported on Mac OS X (it will give /// a compiler error), since compile-time TLS is not supported in the Mac OS X @@ -128,7 +135,7 @@ freely, subject to the following restrictions: /// not support this directive. /// @hideinitializer -#if !defined(_TTHREAD_CPP0X_) && !defined(thread_local) +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) #define thread_local __thread #else @@ -138,8 +145,8 @@ freely, subject to the following restrictions: /// Main name space for TinyThread++. -/// This namespace is more or less equivalent to the \c std namespace for the -/// C++0x thread classes. For instance, the tthread::mutex class corresponds to +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to /// the std::mutex class. namespace tthread { @@ -176,7 +183,7 @@ class mutex { /// Lock the mutex. /// The method will block the calling thread until a lock on the mutex can - /// be obtained. The mutex remains locked until \c unlock() is called. + /// be obtained. The mutex remains locked until @c unlock() is called. /// @see lock_guard inline void lock() { @@ -192,7 +199,7 @@ class mutex { /// Try to lock the mutex. /// The method will try to lock the mutex. If it fails, the function will /// return immediately (non-blocking). - /// @return \c true if the lock was acquired, or \c false if the lock could + /// @return @c true if the lock was acquired, or @c false if the lock could /// not be acquired. inline bool try_lock() { @@ -268,7 +275,7 @@ class recursive_mutex { /// Lock the mutex. /// The method will block the calling thread until a lock on the mutex can - /// be obtained. The mutex remains locked until \c unlock() is called. + /// be obtained. The mutex remains locked until @c unlock() is called. /// @see lock_guard inline void lock() { @@ -282,7 +289,7 @@ class recursive_mutex { /// Try to lock the mutex. /// The method will try to lock the mutex. If it fails, the function will /// return immediately (non-blocking). - /// @return \c true if the lock was acquired, or \c false if the lock could + /// @return @c true if the lock was acquired, or @c false if the lock could /// not be acquired. inline bool try_lock() { @@ -406,7 +413,7 @@ class condition_variable { /// Wait for the condition. /// The function will block the calling thread until the condition variable - /// is woken by \c notify_one(), \c notify_all() or a spurious wake up. + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. /// @param[in] aMutex A mutex that will be unlocked when the wait operation /// starts, an locked again as soon as the wait operation is finished. template @@ -482,7 +489,7 @@ class thread { class id; /// Default constructor. - /// Construct a \c thread object without an associated thread of execution + /// Construct a @c thread object without an associated thread of execution /// (i.e. non-joinable). thread() : mHandle(0), mNotAThread(true) #if defined(_TTHREAD_WIN32_) @@ -491,7 +498,7 @@ class thread { {} /// Thread starting constructor. - /// Construct a \c thread object with a new thread of execution. + /// Construct a @c thread object with a new thread of execution. /// @param[in] aFunction A function pointer to a function of type: /// void fun(void * arg) /// @param[in] aArg Argument to the thread function. @@ -501,24 +508,34 @@ class thread { thread(void (*aFunction)(void *), void * aArg); /// Destructor. - /// @note If the thread is joinable upon destruction, \c std::terminate() + /// @note If the thread is joinable upon destruction, @c std::terminate() /// will be called, which terminates the process. It is always wise to do - /// \c join() before deleting a thread object. + /// @c join() before deleting a thread object. ~thread(); /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). void join(); /// Check if the thread is joinable. /// A thread object is joinable if it has an associated thread of execution. bool joinable() const; + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + /// Return the thread ID of a thread object. id get_id() const; /// Get the native handle for this thread. - /// @note Under Windows, this is a \c HANDLE, and under POSIX systems, this - /// is a \c pthread_t. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. inline native_handle_type native_handle() { return mHandle; @@ -613,18 +630,18 @@ class thread::id { // Related to - minimal to be able to support chrono. typedef long long __intmax_t; -/// Minimal implementation of the \c ratio class. This class provides enough -/// functionality to implement some basic \c chrono classes. +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. template <__intmax_t N, __intmax_t D = 1> class ratio { public: static double _as_double() { return double(N) / double(D); } }; -/// Minimal implementation of the \c chrono namespace. -/// The \c chrono namespace provides types for specifying time intervals. +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. namespace chrono { /// Duration template class. This class provides enough functionality to - /// implement \c this_thread::sleep_for(). + /// implement @c this_thread::sleep_for(). template > class duration { private: _Rep rep_; @@ -652,7 +669,7 @@ namespace chrono { typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. } -/// The namespace \c this_thread provides methods for dealing with the +/// The namespace @c this_thread provides methods for dealing with the /// calling thread. namespace this_thread { /// Return the thread ID of the calling thread. diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 06a9f859c..0a05833ee 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -117,6 +117,7 @@ ServerConnection::ServerConnection(CActiveSocket *socket) core_service->finalize(this, &functions); thread = new tthread::thread(threadFn, (void*)this); + thread->detach(); } ServerConnection::~ServerConnection() @@ -124,6 +125,7 @@ ServerConnection::~ServerConnection() in_error = true; socket->Close(); delete socket; + delete thread; for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it) delete it->second; @@ -363,6 +365,7 @@ ServerMain::~ServerMain() { socket->Close(); delete socket; + delete thread; } bool ServerMain::listen(int port) @@ -376,6 +379,7 @@ bool ServerMain::listen(int port) return false; thread = new tthread::thread(threadFn, this); + thread->detach(); return true; } From bfe7a90ec0087df1aa3643c46a78b5b022e561d3 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 21 May 2016 19:54:20 -0500 Subject: [PATCH 0020/1012] add NOMINMAX to Windows.h includes --- depends/tthread/fast_mutex.h | 1 + depends/tthread/tinythread.h | 1 + 2 files changed, 2 insertions(+) diff --git a/depends/tthread/fast_mutex.h b/depends/tthread/fast_mutex.h index 4d4b7cc43..6627408e4 100644 --- a/depends/tthread/fast_mutex.h +++ b/depends/tthread/fast_mutex.h @@ -47,6 +47,7 @@ freely, subject to the following restrictions: #endif #if defined(_TTHREAD_WIN32_) + #define NOMINMAX #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #define __UNDEF_LEAN_AND_MEAN diff --git a/depends/tthread/tinythread.h b/depends/tthread/tinythread.h index aed7b5856..d575bae33 100644 --- a/depends/tthread/tinythread.h +++ b/depends/tthread/tinythread.h @@ -67,6 +67,7 @@ freely, subject to the following restrictions: // Platform specific includes #if defined(_TTHREAD_WIN32_) + #define NOMINMAX #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #define __UNDEF_LEAN_AND_MEAN From c985cd6f1d65a07e809beb3cde5358a6b361dcca Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 22 May 2016 18:04:56 +0300 Subject: [PATCH 0021/1012] Various changes to advfort.lua Add explicit [fallback] option to the workshop jobs when sometimes the hacky way fails. Fix brewing accepting too many items instead of barrel. Fix tallow making reaction. Add a limited display of item filters when adding items in a job slot. --- scripts/gui/advfort.lua | 61 +++++++++++++++++++++++++---------- scripts/gui/advfort_items.lua | 21 +++++++++++- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 0733e88a2..4f18d071b 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -28,9 +28,10 @@ An example of player digging in adventure mode: version: 0.05 changelog: *0.05 - - fixed some reactions not showing. Now there are duplicated reactions :/ + - fixed some reactions not showing. Now there is a '[fallback]' choice to choose from other way of getting reactions. - fixed brewing accepting too many items instead of barrel - fixed tallow making to accept fat + - display filters *0.044 - added output to clear_jobs of number of cleared jobs - another failed attempt at gather plants fix @@ -1401,7 +1402,37 @@ function usetool:onWorkShopButtonClicked(building,index,choice) self:openShopWindowButtoned(building,true) end end +function usetool:openShopWindowFallback( building,list) + local open_window=false + if not list then --if list is not passed we are responsible for showing the menu + list={} + open_window=true + end + local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) + local adv=df.global.world.units.active[0] + local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building + ,screen=self,bld=building} + if filter_pile then + local count=0 + state.common=filter_pile.common + for i,v in ipairs(filter_pile) do + local label=v.name:lower() + table.insert(list,{job_id=0,text=label,filter=v}) + count=count+1 + end + end + + if open_window then + dialog.showListPrompt("Workshop job choice", "Choose what to make", + COLOR_WHITE,list, + function (index,choice) + onWorkShopJobChosen(state,index,choice) + end + ,nil, nil,true) + end +end +--no reset here means that the button opens submenu function usetool:openShopWindowButtoned(building,no_reset) self:setupFields() local wui=df.global.ui_sidebar_menus.workshop_job @@ -1421,29 +1452,25 @@ function usetool:openShopWindowButtoned(building,no_reset) table.insert(list,{text=label,button=choice,is_button=true}) names_already_in[label]=true end - --add fallback list if for some reason df does not make the buttons - local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) - local adv=df.global.world.units.active[0] - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building - ,screen=self,bld=building} - if filter_pile and not no_reset then - local count=0 - state.common=filter_pile.common - for i,v in ipairs(filter_pile) do - local label=v.name:lower() - if not names_already_in[label] then - table.insert(list,{job_id=0,text=label,filter=v}) - count=count+1 - end - end - print("Added:",count," non-button jobs") + if #list==0 then + --we couldn't use the df hack so let's fill the list from fallback + self:openShopWindowFallback(building,list) + else + --the hack worked. Though we are not sure how well so let's add a button for fallback + table.insert(list,{text='[fallback]'}) end + if #list==0 then qerror("no jobs for this shop") end + dialog.showListPrompt("Workshop job choice", "Choose what to make", COLOR_WHITE,list, function (index,choice) + if choice.text=="[fallback]" then + self:openShopWindowFallback(building) + return + end if choice.is_button then self:onWorkShopButtonClicked(building,index,choice) else diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua index befb12ef5..4cc05da72 100644 --- a/scripts/gui/advfort_items.lua +++ b/scripts/gui/advfort_items.lua @@ -100,6 +100,25 @@ function jobitemEditor:can_remove() local slot=self:get_slot() return #slot.items>0 end +function jobitemEditor:get_item_filters( job_item ) + local true_flags={} + for k,v in pairs(job_item.flags1) do + if v then + table.insert(true_flags,k) + end + end + for k,v in pairs(job_item.flags2) do + if v then + table.insert(true_flags,k) + end + end + for k,v in pairs(job_item.flags3) do + if v then + table.insert(true_flags,k) + end + end + return table.concat(true_flags,"\n") +end function jobitemEditor:add_item() local cur_slot=self:get_slot() local choices={} @@ -110,7 +129,7 @@ function jobitemEditor:add_item() end end gscript.start(function () - local _,_2,choice=gscript.showListPrompt("which item?", "Select item", COLOR_WHITE, choices) + local _,_2,choice=gscript.showListPrompt("which item?", "Select item\nItem filters:\n"..self:get_item_filters(cur_slot.job_item), COLOR_WHITE, choices) if choice ~= nil and choice.item~=nil then self:add_item_to_slot(cur_slot,choice.item) end From 726994969d8a8cfb3ea77aa63677cb51a3740b44 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 22 May 2016 22:54:51 +0300 Subject: [PATCH 0022/1012] fixed a small bug --- scripts/gui/advfort.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 4f18d071b..67a0f4bb3 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1452,6 +1452,9 @@ function usetool:openShopWindowButtoned(building,no_reset) table.insert(list,{text=label,button=choice,is_button=true}) names_already_in[label]=true end + local adv=df.global.world.units.active[0] + local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building + ,screen=self,bld=building} if #list==0 then --we couldn't use the df hack so let's fill the list from fallback self:openShopWindowFallback(building,list) From 9be928541cc27ae5e46c76bdcc072fef6a6ed49d Mon Sep 17 00:00:00 2001 From: carbohydratesn Date: Sun, 22 May 2016 14:03:53 -0600 Subject: [PATCH 0023/1012] Include math.h 3dveins.cpp won't build for me without math.h as a dependency - errors about 'fabsf' and other math functions arch linux, gcc-multilib 6.1.1-1 --- plugins/3dveins.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 3e51e6582..0bd2cd6c1 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "Core.h" #include "Console.h" From fc311a83450f4230413885328726d1c0e371509e Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 24 May 2016 13:26:16 -0500 Subject: [PATCH 0024/1012] convert the key to a string so gm-editor doesn't error on vector elements --- scripts/gui/gm-editor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 576649013..38394ec36 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -188,7 +188,7 @@ function GmEditorUi:find(test) end end function GmEditorUi:find_id() - local key = self:getSelectedKey() + local key = tostring(self:getSelectedKey()) local id = tonumber(self:getSelectedValue()) if not id then return end local opts = {} From 61bff2f668c80944983fe3023044fc553d03ae7b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 25 May 2016 16:12:02 -0400 Subject: [PATCH 0025/1012] Update 3rdparty/lethosor --- scripts/3rdparty/lethosor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor index 26c600132..704aed444 160000 --- a/scripts/3rdparty/lethosor +++ b/scripts/3rdparty/lethosor @@ -1 +1 @@ -Subproject commit 26c60013223e02b5558e31bed8e0625495430995 +Subproject commit 704aed4447f27ae802dae6994479ebc9c46568cc From e76258b040eec1d56a662af949cdc8a384150ee1 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 26 May 2016 15:15:42 -0500 Subject: [PATCH 0026/1012] knowledge category flag --- .../knowledge_scholar_category_flag.methods.inc | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 library/include/df/custom/knowledge_scholar_category_flag.methods.inc diff --git a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc new file mode 100644 index 000000000..f5c4e9a76 --- /dev/null +++ b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc @@ -0,0 +1,13 @@ +df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() const +{ + int32_t value = category * 32; + for (int32_t i = 0; i < 32; i++) + { + if (flags & (1 << i)) + { + value += i; + break; + } + } + return df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag(value); +} From c1ba4350582db5221cca0f7153a0dfbadc8186e8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 27 May 2016 17:41:23 -0400 Subject: [PATCH 0027/1012] Update version and xml --- CMakeLists.txt | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c34e0fedc..aedb6037a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,7 +105,7 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.43.02") +set(DF_VERSION "0.43.03") SET(DFHACK_RELEASE "alpha0") SET(DFHACK_PRERELEASE TRUE) diff --git a/library/xml b/library/xml index af8548bb1..f36723039 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit af8548bb118851f3da2618ab6055066774614cb2 +Subproject commit f36723039bc5b5475c0edd2960c72cccc201e83d From 4d3df60470b200b84057530dcaae6042235781d4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 27 May 2016 17:51:01 -0400 Subject: [PATCH 0028/1012] Update xml (offsets) --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index f36723039..c259ac961 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f36723039bc5b5475c0edd2960c72cccc201e83d +Subproject commit c259ac961bb881233f715e34bfa9ca3af555d7e2 From c0419d919e649effa6afd55d2bef576f2f5a6b00 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 28 May 2016 08:28:17 +0530 Subject: [PATCH 0029/1012] Add lake levels to remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader.cpp | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 8b4cb66f2..36ca4fd90 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -398,6 +398,7 @@ message WorldMap optional int32 cur_year_tick = 21; optional WorldPoles world_poles = 22; repeated RiverTile river_tiles = 23; + repeated int32 water_elevation = 24; } message RegionMaps diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 907e98969..c08736791 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -53,6 +53,7 @@ #include "df/region_map_entry.h" #include "df/world_region_details.h" +#include "df/world_region.h" #include "df/army.h" #include "df/army_flags.h" @@ -1817,6 +1818,7 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, for (int xx = 0; xx < width; xx++) { df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; out->add_elevation(map_entry->elevation); out->add_rainfall(map_entry->rainfall); out->add_vegetation(map_entry->vegetation); @@ -1832,6 +1834,12 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + if (region->type == world_region_type::Lake) + { + out->add_water_elevation(region->lake_surface); + } + else + out->add_water_elevation(99); } DFCoord pos = GetMapCenter(); out->set_center_x(pos.x); @@ -1844,8 +1852,9 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, return CR_OK; } -static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1) +static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_data * worldData) { + df::world_region * region = worldData->regions[e1->region_id]; out->add_rainfall(e1->rainfall); out->add_vegetation(e1->vegetation); out->add_temperature(e1->temperature); @@ -1854,6 +1863,13 @@ static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1) out->add_volcanism(e1->volcanism); out->add_savagery(e1->savagery); out->add_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + { + out->add_water_elevation(region->lake_surface); + } + else + out->add_water_elevation(99); + } static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) @@ -1866,7 +1882,7 @@ static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * wor pos.x = worldData->world_width - 1; if (pos.y >= worldData->world_height) pos.y = worldData->world_height - 1; - AddRegionTiles(out, &worldData->region_map[pos.x][pos.y]); + AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); } static df::coord2d ShiftCoords(df::coord2d source, int direction) From 0867f3d311260d3a1f8cd0bea1c2949355649f76 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 28 May 2016 08:29:12 +0530 Subject: [PATCH 0030/1012] Add dig designations to probe.cpp --- plugins/probe.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 0dae3d6eb..de0e4d521 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -302,6 +302,8 @@ command_result df_probe (color_ostream &out, vector & parameters) out << "salty" << endl; if(des.bits.water_stagnant) out << "stagnant" << endl; + + out.print("%-16s= %s\n", "dig", ENUM_KEY_STR(tile_dig_designation,des.bits.dig).c_str()); #define PRINT_FLAG( FIELD, BIT ) out.print("%-16s= %c\n", #BIT , ( FIELD.bits.BIT ? 'Y' : ' ' ) ) PRINT_FLAG( des, hidden ); From ae3052f79a39ef84e138add084e9787c5a06546b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 28 May 2016 10:28:22 -0400 Subject: [PATCH 0031/1012] Make knowledge_scholar_category_flag::value() non-const const methods can't be wrapped at the moment, at least without changing things in DataFuncs.h --- .../df/custom/knowledge_scholar_category_flag.methods.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc index f5c4e9a76..6fe896804 100644 --- a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc +++ b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc @@ -1,4 +1,4 @@ -df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() const +df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() { int32_t value = category * 32; for (int32_t i = 0; i < 32; i++) From 113325f8852f7522781a732fe91a8ba7b5002d86 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 28 May 2016 10:29:53 -0400 Subject: [PATCH 0032/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index c259ac961..d48de9e90 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c259ac961bb881233f715e34bfa9ca3af555d7e2 +Subproject commit d48de9e904150c4cd0e8502b6edf246737849607 From 569325823df92f7304afe7a7509d9d14b7917326 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 28 May 2016 21:23:26 +0530 Subject: [PATCH 0033/1012] Send world name and time with world center in remotefortressreader.cpp --- library/xml | 2 +- plugins/remotefortressreader.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 98cc1e018..d3182b7f9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 98cc1e01886aaea161d651cf97229ad08e9782b0 +Subproject commit d3182b7f9a241d75e556937cad1f2c8824845b4f diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index c08736791..fb383af65 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1772,6 +1772,10 @@ static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessag out->set_center_x(pos.x); out->set_center_y(pos.y); out->set_center_z(pos.z); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); return CR_OK; } From 9fc43cff4d27b32577d4943878f8d0fde3e219d6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 28 May 2016 20:11:42 -0400 Subject: [PATCH 0034/1012] Make const method wrappers possible Reverts ae3052f Ref #935 --- library/include/DataFuncs.h | 15 +++++++++++++++ .../knowledge_scholar_category_flag.methods.inc | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index cd178263c..bf8e6b244 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -83,6 +83,11 @@ namespace df { typedef RT type; \ typedef CT class_type; \ static const bool is_method = true; \ + }; \ + template struct return_type { \ + typedef RT type; \ + typedef CT class_type; \ + static const bool is_method = true; \ }; #define INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \ @@ -99,10 +104,20 @@ namespace df { static void execute(lua_State *state, int base, void (CT::*cb) FArgs) { \ LOAD_CLASS() Loads; INVOKE_VOID((self->*cb) Args); } \ }; \ + template struct function_wrapper { \ + static const int num_args = Count+1; \ + static void execute(lua_State *state, int base, void (CT::*cb) FArgs const) { \ + LOAD_CLASS() Loads; INVOKE_VOID((self->*cb) Args); } \ + }; \ template struct function_wrapper { \ static const int num_args = Count+1; \ static void execute(lua_State *state, int base, RT (CT::*cb) FArgs) { \ LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ + }; \ + template struct function_wrapper { \ + static const int num_args = Count+1; \ + static void execute(lua_State *state, int base, RT (CT::*cb) FArgs const) { \ + LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ }; #define INSTANTIATE_WRAPPERS(Count, FArgs, OFArgs, Args, OArgs, Loads) \ diff --git a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc index 6fe896804..f5c4e9a76 100644 --- a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc +++ b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc @@ -1,4 +1,4 @@ -df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() +df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() const { int32_t value = category * 32; for (int32_t i = 0; i < 32; i++) From fa1c589391df5f7ab5b90755e69d827311b003ac Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 30 May 2016 16:58:51 -0400 Subject: [PATCH 0035/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d48de9e90..f242eb9e8 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d48de9e904150c4cd0e8502b6edf246737849607 +Subproject commit f242eb9e839056542c2bbfb26a629de6e4007bd7 From b1072b65e4f6fa7d706710e70ea044c0876b36d9 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 31 May 2016 22:53:48 +0530 Subject: [PATCH 0036/1012] Don't send designations in adventure mode --- plugins/remotefortressreader.cpp | 56 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index fb383af65..5620a6f65 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -101,6 +101,7 @@ using namespace df::global; REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(gamemode); #endif // Here go all the command declarations... @@ -1171,32 +1172,37 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N NetBlock->add_subterranean(designation.bits.subterranean); NetBlock->add_water_salt(designation.bits.water_salt); NetBlock->add_water_stagnant(designation.bits.water_stagnant); - switch (designation.bits.dig) - { - case df::enums::tile_dig_designation::No: + if(gamemode && (*gamemode == game_mode::ADVENTURE)) NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - case df::enums::tile_dig_designation::Default: - NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); - break; - case df::enums::tile_dig_designation::UpDownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::Channel: - NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); - break; - case df::enums::tile_dig_designation::Ramp: - NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); - break; - case df::enums::tile_dig_designation::DownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::UpStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); - break; - default: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; + else + { + switch (designation.bits.dig) + { + case df::enums::tile_dig_designation::No: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + case df::enums::tile_dig_designation::Default: + NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); + break; + case df::enums::tile_dig_designation::UpDownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::Channel: + NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); + break; + case df::enums::tile_dig_designation::Ramp: + NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); + break; + case df::enums::tile_dig_designation::DownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::UpStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); + break; + default: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + } } } } From 44b8433282049d0f1e8ea71ebe0027b714775aae Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 3 Jun 2016 00:31:02 +0530 Subject: [PATCH 0037/1012] Make remotefortressreader.cpp mark blocks as hidden that aren't visible directly to the adventurer --- plugins/remotefortressreader.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 5620a6f65..6a227cb2a 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1166,16 +1166,20 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N NetBlock->add_magma(lava); NetBlock->add_water(water); NetBlock->add_aquifer(designation.bits.water_table); - NetBlock->add_hidden(designation.bits.hidden); NetBlock->add_light(designation.bits.light); NetBlock->add_outside(designation.bits.outside); NetBlock->add_subterranean(designation.bits.subterranean); NetBlock->add_water_salt(designation.bits.water_salt); NetBlock->add_water_stagnant(designation.bits.water_stagnant); - if(gamemode && (*gamemode == game_mode::ADVENTURE)) + if (gamemode && (*gamemode == game_mode::ADVENTURE)) + { + auto fog_of_war = DfBlock->fog_of_war[xx][yy]; + NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + } else { + NetBlock->add_hidden(designation.bits.hidden); switch (designation.bits.dig) { case df::enums::tile_dig_designation::No: From 8543121ecea578df33aabcfa5b2f2e3f1bb7ad66 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 3 Jun 2016 00:32:11 +0530 Subject: [PATCH 0038/1012] add fog of war and traffic to probe.cpp --- plugins/probe.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index de0e4d521..b1302868d 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -198,6 +198,7 @@ command_result df_probe (color_ostream &out, vector & parameters) df::tiletype tiletype = mc.tiletypeAt(cursor); df::tile_designation &des = block.designation[tileX][tileY]; df::tile_occupancy &occ = block.occupancy[tileX][tileY]; + uint8_t fog_of_war = block.fog_of_war[tileX][tileY]; /* if(showDesig) { @@ -303,7 +304,8 @@ command_result df_probe (color_ostream &out, vector & parameters) if(des.bits.water_stagnant) out << "stagnant" << endl; - out.print("%-16s= %s\n", "dig", ENUM_KEY_STR(tile_dig_designation,des.bits.dig).c_str()); + out.print("%-16s= %s\n", "dig", ENUM_KEY_STR(tile_dig_designation, des.bits.dig).c_str()); + out.print("%-16s= %s\n", "traffic", ENUM_KEY_STR(tile_traffic, des.bits.traffic).c_str()); #define PRINT_FLAG( FIELD, BIT ) out.print("%-16s= %c\n", #BIT , ( FIELD.bits.BIT ? 'Y' : ' ' ) ) PRINT_FLAG( des, hidden ); @@ -313,6 +315,7 @@ command_result df_probe (color_ostream &out, vector & parameters) PRINT_FLAG( des, water_table ); PRINT_FLAG( des, rained ); PRINT_FLAG( occ, monster_lair); + out.print("%-16s= %d\n", "fog_of_war", fog_of_war); df::coord2d pc(blockX, blockY); From a47a1c83ba637ed7f9c2e1861a589b3029dea65e Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 7 Jun 2016 08:17:40 -0400 Subject: [PATCH 0039/1012] Update xml, clsocket --- depends/clsocket | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/clsocket b/depends/clsocket index dc7ff5dad..55b6bfb81 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit dc7ff5dadf7b1bf96a82149b201d3674c97631d9 +Subproject commit 55b6bfb8121991876282f1ba7db10ab091391e14 diff --git a/library/xml b/library/xml index f242eb9e8..fc914791d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f242eb9e839056542c2bbfb26a629de6e4007bd7 +Subproject commit fc914791dede6811f238ffaa4609690203aaa13a From e45fbfc61d33c470ebea12966aa13a4ee2ac344d Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Sun, 5 Jun 2016 09:09:07 -0400 Subject: [PATCH 0040/1012] Modified dwaftvet plugin to work with latest DFHack Initial commit of the dwarfvet plugin Signed-off-by: Michael Casadevall --- library/include/modules/Buildings.h | 2 + library/modules/Buildings.cpp | 14 + plugins/CMakeLists.txt | 1 + plugins/dwarfvet.cpp | 835 ++++++++++++++++++++++++++++ 4 files changed, 852 insertions(+) create mode 100644 plugins/dwarfvet.cpp diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 1d939665f..de78acda6 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -292,6 +292,8 @@ DFHACK_EXPORT bool isActivityZone(df::building * building); DFHACK_EXPORT bool isPenPasture(df::building * building); DFHACK_EXPORT bool isPitPond(df::building * building); DFHACK_EXPORT bool isActive(df::building * building); +DFHACK_EXPORT bool isHospital(df::building * building); +DFHACK_EXPORT bool isAnimalTraining(df::building * building); DFHACK_EXPORT df::building* findPenPitAt(df::coord coord); } diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index a6589bac1..b1a22e2cc 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1263,6 +1263,20 @@ bool Buildings::isActive(df::building * building) return ((df::building_civzonest*) building)->zone_flags.bits.active != 0; } +bool Buildings::isHospital(df::building * building) + { + if (!isActivityZone(building)) + return false; + return ((df::building_civzonest*) building)->zone_flags.bits.hospital != 0; + } + + bool Buildings::isAnimalTraining(df::building * building) + { + if (!isActivityZone(building)) + return false; + return ((df::building_civzonest*) building)->zone_flags.bits.animal_training != 0; + } + // returns building of pen/pit at cursor position (NULL if nothing found) df::building* Buildings::findPenPitAt(df::coord coord) { diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index cc0de019f..fbf33f459 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,6 +93,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) + DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp new file mode 100644 index 000000000..44b3684e0 --- /dev/null +++ b/plugins/dwarfvet.cpp @@ -0,0 +1,835 @@ +/** + * Copyright (c) 2015, Michael Casadevall + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + **/ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/EventManager.h" +#include "modules/Units.h" +#include "modules/Buildings.h" +#include "modules/Maps.h" +#include "modules/Job.h" + +#include "df/animal_training_level.h" +#include "df/building_type.h" +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/creature_raw.h" +#include "df/job.h" +#include "df/general_ref_unit_workerst.h" +#include "df/profession.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_health_info.h" +#include "df/unit_health_flags.h" +#include "df/world.h" + +#include +#include + +using namespace DFHack; +using namespace DFHack::Units; +using namespace DFHack::Buildings; + +using namespace std; + +DFHACK_PLUGIN("dwarfvet"); +DFHACK_PLUGIN_IS_ENABLED(dwarfvet_enabled); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); + +static vector tracked_units; +static int32_t howOften = 100; + +struct hospital_spot { + int32_t x; + int32_t y; + int32_t z; +}; + +class Patient { + public: + // Constructor/Deconstrctor + Patient(int32_t id, int spot_index, int32_t x, int32_t y, int32_t z); + int32_t getID() { return this->id; }; + int32_t getSpotIndex() { return this->spot_index; }; + int32_t returnX() { return this->spot_in_hospital.x; }; + int32_t returnY() { return this->spot_in_hospital.y; }; + int32_t returnZ() { return this->spot_in_hospital.z; }; + + private: + struct hospital_spot spot_in_hospital; + int id; + int spot_index; +}; + +Patient::Patient(int32_t id, int32_t spot_index, int32_t x, int32_t y, int32_t z){ + this->id = id; + this->spot_in_hospital.x = x; + this->spot_in_hospital.y = y; + this->spot_in_hospital.z = z; +} + +class AnimalHospital { + + public: + // Constructor + AnimalHospital(df::building *, color_ostream &out); + ~AnimalHospital(); + int32_t getID() { return id; } + bool acceptPatient(int32_t id, color_ostream&); + void processPatients(color_ostream &out); + void dischargePatient(Patient * patient, color_ostream &out); + void calculateHospital(bool force, color_ostream &out); + void reportUsage(color_ostream &out); + + // GC + bool to_be_deleted; + + private: + int spots_open; + int32_t id; + int32_t x1; + int32_t x2; + int32_t y1; + int32_t y2; + int32_t z; + int height; + int length; + + // Doing an actual array in C++ is *annoying*, bloody copy constructors */ + vector spots_in_use; + vector building_in_hospital_notification; /* If present, we already notified about this */ + vector accepted_patients; +}; + +AnimalHospital::AnimalHospital(df::building * building, color_ostream &out) { + // Copy in what we need to know + id = building->id; + x1 = building->x1; + x2 = building->x2; + y1 = building->y1; + y2 = building->y2; + z = building->z; + + // Determine how many spots we have for animals + this->length = x2-x1; + this->height = y2-y1; + + // And calculate the hospital! + this->calculateHospital(true, out); +} + +AnimalHospital::~AnimalHospital() { + // Go through and delete all the patients + for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { + delete (*accepted_patient); + } +} + +bool AnimalHospital::acceptPatient(int32_t id, color_ostream &out) { + // This function determines if we can accept a patient, and if we will. + this->calculateHospital(true, out); + + // First, do we have room? + if (!spots_open) return false; + + // Yup, let's find the next open spot, + // and give it to our patient + int spot_cur = 0; // fuck the STL for requiring a second counter to make this usable + for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { + if (*spot == false) { + *spot = true; + break; + } + spot_cur++; + } + + spots_open--; + + // Convert the spot into x/y/z cords. + int offset_y = spot_cur/length; + int offset_x = spot_cur%length; + + // Create the patient! + Patient * patient = new Patient(id, + spot_cur, + this->x1+offset_x, + this->y1+offset_y, + this->z + ); + + accepted_patients.push_back(patient); + return true; +} + +// Before any use of the hospital, we need to make calculate open spots +// and such. This can change (i.e. stuff built in hospital) and +// such so it should be called on each function. +void AnimalHospital::reportUsage(color_ostream &out) { + // Debugging tool to see parts of the hospital in use + int length_cursor = this->length; + + for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { + if (*spot) out.print("t"); + if (!(*spot)) out.print("f"); + length_cursor--; + if (length_cursor < 0) { + out.print("\n"); + length_cursor = this->length; + } + } + out.print("\n"); + +} + +void AnimalHospital::calculateHospital(bool force, color_ostream &out) { + // Only calculate out the hospital if we actually have a patient in it + // (acceptPatient will forcibly rerun this to make sure everything OK + + // Should reduce FPS impact of each calculation tick when the hospitals + // are not in use + //if (!force || (spots_open == length*height)) { + // Hospital is idle, don't recalculate + // return; + //} + + // Calculate out the total area of the hospital + // This can change if a hospital has been resized + this->spots_open = length*height; + this->spots_in_use.assign(this->spots_open, false); + + // The spots_in_use maps one to one with a spot + // starting at the upper-left hand corner, then + // across, then down. i.e. + // + // given hospital zone: + // + // UU + // uU + // + // where U is in use, and u isn't, the array + // would be t,t,f,t + + // Walk the building array and see what stuff is in the hospital, + // then walk the patient array and remark those spots as used. + + // If a patient is in an invalid spot, reassign it + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + + // Check that we're not comparing ourselves; + if (building->id == this->id) { + continue; + } + + // Check if the building is on our z level, if it isn't + // then it can't overlap the hospital (until Toady implements + // multi-z buildings + if (building->z != this->z) { + continue; + } + + // DF defines activity zones multiple times in the building structure + // If axises agree with each other, we're looking at a reflection of + // ourselves + if (building->x1 == this->x1 && + building->x2 == this->x2 && + building->y1 == this->y1 && + building->y2 == this->y2) { + continue; + } + + // Check for X/Y overlap + // I can't believe I had to look this up -_-; + // http://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other + if ((this->x1 > building->x2 || + building->x1 > this->x2 || + this->y1 > building->y2 || + building->y1 > this->y2)) { + continue; + } + + // Crap, building overlaps, we need to figure out where it is in the hospital + // NOTE: under some conditions, this generates a false warning. Not a lot I can do about it + + // Mark spots used by that building as used; FIXME: handle special logic for traction benches and such + int building_offset_x = building->x1 - this->x1; + int building_offset_y = building->y1 - this->y1; + int building_length = building->x2 - building->x1 + 1; + int building_height = building->y2 - building->y1 + 1; + + // Cap the used calculation to only include the part in the hospital + if (this->x1 > building->x1) { + building_offset_x -= (this->x1 - building->x1); + } + + if (this->y1 > building->y1) { + building_offset_y -= (building->y1 - this->y1); + } + + if ((this->x2 < building->x2) && building_offset_x) { + building_length -= (this->x2 - building->x2) + 1; + } + + if ((this->y2 < building->y2) && building_offset_y) { + building_height = (building->y2 - this->y2) + 1; + } + + // Quick explination, if a building is north or east of the activity zone, + // we get a negative offset, we'll just skip those lines below. If its + // south or west, we make the building length/height lower to compinsate. + + /* if we have a negative x offset, we correct that */ + if (building_offset_x < 0) { + building_height += building_offset_x; + building_offset_x = 0; + } + + /* Handle negative y offset */ + if (building_offset_y < 0) { + building_length += building_offset_y; + building_offset_y = 0; + }; + + /* Advance the pointer to first row we need to mark */ + int spot_cur = 0; + if (building_offset_y) { + spot_cur = (length+1) * building_offset_y; + } + + spot_cur += building_offset_x; + /* Start marking! */ + for (int i = 0; i != building_height; i++) { + for (int j = 0; j != building_length; j++) { + spots_in_use[spot_cur+j] = true; + } + + // Wind the cursor to the start of the next row + spot_cur += length+1; + } + + // *phew*, done. Now repeat the process for the next building! + } + +} + +// Self explanatory +void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { + int32_t id = patient->getID(); + + // Remove them from the hospital + + // We can safely iterate here because once we delete the unit + // we no longer use the iterator + for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { + if ( (*accepted_patient)->getID() == id) { + out.print("Discharging unit %d from hospital %d\n", id, this->id); + // Reclaim their spot + spots_in_use[(*accepted_patient)->getSpotIndex()] = false; + this->spots_open++; + delete (*accepted_patient); + this->accepted_patients.erase(accepted_patient); + break; + } + } + + // And the master list + for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { + if ((*it) == id) { + tracked_units.erase(it); + break; + } + } + + return; +} + +void AnimalHospital::processPatients(color_ostream &out) { + // Where the magic happens + for (vector::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { + int id = (*patient)->getID(); + df::unit * real_unit; + // Appears the health bits can get freed/realloced too -_-;, Find the unit from the main + // index and check it there. + auto units = world->units.all; + + for ( size_t a = 0; a < units.size(); a++ ) { + real_unit = units[a]; + if (real_unit->id == id) { + break; + } + } + + // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed + if (real_unit->flags1.bits.dead || !real_unit->health->flags.bits.needs_healthcare) { + // discharge the patient from the hospital + this->dischargePatient(*patient, out); + return; + } + + // Give the unit a job if they don't have any + if (!real_unit->job.current_job) { + // Create REST struct + df::job * job = new df::job; + DFHack::Job::linkIntoWorld(job); + + job->pos.x = (*patient)->returnX(); + job->pos.y = (*patient)->returnY(); + job->pos.z = (*patient)->returnZ(); + job->flags.bits.special = 1; + job->job_type = df::enums::job_type::Rest; + df::general_ref *ref = df::allocate(); + ref->setID(real_unit->id); + job->general_refs.push_back(ref); + real_unit->job.current_job = job; + job->wait_timer = 1600; + out.print("Telling intelligent unit %d to report to the hospital!\n", real_unit->id); + } + } +} + + +static vector animal_hospital_zones; + +void delete_animal_hospital_vector(color_ostream &out) { + out.print("Clearing all animal hospitals\n"); + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + delete (*animal_hospital); + } + animal_hospital_zones.clear(); +} + +command_result dwarfvet(color_ostream &out, std::vector & parameters); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "dwarfvet", + "Allows animals to be cared for in animal hospitals (activity zones that are animal training + hospital combined).", + dwarfvet, + false, //allow non-interactive use + "dwarfvet enable\n" + " enables animals to use animal hospitals (requires dwarf with Animal Caretaker labor enabled)\n" + "dwarfvet report\n" + " displays all zones dwarfvet considers animal hospitals and their current location on the map\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +bool isActiveAnimalHospital(df::building * building) { + if (Buildings::isHospital(building) && Buildings::isAnimalTraining(building) && Buildings::isActive(building)) { + return true; + } + + return false; +} + +bool compareAnimalHospitalZones(df::building * hospital1, df::building * hospital2) { + // We compare hospitals by checking if positions are identical, not by ID + // since activity zones can easily be changed in size + + if ( hospital1->x1 == hospital2->x1 && + hospital1->x2 == hospital2->x2 && + hospital1->y1 == hospital2->y1 && + hospital1->y2 == hospital2->y2 && + hospital1->z == hospital1->z) { + return true; + } + + return false; +} + +void tickHandler(color_ostream& out, void* data) { + if ( !dwarfvet_enabled ) + return; + CoreSuspender suspend; + int32_t own_race_id = df::global::ui->race_id; + int32_t own_civ_id = df::global::ui->civ_id; + auto units = world->units.all; + + /** + * Generate a list of animal hospitals on the map + * + * Since activity zones can change any instant given user interaction + * we need to be constantly on the lookout for changed zones, and update + * our cached list on the fly if necessary. + **/ + + vector hospitals_on_map; + + // Because C++ iterators suck, we're going to build a temporary vector with the AHZ, and then + // copy it for my own bloody sanity (and compilance with the STL spec) + vector ahz_scratch; + + // Holding area for things to be added to the scratch + vector to_be_added; + + + // Walk the building tree, and generate a list of animal hospitals on the map + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + if (isActiveAnimalHospital(building)) { + hospitals_on_map.push_back(building); + } + } + + int count_of_hospitals = hospitals_on_map.size(); + int hospitals_cached = animal_hospital_zones.size(); + //out.print ("count_of_Hospitals: %d, hospitals_cached: %d\n", count_of_hospitals, hospitals_cached); + // It's possible our hospital cache is empty, if so, simply copy it, and jump to the main logic + if (!hospitals_cached && count_of_hospitals) { + out.print("Populating hospital cache:\n"); + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + out.print(" Found animal hospital %d at x1: %d, y1: %d from valid hospital list\n", + hospital->getID(), + (*current_hospital)->x1, + (*current_hospital)->y1, + (*current_hospital)->z + ); + animal_hospital_zones.push_back(hospital); + } + + goto processUnits; + } + + if (!count_of_hospitals && !hospitals_cached) { + // No hospitals found, delete any cache, and return + delete_animal_hospital_vector(out); + out.print("No hospitals found, plugin sleeping ...\n"); + goto cleanup; + } + + // Now walk our list of known hospitals, do a bit of checking, then compare + // TODO: this doesn't handle zone resizes at all + + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + // If a zone is changed at all, DF seems to reallocate it. + // + // Each AnimalHospital has a "to_be_deleted" bool. We're going to set that to true, and clear it if we can't + // find a matching hospital. This limits the number of times we need to walk through the AHZ list to twice, and + // lets us cleanly report it later + // + // Surviving hospitals will be copied to scratch which will become the new AHZ vector + + (*animal_hospital)->to_be_deleted = true; + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + + /* Keep the hospital if its still valid */ + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + ahz_scratch.push_back(*animal_hospital); + (*animal_hospital)->to_be_deleted = false; + break; + } + + } + } + + // Report what we're deleting by checking the to_be_deleted bool. + // + // Whatsever left is added to the pending add list + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + if ((*animal_hospital)->to_be_deleted) { + out.print("Hospital #%d removed\n", (*animal_hospital)->getID()); + delete *animal_hospital; + } + } + + /* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */ + + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + bool new_hospital = true; + + for (vector::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + // Next if we're already here + new_hospital = false; + break; + } + } + + // Add it if its new + if (new_hospital == true) to_be_added.push_back(*current_hospital); + } + + /* Now add it to the scratch AHZ */ + for (vector::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { + // Add it to the vector + out.print("Adding new hospital #id at x1 %d y1: %d z: %d\n", + (*current_hospital)->id, + (*current_hospital)->x1, + (*current_hospital)->y1, + (*current_hospital)->z + ); + AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + ahz_scratch.push_back(hospital); + } + + /* Copy the scratch to the AHZ */ + animal_hospital_zones = ahz_scratch; + + // We always recheck the cache instead of counts because someone might have removed then added a hospital +/* if (hospitals_cached != count_of_hospitals) { + out.print("Hospitals on the map changed, rebuilding cache\n"); + + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + bool add_hospital = true; + + for (vector::iterator map_hospital = animal_hospital_zones.begin(); map_hospital != animal_hospital_zones.end(); map_hospital++) { + if (compareAnimalHospitalZones(*map_hospital, *current_hospital)) { + // Same hospital, we're good + add_hospital = false; + break; + } + } + + // Add it to the list + if (add_hospital) { + out.print("Adding zone at x1: %d, y1: %d to valid hospital list\n", (*current_hospital)->x1, (*current_hospital)->y1); + animal_hospital_zones.push_back(*current_hospital); + } + } + } +*/ +processUnits: + /* Code borrowed from petcapRemover.cpp */ + for ( size_t a = 0; a < units.size(); a++ ) { + df::unit* unit = units[a]; + + /* As hilarious as it would be, lets not treat FB :) */ + if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) { + continue; + } + + if ( !Units::isTamable(unit)) { + continue; + } + + /** + * So, for a unit to be elligable for the hospital, all the following must be true + * + * 1. It must be a member of our civilization + * 2. It must be tame (semi-wild counts for this) + * 2.1 If its not a dwarf, AND untame clear its civ out so traps work + * 3. It must have a health struct (which is generated by combat) + * 4. health->needs_healthcare must be set to true + * 5. If health->requires_recovery is set, the creature can't move under its own power + * and a Recover Wounded or Pen/Pasture job MUST be created by hand - TODO + * 6. An open spot in the "Animal Hospital" (activity zone with hospital+animal training set) + * must be available + * + * I apologize if this excessively verbose, but the healthcare system is stupidly conplex + * and there's tons of edgecases to watch out for, and I want someone else to ACTUALLY + * beside me able to understand what's going on + */ + + // 1. Make sure its our own civ + if (!Units::isOwnCiv(unit)) { + continue; + } + + // 2. Check for tameness + if (unit->training_level == df::animal_training_level::WildUntamed) { + // We don't IMMEDIATELY continue here, if the unit is + // part of our civ, it indiciates it WAS tamed, and reverted + // from SemiWild. Clear its civ flag so it looses TRAPAVOID + // + // Unfortunately, dwarves (or whatever is CIV_SELECTABLE) + // also have a default taming level of WildUntamed so + // check for this case + // + // Furthermore, it MIGHT be a werebeast, so check THAT too + // and exclude those as well. + // + // Finally, this breaks makeown. I might need to write a patch + // to set the tameness of "makeowned" units so dwarfvet can notice + // it + + if (unit->race == own_race_id || unit->enemy.normal_race == own_race_id) { + continue; + } else { + unit->civ_id = -1; + out.print ("Clearing civ on unit: %d", unit->id); + } + } + + // 3. Check for health struct + if (!unit->health) { + // Unit has not been injured ever; health struct MIA + continue; + } + + // 4. Check the healthcare flags + if (unit->health->flags.bits.needs_healthcare) { + /** + * So, for dwarves to care for a unit it must be resting in + * in a hospital zone. Since non-dwarves never take jobs + * this why animal healthcare doesn't work for animals despite + * animal caretaker being coded in DF itself + * + * How a unit gets there is dependent on several factors. If + * a unit can move under its own power, it will take the rest + * job, with a position of a bed in the hospital, then move + * into that bed and fall asleep. This triggers a doctor to + * treat the unit. + * + * If a unit *can't* move, it will set needs_recovery, which + * creates a "Recover Wounded" job in the job list, and then + * create the "Rest" job as listed above. Another dwarf with + * the right labors will go recover the unit, then the above + * logic kicks off. + * + * The necessary flags seem to be properly set for all units + * on the map, so in theory, we just need to make the jobs and + * we're in business, but from a realism POV, I don't think + * non-sentient animals would be smart enough to go to the + * hospital on their own, so instead, we're going to do the following + * + * If a unit CAN_THINK, and can move let it act like a dwarf, + * it will try and find an open spot in the hospital, and if so, + * go there to be treated. In vanilla, the only tamable animal + * with CAN_THINK are Gremlins, so this is actually an edge case + * but its the easiest to code. + * + * TODO: figure out exact logic for non-thinking critters. + */ + + // Now we need to find if this unit can be accepted at a hospital + bool awareOfUnit = false; + for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { + if ((*it) == unit->id) { + awareOfUnit = true; + } + } + // New unit for dwarfvet to be aware of! + if (!awareOfUnit) { + // The master list handles all patients which are accepted + // Check if this is a unit we're already aware of + + bool patient_accepted = false; + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end();) { + if ((*animal_hospital)->acceptPatient(unit->id, out)) { + out.print("Accepted patient %d at hospital %d\n", unit->id, (*animal_hospital)->getID()); + patient_accepted = true; + tracked_units.push_back(unit->id); + break; + } + + + } + } + } + } + + // The final step, process all patients! + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + (*animal_hospital)->calculateHospital(true, out); + (*animal_hospital)->processPatients(out); + } + +cleanup: + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); +} + +command_result dwarfvet (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + for ( size_t a = 0; a < parameters.size(); a++ ) { + if ( parameters[a] == "enable" ) { + out.print("dwarfvet enabled!\n"); + dwarfvet_enabled = true; + } + if ( parameters[a] == "disable") { + out.print("dwarvet disabled!\n"); + dwarfvet_enabled = false; + } + if ( parameters[a] == "report") { + out.print("Current animal hospitals are:\n"); + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + if (isActiveAnimalHospital(building)) { + out.print(" at x1: %d, x2%: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z); + } + } + return CR_OK; + } + if ( parameters[a] == "report-usage") { + out.print("Current animal hospitals are:\n"); + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + (*animal_hospital)->calculateHospital(true, out); + (*animal_hospital)->reportUsage(out); + } + return CR_OK; + } + } + + if ( !dwarfvet_enabled ) { + return CR_OK; + } + + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (enable && !dwarfvet_enabled) { + dwarfvet_enabled = true; + } + else if (!enable && dwarfvet_enabled) { + delete_animal_hospital_vector(out); + dwarfvet_enabled = false; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) + { + case DFHack::SC_MAP_LOADED: + break; + case DFHack::SC_MAP_UNLOADED: + delete_animal_hospital_vector(out); + dwarfvet_enabled = false; + break; + default: + break; + } + return CR_OK; +} \ No newline at end of file From fead8a80ff5d18743a1f11a8a710e23a62098abc Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Sun, 5 Jun 2016 11:05:28 -0400 Subject: [PATCH 0041/1012] Fix tabs to spaces Signed-off-by: Michael Casadevall --- plugins/dwarfvet.cpp | 84 ++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 44b3684e0..920e13d34 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -175,7 +175,7 @@ bool AnimalHospital::acceptPatient(int32_t id, color_ostream &out) { // Create the patient! Patient * patient = new Patient(id, - spot_cur, + spot_cur, this->x1+offset_x, this->y1+offset_y, this->z @@ -342,8 +342,8 @@ void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { // Remove them from the hospital - // We can safely iterate here because once we delete the unit - // we no longer use the iterator + // We can safely iterate here because once we delete the unit + // we no longer use the iterator for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { if ( (*accepted_patient)->getID() == id) { out.print("Discharging unit %d from hospital %d\n", id, this->id); @@ -485,12 +485,12 @@ void tickHandler(color_ostream& out, void* data) { vector hospitals_on_map; - // Because C++ iterators suck, we're going to build a temporary vector with the AHZ, and then - // copy it for my own bloody sanity (and compilance with the STL spec) - vector ahz_scratch; + // Because C++ iterators suck, we're going to build a temporary vector with the AHZ, and then + // copy it for my own bloody sanity (and compilance with the STL spec) + vector ahz_scratch; - // Holding area for things to be added to the scratch - vector to_be_added; + // Holding area for things to be added to the scratch + vector to_be_added; // Walk the building tree, and generate a list of animal hospitals on the map @@ -531,70 +531,70 @@ void tickHandler(color_ostream& out, void* data) { // Now walk our list of known hospitals, do a bit of checking, then compare // TODO: this doesn't handle zone resizes at all - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { // If a zone is changed at all, DF seems to reallocate it. // // Each AnimalHospital has a "to_be_deleted" bool. We're going to set that to true, and clear it if we can't // find a matching hospital. This limits the number of times we need to walk through the AHZ list to twice, and - // lets us cleanly report it later - // - // Surviving hospitals will be copied to scratch which will become the new AHZ vector + // lets us cleanly report it later + // + // Surviving hospitals will be copied to scratch which will become the new AHZ vector (*animal_hospital)->to_be_deleted = true; for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { - /* Keep the hospital if its still valid */ + /* Keep the hospital if its still valid */ if ((*animal_hospital)->getID() == (*current_hospital)->id) { - ahz_scratch.push_back(*animal_hospital); + ahz_scratch.push_back(*animal_hospital); (*animal_hospital)->to_be_deleted = false; - break; - } + break; + } } } // Report what we're deleting by checking the to_be_deleted bool. - // - // Whatsever left is added to the pending add list + // + // Whatsever left is added to the pending add list for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { if ((*animal_hospital)->to_be_deleted) { out.print("Hospital #%d removed\n", (*animal_hospital)->getID()); delete *animal_hospital; - } + } } /* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */ for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { - bool new_hospital = true; - - for (vector::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { - if ((*animal_hospital)->getID() == (*current_hospital)->id) { - // Next if we're already here - new_hospital = false; - break; - } - } - - // Add it if its new - if (new_hospital == true) to_be_added.push_back(*current_hospital); - } - - /* Now add it to the scratch AHZ */ - for (vector::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { - // Add it to the vector + bool new_hospital = true; + + for (vector::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + // Next if we're already here + new_hospital = false; + break; + } + } + + // Add it if its new + if (new_hospital == true) to_be_added.push_back(*current_hospital); + } + + /* Now add it to the scratch AHZ */ + for (vector::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { + // Add it to the vector out.print("Adding new hospital #id at x1 %d y1: %d z: %d\n", (*current_hospital)->id, (*current_hospital)->x1, (*current_hospital)->y1, (*current_hospital)->z - ); - AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + ); + AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); ahz_scratch.push_back(hospital); - } + } - /* Copy the scratch to the AHZ */ - animal_hospital_zones = ahz_scratch; + /* Copy the scratch to the AHZ */ + animal_hospital_zones = ahz_scratch; // We always recheck the cache instead of counts because someone might have removed then added a hospital /* if (hospitals_cached != count_of_hospitals) { @@ -808,7 +808,7 @@ command_result dwarfvet (color_ostream &out, std::vector & paramet DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable && !dwarfvet_enabled) { - dwarfvet_enabled = true; + dwarfvet_enabled = true; } else if (!enable && dwarfvet_enabled) { delete_animal_hospital_vector(out); From e99187dc866c346c8451fde7809c9a0c5c86a3a5 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Sun, 5 Jun 2016 11:45:58 -0400 Subject: [PATCH 0042/1012] Add documentation for dwarfvet Signed-off-by: Michael Casadevall --- docs/Plugins.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 75d2d988d..34bbef32e 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1109,6 +1109,27 @@ Example: This will confiscate rotten and dropped food, garbage on the floors and any worn items with 'X' damage and above. +.. _dwarfvet: + +dwarfvet +============ +Enables Animal Caretaker functionality + +Always annoyed your dragons become useless after a minor injury? Well, with +dwarfvet, your animals become first rate members of your fort. It can also +be used to train medical skills. + +Animals need to be treated in an animal hospital, which is simply a hospital +that is also an animal training zone. The console will print out a list on game +load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker +labor to treat animals. Normal medical skills are used (and no experience is given +to the Animal Caretaker skill). + +Options: + +:enable: Enables Animal Caretakers to treat and manage animals +:disable: Turns off the plguin +:report: Reports all zones that the game considers animal hospitals .. _dwarfmonitor: From 0dc62a5c9c071008069e091d2f93bb4cc222573a Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Wed, 8 Jun 2016 04:07:50 -0400 Subject: [PATCH 0043/1012] Add to NEWS.rst and authors Signed-off-by: Michael Casadevall --- NEWS.rst | 1 + docs/Authors.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 7571ec45a..025aabfbc 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -40,6 +40,7 @@ Lua New Features ------------ - `gui/gm-editor` it's now possible to insert default types to containers. For primitive types leave the type entry empty, for references use ``*``. +- `dwarfvet` enables animal caretaking. Fixes ----- diff --git a/docs/Authors.rst b/docs/Authors.rst index c4462a987..cb6d0db7f 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -57,6 +57,7 @@ Meneth32 Meph Michon van Dooren MaienM miffedmap miffedmap +Michael Casadevall NCommander Mike Stewart thewonderidiot Mikko Juola Noeda Adeon MithrilTuxedo MithrilTuxedo From 628c8ee5bf4c59f00c41b9aa70c7c91828711d78 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Wed, 8 Jun 2016 06:10:18 -0400 Subject: [PATCH 0044/1012] Fix ordering in Authors.rst Signed-off-by: Michael Casadevall --- docs/Authors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index cb6d0db7f..bc1c1c7cb 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -55,9 +55,9 @@ Max maxthyme Max^TM melkor217 melkor217 Meneth32 Meph +Michael Casadevall NCommander Michon van Dooren MaienM miffedmap miffedmap -Michael Casadevall NCommander Mike Stewart thewonderidiot Mikko Juola Noeda Adeon MithrilTuxedo MithrilTuxedo From f908a1d1b661a655a5dd15c8950e7db07d2f4d57 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Jun 2016 21:44:15 -0400 Subject: [PATCH 0045/1012] Replace catsplosion plugin with a script Closes #938, #722 --- NEWS.rst | 1 + docs/Plugins.rst | 7 -- plugins/CMakeLists.txt | 1 - plugins/catsplosion.cpp | 166 ---------------------------------------- scripts/catsplosion.lua | 97 +++++++++++++++++++++++ 5 files changed, 98 insertions(+), 174 deletions(-) delete mode 100644 plugins/catsplosion.cpp create mode 100644 scripts/catsplosion.lua diff --git a/NEWS.rst b/NEWS.rst index 7571ec45a..781c22b7b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -51,6 +51,7 @@ Fixes Misc Improvements ----------------- +- `catsplosion`: now a lua script instead of a plugin - `fix/diplomats`: replaces ``fixdiplomats`` - `fix/merchants`: replaces ``fixmerchants`` diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 75d2d988d..ba502c38e 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2174,13 +2174,6 @@ Usage: * When viewing unit details, body-swaps into that unit. * In the main adventure mode screen, reverts transient swap. -.. _catsplosion: - -catsplosion -=========== -Makes cats just *multiply*. It is not a good idea to run this more than once or -twice. - .. _createitem: createitem diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index cc0de019f..c24b940b9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -78,7 +78,6 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(buildingplan buildingplan.cpp LINK_LIBRARIES buildingplan-lib) - DFHACK_PLUGIN(catsplosion catsplosion.cpp) DFHACK_PLUGIN(changeitem changeitem.cpp) DFHACK_PLUGIN(changelayer changelayer.cpp) DFHACK_PLUGIN(changevein changevein.cpp) diff --git a/plugins/catsplosion.cpp b/plugins/catsplosion.cpp deleted file mode 100644 index eee39a996..000000000 --- a/plugins/catsplosion.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Catsplosion -// By Zhentar , Further modified by dark_rabite, peterix, belal -// This work of evil makes animals pregnant -// and due within 2 in-game hours... - -#include -#include -#include -#include -#include // for rand() -#include // for std::transform -#include -#include -#include -using namespace std; - -#include "DFHack.h" -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "DataDefs.h" -#include -#include -#include - -using namespace DFHack; - -DFHACK_PLUGIN("catsplosion"); -REQUIRE_GLOBAL(world); - -command_result catsplosion (color_ostream &out, std::vector & parameters); - -// Mandatory init function. If you have some global state, create it here. -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - // Fill the command list with your commands. - commands.push_back(PluginCommand( - "catsplosion", "Make cats just /multiply/.", - catsplosion, false, - " Makes cats abnormally abundant, if you provide some base population ;)\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result catsplosion (color_ostream &out, std::vector & parameters) -{ - if (!Core::getInstance().isWorldLoaded()) - { - out.printerr("World not loaded.\n"); - return CR_FAILURE; - } - bool list_only = false; - list s_creatures; - if (parameters.size()) - { - for (size_t i = 0; i < parameters.size(); i++) - { - if (parameters[i] == "list") - { - list_only = true; - } - else - { - s_creatures.push_back(parameters[i]); - } - } - } - else - { - s_creatures.push_back("CAT"); - } - // make the creature list unique ... with cats. they are always unique - s_creatures.unique(); - // SUSPEND THE CORE! ::Evil laugh:: - CoreSuspender susp; - - uint32_t numCreatures; - if(!(numCreatures = Units::getNumCreatures())) - { - out.printerr("Can't get any creatures.\n"); - return CR_FAILURE; - } - - int totalcount=0; - int totalchanged=0; - int totalcreated=0; - string sextype; - - int maxlength = 0; - map > male_counts; - map > female_counts; - - // classify - for(uint32_t i =0;i < numCreatures;i++) - { - df::unit * creature = Units::GetCreature(i); - df::creature_raw *raw = world->raws.creatures.all[creature->race]; - if(Units::isFemale(creature)) - { - female_counts[raw->creature_id].push_back(creature); - male_counts[raw->creature_id].size(); - } - else // male, other, etc. - { - male_counts[raw->creature_id].push_back(creature); - female_counts[raw->creature_id].size(); //auto initialize the females as well - } - } - - if (list_only) - { - out.print("Type Male # Female #\n"); - for (auto it1 = male_counts.begin(); it1!=male_counts.end(); it1++) - { - out.print("%22s %6d %8d\n", it1->first.c_str(), it1->second.size(), female_counts[it1->first].size()); - } - return CR_OK; - } - - // process - for (list::iterator it = s_creatures.begin(); it != s_creatures.end(); ++it) - { - std::string clinput = *it; - std::transform(clinput.begin(), clinput.end(), clinput.begin(), ::toupper); - vector &females = female_counts[clinput]; - uint32_t sz_fem = females.size(); - totalcount += sz_fem; - for(uint32_t i = 0; i < sz_fem; i++)// max 1 pregnancy - { - df::unit * female = females[i]; - // accelerate - if(female->relations.pregnancy_timer != 0) - { - female->relations.pregnancy_timer = rand() % 100 + 1; - totalchanged++; - } - else if(!female->relations.pregnancy_genes) - { - df::unit_genes *preg = new df::unit_genes; - preg->appearance = female->appearance.genes.appearance; - preg->colors = female->appearance.genes.colors; - female->relations.pregnancy_genes = preg; - female->relations.pregnancy_timer = rand() % 100 + 1; - female->relations.pregnancy_caste = 1; - totalcreated ++; - } - } - } - if(totalchanged) - out.print("%d pregnancies accelerated.\n", totalchanged); - if(totalcreated) - out.print("%d pregnancies created.\n", totalcreated); - if (!totalcount) - { - out.printerr("No creatures matched.\n"); - return CR_FAILURE; - } - out.print("Total creatures checked: %d\n", totalcount); - return CR_OK; -} diff --git a/scripts/catsplosion.lua b/scripts/catsplosion.lua new file mode 100644 index 000000000..bd3728b9c --- /dev/null +++ b/scripts/catsplosion.lua @@ -0,0 +1,97 @@ +-- Make cats just /multiply/. +--[[=begin + +catsplosion +=========== +Makes cats (and other animals) just *multiply*. It is not a good idea to run this +more than once or twice. + +Usage: + +:catsplosion: Make all cats pregnant +:catsplosion list: List IDs of all animals on the map +:catsplosion ID ...: Make animals with given ID(s) pregnant + +Animals will give birth within two in-game hours (100 ticks or fewer). + +=end]] + +world = df.global.world + +if not dfhack.isWorldLoaded() then + qerror('World not loaded.') +end + +args = {...} +list_only = false +creatures = {} + +if #args > 0 then + for _, arg in pairs(args) do + if arg == 'list' then + list_only = true + else + creatures[arg:upper()] = true + end + end +else + creatures.CAT = true +end + +total = 0 +total_changed = 0 +total_created = 0 + +males = {} +females = {} + +for _, unit in pairs(world.units.all) do + local id = world.raws.creatures.all[unit.race].creature_id + males[id] = males[id] or {} + females[id] = females[id] or {} + table.insert((dfhack.units.isFemale(unit) and females or males)[id], unit) +end + +if list_only then + print("Type Male # Female #") + -- sort IDs alphabetically + local ids = {} + for id in pairs(males) do + table.insert(ids, id) + end + table.sort(ids) + for _, id in pairs(ids) do + print(("%22s %6d %8d"):format(id, #males[id], #females[id])) + end + return +end + +for id in pairs(creatures) do + local females = females[id] or {} + total = total + #females + for _, female in pairs(females) do + if female.relations.pregnancy_timer ~= 0 then + female.relations.pregnancy_timer = math.random(1, 100) + total_changed = total_changed + 1 + elseif not female.relations.pregnancy_genes then + local preg = df.unit_genes:new() + preg.appearance:assign(female.appearance.genes.appearance) + preg.colors:assign(female.appearance.genes.colors) + female.relations.pregnancy_genes = preg + female.relations.pregnancy_timer = math.random(1, 100) + female.relations.pregnancy_caste = 1 + total_created = total_created + 1 + end + end +end + +if total_changed ~= 0 then + print(("%d pregnancies accelerated."):format(total_changed)) +end +if total_created ~= 0 then + print(("%d pregnancies created."):format(total_created)) +end +if total == 0 then + qerror("No creatures matched.") +end +print(("Total creatures checked: %d"):format(total)) From a03b32846af3daaa135b85cf9d971b71cb313dae Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Mon, 13 Jun 2016 16:57:45 -0400 Subject: [PATCH 0046/1012] Modify DFHack to handle new produce() prototype. Major thanks to lethosor, and ragundo for their help in this Signed-off-by: Michael Casadevall --- library/modules/Items.cpp | 5 +++-- plugins/add-spatter.cpp | 4 ++-- plugins/createitem.cpp | 4 ++-- plugins/diggingInvaders/assignJob.cpp | 4 ++-- plugins/eventful.cpp | 7 ++++--- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 4c224a0d6..79bed0f89 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1375,8 +1375,9 @@ int32_t Items::createItem(df::item_type item_type, int16_t item_subtype, int16_t df::enums::game_type::game_type type = *df::global::gametype; prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, job_skill::NONE, - df::historical_entity::find(unit->civ_id), - ((type == df::enums::game_type::DWARF_MAIN) || (type == df::enums::game_type::DWARF_RECLAIM)) ? df::world_site::find(df::global::ui->site_id) : NULL); + df::historical_entity::find(unit->civ_id), 0, + ((type == df::enums::game_type::DWARF_MAIN) || (type == df::enums::game_type::DWARF_RECLAIM)) ? df::world_site::find(df::global::ui->site_id) : NULL, + 0); if ( out_items.size() != 1 ) return -1; diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 45d636d7c..1e835b8b6 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -249,7 +249,7 @@ struct product_hook : improvement_product { std::vector *in_reag, std::vector *in_items, int32_t quantity, df::job_skill skill, - df::historical_entity *entity, df::world_site *site) + df::historical_entity *entity, int32_t unk, df::world_site *site, void* unk2) ) { if (auto product = products[this]) { @@ -295,7 +295,7 @@ struct product_hook : improvement_product { return; } - INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, site); + INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, unk, site, unk2); } }; diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 156176bbe..e65d19793 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -86,8 +86,8 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it building = df::building::find(dest_building); prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, job_skill::NONE, - df::historical_entity::find(unit->civ_id), - (World::isFortressMode()) ? df::world_site::find(ui->site_id) : NULL); + df::historical_entity::find(unit->civ_id), 0, + (World::isFortressMode()) ? df::world_site::find(ui->site_id) : NULL, 0); if (!out_items.size()) return false; // if we asked to make shoes and we got twice as many as we asked, then we're okay diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index b7ca6f161..6e3874cbf 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -257,8 +257,8 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map in_reag; vector in_items; prod->produce(firstInvader, &out_products, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE, - df::historical_entity::find(firstInvader->civ_id), - df::world_site::find(df::global::ui->site_id)); + df::historical_entity::find(firstInvader->civ_id), 0, + df::world_site::find(df::global::ui->site_id), 0); if ( out_items.size() != 1 ) { out.print("%s, %d: wrong size: %d.\n", __FILE__, __LINE__, out_items.size()); diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 57bd0ed7f..45b94510c 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -298,12 +298,12 @@ struct product_hook : item_product { std::vector *in_reag, std::vector *in_items, int32_t quantity, df::job_skill skill, - df::historical_entity *entity, df::world_site *site) + df::historical_entity *entity, int32_t unk, df::world_site *site, void* unk2) ) { color_ostream_proxy out(Core::getInstance().getConsole()); auto product = products[this]; if ( !product ) { - INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, site); + INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, unk, site, unk2); return; } df::reaction* this_reaction=product->react; @@ -314,7 +314,8 @@ struct product_hook : item_product { return; size_t out_item_count = out_items->size(); - INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, site); + + INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, unk, site, unk2); if ( out_items->size() == out_item_count ) return; //if it produced something, call the scripts From 90c7dc41ecd04da426157b8a18532b377834bed8 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Mon, 13 Jun 2016 17:10:58 -0400 Subject: [PATCH 0047/1012] Update where library/xml to point to current master Signed-off-by: Michael Casadevall --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index fc914791d..e57ddb652 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fc914791dede6811f238ffaa4609690203aaa13a +Subproject commit e57ddb652423680698756ecfc84fdcf7fb840948 From d51968f26ee4a273af8c76fbfb0ccc2619a2052f Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 13 Jun 2016 17:12:16 -0400 Subject: [PATCH 0048/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index fc914791d..e57ddb652 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fc914791dede6811f238ffaa4609690203aaa13a +Subproject commit e57ddb652423680698756ecfc84fdcf7fb840948 From f07bc03c69f704027ecd0fd3ff0d3a0016585061 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 13:21:51 -0400 Subject: [PATCH 0049/1012] Update clsocket and Open()/Listen() usage --- depends/clsocket | 2 +- library/RemoteClient.cpp | 2 +- library/RemoteServer.cpp | 2 +- plugins/luasocket.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/depends/clsocket b/depends/clsocket index 55b6bfb81..b08d765a7 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit 55b6bfb8121991876282f1ba7db10ab091391e14 +Subproject commit b08d765a7ddacf66693cb2721833b3b56b092aa3 diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 09861ad5f..07f9c6961 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -155,7 +155,7 @@ bool RemoteClient::connect(int port) return false; } - if (!socket->Open((const uint8 *)"localhost", port)) + if (!socket->Open("localhost", port)) { default_output().printerr("Could not connect to localhost: %d\n", port); return false; diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 0a05833ee..df65e8e5a 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -375,7 +375,7 @@ bool ServerMain::listen(int port) socket->Initialize(); - if (!socket->Listen((const uint8 *)"127.0.0.1", port)) + if (!socket->Listen("127.0.0.1", port)) return false; thread = new tthread::thread(threadFn, this); diff --git a/plugins/luasocket.cpp b/plugins/luasocket.cpp index fba059fdc..0259c562f 100644 --- a/plugins/luasocket.cpp +++ b/plugins/luasocket.cpp @@ -83,7 +83,7 @@ static int lua_socket_bind(std::string ip,int port) handle_error(err,false); } sock->SetBlocking(); - if(!sock->Listen((uint8_t*)ip.c_str(),port)) + if(!sock->Listen(ip.c_str(),port)) { handle_error(sock->GetSocketError(),false); } @@ -238,7 +238,7 @@ static int lua_socket_connect(std::string ip,int port) delete sock; throw std::runtime_error(CSimpleSocket::DescribeError(err)); } - if(!sock->Open((const uint8_t*)ip.c_str(),port)) + if(!sock->Open(ip.c_str(),port)) { CSimpleSocket::CSocketError err=sock->GetSocketError(); delete sock; From 4f3a2b39d785cead3895d2a8d8f7b1f5374fedf6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 13:21:59 -0400 Subject: [PATCH 0050/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index e57ddb652..5a9ded892 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e57ddb652423680698756ecfc84fdcf7fb840948 +Subproject commit 5a9ded89222d87daa006fee96e4605ecf0567531 From 450fcdba3110fa55e118661298f28a326f47a4e3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 14:07:01 -0400 Subject: [PATCH 0051/1012] Bump version to alpha1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aedb6037a..9b9b985c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.03") -SET(DFHACK_RELEASE "alpha0") +SET(DFHACK_RELEASE "alpha1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 39c96c474a827cb01ffc7a2ba8b450be8aa14565 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Tue, 14 Jun 2016 16:13:26 -0400 Subject: [PATCH 0052/1012] Fix ordering, news, and indentation Signed-off-by: Michael Casadevall --- NEWS.rst | 5 ++++- docs/Plugins.rst | 44 +++++++++++++++++++++--------------------- plugins/CMakeLists.txt | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 025aabfbc..1b1b594b5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -37,10 +37,13 @@ Lua --- - Label widgets can now easily register handlers for mouse clicks +New Plugins +----------- +- `dwarfvet` enables animal caretaking. + New Features ------------ - `gui/gm-editor` it's now possible to insert default types to containers. For primitive types leave the type entry empty, for references use ``*``. -- `dwarfvet` enables animal caretaking. Fixes ----- diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 34bbef32e..f352d97fe 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1109,28 +1109,6 @@ Example: This will confiscate rotten and dropped food, garbage on the floors and any worn items with 'X' damage and above. -.. _dwarfvet: - -dwarfvet -============ -Enables Animal Caretaker functionality - -Always annoyed your dragons become useless after a minor injury? Well, with -dwarfvet, your animals become first rate members of your fort. It can also -be used to train medical skills. - -Animals need to be treated in an animal hospital, which is simply a hospital -that is also an animal training zone. The console will print out a list on game -load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker -labor to treat animals. Normal medical skills are used (and no experience is given -to the Animal Caretaker skill). - -Options: - -:enable: Enables Animal Caretakers to treat and manage animals -:disable: Turns off the plguin -:report: Reports all zones that the game considers animal hospitals - .. _dwarfmonitor: dwarfmonitor @@ -1208,6 +1186,28 @@ Some widgets support additional options: displayed as ``-1`` when the cursor is outside of the DF window; otherwise, nothing will be displayed. +.. _dwarfvet: + +dwarfvet +============ +Enables Animal Caretaker functionality + +Always annoyed your dragons become useless after a minor injury? Well, with +dwarfvet, your animals become first rate members of your fort. It can also +be used to train medical skills. + +Animals need to be treated in an animal hospital, which is simply a hospital +that is also an animal training zone. The console will print out a list on game +load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker +labor to treat animals. Normal medical skills are used (and no experience is given +to the Animal Caretaker skill). + +Options: + +:enable: Enables Animal Caretakers to treat and manage animals +:disable: Turns off the plguin +:report: Reports all zones that the game considers animal hospitals + .. _workNow: workNow diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fbf33f459..79d2f0092 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,7 +93,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) - DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) + DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) From 6258870e2c8c65b9bdb620af243270cea391b69e Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 19:40:48 -0400 Subject: [PATCH 0053/1012] Remove scripts/ folder --- .gitmodules | 18 - scripts/3rdparty/dscorbett | 1 - scripts/3rdparty/kane-t | 1 - scripts/3rdparty/lethosor | 1 - scripts/3rdparty/maienm | 1 - scripts/3rdparty/maxthyme | 1 - scripts/3rdparty/roses | 1 - scripts/CMakeLists.txt | 14 - scripts/Scripts.cmake | 19 - scripts/about.txt | 2 - scripts/adaptation.rb | 104 - scripts/add-thought.lua | 93 - scripts/armoks-blessing.lua | 202 -- scripts/autofarm.rb | 192 -- scripts/autolabor-artisans.lua | 79 - scripts/autounsuspend.rb | 49 - scripts/ban-cooking.rb | 132 -- scripts/binpatch.lua | 51 - scripts/brainwash.lua | 70 - scripts/burial.lua | 27 - scripts/catsplosion.lua | 97 - scripts/colonies.lua | 82 - scripts/create-items.rb | 207 -- scripts/deathcause.rb | 75 - scripts/deteriorateclothes.rb | 81 - scripts/deterioratecorpses.rb | 106 - scripts/deterioratefood.rb | 96 - scripts/devel/about.txt | 6 - scripts/devel/all-bob.lua | 15 - scripts/devel/check-release.lua | 59 - scripts/devel/clear-script-env.lua | 26 - scripts/devel/cmptiles.lua | 50 - scripts/devel/export-dt-ini.lua | 515 ----- scripts/devel/find-offsets.lua | 1936 ----------------- scripts/devel/inject-raws.lua | 197 -- scripts/devel/inspect-screen.lua | 110 - scripts/devel/light.lua | 388 ---- scripts/devel/list-filters.lua | 78 - scripts/devel/lsmem.lua | 21 - scripts/devel/lua-example.lua | 14 - scripts/devel/make-dt.pl | 492 ----- scripts/devel/nuke-items.lua | 22 - scripts/devel/pop-screen.lua | 10 - scripts/devel/prepare-save.lua | 98 - scripts/devel/print-args.lua | 15 - scripts/devel/print-args2.lua | 17 - scripts/devel/save-version.lua | 154 -- scripts/devel/scanitemother.rb | 16 - scripts/devel/spawn-unit-helper.rb | 35 - scripts/devel/test-perlin.lua | 193 -- scripts/devel/unforbidall.rb | 10 - scripts/devel/unit-path.lua | 225 -- scripts/devel/watch-minecarts.lua | 83 - scripts/digfort.rb | 94 - scripts/drain-aquifer.lua | 40 - scripts/elevate-mental.lua | 52 - scripts/elevate-physical.lua | 51 - scripts/emigration.lua | 132 -- scripts/exportlegends.lua | 836 ------- scripts/exterminate.rb | 192 -- scripts/feature.lua | 75 - scripts/fix-ster.lua | 122 -- scripts/fix/about.txt | 1 - scripts/fix/blood-del.lua | 62 - scripts/fix/build-location.lua | 45 - scripts/fix/dead-units.lua | 37 - scripts/fix/diplomats.lua | 106 - scripts/fix/dry-buckets.lua | 28 - scripts/fix/fat-dwarves.lua | 33 - scripts/fix/feeding-timers.lua | 43 - scripts/fix/item-occupancy.lua | 131 -- scripts/fix/loyaltycascade.rb | 70 - scripts/fix/merchants.lua | 104 - scripts/fix/population-cap.lua | 47 - scripts/fix/stable-temp.lua | 71 - scripts/fix/stuckdoors.rb | 32 - scripts/fixnaked.lua | 50 - scripts/forum-dwarves.lua | 133 -- scripts/full-heal.lua | 153 -- scripts/gaydar.lua | 208 -- scripts/growcrops.rb | 64 - scripts/gui/about.txt | 6 - scripts/gui/advfort.lua | 1894 ---------------- scripts/gui/advfort_items.lua | 226 -- scripts/gui/assign-rack.lua | 209 -- scripts/gui/autobutcher.lua | 662 ------ scripts/gui/choose-weapons.lua | 169 -- scripts/gui/clone-uniform.lua | 58 - scripts/gui/companion-order.lua | 490 ----- scripts/gui/confirm-opts.lua | 74 - scripts/gui/create-item.lua | 260 --- scripts/gui/dfstatus.lua | 229 -- scripts/gui/family-affairs.lua | 292 --- scripts/gui/gm-editor.lua | 536 ----- scripts/gui/gm-unit.lua | 604 ----- scripts/gui/guide-path.lua | 205 -- scripts/gui/hack-wish.lua | 8 - scripts/gui/hello-world.lua | 31 - scripts/gui/liquids.lua | 326 --- scripts/gui/mechanisms.lua | 146 -- scripts/gui/mod-manager.lua | 361 --- scripts/gui/no-dfhack-init.lua | 37 - scripts/gui/power-meter.lua | 135 -- scripts/gui/prerelease-warning.lua | 141 -- scripts/gui/rename.lua | 93 - scripts/gui/room-list.lua | 259 --- scripts/gui/siege-engine.lua | 536 ----- scripts/gui/stockpiles.lua | 78 - scripts/gui/unit-info-viewer.lua | 800 ------- scripts/gui/workflow.lua | 1099 ---------- scripts/gui/workshop-job.lua | 332 --- scripts/hfs-pit.lua | 105 - scripts/hotkey-notes.lua | 16 - scripts/item-descriptions.lua | 663 ------ scripts/lever.rb | 152 -- scripts/locate-ore.rb | 102 - scripts/lua.lua | 91 - scripts/make-legendary.lua | 141 -- scripts/make-monarch.lua | 39 - scripts/markdown.lua | 239 -- scripts/masspit.rb | 51 - scripts/migrants-now.lua | 16 - scripts/modtools/about.txt | 17 - scripts/modtools/add-syndrome.lua | 114 - scripts/modtools/anonymous-script.lua | 27 - scripts/modtools/create-item.lua | 140 -- scripts/modtools/create-unit.lua | 575 ----- scripts/modtools/equip-item.lua | 105 - scripts/modtools/extra-gamelog.lua | 192 -- scripts/modtools/force.lua | 90 - scripts/modtools/interaction-trigger.lua | 176 -- scripts/modtools/invader-item-destroyer.lua | 184 -- scripts/modtools/item-trigger.lua | 294 --- scripts/modtools/moddable-gods.lua | 102 - scripts/modtools/outside-only.lua | 141 -- scripts/modtools/projectile-trigger.lua | 108 - scripts/modtools/random-trigger.lua | 179 -- scripts/modtools/reaction-product-trigger.lua | 132 -- .../modtools/reaction-trigger-transition.lua | 147 -- scripts/modtools/reaction-trigger.lua | 241 -- scripts/modtools/skill-change.lua | 115 - scripts/modtools/spawn-flow.lua | 91 - scripts/modtools/syndrome-trigger.lua | 122 -- scripts/modtools/transform-unit.lua | 164 -- scripts/multicmd.rb | 16 - scripts/points.lua | 13 - scripts/position.lua | 51 - scripts/pref-adjust.lua | 106 - scripts/putontable.lua | 36 - scripts/quicksave.lua | 39 - scripts/region-pops.lua | 199 -- scripts/rejuvenate.lua | 30 - scripts/remove-stress.lua | 41 - scripts/remove-wear.lua | 62 - scripts/repeat.lua | 85 - scripts/setfps.lua | 18 - scripts/show-unit-syndromes.rb | 1035 --------- scripts/siren.lua | 136 -- scripts/source.rb | 114 - scripts/spawnunit.lua | 65 - scripts/startdwarf.rb | 20 - scripts/starvingdead.rb | 92 - scripts/stripcaged.rb | 227 -- scripts/superdwarf.rb | 91 - scripts/teleport.lua | 58 - scripts/tidlers.lua | 10 - scripts/twaterlvl.lua | 11 - scripts/undump-buildings.lua | 36 - scripts/unsuspend.rb | 28 - scripts/view-item-info.lua | 407 ---- scripts/warn-starving.lua | 140 -- scripts/weather.lua | 46 - 172 files changed, 28178 deletions(-) delete mode 160000 scripts/3rdparty/dscorbett delete mode 160000 scripts/3rdparty/kane-t delete mode 160000 scripts/3rdparty/lethosor delete mode 160000 scripts/3rdparty/maienm delete mode 160000 scripts/3rdparty/maxthyme delete mode 160000 scripts/3rdparty/roses delete mode 100644 scripts/CMakeLists.txt delete mode 100644 scripts/Scripts.cmake delete mode 100644 scripts/about.txt delete mode 100644 scripts/adaptation.rb delete mode 100644 scripts/add-thought.lua delete mode 100644 scripts/armoks-blessing.lua delete mode 100644 scripts/autofarm.rb delete mode 100644 scripts/autolabor-artisans.lua delete mode 100644 scripts/autounsuspend.rb delete mode 100644 scripts/ban-cooking.rb delete mode 100644 scripts/binpatch.lua delete mode 100644 scripts/brainwash.lua delete mode 100644 scripts/burial.lua delete mode 100644 scripts/catsplosion.lua delete mode 100644 scripts/colonies.lua delete mode 100644 scripts/create-items.rb delete mode 100644 scripts/deathcause.rb delete mode 100644 scripts/deteriorateclothes.rb delete mode 100644 scripts/deterioratecorpses.rb delete mode 100644 scripts/deterioratefood.rb delete mode 100644 scripts/devel/about.txt delete mode 100644 scripts/devel/all-bob.lua delete mode 100644 scripts/devel/check-release.lua delete mode 100644 scripts/devel/clear-script-env.lua delete mode 100644 scripts/devel/cmptiles.lua delete mode 100644 scripts/devel/export-dt-ini.lua delete mode 100644 scripts/devel/find-offsets.lua delete mode 100644 scripts/devel/inject-raws.lua delete mode 100644 scripts/devel/inspect-screen.lua delete mode 100644 scripts/devel/light.lua delete mode 100644 scripts/devel/list-filters.lua delete mode 100644 scripts/devel/lsmem.lua delete mode 100644 scripts/devel/lua-example.lua delete mode 100644 scripts/devel/make-dt.pl delete mode 100644 scripts/devel/nuke-items.lua delete mode 100644 scripts/devel/pop-screen.lua delete mode 100644 scripts/devel/prepare-save.lua delete mode 100644 scripts/devel/print-args.lua delete mode 100644 scripts/devel/print-args2.lua delete mode 100644 scripts/devel/save-version.lua delete mode 100644 scripts/devel/scanitemother.rb delete mode 100644 scripts/devel/spawn-unit-helper.rb delete mode 100644 scripts/devel/test-perlin.lua delete mode 100644 scripts/devel/unforbidall.rb delete mode 100644 scripts/devel/unit-path.lua delete mode 100644 scripts/devel/watch-minecarts.lua delete mode 100644 scripts/digfort.rb delete mode 100644 scripts/drain-aquifer.lua delete mode 100644 scripts/elevate-mental.lua delete mode 100644 scripts/elevate-physical.lua delete mode 100644 scripts/emigration.lua delete mode 100644 scripts/exportlegends.lua delete mode 100644 scripts/exterminate.rb delete mode 100644 scripts/feature.lua delete mode 100644 scripts/fix-ster.lua delete mode 100644 scripts/fix/about.txt delete mode 100644 scripts/fix/blood-del.lua delete mode 100644 scripts/fix/build-location.lua delete mode 100644 scripts/fix/dead-units.lua delete mode 100644 scripts/fix/diplomats.lua delete mode 100644 scripts/fix/dry-buckets.lua delete mode 100644 scripts/fix/fat-dwarves.lua delete mode 100644 scripts/fix/feeding-timers.lua delete mode 100644 scripts/fix/item-occupancy.lua delete mode 100644 scripts/fix/loyaltycascade.rb delete mode 100644 scripts/fix/merchants.lua delete mode 100644 scripts/fix/population-cap.lua delete mode 100644 scripts/fix/stable-temp.lua delete mode 100644 scripts/fix/stuckdoors.rb delete mode 100644 scripts/fixnaked.lua delete mode 100644 scripts/forum-dwarves.lua delete mode 100644 scripts/full-heal.lua delete mode 100644 scripts/gaydar.lua delete mode 100644 scripts/growcrops.rb delete mode 100644 scripts/gui/about.txt delete mode 100644 scripts/gui/advfort.lua delete mode 100644 scripts/gui/advfort_items.lua delete mode 100644 scripts/gui/assign-rack.lua delete mode 100644 scripts/gui/autobutcher.lua delete mode 100644 scripts/gui/choose-weapons.lua delete mode 100644 scripts/gui/clone-uniform.lua delete mode 100644 scripts/gui/companion-order.lua delete mode 100644 scripts/gui/confirm-opts.lua delete mode 100644 scripts/gui/create-item.lua delete mode 100644 scripts/gui/dfstatus.lua delete mode 100644 scripts/gui/family-affairs.lua delete mode 100644 scripts/gui/gm-editor.lua delete mode 100644 scripts/gui/gm-unit.lua delete mode 100644 scripts/gui/guide-path.lua delete mode 100644 scripts/gui/hack-wish.lua delete mode 100644 scripts/gui/hello-world.lua delete mode 100644 scripts/gui/liquids.lua delete mode 100644 scripts/gui/mechanisms.lua delete mode 100644 scripts/gui/mod-manager.lua delete mode 100644 scripts/gui/no-dfhack-init.lua delete mode 100644 scripts/gui/power-meter.lua delete mode 100644 scripts/gui/prerelease-warning.lua delete mode 100644 scripts/gui/rename.lua delete mode 100644 scripts/gui/room-list.lua delete mode 100644 scripts/gui/siege-engine.lua delete mode 100644 scripts/gui/stockpiles.lua delete mode 100644 scripts/gui/unit-info-viewer.lua delete mode 100644 scripts/gui/workflow.lua delete mode 100644 scripts/gui/workshop-job.lua delete mode 100644 scripts/hfs-pit.lua delete mode 100644 scripts/hotkey-notes.lua delete mode 100644 scripts/item-descriptions.lua delete mode 100644 scripts/lever.rb delete mode 100644 scripts/locate-ore.rb delete mode 100644 scripts/lua.lua delete mode 100644 scripts/make-legendary.lua delete mode 100644 scripts/make-monarch.lua delete mode 100644 scripts/markdown.lua delete mode 100644 scripts/masspit.rb delete mode 100644 scripts/migrants-now.lua delete mode 100644 scripts/modtools/about.txt delete mode 100644 scripts/modtools/add-syndrome.lua delete mode 100644 scripts/modtools/anonymous-script.lua delete mode 100644 scripts/modtools/create-item.lua delete mode 100644 scripts/modtools/create-unit.lua delete mode 100644 scripts/modtools/equip-item.lua delete mode 100644 scripts/modtools/extra-gamelog.lua delete mode 100644 scripts/modtools/force.lua delete mode 100644 scripts/modtools/interaction-trigger.lua delete mode 100644 scripts/modtools/invader-item-destroyer.lua delete mode 100644 scripts/modtools/item-trigger.lua delete mode 100644 scripts/modtools/moddable-gods.lua delete mode 100644 scripts/modtools/outside-only.lua delete mode 100644 scripts/modtools/projectile-trigger.lua delete mode 100644 scripts/modtools/random-trigger.lua delete mode 100644 scripts/modtools/reaction-product-trigger.lua delete mode 100644 scripts/modtools/reaction-trigger-transition.lua delete mode 100644 scripts/modtools/reaction-trigger.lua delete mode 100644 scripts/modtools/skill-change.lua delete mode 100644 scripts/modtools/spawn-flow.lua delete mode 100644 scripts/modtools/syndrome-trigger.lua delete mode 100644 scripts/modtools/transform-unit.lua delete mode 100644 scripts/multicmd.rb delete mode 100644 scripts/points.lua delete mode 100644 scripts/position.lua delete mode 100644 scripts/pref-adjust.lua delete mode 100644 scripts/putontable.lua delete mode 100644 scripts/quicksave.lua delete mode 100644 scripts/region-pops.lua delete mode 100644 scripts/rejuvenate.lua delete mode 100644 scripts/remove-stress.lua delete mode 100644 scripts/remove-wear.lua delete mode 100644 scripts/repeat.lua delete mode 100644 scripts/setfps.lua delete mode 100644 scripts/show-unit-syndromes.rb delete mode 100644 scripts/siren.lua delete mode 100644 scripts/source.rb delete mode 100644 scripts/spawnunit.lua delete mode 100644 scripts/startdwarf.rb delete mode 100644 scripts/starvingdead.rb delete mode 100644 scripts/stripcaged.rb delete mode 100644 scripts/superdwarf.rb delete mode 100644 scripts/teleport.lua delete mode 100644 scripts/tidlers.lua delete mode 100644 scripts/twaterlvl.lua delete mode 100644 scripts/undump-buildings.lua delete mode 100644 scripts/unsuspend.rb delete mode 100644 scripts/view-item-info.lua delete mode 100644 scripts/warn-starving.lua delete mode 100644 scripts/weather.lua diff --git a/.gitmodules b/.gitmodules index 6085402ac..9f1b48395 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,21 +10,3 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git -[submodule "scripts/3rdparty/lethosor"] - path = scripts/3rdparty/lethosor - url = git://github.com/DFHack/lethosor-scripts -[submodule "scripts/3rdparty/roses"] - path = scripts/3rdparty/roses - url = git://github.com/DFHack/roses-scripts.git -[submodule "scripts/3rdparty/maxthyme"] - path = scripts/3rdparty/maxthyme - url = git://github.com/DFHack/maxthyme-scripts.git -[submodule "scripts/3rdparty/dscorbett"] - path = scripts/3rdparty/dscorbett - url = git://github.com/DFHack/dscorbett-scripts.git -[submodule "scripts/3rdparty/kane-t"] - path = scripts/3rdparty/kane-t - url = git://github.com/DFHack/kane-t-scripts.git -[submodule "scripts/3rdparty/maienm"] - path = scripts/3rdparty/maienm - url = git://github.com/DFHack/maienm-scripts.git diff --git a/scripts/3rdparty/dscorbett b/scripts/3rdparty/dscorbett deleted file mode 160000 index 4353c1040..000000000 --- a/scripts/3rdparty/dscorbett +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4353c10401ced7aec89f002947d1252f30237789 diff --git a/scripts/3rdparty/kane-t b/scripts/3rdparty/kane-t deleted file mode 160000 index 0a75d5ff6..000000000 --- a/scripts/3rdparty/kane-t +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0a75d5ff69916cf9b3739f4b20d36ab4cfdcf824 diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor deleted file mode 160000 index 704aed444..000000000 --- a/scripts/3rdparty/lethosor +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 704aed4447f27ae802dae6994479ebc9c46568cc diff --git a/scripts/3rdparty/maienm b/scripts/3rdparty/maienm deleted file mode 160000 index 45c78449e..000000000 --- a/scripts/3rdparty/maienm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 45c78449e71d1ba263044fb00108509088ad0026 diff --git a/scripts/3rdparty/maxthyme b/scripts/3rdparty/maxthyme deleted file mode 160000 index b337e931b..000000000 --- a/scripts/3rdparty/maxthyme +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b337e931b8b7a167ee5ce1ac6b5c3155c291f260 diff --git a/scripts/3rdparty/roses b/scripts/3rdparty/roses deleted file mode 160000 index 4b6e77265..000000000 --- a/scripts/3rdparty/roses +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b6e772654df6805b66f77900a4618bbf9b54dab diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt deleted file mode 100644 index e9d899970..000000000 --- a/scripts/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -include(Scripts.cmake) -DFHACK_3RDPARTY_SCRIPT_REPO(dscorbett) -DFHACK_3RDPARTY_SCRIPT_REPO(kane-t) -DFHACK_3RDPARTY_SCRIPT_REPO(lethosor) -DFHACK_3RDPARTY_SCRIPT_REPO(maienm) -DFHACK_3RDPARTY_SCRIPT_REPO(maxthyme) -# DFHACK_3RDPARTY_SCRIPT_REPO(roses) - -install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts - DESTINATION ${DFHACK_DATA_DESTINATION} - FILES_MATCHING PATTERN "*.lua" - PATTERN "*.rb" - PATTERN "3rdparty" EXCLUDE - ) diff --git a/scripts/Scripts.cmake b/scripts/Scripts.cmake deleted file mode 100644 index 3e7de5424..000000000 --- a/scripts/Scripts.cmake +++ /dev/null @@ -1,19 +0,0 @@ -include(../plugins/Plugins.cmake) - -MACRO(DFHACK_SCRIPTS) - PARSE_ARGUMENTS(SCRIPT - "SUBDIRECTORY" - "SOME_OPT" - ${ARGN} - ) - CAR(SCRIPT_SUBDIRECTORY ${SCRIPT_SUBDIRECTORY}) - install(FILES ${SCRIPT_DEFAULT_ARGS} - DESTINATION ${DFHACK_DATA_DESTINATION}/scripts/${SCRIPT_SUBDIRECTORY}) -ENDMACRO() - -MACRO(DFHACK_3RDPARTY_SCRIPT_REPO repo_path) - if(NOT EXISTS ${dfhack_SOURCE_DIR}/scripts/3rdparty/${repo_path}/CMakeLists.txt) - MESSAGE(SEND_ERROR "Script submodule scripts/3rdparty/${repo_path} does not exist - run `git submodule update --init`.") - endif() - add_subdirectory(3rdparty/${repo_path}) -ENDMACRO() diff --git a/scripts/about.txt b/scripts/about.txt deleted file mode 100644 index 818891af5..000000000 --- a/scripts/about.txt +++ /dev/null @@ -1,2 +0,0 @@ -Basic scripts are not stored in any subdirectory, and can be invoked directly. -They are generally useful tools for any player. diff --git a/scripts/adaptation.rb b/scripts/adaptation.rb deleted file mode 100644 index 50cf80a86..000000000 --- a/scripts/adaptation.rb +++ /dev/null @@ -1,104 +0,0 @@ -# View or set cavern adaptation levels -# based on removebadthoughts.rb -=begin - -adaptation -========== -View or set level of cavern adaptation for the selected unit or the whole fort. -Usage: ``adaptation [value]``. The ``value`` must be -between 0 and 800,000 inclusive. - -=end - -# Color constants, values mapped to color_value enum in include/ColorText.h -COLOR_GREEN = 2 -COLOR_RED = 4 -COLOR_YELLOW = 14 -COLOR_WHITE = 15 - -def usage(s) - if nil != s - puts(s) - end - puts "Usage: adaptation [value]" - throw :script_finished -end - -mode = $script_args[0] || 'help' -who = $script_args[1] -value = $script_args[2] - -if 'help' == mode - usage(nil) -elsif 'show' != mode && 'set' != mode - usage("Invalid mode '#{mode}': must be either 'show' or 'set'") -end - -if nil == who - usage("Target not specified") -elsif 'him' != who && 'all' != who - usage("Invalid target '#{who}'") -end - -if 'set' == mode - if nil == value - usage("Value not specified") - elsif !/[[:digit:]]/.match(value) - usage("Invalid value '#{value}'") - end - - if 0 > value.to_i || 800000 < value.to_i - usage("Value must be between 0 and 800000") - end - value = value.to_i -end - -num_set = 0 - -set_adaptation_value = lambda { |u,v| - next if !df.unit_iscitizen(u) - next if u.flags1.dead - u.status.misc_traits.each { |t| - if t.id == :CaveAdapt - # TBD: expose the color_ostream console and color values of - # t.value based on adaptation level - if mode == 'show' - if df.respond_to?(:print_color) - print "Unit #{u.id} (#{u.name}) has an adaptation of " - case t.value - when 0..399999 - #df.print_color(COLOR_GREEN, "#{t.value}\n") - print "#{t.value}\n" - when 400000..599999 - df.print_color(COLOR_YELLOW, "#{t.value}\n") - else - df.print_color(COLOR_RED, "#{t.value}\n") - end - else - puts "Unit #{u.id} (#{u.name}) has an adaptation of #{t.value}" - end - elsif mode == 'set' - puts "Unit #{u.id} (#{u.name}) changed from #{t.value} to #{v}" - t.value = v - num_set += 1 - end - end - } -} - -case who -when 'him' - if u = df.unit_find - set_adaptation_value[u,value] - else - puts 'Please select a dwarf ingame' - end -when 'all' - df.unit_citizens.each { |uu| - set_adaptation_value[uu,value] - } -end - -if 'set' == mode - puts "#{num_set} unit#{'s' if num_set != 1} updated." -end diff --git a/scripts/add-thought.lua b/scripts/add-thought.lua deleted file mode 100644 index 0f82b555c..000000000 --- a/scripts/add-thought.lua +++ /dev/null @@ -1,93 +0,0 @@ --- Adds emotions to creatures. ---@ module = true - ---[[=begin - -add-thought -=========== -Adds a thought or emotion to the selected unit. Can be used by other scripts, -or the gui invoked by running ``add-thought gui`` with a unit selected. - -=end]] - -local utils=require('utils') - -function addEmotionToUnit(unit,thought,emotion,severity,strength,subthought) - local emotions=unit.status.current_soul.personality.emotions - if not (type(emotion)=='number') then emotion=df.emotion_type[emotion] end - if not (type(thought)=='number') then thought=df.unit_thought_type[thought] end - emotions:insert('#',{new=df.unit_personality.T_emotions, - type=emotion, - unk2=1, - strength=strength, - thought=thought, - subthought=subthought, - severity=severity, - flags=0, - unk7=0, - year=df.global.cur_year, - year_tick=df.global.cur_year_tick - }) - local divider=df.emotion_type.attrs[emotion].divider - if divider~=0 then - unit.status.current_soul.personality.stress_level=unit.status.current_soul.personality.stress_level+math.ceil(severity/df.emotion_type.attrs[emotion].divider) - end -end - -validArgs = validArgs or utils.invert({ - 'unit', - 'thought', - 'emotion', - 'severity', - 'strength', - 'subthought', - 'gui' -}) - -function tablify(iterableObject) - t={} - for k,v in ipairs(iterableObject) do - t[k] = v~=nil and v or 'nil' - end - return t -end - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) - -local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) - -if not unit then qerror('A unit must be specified or selected.') end -if args.gui then - local script=require('gui.script') - script.start(function() - local tok,thought=script.showListPrompt('emotions','Which thought?',COLOR_WHITE,tablify(df.unit_thought_type),10,true) - if tok then - local eok,emotion=script.showListPrompt('emotions','Which emotion?',COLOR_WHITE,tablify(df.emotion_type),10,true) - if eok then - local sok,severity=script.showInputPrompt('emotions','At what severity?',COLOR_WHITE,'0') - if sok then - local stok,strength=script.showInputPrompt('emotions','At what strength?',COLOR_WHITE,'0') - if stok then - addEmotionToUnit(unit,thought,emotion,severity,strength,0) - end - end - end - end - end) -else - local thought = args.thought or 180 - - local emotion = args.emotion or -1 - - local severity = args.severity or 0 - - local subthought = args.subthought or 0 - - local strength = args.strength or 0 - - addEmotionToUnit(unit,thought,emotion,severity,strength,subthought) -end diff --git a/scripts/armoks-blessing.lua b/scripts/armoks-blessing.lua deleted file mode 100644 index dfa99daad..000000000 --- a/scripts/armoks-blessing.lua +++ /dev/null @@ -1,202 +0,0 @@ --- Adjust all attributes of all dwarves to an ideal --- by vjek ---[[=begin - -armoks-blessing -=============== -Runs the equivalent of `rejuvenate`, `elevate-physical`, `elevate-mental`, and -`brainwash` on all dwarves currently on the map. This is an extreme change, -which sets every stat to an ideal - legendary skills, great traits, and -easy-to-satisfy preferences. - -Without arguments, all attributes, age & personalities are adjusted. -Arguments allow for skills to be adjusted as well. - -=end]] -function rejuvenate(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local current_year=df.global.cur_year - local newbirthyear=current_year - 20 - if unit.relations.birth_year < newbirthyear then - unit.relations.birth_year=newbirthyear - end - if unit.relations.old_year < current_year+100 then - unit.relations.old_year=current_year+100 - end - -end --- --------------------------------------------------------------------------- -function brainwash_unit(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local profile ={75,25,25,75,25,25,25,99,25,25,25,50,75,50,25,75,75,50,75,75,25,75,75,50,75,25,50,25,75,75,75,25,75,75,25,75,25,25,75,75,25,75,75,75,25,75,75,25,25,50} - local i - - for i=1, #profile do - unit.status.current_soul.personality.traits[i-1]=profile[i] - end - -end --- --------------------------------------------------------------------------- -function elevate_attributes(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local ok,f,t,k = pcall(pairs,unit.status.current_soul.mental_attrs) - if ok then - for k,v in f,t,k do - v.value=v.max_value - end - end - - local ok,f,t,k = pcall(pairs,unit.body.physical_attrs) - if ok then - for k,v in f,t,k do - v.value=v.max_value - end - end -end --- --------------------------------------------------------------------------- --- this function will return the number of elements, starting at zero. --- useful for counting things where #foo doesn't work -function count_this(to_be_counted) - local count = -1 - local var1 = "" - while var1 ~= nil do - count = count + 1 - var1 = (to_be_counted[count]) - end - count=count-1 - return count -end --- --------------------------------------------------------------------------- -function make_legendary(skillname,unit) - local skillnamenoun,skillnum - - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - if (df.job_skill[skillname]) then - skillnamenoun = df.job_skill.attrs[df.job_skill[skillname]].caption_noun - else - print ("The skill name provided is not in the list.") - return - end - - if skillnamenoun ~= nil then - utils = require 'utils' - skillnum = df.job_skill[skillname] - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = skillnum, rating = 20 }, 'id') - print (unit.name.first_name.." is now a Legendary "..skillnamenoun) - else - print ("Empty skill name noun, bailing out!") - return - end -end --- --------------------------------------------------------------------------- -function BreathOfArmok(unit) - - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - local i - - local count_max = count_this(df.job_skill) - utils = require 'utils' - for i=0, count_max do - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - print ("The breath of Armok has engulfed "..unit.name.first_name) -end --- --------------------------------------------------------------------------- -function LegendaryByClass(skilltype,v) - unit=v - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - utils = require 'utils' - local i - local skillclass - local count_max = count_this(df.job_skill) - for i=0, count_max do - skillclass = df.job_skill_class[df.job_skill.attrs[i].type] - if skilltype == skillclass then - print ("Skill "..df.job_skill.attrs[i].caption.." is type: "..skillclass.." and is now Legendary for "..unit.name.first_name) - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - end -end --- --------------------------------------------------------------------------- -function PrintSkillList() - local count_max = count_this(df.job_skill) - local i - for i=0, count_max do - print("'"..df.job_skill.attrs[i].caption.."' "..df.job_skill[i].." Type: "..df.job_skill_class[df.job_skill.attrs[i].type]) - end - print ("Provide the UPPER CASE argument, for example: PROCESSPLANTS rather than Threshing") -end --- --------------------------------------------------------------------------- -function PrintSkillClassList() - local i - local count_max = count_this(df.job_skill_class) - for i=0, count_max do - print(df.job_skill_class[i]) - end - print ("Provide one of these arguments, and all skills of that type will be made Legendary") - print ("For example: Medical will make all medical skills legendary") -end --- --------------------------------------------------------------------------- -function adjust_all_dwarves(skillname) - for _,v in ipairs(df.global.world.units.all) do - if v.race == df.global.ui.race_id then - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(v))) - brainwash_unit(v) - elevate_attributes(v) - rejuvenate(v) - if skillname then - if skillname=="Normal" or skillname=="Medical" or skillname=="Personal" or skillname=="Social" or skillname=="Cultural" or skillname=="MilitaryWeapon" or skillname=="MilitaryAttack" or skillname=="MilitaryDefense" or skillname=="MilitaryMisc" then - LegendaryByClass(skillname,v) - elseif skillname=="all" then - BreathOfArmok(v) - else - make_legendary(skillname,v) - end - end - end - end -end --- --------------------------------------------------------------------------- --- main script operation starts here --- --------------------------------------------------------------------------- -local opt = ... -local skillname - -if opt then - if opt=="list" then - PrintSkillList() - return - end - if opt=="classes" then - PrintSkillClassList() - return - end - skillname = opt -else - print ("No skillname supplied, no skills will be adjusted. Pass argument 'list' to see a skill list, 'classes' to show skill classes, or use 'all' if you want all skills legendary.") -end - -adjust_all_dwarves(skillname) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb deleted file mode 100644 index 1bf07c599..000000000 --- a/scripts/autofarm.rb +++ /dev/null @@ -1,192 +0,0 @@ -# Select crops to plant based on current stocks -=begin - -autofarm -======== -Automatically handle crop selection in farm plots based on current plant stocks. -Selects a crop for planting if current stock is below a threshold. -Selected crops are dispatched on all farmplots. - -Usage:: - - autofarm start - autofarm default 30 - autofarm threshold 150 helmet_plump tail_pig - -=end -class AutoFarm - - def initialize - @thresholds = Hash.new(50) - @lastcounts = Hash.new(0) - end - - def setthreshold(id, v) - list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id } - if tok = df.match_rawname(id, list) - @thresholds[tok] = v.to_i - else - puts "No plant with id #{id}, try one of " + - list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ') - end - end - - def setdefault(v) - @thresholds.default = v.to_i - end - - def is_plantable(plant) - has_seed = plant.flags[:SEED] - season = df.cur_season - harvest = df.cur_season_tick + plant.growdur * 10 - will_finish = harvest < 10080 - can_plant = has_seed && plant.flags[season] - can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) - can_plant - end - - def find_plantable_plants - plantable = {} - counts = Hash.new(0) - - df.world.items.other[:SEEDS].each { |i| - if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && - !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && - !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact) - counts[i.mat_index] += i.stack_size - end - } - - counts.keys.each { |i| - if df.ui.tasks.discovered_plants[i] - plant = df.world.raws.plants.all[i] - if is_plantable(plant) - plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0) - plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0) - end - end - } - - return plantable - end - - def set_farms(plants, farms) - return if farms.length == 0 - if plants.length == 0 - plants = [-1] - end - - season = df.cur_season - - farms.each_with_index { |f, idx| - f.plant_id[season] = plants[idx % plants.length] - } - end - - def process - plantable = find_plantable_plants - @lastcounts = Hash.new(0) - - df.world.items.other[:PLANT].each { |i| - if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && - !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && - !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact && plantable.has_key?(i.mat_index)) - id = df.world.raws.plants.all[i.mat_index].id - @lastcounts[id] += i.stack_size - end - } - - return unless @running - - plants_s = [] - plants_u = [] - - plantable.each_key { |k| - plant = df.world.raws.plants.all[k] - if (@lastcounts[plant.id] < @thresholds[plant.id]) - plants_s.push(k) if plantable[k] == :Surface - plants_u.push(k) if plantable[k] == :Underground - end - } - - farms_s = [] - farms_u = [] - df.world.buildings.other[:FARM_PLOT].each { |f| - if (f.flags.exists) - underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean - farms_s.push(f) unless underground - farms_u.push(f) if underground - end - } - - set_farms(plants_s, farms_s) - set_farms(plants_u, farms_u) - end - - def start - return if @running - @onupdate = df.onupdate_register('autofarm', 1200) { process } - @running = true - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - stat = @running ? "Running." : "Stopped." - @lastcounts.each { |k,v| - stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}" - } - @thresholds.each { |k,v| - stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k) - } - stat << "\nDefault: #{@thresholds.default}" - stat - end - -end - -$AutoFarm ||= AutoFarm.new - -case $script_args[0] -when 'start', 'enable' - $AutoFarm.start - puts $AutoFarm.status - -when 'end', 'stop', 'disable' - $AutoFarm.stop - puts 'Stopped.' - -when 'default' - $AutoFarm.setdefault($script_args[1]) - -when 'threshold' - t = $script_args[1] - $script_args[2..-1].each {|i| - $AutoFarm.setthreshold(i, t) - } - -when 'delete' - $AutoFarm.stop - $AutoFarm = nil - -when 'help', '?' - puts < 0 - puts "Unsuspended #{count} job(s)." - df.process_jobs = true - end - end - - def start - @running = true - @onupdate = df.onupdate_register('autounsuspend', 5) { process if @running } - end - - def stop - @running = false - df.onupdate_unregister(@onupdate) - end -end - -case $script_args[0] -when 'start' - $AutoUnsuspend ||= AutoUnsuspend.new - $AutoUnsuspend.start - -when 'end', 'stop' - $AutoUnsuspend.stop - -else - puts $AutoUnsuspend && $AutoUnsuspend.running ? 'Running.' : 'Stopped.' -end diff --git a/scripts/ban-cooking.rb b/scripts/ban-cooking.rb deleted file mode 100644 index 535f39126..000000000 --- a/scripts/ban-cooking.rb +++ /dev/null @@ -1,132 +0,0 @@ -# convenient way to ban cooking categories of food -=begin - -ban-cooking -=========== -A more convenient way to ban cooking various categories of foods than the -kitchen interface. Usage: ``ban-cooking ``. Valid types are ``booze``, -``honey``, ``tallow``, ``oil``, ``seeds`` (non-tree plants with seeds), -``brew``, ``mill``, ``thread``, and ``milk``. - -=end - -already_banned = {} -kitchen = df.ui.kitchen -kitchen.item_types.length.times { |i| - already_banned[[kitchen.mat_types[i], kitchen.mat_indices[i], kitchen.item_types[i], kitchen.item_subtypes[i]]] = kitchen.exc_types[i] & 1 -} -ban_cooking = lambda { |mat_type, mat_index, type| - subtype = -1 - key = [mat_type, mat_index, type, subtype] - if already_banned[key] - next if already_banned[key] == 1 - - index = kitchen.mat_types.zip(kitchen.mat_indices, kitchen.item_types, kitchen.item_subtypes) - kitchen.exc_types[index] |= 1 - already_banned[key] = 1 - next - end - df.ui.kitchen.mat_types << mat_type - df.ui.kitchen.mat_indices << mat_index - df.ui.kitchen.item_types << type - df.ui.kitchen.item_subtypes << subtype - df.ui.kitchen.exc_types << 1 - already_banned[key] = 1 -} - -$script_args.each do |arg| - case arg - when 'booze' - df.world.raws.plants.all.each_with_index do |p, i| - p.material.each_with_index do |m, j| - if m.flags[:ALCOHOL] - ban_cooking[j + DFHack::MaterialInfo::PLANT_BASE, i, :DRINK] - end - end - end - df.world.raws.creatures.all.each_with_index do |c, i| - c.material.each_with_index do |m, j| - if m.flags[:ALCOHOL] - ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :DRINK] - end - end - end - - when 'honey' - # hard-coded in the raws of the mead reaction - honey = df.decode_mat('CREATURE:HONEY_BEE:HONEY') - ban_cooking[honey.mat_type, honey.mat_index, :LIQUID_MISC] - - when 'tallow' - df.world.raws.creatures.all.each_with_index do |c, i| - c.material.each_with_index do |m, j| - if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SOAP_MAT') - ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :GLOB] - end - end - end - - when 'oil' - df.world.raws.plants.all.each_with_index do |p, i| - p.material.each_with_index do |m, j| - if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SOAP_MAT') - ban_cooking[j + DFHack::MaterialInfo::PLANT_BASE, i, :LIQUID_MISC] - end - end - end - - when 'seeds' - df.world.raws.plants.all.each do |p| - m = df.decode_mat(p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat).material - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SEED_MAT') - - if not p.flags[:TREE] - p.growths.each do |g| - m = df.decode_mat(g).material - ban_cooking[g.mat_type, g.mat_index, :PLANT_GROWTH] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SEED_MAT') - end - end - end - - when 'brew' - df.world.raws.plants.all.each do |p| - m = df.decode_mat(p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat).material - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('DRINK_MAT') - - p.growths.each do |g| - m = df.decode_mat(g).material - ban_cooking[g.mat_type, g.mat_index, :PLANT_GROWTH] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('DRINK_MAT') - end - end - - when 'mill' - df.world.raws.plants.all.each do |p| - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.flags[:MILL] - end - - when 'thread' - df.world.raws.plants.all.each do |p| - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.flags[:THREAD] - end - - when 'milk' - df.world.raws.creatures.all.each_with_index do |c, i| - c.material.each_with_index do |m, j| - if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('CHEESE_MAT') - ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :LIQUID_MISC] - end - end - end - - else - puts "ban-cooking booze - bans cooking of drinks" - puts "ban-cooking honey - bans cooking of honey bee honey" - puts "ban-cooking tallow - bans cooking of tallow" - puts "ban-cooking oil - bans cooking of oil" - puts "ban-cooking seeds - bans cooking of plants that have seeds (tree seeds don't count)" - puts "ban-cooking brew - bans cooking of plants that can be brewed into alcohol" - puts "ban-cooking mill - bans cooking of plants that can be milled into powder" - puts "ban-cooking thread - bans cooking of plants that can be turned into thread" - puts "ban-cooking milk - bans cooking of creature liquids that can be turned into cheese" - end -end diff --git a/scripts/binpatch.lua b/scripts/binpatch.lua deleted file mode 100644 index ac7705250..000000000 --- a/scripts/binpatch.lua +++ /dev/null @@ -1,51 +0,0 @@ --- Apply or remove binary patches at runtime. ---[[=begin - -binpatch -======== -Implements functions for in-memory binpatches. See `binpatches`. - -=end]] - -local bp = require('binpatch') - -function run_command(cmd,name) - local pfix = name..': ' - - local patch, err = bp.load_dif_file(name) - if not patch then - dfhack.printerr(pfix..err) - return - end - - if cmd == 'check' then - local status, addr = patch:status() - if status == 'conflict' then - dfhack.printerr(string.format('%sconflict at address %x', pfix, addr)) - else - print(pfix..'patch is '..status) - end - elseif cmd == 'apply' then - local ok, msg = patch:apply() - if ok then - print(pfix..msg) - else - dfhack.printerr(pfix..msg) - end - elseif cmd == 'remove' then - local ok, msg = patch:remove() - if ok then - print(pfix..msg) - else - dfhack.printerr(pfix..msg) - end - else - qerror('Invalid command: '..cmd) - end -end - -local cmd,name = ... -if not cmd or not name then - qerror('Usage: binpatch check/apply/remove ') -end -run_command(cmd, name) diff --git a/scripts/brainwash.lua b/scripts/brainwash.lua deleted file mode 100644 index b28f648f7..000000000 --- a/scripts/brainwash.lua +++ /dev/null @@ -1,70 +0,0 @@ --- Brainwash a dwarf, modifying their personality --- usage is: target a unit in DF, and execute this script in dfhack --- by vjek ---[[=begin - -brainwash -========= -Modify the personality traits of the selected dwarf to match an 'ideal' -personality - as stable and reliable as possible. This makes dwarves very -stable, preventing tantrums even after months of misery. - -=end]] - -function brainwash_unit(profile) - local i,unit_name - - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - - unit_name=dfhack.TranslateName(dfhack.units.getVisibleName(unit)) - - print("Previous personality values for "..unit_name) - printall(unit.status.current_soul.personality.traits) - - --now set new personality - for i=1, #profile do - unit.status.current_soul.personality.traits[i-1]=profile[i] - end - - print("New personality values for "..unit_name) - printall(unit.status.current_soul.personality.traits) - print(unit_name.." has been brainwashed, praise Armok!") -end - --- main script starts here --- profiles are listed here and passed to the brainwash function --- -local baseline={50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50} -local ideal={75,25,25,75,25,25,25,99,25,25,25,50,75,50,25,75,75,50,75,75,25,75,75,50,75,25,50,25,75,75,75,25,75,75,25,75,25,25,75,75,25,75,75,75,25,75,75,25,25,50} -local stepford={99,1,1,99,1,1,1,99,1,1,1,1,50,50,1,99,99,50,50,50,1,1,99,50,50,50,50,50,50,99,50,1,1,99,1,99,1,1,99,99,1,99,99,99,1,50,50,1,1,1} -local wrecked={1,99,99,1,99,99,99,1,99,99,99,1,1,99,99,1,1,1,1,1,99,1,1,99,1,99,99,99,1,1,1,99,1,1,99,1,99,99,1,1,99,1,1,1,99,1,1,99,99,99} -local opt = ... - -if opt then - if opt=="ideal" then - brainwash_unit(ideal) - return - end - if opt=="baseline" then - brainwash_unit(baseline) - return - end - if opt=="stepford" then - brainwash_unit(stepford) - return - end - if opt=="wrecked" then - brainwash_unit(wrecked) - return - end -else - print ("Invalid or missing personality argument.\nValid choices are ideal , baseline , stepford, and wrecked.") - print ("ideal will create a reliable dwarf with generally positive personality traits.") - print ("baseline will reset all personality traits to a default / the average.") - print ("stepford amplifies all good qualities to an excessive degree.") - print ("wrecked amplifies all bad qualities to an excessive degree.") -end diff --git a/scripts/burial.lua b/scripts/burial.lua deleted file mode 100644 index 3023f9128..000000000 --- a/scripts/burial.lua +++ /dev/null @@ -1,27 +0,0 @@ --- allows burial in unowned coffins --- by Putnam https://gist.github.com/Putnam3145/e7031588f4d9b24b9dda ---[[=begin - -burial -====== -Sets all unowned coffins to allow burial. ``burial -pets`` also allows burial -of pets. - -=end]] - -local utils=require('utils') - -validArgs = validArgs or utils.invert({ - 'pets' -}) - -local args = utils.processArgs({...}, validArgs) - -for k,v in ipairs(df.global.world.buildings.other.COFFIN) do - if v.owner_id==-1 then - v.burial_mode.allow_burial=true - if not args.pets then - v.burial_mode.no_pets=true - end - end -end \ No newline at end of file diff --git a/scripts/catsplosion.lua b/scripts/catsplosion.lua deleted file mode 100644 index bd3728b9c..000000000 --- a/scripts/catsplosion.lua +++ /dev/null @@ -1,97 +0,0 @@ --- Make cats just /multiply/. ---[[=begin - -catsplosion -=========== -Makes cats (and other animals) just *multiply*. It is not a good idea to run this -more than once or twice. - -Usage: - -:catsplosion: Make all cats pregnant -:catsplosion list: List IDs of all animals on the map -:catsplosion ID ...: Make animals with given ID(s) pregnant - -Animals will give birth within two in-game hours (100 ticks or fewer). - -=end]] - -world = df.global.world - -if not dfhack.isWorldLoaded() then - qerror('World not loaded.') -end - -args = {...} -list_only = false -creatures = {} - -if #args > 0 then - for _, arg in pairs(args) do - if arg == 'list' then - list_only = true - else - creatures[arg:upper()] = true - end - end -else - creatures.CAT = true -end - -total = 0 -total_changed = 0 -total_created = 0 - -males = {} -females = {} - -for _, unit in pairs(world.units.all) do - local id = world.raws.creatures.all[unit.race].creature_id - males[id] = males[id] or {} - females[id] = females[id] or {} - table.insert((dfhack.units.isFemale(unit) and females or males)[id], unit) -end - -if list_only then - print("Type Male # Female #") - -- sort IDs alphabetically - local ids = {} - for id in pairs(males) do - table.insert(ids, id) - end - table.sort(ids) - for _, id in pairs(ids) do - print(("%22s %6d %8d"):format(id, #males[id], #females[id])) - end - return -end - -for id in pairs(creatures) do - local females = females[id] or {} - total = total + #females - for _, female in pairs(females) do - if female.relations.pregnancy_timer ~= 0 then - female.relations.pregnancy_timer = math.random(1, 100) - total_changed = total_changed + 1 - elseif not female.relations.pregnancy_genes then - local preg = df.unit_genes:new() - preg.appearance:assign(female.appearance.genes.appearance) - preg.colors:assign(female.appearance.genes.colors) - female.relations.pregnancy_genes = preg - female.relations.pregnancy_timer = math.random(1, 100) - female.relations.pregnancy_caste = 1 - total_created = total_created + 1 - end - end -end - -if total_changed ~= 0 then - print(("%d pregnancies accelerated."):format(total_changed)) -end -if total_created ~= 0 then - print(("%d pregnancies created."):format(total_created)) -end -if total == 0 then - qerror("No creatures matched.") -end -print(("Total creatures checked: %d"):format(total)) diff --git a/scripts/colonies.lua b/scripts/colonies.lua deleted file mode 100644 index 2e755ea18..000000000 --- a/scripts/colonies.lua +++ /dev/null @@ -1,82 +0,0 @@ --- List, create, or change wild colonies (eg honey bees) --- By PeridexisErrant and Warmist - -local help = [[=begin - -colonies -======== -List vermin colonies, place honey bees, or convert all vermin -to honey bees. Usage: - -:colonies: List all vermin colonies on the map. -:colonies place: Place a honey bee colony under the cursor. -:colonies convert: Convert all existing colonies to honey bees. - -The ``place`` and ``convert`` subcommands by default create or -convert to honey bees, as this is the most commonly useful. -However both accept an optional flag to use a different vermin -type, for example ``colonies place ANT`` creates an ant colony -and ``colonies convert TERMITE`` ends your beekeeping industry. - -=end]] - -function findVermin(target_verm) - for k,v in pairs(df.global.world.raws.creatures.all) do - if v.creature_id == target_verm then - return k - end - end - qerror("No vermin found with name: "..target_verm) -end - -function list_colonies() - for idx, col in pairs(df.global.world.vermin.colonies) do - race = df.global.world.raws.creatures.all[col.race].creature_id - print(race..' at '..col.pos.x..', '..col.pos.y..', '..col.pos.z) - end -end - -function convert_vermin_to(target_verm) - local vermin_id = findVermin(target_verm) - local changed = 0 - for _, verm in pairs(df.global.world.vermin.colonies) do - verm.race = vermin_id - verm.caste = -1 -- check for queen bee? - verm.amount = 18826 - verm.visible = true - changed = changed + 1 - end - print('Converted '..changed..' colonies to '..target_verm) -end - -function place_vermin(target_verm) - local pos = copyall(df.global.cursor) - if pos.x == -30000 then - qerror("Cursor must be pointing somewhere") - end - local verm = df.vermin:new() - verm.race = findVermin(target_verm) - verm.flags.is_colony = true - verm.caste = -1 -- check for queen bee? - verm.amount = 18826 - verm.visible = true - verm.pos:assign(pos) - df.global.world.vermin.colonies:insert("#", verm) - df.global.world.vermin.all:insert("#", verm) -end - -local args = {...} -local target_verm = args[2] or "HONEY_BEE" - -if args[1] == 'help' or args[1] == '?' then - print(help) -elseif args[1] == 'convert' then - convert_vermin_to(target_verm) -elseif args[1] == 'place' then - place_vermin(target_verm) -else - if #df.global.world.vermin.colonies < 1 then - dfhack.printerr('There are no colonies on the map.') - end - list_colonies() -end diff --git a/scripts/create-items.rb b/scripts/create-items.rb deleted file mode 100644 index 544bd8ce0..000000000 --- a/scripts/create-items.rb +++ /dev/null @@ -1,207 +0,0 @@ -# create first necessity items under cursor -=begin - -create-items -============ -Spawn items under the cursor, to get your fortress started. - -The first argument gives the item category, the second gives the material, -and the optionnal third gives the number of items to create (defaults to 20). - -Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``, -``web``. - -Instead of material, using ``list`` makes the script list eligible materials. - -The ``web`` item category will create an uncollected cobweb on the floor. - -Note that the script does not enforce anything, and will let you create -boulders of toad blood and stuff like that. -However the ``list`` mode will only show 'normal' materials. - -Examples:: - - create-items boulders COAL_BITUMINOUS 12 - create-items plant tail_pig - create-items log list - create-items web CREATURE:SPIDER_CAVE_GIANT:SILK - create-items bar CREATURE:CAT:SOAP - create-items bar adamantine - -=end - -category = $script_args[0] || 'help' -mat_raw = $script_args[1] || 'list' -count = $script_args[2] - - -category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help' - -if category == 'help' - puts < 5 - df.curview.feed_keys(:CURSOR_DOWN_Z) - df.curview.feed_keys(:CURSOR_UP_Z) -else - df.curview.feed_keys(:CURSOR_UP_Z) - df.curview.feed_keys(:CURSOR_DOWN_Z) -end diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb deleted file mode 100644 index 6b2f252c0..000000000 --- a/scripts/deathcause.rb +++ /dev/null @@ -1,75 +0,0 @@ -# show death cause of a creature -=begin - -deathcause -========== -Select a body part ingame, or a unit from the :kbd:`u` unit list, and this -script will display the cause of death of the creature. - -=end - -def display_death_event(e) - str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}" - str << " (cause: #{e.death_cause.to_s.downcase})," - str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 - str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON - str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.shooter_item_subtype].name}" if e.weapon.shooter_item_type == :WEAPON - - puts str.chomp(',') + '.' -end - -def display_death_unit(u) - death_info = u.counters.death_tg - killer = death_info.killer_tg if death_info - - str = "The #{u.race_tg.name[0]}" - str << " #{u.name}" if u.name.has_name - str << " died" - str << " in year #{death_info.event_year}" if death_info - str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1 - str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer - - puts str.chomp(',') + '.' -end - -item = df.item_find(:selected) -unit = df.unit_find(:selected) - -if !item or !item.kind_of?(DFHack::ItemBodyComponent) - item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } -end - -if item and item.kind_of?(DFHack::ItemBodyComponent) - hf = item.hist_figure_id -elsif unit - hf = unit.hist_figure_id -end - -if not hf - puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" - -elsif hf == -1 - if unit ||= item.unit_tg - display_death_unit(unit) - else - puts "Not a historical figure, cannot death find info" - end - -else - histfig = df.world.history.figures.binsearch(hf) - unit = histfig ? df.unit_find(histfig.unit_id) : nil - if unit and not unit.flags1.dead and not unit.flags3.ghostly - puts "#{unit.name} is not dead yet !" - - else - events = df.world.history.events - (0...events.length).reverse_each { |i| - e = events[i] - if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf - display_death_event(e) - break - end - } - end -end - diff --git a/scripts/deteriorateclothes.rb b/scripts/deteriorateclothes.rb deleted file mode 100644 index 33084185e..000000000 --- a/scripts/deteriorateclothes.rb +++ /dev/null @@ -1,81 +0,0 @@ -# Increase the rate at which clothes wear out -=begin - -deteriorateclothes -================== -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. All of those slightly worn wool shoes that dwarves -scatter all over the place will deteriorate at a greatly increased rate, -and eventually just crumble into nothing. As warm and fuzzy as a dining -room full of used socks makes your dwarves feel, your FPS does not like it. - -Usage: ``deteriorateclothes (start|stop)`` - -=end - -class DeteriorateClothes - - def initialize - end - - def process - return false unless @running - - items = [df.world.items.other[:GLOVES], - df.world.items.other[:ARMOR], - df.world.items.other[:SHOES], - df.world.items.other[:PANTS], - df.world.items.other[:HELM]] - - items.each { |type| - type.each { |i| - if (i.subtype.armorlevel == 0 and i.flags.on_ground == true and i.wear > 0) - i.wear_timer *= i.wear + 0.5 - if (i.wear > 2) - i.flags.garbage_collect = true - end - end - } - } - - - end - - def start - @onupdate = df.onupdate_register('deteriorateclothes', 1200, 1200) { process } - @running = true - - puts "Deterioration of old clothes commencing..." - - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - @running ? 'Running.' : 'Stopped.' - end - -end - -case $script_args[0] -when 'start' - if ($DeteriorateClothes) - $DeteriorateClothes.stop - end - - $DeteriorateClothes = DeteriorateClothes.new - $DeteriorateClothes.start - -when 'end', 'stop' - $DeteriorateClothes.stop - -else - if $DeteriorateClothes - puts $DeteriorateClothes.status - else - puts 'Not loaded.' - end -end \ No newline at end of file diff --git a/scripts/deterioratecorpses.rb b/scripts/deterioratecorpses.rb deleted file mode 100644 index 56003f6d0..000000000 --- a/scripts/deterioratecorpses.rb +++ /dev/null @@ -1,106 +0,0 @@ -# Make corpse parts decay and vanish over time -=begin - -deterioratecorpses -================== -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. - -In long running forts, especially evil biomes, you end up with a lot -of toes, teeth, fingers, and limbs scattered all over the place. -Various corpses from various sieges, stray kitten corpses, probably -some heads. Basically, your map will look like a giant pile of -assorted body parts, all of which individually eat up a small part -of your FPS, which collectively eat up quite a bit. - -In addition, this script also targets various butchery byproducts. -Enjoying your thriving animal industry? Your FPS does not. Those -thousands of skulls, bones, hooves, and wool eat up precious FPS -that could be used to kill goblins and elves. Whose corpses will -also get destroyed by the script to kill more goblins and elves. - -This script causes all of those to rot away into nothing after -several months. - -Usage: ``deterioratecorpses (start|stop)`` - -=end - -class DeteriorateCorpses - - def initialize - end - - def process - return false unless @running - - df.world.items.other[:ANY_CORPSE].each { |i| - if (i.flags.dead_dwarf == false) - i.wear_timer += 1 - if (i.wear_timer > 24 + rand(8)) - i.wear_timer = 0 - i.wear += 1 - end - if (i.wear > 3) - i.flags.garbage_collect = true - end - - end - - } - - df.world.items.other[:REMAINS].each { |i| - if (i.flags.dead_dwarf == false) - i.wear_timer += 1 - if (i.wear_timer > 6) - i.wear_timer = 0 - i.wear += 1 - end - if (i.wear > 3) - i.flags.garbage_collect = true - end - - end - - } - - end - - def start - @onupdate = df.onupdate_register('deterioratecorpses', 1200, 1200) { process } - @running = true - - puts "Deterioration of body parts commencing..." - - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - @running ? 'Running.' : 'Stopped.' - end - -end - -case $script_args[0] -when 'start' - if ($DeteriorateCorpses) - $DeteriorateCorpses.stop - end - - $DeteriorateCorpses = DeteriorateCorpses.new - $DeteriorateCorpses.start - -when 'end', 'stop' - $DeteriorateCorpses.stop - -else - if $DeteriorateCorpses - puts $DeteriorateCorpses.status - else - puts 'Not loaded.' - end -end \ No newline at end of file diff --git a/scripts/deterioratefood.rb b/scripts/deterioratefood.rb deleted file mode 100644 index c59171ea5..000000000 --- a/scripts/deterioratefood.rb +++ /dev/null @@ -1,96 +0,0 @@ -# Food and plants decay, and vanish after a few months -=begin - -deterioratefood -=============== -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. - -With this script running, all food and plants wear out and disappear -after several months. Barrels and stockpiles will keep them from -rotting, but it won't keep them from decaying. No more sitting on a -hundred years worth of food. No more keeping barrels of pig tails -sitting around until you decide to use them. Either use it, eat it, -or lose it. Seeds, are excluded from this, if you aren't planning on -using your pig tails, hold onto the seeds for a rainy day. - -This script is...pretty far reaching. However, almost all long -running forts I've had end up sitting on thousands and thousands of -food items. Several thousand cooked meals, three thousand plump -helmets, just as many fish and meat. It gets pretty absurd. And your -FPS doesn't like it. - -Usage: ``deterioratefood (start|stop)`` - -=end - -class DeteriorateFood - - def initialize - end - - def process - return false unless @running - - items = [df.world.items.other[:FISH], - df.world.items.other[:FISH_RAW], - df.world.items.other[:EGG], - df.world.items.other[:CHEESE], - df.world.items.other[:PLANT], - df.world.items.other[:PLANT_GROWTH], - df.world.items.other[:FOOD]] - - items.each { |type| - type.each { |i| - i.wear_timer += 1 - if (i.wear_timer > 24 + rand(8)) - i.wear_timer = 0 - i.wear += 1 - end - if (i.wear > 3) - i.flags.garbage_collect = true - end - } - } - - - end - - def start - @onupdate = df.onupdate_register('deterioratefood', 1200, 1200) { process } - @running = true - - puts "Deterioration of food commencing..." - - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - @running ? 'Running.' : 'Stopped.' - end - -end - -case $script_args[0] -when 'start' - if ($DeteriorateFood) - $DeteriorateFood.stop - end - - $DeteriorateFood = DeteriorateFood.new - $DeteriorateFood.start - -when 'end', 'stop' - $DeteriorateFood.stop - -else - if $DeteriorateFood - puts $DeteriorateFood.status - else - puts 'Not loaded.' - end -end \ No newline at end of file diff --git a/scripts/devel/about.txt b/scripts/devel/about.txt deleted file mode 100644 index 12b1f1853..000000000 --- a/scripts/devel/about.txt +++ /dev/null @@ -1,6 +0,0 @@ -``devel/*`` scripts are intended for developer use, but many may -be of interest to anyone investigating odd phenomema or just messing -around. They are documented to encourage such inquiry. - -Some can PERMANENTLY DAMAGE YOUR SAVE if misused, so please be careful. -The warnings are real; if in doubt make backups before running the command. diff --git a/scripts/devel/all-bob.lua b/scripts/devel/all-bob.lua deleted file mode 100644 index 163f708dd..000000000 --- a/scripts/devel/all-bob.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Changes the first name of all units to "Bob" ---author expwnent --- ---[[=begin - -devel/all-bob -============= -Changes the first name of all units to "Bob". -Useful for testing `modtools/interaction-trigger` events. - -=end]] - -for _,v in ipairs(df.global.world.units.all) do - v.name.first_name = "Bob" -end diff --git a/scripts/devel/check-release.lua b/scripts/devel/check-release.lua deleted file mode 100644 index fcc50642e..000000000 --- a/scripts/devel/check-release.lua +++ /dev/null @@ -1,59 +0,0 @@ --- basic check for release readiness ---[[=begin -devel/check-release -=================== -Basic checks for release readiness -=end]] - -ok = true -function err(s) - dfhack.printerr(s) - ok = false -end -function warn(s) - dfhack.color(COLOR_YELLOW) - dfhack.print(s .. '\n') - dfhack.color(nil) -end - -dfhack_ver = dfhack.getDFHackVersion() -git_desc = dfhack.getGitDescription() -git_commit = dfhack.getGitCommit() -if not dfhack.isRelease() then - err('This build is not tagged as a release') - print[[ -This is probably due to missing git tags. -Try running `git fetch origin --tags` in the DFHack source tree. -]] -end - -expected_git_desc = ('%s-0-g%s'):format(dfhack_ver, git_commit:sub(1, 7)) -if git_desc:sub(1, #expected_git_desc) ~= expected_git_desc then - err(([[Bad git description: -Expected %s, got %s]]):format(expected_git_desc, git_desc)) - print[[ -Ensure that the DFHack source tree is up-to-date (`git pull`) and CMake is -installing DFHack to this DF folder. -]] -end - -if not dfhack.gitXmlMatch() then - err('library/xml submodule commit does not match tracked commit\n' .. - ('Expected %s, got %s'):format( - dfhack.getGitXmlCommit():sub(1, 7), - dfhack.getGitXmlExpectedCommit():sub(1, 7) - )) - print('Try running `git submodule update` in the DFHack source tree.') -end - -if dfhack.isPrerelease() then - warn('This build is marked as a prerelease.') - print('If this is not intentional, be sure your DFHack tree is up-to-date\n' .. - '(`git pull`) and try again.') -end - -if not ok then - err('This build is not release-ready!') -else - print('Release checks succeeded') -end diff --git a/scripts/devel/clear-script-env.lua b/scripts/devel/clear-script-env.lua deleted file mode 100644 index 90c8db0ed..000000000 --- a/scripts/devel/clear-script-env.lua +++ /dev/null @@ -1,26 +0,0 @@ --- Clear script environment ---[[=begin - -devel/clear-script-env -====================== -Clears the environment of the specified lua script(s). - -=end]] -args = {...} -if #args < 1 then qerror("script name(s) required") end -for _, name in pairs(args) do - local file = dfhack.findScript(name) - if file then - local script = dfhack.internal.scripts[file] - if script then - local env = script.env - while next(env) do - env[next(env)] = nil - end - else - dfhack.printerr("Script not loaded: " .. name) - end - else - dfhack.printerr("Can't find script: " .. name) - end -end diff --git a/scripts/devel/cmptiles.lua b/scripts/devel/cmptiles.lua deleted file mode 100644 index bb0871051..000000000 --- a/scripts/devel/cmptiles.lua +++ /dev/null @@ -1,50 +0,0 @@ --- Lists and/or compares two tiletype material groups. ---[[=begin - -devel/cmptiles -============== -Lists and/or compares two tiletype material groups. - -Usage: ``devel/cmptiles material1 [material2]`` - -=end]] - -local nmat1,nmat2=... -local mat1 = df.tiletype_material[nmat1] -local mat2 = df.tiletype_material[nmat2] - -local tmat1 = {} -local tmat2 = {} - -local attrs = df.tiletype.attrs - -for i=df.tiletype._first_item,df.tiletype._last_item do - local shape = df.tiletype_shape[attrs[i].shape] or '' - local variant = df.tiletype_variant[attrs[i].variant] or '' - local special = df.tiletype_special[attrs[i].special] or '' - local direction = attrs[i].direction or '' - local code = shape..':'..variant..':'..special..':'..direction - - if attrs[i].material == mat1 then - tmat1[code] = true - end - if attrs[i].material == mat2 then - tmat2[code] = true - end -end - -local function list_diff(n, t1, t2) - local lst = {} - for k,v in pairs(t1) do - if not t2[k] then - lst[#lst+1] = k - end - end - table.sort(lst) - for k,v in ipairs(lst) do - print(n, v) - end -end - -list_diff(nmat1,tmat1,tmat2) -list_diff(nmat2,tmat2,tmat1) diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua deleted file mode 100644 index b6a2989eb..000000000 --- a/scripts/devel/export-dt-ini.lua +++ /dev/null @@ -1,515 +0,0 @@ --- 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' -local ms = require 'memscan' - --- Utility functions - -local globals = df.global -local global_addr = dfhack.internal.getAddress -local os_type = dfhack.getOSType() -local rdelta = dfhack.internal.getRebaseDelta() -local lines = {} -local complete = true - -local function header(name) - table.insert(lines, '') - table.insert(lines, '['..name..']') -end - -local function value(name,addr) - local line - - if not addr then - complete = false - line = name..'=0x0' - elseif addr < 0x10000 then - line = string.format('%s=0x%04x',name,addr) - else - line = string.format('%s=0x%08x',name,addr) - end - - table.insert(lines, line) -end -local function address(name,base,field,...) - local addr - - if base == globals then - addr = global_addr(field) - if addr and select('#',...) > 0 then - _,addr = df.sizeof(ms.field_ref(base,field,...)) - end - elseif base._kind == 'class-type' then - -- field_offset crashes with classes due to vtable problems, - -- so we have to create a real temporary object here. - local obj = df.new(base) - if obj then - local _,a1 = df.sizeof(obj) - local _,a2 = df.sizeof(ms.field_ref(obj,field,...)) - addr = a2-a1 - obj:delete() - end - else - addr = ms.field_offset(base,field,...) - end - - value(name, addr) -end - - --- List of actual values -header('addresses') -address('cur_year_tick',globals,'cur_year_tick') -address('current_year',globals,'cur_year') -address('dwarf_civ_index',globals,'ui','civ_id') -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') -address('itemdef_tool_vector',globals,'world','raws','itemdefs','tools') -address('itemdef_instrument_vector',globals,'world','raws','itemdefs','instruments') -address('itemdef_armor_vector',globals,'world','raws','itemdefs','armor') -address('itemdef_ammo_vector',globals,'world','raws','itemdefs','ammo') -address('itemdef_siegeammo_vector',globals,'world','raws','itemdefs','siege_ammo') -address('itemdef_glove_vector',globals,'world','raws','itemdefs','gloves') -address('itemdef_shoe_vector',globals,'world','raws','itemdefs','shoes') -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('all_syndromes_vector',globals,'world','raws','syndromes','all') -address('events_vector',globals,'world','history','events') -address('historical_figures_vector',globals,'world','history','figures') -address('world_site_type',df.world_site,'type') -address('active_sites_vector',df.world_data,'active_site') - -header('offsets') -address('word_table',df.language_translation,'words') -value('string_buffer_offset', 0x0000) - -header('word_offsets') -address('base',df.language_word,'word') -address('noun_singular',df.language_word,'forms','Noun') -address('noun_plural',df.language_word,'forms','NounPlural') -address('adjective',df.language_word,'forms','Adjective') -address('verb',df.language_word,'forms','Verb') -address('present_simple_verb',df.language_word,'forms','Verb3rdPerson') -address('past_simple_verb',df.language_word,'forms','VerbPast') -address('past_participle_verb',df.language_word,'forms','VerbPassive') -address('present_participle_verb',df.language_word,'forms','VerbGerund') -address('words',df.language_name,'words') -address('word_type',df.language_name,'parts_of_speech') -address('language_id',df.language_name,'language') - -header('general_ref_offsets') ---WARNING below value should be: "general_ref::vtable","1","0x8","0x4","vmethod","getType","general_ref_type","" -value('ref_type',0x8) -address('artifact_id',df.general_ref_artifact,'artifact_id') -address('item_id',df.general_ref_item,'item_id') - -header('race_offsets') -address('name_singular',df.creature_raw,'name',0) -address('name_plural',df.creature_raw,'name',1) -address('adjective',df.creature_raw,'name',2) -address('baby_name_singular',df.creature_raw,'general_baby_name',0) -address('baby_name_plural',df.creature_raw,'general_baby_name',1) -address('child_name_singular',df.creature_raw,'general_child_name',0) -address('child_name_plural',df.creature_raw,'general_child_name',1) -address('pref_string_vector',df.creature_raw,'prefstring') -address('castes_vector',df.creature_raw,'caste') -address('pop_ratio_vector',df.creature_raw,'pop_ratio') -address('materials_vector',df.creature_raw,'material') -address('flags',df.creature_raw,'flags') -address('tissues_vector',df.creature_raw,'tissue') - -header('caste_offsets') -address('caste_name',df.caste_raw,'caste_name') -address('caste_descr',df.caste_raw,'description') -address('caste_trait_ranges',df.caste_raw,'personality','a') -address('caste_phys_att_ranges',df.caste_raw,'attributes','phys_att_range') -address('baby_age',df.caste_raw,'misc','baby_age') -address('child_age',df.caste_raw,'misc','child_age') -address('adult_size',df.caste_raw,'misc','adult_size') -address('flags',df.caste_raw,'flags') -address('body_info',df.caste_raw,'body_info') -address('skill_rates',df.caste_raw,'skill_rates') -address('caste_att_rates',df.caste_raw,'attributes','phys_att_rates') -address('caste_att_caps',df.caste_raw,'attributes','phys_att_cap_perc') -address('shearable_tissues_vector',df.caste_raw,'shearable_tissue_layer') -address('extracts',df.caste_raw,'extracts','extract_matidx') - -header('hist_entity_offsets') -address('histfigs',df.historical_entity,'histfig_ids') -address('beliefs',df.historical_entity,'resources','values') -address('squads',df.historical_entity,'squads') -address('positions',df.historical_entity,'positions','own') -address('assignments',df.historical_entity,'positions','assignments') -address('assign_hist_id',df.entity_position_assignment,'histfig') -address('assign_position_id',df.entity_position_assignment,'position_id') -address('position_id',df.entity_position,'id') -address('position_name',df.entity_position,'name') -address('position_female_name',df.entity_position,'name_female') -address('position_male_name',df.entity_position,'name_male') - -header('hist_figure_offsets') -address('hist_race',df.historical_figure,'race') -address('hist_name',df.historical_figure,'name') -address('id',df.historical_figure,'id') -address('hist_fig_info',df.historical_figure,'info') -address('reputation',df.historical_figure_info,'reputation') -address('current_ident',df.historical_figure_info.T_reputation,'cur_identity') -address('fake_name',df.identity,'name') -address('fake_birth_year',df.identity,'birth_year') -address('fake_birth_time',df.identity,'birth_second') -address('kills',df.historical_figure_info,'kills') -address('killed_race_vector',df.historical_kills,'killed_race') -address('killed_undead_vector',df.historical_kills,'killed_undead') -address('killed_counts_vector',df.historical_kills,'killed_count') - -header('hist_event_offsets') -address('event_year',df.history_event,'year') -address('id',df.history_event,'id') -address('killed_hist_id',df.history_event_hist_figure_diedst,'victim_hf') - -header('item_offsets') -if os_type == 'darwin' then - value('item_type',0x4) -else - value('item_type',0x1) -end -address('item_def',df.item_ammost,'subtype') --currently same for all -address('id',df.item,'id') -address('general_refs',df.item,'general_refs') -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') -address('sub_type',df.itemdef,'subtype') -address('name',df.itemdef_armorst,'name') -address('name_plural',df.itemdef_armorst,'name_plural') -address('adjective',df.itemdef_armorst,'name_preplural') - -header('item_filter_offsets') -address('item_subtype',df.item_filter_spec,'item_subtype') -address('mat_class',df.item_filter_spec,'material_class') -address('mat_type',df.item_filter_spec,'mattype') -address('mat_index',df.item_filter_spec,'matindex') - -header('weapon_subtype_offsets') -address('single_size',df.itemdef_weaponst,'two_handed') -address('multi_size',df.itemdef_weaponst,'minimum_size') -address('ammo',df.itemdef_weaponst,'ranged_ammo') -address('melee_skill',df.itemdef_weaponst,'skill_melee') -address('ranged_skill',df.itemdef_weaponst,'skill_ranged') - -header('armor_subtype_offsets') -address('layer',df.armor_properties,'layer') -address('mat_name',df.itemdef_armorst,'material_placeholder') -address('other_armor_level',df.itemdef_helmst,'armorlevel') -address('armor_adjective',df.itemdef_armorst,'adjective') -address('armor_level',df.itemdef_armorst,'armorlevel') -address('chest_armor_properties',df.itemdef_armorst,'props') -address('pants_armor_properties',df.itemdef_pantsst,'props') -address('other_armor_properties',df.itemdef_helmst,'props') - -header('material_offsets') -address('solid_name',df.material_common,'state_name','Solid') -address('liquid_name',df.material_common,'state_name','Liquid') -address('gas_name',df.material_common,'state_name','Gas') -address('powder_name',df.material_common,'state_name','Powder') -address('paste_name',df.material_common,'state_name','Paste') -address('pressed_name',df.material_common,'state_name','Pressed') -address('flags',df.material_common,'flags') -address('inorganic_materials_vector',df.inorganic_raw,'material') -address('inorganic_flags',df.inorganic_raw,'flags') - -header('plant_offsets') -address('name',df.plant_raw,'name') -address('name_plural',df.plant_raw,'name_plural') -address('name_leaf_plural',df.plant_raw,'leaves_plural') -address('name_seed_plural',df.plant_raw,'seed_plural') -address('materials_vector',df.plant_raw,'material') -address('flags',df.plant_raw,'flags') - -header('descriptor_offsets') -address('color_name',df.descriptor_color,'name') -address('shape_name_plural',df.descriptor_shape,'name_plural') - -header('health_offsets') -address('parent_id',df.body_part_raw,'con_part_id') -address('body_part_flags',df.body_part_raw,'flags') -address('layers_vector',df.body_part_raw,'layers') -address('number',df.body_part_raw,'number') -address('names_vector',df.body_part_raw,'name_singular') -address('names_plural_vector',df.body_part_raw,'name_plural') -address('layer_tissue',df.body_part_layer_raw,'tissue_id') -address('layer_global_id',df.body_part_layer_raw,'layer_id') -address('tissue_name',df.tissue_template,'tissue_name_singular') -address('tissue_flags',df.tissue_template,'flags') - -header('dwarf_offsets') -address('first_name',df.unit,'name','first_name') -address('nick_name',df.unit,'name','nickname') -address('last_name',df.unit,'name','words') -address('custom_profession',df.unit,'custom_profession') -address('profession',df.unit,'profession') -address('race',df.unit,'race') -address('flags1',df.unit,'flags1') -address('flags2',df.unit,'flags2') -address('flags3',df.unit,'flags3') -address('meeting',df.unit,'meeting') -address('caste',df.unit,'caste') -address('sex',df.unit,'sex') -address('id',df.unit,'id') -address('animal_type',df.unit,'training_level') -address('civ',df.unit,'civ_id') -address('specific_refs',df.unit,'specific_refs') -address('squad_id',df.unit,'military','squad_id') -address('squad_position',df.unit,'military','squad_position') -address('recheck_equipment',df.unit,'military','pickup_flags') -address('mood',df.unit,'mood') -address('birth_year',df.unit,'relations','birth_year') -address('birth_time',df.unit,'relations','birth_time') -address('pet_owner_id',df.unit,'relations','pet_owner_id') -address('current_job',df.unit,'job','current_job') -address('physical_attrs',df.unit,'body','physical_attrs') -address('body_size',df.unit,'appearance','body_modifiers') -address('size_info',df.unit,'body','size_info') -address('curse',df.unit,'curse','name') -address('curse_add_flags1',df.unit,'curse','add_tags1') -address('turn_count',df.unit,'curse','time_on_site') -address('souls',df.unit,'status','souls') -address('states',df.unit,'status','misc_traits') -address('labors',df.unit,'status','labors') -address('hist_id',df.unit,'hist_figure_id') -address('artifact_name',df.unit,'status','artifact_name') -address('active_syndrome_vector',df.unit,'syndromes','active') -address('syn_sick_flag',df.unit_syndrome,'flags') -address('unit_health_info',df.unit,'health') -address('temp_mood',df.unit,'counters','soldier_mood') -address('counters1',df.unit,'counters','winded') -address('counters2',df.unit, 'counters','pain') -address('counters3',df.unit, 'counters2','paralysis') -address('limb_counters',df.unit,'status2','limbs_stand_max') -address('blood',df.unit,'body','blood_max') -address('body_component_info',df.unit,'body','components') -address('layer_status_vector',df.body_component_info,'layer_status') -address('wounds_vector',df.unit,'body','wounds') -address('mood_skill',df.unit,'job','mood_skill') -address('used_items_vector',df.unit,'used_items') -address('affection_level',df.unit_item_use,'affection_level') -address('inventory',df.unit,'inventory') -address('inventory_item_mode',df.unit_inventory_item,'mode') -address('inventory_item_bodypart',df.unit_inventory_item,'body_part_id') - -header('syndrome_offsets') -address('cie_effects',df.syndrome,'ce') -address('cie_end',df.creature_interaction_effect,'end') -address('cie_first_perc',df.creature_interaction_effect_phys_att_changest,'phys_att_perc') --same for mental -address('cie_phys',df.creature_interaction_effect_phys_att_changest,'phys_att_add') -address('cie_ment',df.creature_interaction_effect_ment_att_changest,'ment_att_add') -address('syn_classes_vector',df.syndrome,'syn_class') -address('trans_race_id',df.creature_interaction_effect_body_transformationst,'race') - -header('unit_wound_offsets') -address('parts',df.unit_wound,'parts') -address('id',df.unit_wound.T_parts,'body_part_id') -address('layer',df.unit_wound.T_parts,'layer_idx') -address('general_flags',df.unit_wound,'flags') -address('flags1',df.unit_wound.T_parts,'flags1') -address('flags2',df.unit_wound.T_parts,'flags2') -address('effects_vector',df.unit_wound.T_parts,'effect_type') -address('bleeding',df.unit_wound.T_parts,'bleeding') -address('pain',df.unit_wound.T_parts,'pain') -address('cur_pen',df.unit_wound.T_parts,'cur_penetration_perc') -address('max_pen',df.unit_wound.T_parts,'max_penetration_perc') - -header('soul_details') -address('name',df.unit_soul,'name') -address('orientation',df.unit_soul,'orientation_flags') -address('mental_attrs',df.unit_soul,'mental_attrs') -address('skills',df.unit_soul,'skills') -address('preferences',df.unit_soul,'preferences') -address('personality',df.unit_soul,'personality') -address('beliefs',df.unit_personality,'values') -address('emotions',df.unit_personality,'emotions') -address('goals',df.unit_personality,'dreams') -address('goal_realized',df.unit_personality.T_dreams,'unk8') -address('traits',df.unit_personality,'traits') -address('stress_level',df.unit_personality,'stress_level') - -header('emotion_offsets') -address('emotion_type',df.unit_personality.T_emotions,'type') -address('strength',df.unit_personality.T_emotions,'strength') -address('thought_id',df.unit_personality.T_emotions,'thought') -address('sub_id',df.unit_personality.T_emotions,'subthought') -address('level',df.unit_personality.T_emotions,'severity') -address('year',df.unit_personality.T_emotions,'year') -address('year_tick',df.unit_personality.T_emotions,'year_tick') - -header('job_details') -address('id',df.job,'job_type') -address('mat_type',df.job,'mat_type') -address('mat_index',df.job,'mat_index') -address('mat_category',df.job,'material_category') -value('on_break_flag',df.misc_trait_type.OnBreak) -address('sub_job_id',df.job,'reaction_name') -address('reaction',df.reaction,'name') -address('reaction_skill',df.reaction,'skill') - -header('squad_offsets') -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') -address('armor_vector',df.squad_position,'uniform','body') -address('helm_vector',df.squad_position,'uniform','head') -address('pants_vector',df.squad_position,'uniform','pants') -address('gloves_vector',df.squad_position,'uniform','gloves') -address('shoes_vector',df.squad_position,'uniform','shoes') -address('shield_vector',df.squad_position,'uniform','shield') -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,'type') -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,'participant_actions') -address('perf_histfig',df.activity_event_performancest.T_participant_actions,'histfig_id') - --- Final creation of the file - -local out = io.open('therapist.ini', 'w') - -out:write('[info]\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') - -for i,v in ipairs(lines) do - out:write(v..'\n') -end - -out:write[[ - -[valid_flags_2] -size=0 - -[invalid_flags_1] -size=9 -1\name=a skeleton -1\value=0x00002000 -2\name=a merchant -2\value=0x00000040 -3\name=outpost liason or diplomat -3\value=0x00000800 -4\name=an invader or hostile -4\value=0x00020000 -5\name=an invader or hostile -5\value=0x00080000 -6\name=resident, invader or ambusher -6\value=0x00600000 -7\name=part of a merchant caravan -7\value=0x00000080 -8\name="Dead, Jim." -8\value=0x00000002 -9\name=marauder -9\value=0x00000010 - -[invalid_flags_2] -size=5 -1\name="killed, Jim." -1\value=0x00000080 -2\name=from the Underworld. SPOOKY! -2\value=0x00040000 -3\name=resident -3\value=0x00080000 -4\name=uninvited visitor -4\value=0x00400000 -5\name=visitor -5\value=0x00800000 - -[invalid_flags_3] -size=1 -1\name=a ghost -1\value=0x00001000 -]] - -out:close() diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua deleted file mode 100644 index 9cfacece0..000000000 --- a/scripts/devel/find-offsets.lua +++ /dev/null @@ -1,1936 +0,0 @@ --- Find some offsets for linux. ---[[=begin - -devel/find-offsets -================== -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -Running this script on a new DF version will NOT -MAKE IT RUN CORRECTLY if any data structures -changed, thus possibly leading to CRASHES AND/OR -PERMANENT SAVE CORRUPTION. - -Finding the first few globals requires this script to be -started immediately after loading the game, WITHOUT -first loading a world. The rest expect a loaded save, -not a fresh embark. Finding current_weather requires -a special save previously processed with `devel/prepare-save` -on a DF version with working dfhack. - -The script expects vanilla game configuration, without -any custom tilesets or init file changes. Never unpause -the game unless instructed. When done, quit the game -without saving using 'die'. - -Arguments: - -* global names to force finding them -* ``all`` to force all globals -* ``nofeed`` to block automated fake input searches -* ``nozoom`` to disable neighboring object heuristics - -=end]] - -local utils = require 'utils' -local ms = require 'memscan' -local gui = require 'gui' - -local is_known = dfhack.internal.getAddress - -local os_type = dfhack.getOSType() - -local force_scan = {} -for _,v in ipairs({...}) do - force_scan[v] = true -end - -collectgarbage() - -function prompt_proceed(indent) - if not indent then indent = 0 end - return utils.prompt_yes_no(string.rep(' ', indent) .. 'Proceed?', true) -end - -print[[ -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -Running this script on a new DF version will NOT -MAKE IT RUN CORRECTLY if any data structures -changed, thus possibly leading to CRASHES AND/OR -PERMANENT SAVE CORRUPTION. - -Finding the first few globals requires this script to be -started immediately after loading the game, WITHOUT -first loading a world. The rest expect a loaded save, -not a fresh embark. Finding current_weather requires -a special save previously processed with devel/prepare-save -on a DF version with working dfhack. - -The script expects vanilla game configuration, without -any custom tilesets or init file changes. Never unpause -the game unless instructed. When done, quit the game -without saving using 'die'. -]] - -if not utils.prompt_yes_no('Proceed?') then - return -end - --- Data segment location - -local data = ms.get_data_segment() -if not data then - qerror('Could not find data segment') -end - -print('\nData section: '..tostring(data)) -if data.size < 5000000 then - qerror('Data segment too short.') -end - -local searcher = ms.DiffSearcher.new(data) - -local function get_screen(class, prompt) - if not is_known('gview') then - print('Please navigate to '..prompt) - if not prompt_proceed() then - return nil - end - return true - end - - while true do - local cs = dfhack.gui.getCurViewscreen(true) - if not df.is_instance(class, cs) then - print('Please navigate to '..prompt) - if not prompt_proceed() then - return nil - end - else - return cs - end - end -end - -local function screen_title() - return get_screen(df.viewscreen_titlest, 'the title screen') -end -local function screen_dwarfmode() - return get_screen(df.viewscreen_dwarfmodest, 'the main dwarf mode screen') -end - -local function validate_offset(name,validator,addr,tname,...) - local obj = data:object_by_field(addr,tname,...) - if obj and not validator(obj) then - obj = nil - end - ms.found_offset(name,obj) -end - -local function zoomed_searcher(startn, end_or_sz) - if force_scan.nozoom then - return nil - end - local sv = is_known(startn) - if not sv then - return nil - end - local ev - if type(end_or_sz) == 'number' then - ev = sv + end_or_sz - if end_or_sz < 0 then - sv, ev = ev, sv - end - else - ev = is_known(end_or_sz) - if not ev then - return nil - end - end - sv = sv - (sv % 4) - ev = ev + 3 - ev = ev - (ev % 4) - if data:contains_range(sv, ev-sv) then - return ms.DiffSearcher.new(ms.MemoryArea.new(sv,ev)) - end -end - -local finder_searches = {} -local function exec_finder(finder, names, validators) - if type(names) ~= 'table' then - names = { names } - end - if type(validators) ~= 'table' then - validators = { validators } - end - local search = force_scan['all'] - for k,v in ipairs(names) do - if force_scan[v] or not is_known(v) then - table.insert(finder_searches, v) - search = true - elseif validators[k] then - if not validators[k](df.global[v]) then - dfhack.printerr('Validation failed for '..v..', will try to find again') - table.insert(finder_searches, v) - search = true - end - end - end - if search then - local ok, err = dfhack.safecall(finder) - if not ok then - if tostring(err):find('abort') or not utils.prompt_yes_no('Proceed with the rest of the script?') then - searcher:reset() - qerror('Quit') - end - end - else - print('Already known: '..table.concat(names,', ')) - end -end - -local ordinal_names = { - [0] = '1st entry', - [1] = '2nd entry', - [2] = '3rd entry' -} -setmetatable(ordinal_names, { - __index = function(self,idx) return (idx+1)..'th entry' end -}) - -local function list_index_choices(length_func) - return function(id) - if id > 0 then - local ok, len = pcall(length_func) - if not ok then - len = 5 - elseif len > 10 then - len = 10 - end - return id % len - else - return 0 - end - end -end - -local function can_feed() - return not force_scan['nofeed'] and is_known 'gview' -end - -local function dwarfmode_feed_input(...) - local screen = screen_dwarfmode() - if not df.isvalid(screen) then - qerror('could not retrieve dwarfmode screen') - end - try_save_cursor() - for _,v in ipairs({...}) do - gui.simulateInput(screen, v) - end -end - -local function dwarfmode_step_frames(count) - local screen = screen_dwarfmode() - if not df.isvalid(screen) then - qerror('could not retrieve dwarfmode screen') - end - - for i = 1,(count or 1) do - gui.simulateInput(screen, 'D_ONESTEP') - if screen.keyRepeat ~= 1 then - qerror('Could not step one frame: . did not work') - end - screen:logic() - end -end - -local function dwarfmode_to_top() - if not can_feed() then - return false - end - - local screen = screen_dwarfmode() - if not df.isvalid(screen) then - return false - end - - for i=0,10 do - if is_known 'ui' and df.global.ui.main.mode == df.ui_sidebar_mode.Default then - break - end - gui.simulateInput(screen, 'LEAVESCREEN') - end - - -- force pause just in case - screen.keyRepeat = 1 - return true -end - -local prev_cursor = df.global.T_cursor:new() -prev_cursor.x = -30000 -function try_save_cursor() - if not dfhack.internal.getAddress('cursor') then return end - for _, v in pairs(df.global.cursor) do - if v < 0 then - return - end - end - prev_cursor:assign(df.global.cursor) -end - -function try_restore_cursor() - if not dfhack.internal.getAddress('cursor') then return end - if prev_cursor.x >= 0 then - df.global.cursor:assign(prev_cursor) - dwarfmode_feed_input('CURSOR_DOWN_Z', 'CURSOR_UP_Z') - end -end - -local function feed_menu_choice(catnames,catkeys,enum,enter_seq,exit_seq,prompt) - return function (idx) - if idx == 0 and prompt and not prompt_proceed(2) then - return false - end - 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 - return true, enum[catnames[idx]] - else - return true, catnames[idx] - end - end -end - -local function feed_list_choice(count,upkey,downkey) - return function(idx) - if idx > 0 then - local ok, len - if type(count) == 'number' then - ok, len = true, count - else - ok, len = pcall(count) - end - if not ok then - len = 5 - elseif len > 10 then - len = 10 - end - - local hcnt = len-1 - local rix = 1 + (idx-1) % (hcnt*2) - - if rix >= hcnt then - dwarfmode_feed_input(upkey or 'SECONDSCROLL_UP') - return true, hcnt*2 - rix - else - dwarfmode_feed_input(donwkey or 'SECONDSCROLL_DOWN') - return true, rix - end - else - print(' Please select the first list item.') - if not prompt_proceed(2) then - return false - end - return true, 0 - end - end -end - -local function feed_menu_bool(enter_seq, exit_seq) - return function(idx) - if idx == 0 then - if not prompt_proceed(2) then - return false - end - return true, 0 - end - if idx == 5 then - print(' Please resize the game window.') - if not prompt_proceed(2) then - return false - end - end - if idx%2 == 1 then - dwarfmode_feed_input(table.unpack(enter_seq)) - return true, 1 - else - dwarfmode_feed_input(table.unpack(exit_seq)) - return true, 0 - end - end -end - --- --- Cursor group --- - -local function find_cursor() - if not screen_title() then - return false - end - - -- Unpadded version - local idx, addr = data.int32_t:find_one{ - -30000, -30000, -30000, - -30000, -30000, -30000, -30000, -30000, -30000, - df.game_mode.NONE, df.game_type.NONE - } - if idx then - ms.found_offset('cursor', addr) - ms.found_offset('selection_rect', addr + 12) - ms.found_offset('gamemode', addr + 12 + 24) - ms.found_offset('gametype', addr + 12 + 24 + 4) - return true - end - - -- Padded version - idx, addr = data.int32_t:find_one{ - -30000, -30000, -30000, 0, - -30000, -30000, -30000, -30000, -30000, -30000, 0, 0, - df.game_mode.NONE, 0, 0, 0, df.game_type.NONE - } - if idx then - ms.found_offset('cursor', addr) - ms.found_offset('selection_rect', addr + 0x10) - ms.found_offset('gamemode', addr + 0x30) - ms.found_offset('gametype', addr + 0x40) - return true - end - - dfhack.printerr('Could not find cursor.') - return false -end - --- --- Announcements --- - -local function find_announcements() - local idx, addr = data.int32_t:find_one{ - 25, 25, 31, 31, 24, 24, 40, 40, 40, 40, 40, 40, 40 - } - if idx then - ms.found_offset('announcements', addr) - return - end - - dfhack.printerr('Could not find announcements.') -end - --- --- d_init --- - -local function is_valid_d_init(di) - if di.sky_tile ~= 178 then - print('Sky tile expected 178, found: '..di.sky_tile) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - local ann = is_known 'announcements' - local size,ptr = di:sizeof() - if ann and ptr+size ~= ann then - print('Announcements not immediately after d_init.') - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_d_init() - local idx, addr = data.int16_t:find_one{ - 1,0, 2,0, 5,0, 25,0, -- path_cost - 4,4, -- embark_rect - 20,1000,1000,1000,1000 -- store_dist - } - if idx then - validate_offset('d_init', is_valid_d_init, addr, df.d_init, 'path_cost') - return - end - - dfhack.printerr('Could not find d_init') -end - --- --- gview --- - -local function find_gview() - local vs_vtable = dfhack.internal.getVTable('viewscreenst') - if not vs_vtable then - dfhack.printerr('Cannot search for gview - no viewscreenst vtable.') - return - end - - local idx, addr = data.uint32_t:find_one{0, vs_vtable} - if idx then - ms.found_offset('gview', addr) - return - end - - idx, addr = data.uint32_t:find_one{100, vs_vtable} - if idx then - ms.found_offset('gview', addr) - return - end - - dfhack.printerr('Could not find gview') -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) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - return true -end - -local function find_enabler() - -- Data from data/init/colors.txt - 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, - 0, 0, 255, 0, 255, 0, 0, 255, 255, - 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 - end - - dfhack.printerr('Could not find enabler') -end - --- --- gps --- - -local function is_valid_gps(g) - if g.clipx[0] < 0 or g.clipx[0] > g.clipx[1] or g.clipx[1] >= g.dimx then - dfhack.printerr('Invalid clipx: ', g.clipx[0], g.clipx[1], g.dimx) - end - if g.clipy[0] < 0 or g.clipy[0] > g.clipy[1] or g.clipy[1] >= g.dimy then - dfhack.printerr('Invalid clipy: ', g.clipy[0], g.clipy[1], g.dimy) - end - - return true -end - -local function find_gps() - print('\nPlease ensure the mouse cursor is not over the game window.') - if not prompt_proceed() then - return - end - - local zone - if os_type == 'windows' or os_type == 'linux' then - zone = zoomed_searcher('cursor', 0x1000) - elseif os_type == 'darwin' then - zone = zoomed_searcher('enabler', 0x1000) - end - zone = zone or searcher - - 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 - end - - dfhack.printerr('Could not find gps') -end - --- --- World --- - -local function is_valid_world(world) - if not ms.is_valid_vector(world.units.all, 4) - or not ms.is_valid_vector(world.units.active, 4) - or not ms.is_valid_vector(world.units.bad, 4) - or not ms.is_valid_vector(world.history.figures, 4) - or not ms.is_valid_vector(world.features.map_features, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if #world.units.all == 0 or #world.units.all ~= #world.units.bad then - print('Different or zero size of units.all and units.bad:'..#world.units.all..' vs '..#world.units.bad) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_world() - local catnames = { - 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' - } - local catkeys = { - 'STOCKPILE_GRAVEYARD', 'STOCKPILE_REFUSE', 'STOCKPILE_STONE', 'STOCKPILE_WOOD', - 'STOCKPILE_GEM', 'STOCKPILE_BARBLOCK', 'STOCKPILE_CLOTH', 'STOCKPILE_LEATHER', - 'STOCKPILE_AMMO', 'STOCKPILE_COINS' - } - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_STOCKPILES') - - addr = searcher:find_interactive( - 'Auto-searching for world.', - 'int32_t', - feed_menu_choice(catnames, catkeys, df.stockpile_category), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for world. Please open the stockpile creation -menu, and select different types as instructed below:]], - 'int32_t', catnames, df.stockpile_category - ) - end - - validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type') -end - --- --- UI --- - -local function is_valid_ui(ui) - if not ms.is_valid_vector(ui.economic_stone, 1) - or not ms.is_valid_vector(ui.dipscripts, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if ui.follow_item ~= -1 or ui.follow_unit ~= -1 then - print('Invalid follow state: '..ui.follow_item..', '..ui.follow_unit) - return false - end - - return true -end - -local function find_ui() - local catnames = { - 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', - 'DesignateUpStair', 'DesignateDownStair', 'DesignateUpDownStair', - 'DesignateUpRamp', 'DesignateChopTrees' - } - local catkeys = { - 'DESIGNATE_DIG', 'DESIGNATE_CHANNEL', 'DESIGNATE_DIG_REMOVE_STAIRS_RAMPS', - 'DESIGNATE_STAIR_UP', 'DESIGNATE_STAIR_DOWN', 'DESIGNATE_STAIR_UPDOWN', - 'DESIGNATE_RAMP', 'DESIGNATE_CHOP' - } - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_DESIGNATE') - - addr = searcher:find_interactive( - 'Auto-searching for ui.', - 'int16_t', - feed_menu_choice(catnames, catkeys, df.ui_sidebar_mode), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui. Please open the designation -menu, and switch modes as instructed below:]], - 'int16_t', catnames, df.ui_sidebar_mode - ) - end - - validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode') -end - --- --- ui_sidebar_menus --- - -local function is_valid_ui_sidebar_menus(usm) - if not ms.is_valid_vector(usm.workshop_job.choices_all, 4) - or not ms.is_valid_vector(usm.workshop_job.choices_visible, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if #usm.workshop_job.choices_all == 0 - or #usm.workshop_job.choices_all ~= #usm.workshop_job.choices_visible then - print('Different or zero size of visible and all choices:'.. - #usm.workshop_job.choices_all..' vs '..#usm.workshop_job.choices_visible) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_ui_sidebar_menus() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - - addr = searcher:find_interactive([[ -Auto-searching for ui_sidebar_menus. Please select a Mason's, -Craftsdwarf's, or Carpenter's workshop:]], - 'int32_t', - function(idx) - if idx == 0 then - prompt_proceed(2) - -- ensure that the job list isn't full - dwarfmode_feed_input('BUILDJOB_CANCEL', 'BUILDJOB_ADD') - return true, 0 - end - return feed_list_choice(7)(idx) - end, - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_sidebar_menus. Please switch to 'q' mode, -select a Mason, Craftsdwarfs, or Carpenters workshop, open -the Add Job menu, and move the cursor within:]], - 'int32_t', - { 0, 1, 2, 3, 4, 5, 6 }, - ordinal_names - ) - end - - validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus, - addr, df.ui_sidebar_menus, 'workshop_job', 'cursor') -end - --- --- ui_build_selector --- - -local function is_valid_ui_build_selector(ubs) - if not ms.is_valid_vector(ubs.requirements, 4) - or not ms.is_valid_vector(ubs.choices, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if ubs.building_type ~= df.building_type.Trap - or ubs.building_subtype ~= df.trap_type.PressurePlate then - print('Invalid building type and subtype:'..ubs.building_type..','..ubs.building_subtype) - return false - end - - return true -end - -local function find_ui_build_selector() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive([[ -Auto-searching for ui_build_selector. This requires mechanisms.]], - 'int32_t', - function(idx) - if idx == 0 then - dwarfmode_to_top() - dwarfmode_feed_input( - 'D_BUILDING', - 'HOTKEY_BUILDING_TRAP', - 'HOTKEY_BUILDING_TRAP_TRIGGER', - 'BUILDING_TRIGGER_ENABLE_CREATURE' - ) - else - dwarfmode_feed_input('BUILDING_TRIGGER_MIN_SIZE_UP') - end - return true, 5000 + 1000*idx - end, - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_build_selector. Please start constructing -a pressure plate, and enable creatures. Then change the min -weight as requested, knowing that the ui shows 5000 as 5K:]], - 'int32_t', - { 5000, 6000, 7000, 8000, 9000, 10000, 11000 } - ) - end - - validate_offset('ui_build_selector', is_valid_ui_build_selector, - addr, df.ui_build_selector, 'plate_info', 'unit_min') -end - --- --- init --- - -local function is_valid_init(i) - -- derived from curses_*.png image sizes presumably - if i.font.small_font_dispx ~= 8 or i.font.small_font_dispy ~= 12 or - i.font.large_font_dispx ~= 10 or i.font.large_font_dispy ~= 12 then - print('Unexpected font sizes: ', - i.font.small_font_dispx, i.font.small_font_dispy, - i.font.large_font_dispx, i.font.large_font_dispy) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_init() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('ui_build_selector', 0x3000) - elseif os_type == 'linux' or os_type == 'darwin' then - zone = zoomed_searcher('d_init', -0x2000) - end - zone = zone or searcher - - local idx, addr = zone.area.int32_t:find_one{250, 150, 15, 0} - if idx then - validate_offset('init', is_valid_init, addr, df.init, 'input', 'hold_time') - return - end - - local w,h = ms.get_screen_size() - - local idx, addr = zone.area.int32_t:find_one{w, h} - if idx then - validate_offset('init', is_valid_init, addr, df.init, 'display', 'grid_x') - return - end - - dfhack.printerr('Could not find init') -end - --- --- current_weather --- - -local function find_current_weather() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('crime_next_id', 512) - elseif os_type == 'darwin' then - zone = zoomed_searcher('cursor', -64) - elseif os_type == 'linux' then - zone = zoomed_searcher('ui_building_assign_type', -512) - end - zone = zone or searcher - - local wbytes = { - 2, 1, 0, 2, 0, - 1, 2, 1, 0, 0, - 2, 0, 2, 1, 2, - 1, 2, 0, 1, 1, - 2, 0, 1, 0, 2 - } - - local idx, addr = zone.area.int8_t:find_one(wbytes) - if idx then - ms.found_offset('current_weather', addr) - return - end - - dfhack.printerr('Could not find current_weather - must be a wrong save.') -end - --- --- ui_menu_width --- - -local function find_ui_menu_width() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive('Auto-searching for ui_menu_width', 'int8_t', function(idx) - local val = (idx % 3) + 1 - if idx == 0 then - print('Switch to the default [map][menu][map] layout (with Tab)') - if not prompt_proceed(2) then return false end - else - dwarfmode_feed_input('CHANGETAB', val ~= 3 and 'CHANGETAB') - end - return true, val - end) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_menu_width. Please exit to the main -dwarfmode menu, then use Tab to do as instructed below:]], - 'int8_t', - { 2, 3, 1 }, - { [2] = 'switch to the most usual [mapmap][menu] layout', - [3] = 'hide the menu completely', - [1] = 'switch to the default [map][menu][map] layout' } - ) - end - - ms.found_offset('ui_menu_width', addr) - - -- NOTE: Assume that the vars are adjacent, as always - ms.found_offset('ui_area_map_width', addr+1) - - -- reset to make sure view is small enough for window_x/y scan on small maps - df.global.ui_menu_width = 2 - df.global.ui_area_map_width = 3 -end - --- --- ui_selected_unit --- - -local function find_ui_selected_unit() - if not is_known 'world' then - dfhack.printerr('Cannot search for ui_selected_unit: no world') - return - end - - for i,unit in ipairs(df.global.world.units.active) do - -- This function does a lot of things and accesses histfigs, souls and so on: - --dfhack.units.setNickname(unit, i) - - -- Instead use just a simple bit of code that only requires the start of the - -- unit to be valid. It may not work properly with vampires or reset later - -- if unpaused, but is sufficient for this script and won't crash: - unit.name.nickname = tostring(i) - unit.name.has_name = true - end - - local addr = searcher:find_menu_cursor([[ -Searching for ui_selected_unit. Please activate the 'v' -mode, point it at units, and enter their numeric nickname -into the prompts below:]], - 'int32_t', - function() - return utils.prompt_input(' Enter index: ', utils.check_number) - end, - 'noprompt' - ) - ms.found_offset('ui_selected_unit', addr) -end - --- --- ui_unit_view_mode --- - -local function find_ui_unit_view_mode() - local catnames = { 'General', 'Inventory', 'Preferences', 'Wounds' } - local catkeys = { 'UNITVIEW_GEN', 'UNITVIEW_INV', 'UNITVIEW_PRF', 'UNITVIEW_WND' } - local addr - - if dwarfmode_to_top() and is_known('ui_selected_unit') then - dwarfmode_feed_input('D_VIEWUNIT') - - if df.global.ui_selected_unit < 0 then - df.global.ui_selected_unit = 0 - end - - addr = searcher:find_interactive( - 'Auto-searching for ui_unit_view_mode.', - 'int32_t', - feed_menu_choice(catnames, catkeys, df.ui_unit_view_mode.T_value), - 10 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_unit_view_mode. Having selected a unit -with 'v', switch the pages as requested:]], - 'int32_t', catnames, df.ui_unit_view_mode.T_value - ) - end - - ms.found_offset('ui_unit_view_mode', addr) -end - --- --- ui_look_cursor --- - -local function look_item_list_count() - return #df.global.ui_look_list.items -end - -local function find_ui_look_cursor() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_LOOK') - - addr = searcher:find_interactive([[ -Auto-searching for ui_look_cursor. Please select a tile -with at least 5 items or units on the ground, and move -the cursor as instructed:]], - 'int32_t', - feed_list_choice(look_item_list_count), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_look_cursor. Please activate the 'k' -mode, find a tile with many items or units on the ground, -and select list entries as instructed:]], - 'int32_t', - list_index_choices(look_item_list_count), - ordinal_names - ) - end - - ms.found_offset('ui_look_cursor', addr) -end - --- --- ui_building_item_cursor --- - -local function building_item_list_count() - return #df.global.world.selected_building.contained_items -end - -local function find_ui_building_item_cursor() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDITEM') - - addr = searcher:find_interactive([[ -Auto-searching for ui_building_item_cursor. Please highlight a -workshop, trade depot or other building with at least 5 contained -items, and select as instructed:]], - 'int32_t', - feed_list_choice(building_item_list_count), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_building_item_cursor. Please activate the 't' -mode, find a cluttered workshop, trade depot, or other building -with many contained items, and select as instructed:]], - 'int32_t', - list_index_choices(building_item_list_count), - ordinal_names - ) - end - - ms.found_offset('ui_building_item_cursor', addr) -end - --- --- ui_workshop_in_add --- - -local function find_ui_workshop_in_add() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - - addr = searcher:find_interactive([[ -Auto-searching for ui_workshop_in_add. Please select a -workshop, e.g. Carpenters or Masons.]], - 'int8_t', - feed_menu_bool( - { 'BUILDJOB_CANCEL', 'BUILDJOB_ADD' }, - { 'SELECT', 'SELECT', 'SELECT', 'SELECT', 'SELECT' } - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_workshop_in_add. Please activate the 'q' -mode, find a workshop without jobs (or delete jobs), -and do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the add job menu', - [0] = 'add job, thus exiting the menu' } - ) - end - - ms.found_offset('ui_workshop_in_add', addr) -end - --- --- ui_workshop_job_cursor --- - -local function workshop_job_list_count() - return #df.global.world.selected_building.jobs -end - -local function find_ui_workshop_job_cursor() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - addr = searcher:find_interactive([[ -Auto-searching for ui_workshop_job_cursor. Please highlight a -Mason's or Carpenter's workshop, or any building with a job -selection interface navigable with just "Enter":]], - 'int32_t', - function(idx) - if idx == 0 then prompt_proceed(2) end - for i = 1, 10 - workshop_job_list_count() do - dwarfmode_feed_input('BUILDJOB_ADD', 'SELECT', 'SELECT', 'SELECT', 'SELECT', 'SELECT') - end - dwarfmode_feed_input('SECONDSCROLL_DOWN') - -- adding jobs resets the cursor position, so it is difficult to determine here - return true - end, - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_workshop_job_cursor. Please activate the 'q' -mode, find a workshop with many jobs, and select as instructed:]], - 'int32_t', - list_index_choices(workshop_job_list_count), - ordinal_names - ) - end - - ms.found_offset('ui_workshop_job_cursor', addr) -end - --- --- ui_building_in_assign --- - -local function find_ui_building_in_assign() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - try_restore_cursor() - - addr = searcher:find_interactive([[ -Auto-searching for ui_building_in_assign. Please select a room, -i.e. a bedroom, tomb, office, dining room or statue garden.]], - 'int8_t', - feed_menu_bool( - { { 'BUILDJOB_STATUE_ASSIGN', 'BUILDJOB_COFFIN_ASSIGN', - 'BUILDJOB_CHAIR_ASSIGN', 'BUILDJOB_TABLE_ASSIGN', - 'BUILDJOB_BED_ASSIGN' } }, - { 'LEAVESCREEN' } - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_building_in_assign. Please activate -the 'q' mode, select a room building (e.g. a bedroom) -and do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Assign owner menu', - [0] = 'press Esc to exit assign' } - ) - end - - ms.found_offset('ui_building_in_assign', addr) -end - --- --- ui_building_in_resize --- - -local function find_ui_building_in_resize() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - try_restore_cursor() - - addr = searcher:find_interactive([[ -Auto-searching for ui_building_in_resize. Please select a room, -i.e. a bedroom, tomb, office, dining room or statue garden.]], - 'int8_t', - feed_menu_bool( - { { 'BUILDJOB_STATUE_SIZE', 'BUILDJOB_COFFIN_SIZE', - 'BUILDJOB_CHAIR_SIZE', 'BUILDJOB_TABLE_SIZE', - 'BUILDJOB_BED_SIZE' } }, - { 'LEAVESCREEN' } - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_building_in_resize. Please activate -the 'q' mode, select a room building (e.g. a bedroom) -and do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Resize room mode', - [0] = 'press Esc to exit resize' } - ) - end - - ms.found_offset('ui_building_in_resize', addr) -end - --- --- ui_lever_target_type --- -local function find_ui_lever_target_type() - local catnames = { - 'Bridge', 'Door', 'Floodgate', - 'Cage', 'Chain', 'TrackStop', - 'GearAssembly', - } - local catkeys = { - 'HOTKEY_TRAP_BRIDGE', 'HOTKEY_TRAP_DOOR', 'HOTKEY_TRAP_FLOODGATE', - 'HOTKEY_TRAP_CAGE', 'HOTKEY_TRAP_CHAIN', 'HOTKEY_TRAP_TRACK_STOP', - 'HOTKEY_TRAP_GEAR_ASSEMBLY', - } - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - - addr = searcher:find_interactive( - 'Auto-searching for ui_lever_target_type. Please select a lever:', - 'int8_t', - feed_menu_choice(catnames, catkeys, df.lever_target_type, - {'BUILDJOB_ADD'}, - {'LEAVESCREEN', 'LEAVESCREEN'}, - true -- prompt - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_lever_target_type. Please select a lever with -'q' and enter the "add task" menu with 'a':]], - 'int8_t', catnames, df.lever_target_type - ) - end - - ms.found_offset('ui_lever_target_type', addr) -end - --- --- window_x --- - -local function feed_window_xyz(dec,inc,step) - return function(idx) - if idx == 0 then - for i = 1,30 do dwarfmode_feed_input(dec) end - else - dwarfmode_feed_input(inc) - end - return true, nil, step - end -end - -local function find_window_x() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for window_x.', - 'int32_t', - feed_window_xyz('CURSOR_LEFT_FAST', 'CURSOR_RIGHT', 10), - 20 - ) - - dwarfmode_feed_input('D_HOTKEY1') - end - - if not addr then - addr = searcher:find_counter([[ -Searching for window_x. Please exit to main dwarfmode menu, -scroll to the LEFT edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Right to scroll right one step.' - ) - end - - ms.found_offset('window_x', addr) -end - --- --- window_y --- - -local function find_window_y() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for window_y.', - 'int32_t', - feed_window_xyz('CURSOR_UP_FAST', 'CURSOR_DOWN', 10), - 20 - ) - - dwarfmode_feed_input('D_HOTKEY1') - end - - if not addr then - addr = searcher:find_counter([[ -Searching for window_y. Please exit to main dwarfmode menu, -scroll to the TOP edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Down to scroll down one step.' - ) - end - - ms.found_offset('window_y', addr) -end - --- --- window_z --- - -local function find_window_z() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for window_z.', - 'int32_t', - feed_window_xyz('CURSOR_UP_Z', 'CURSOR_DOWN_Z', -1), - 30 - ) - - dwarfmode_feed_input('D_HOTKEY1') - end - - if not addr then - addr = searcher:find_counter([[ -Searching for window_z. Please exit to main dwarfmode menu, -scroll to a Z level near surface, then do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int32_t', -1, - "Please press '>' to scroll one Z level down." - ) - end - - ms.found_offset('window_z', addr) -end - --- --- cur_year --- - -local function find_cur_year() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('formation_next_id', 32) - elseif os_type == 'darwin' then - zone = zoomed_searcher('cursor', -32) - elseif os_type == 'linux' then - zone = zoomed_searcher('ui_building_assign_type', -512) - end - if not zone then - dfhack.printerr('Cannot search for cur_year - prerequisites missing.') - return - end - - local yvalue = utils.prompt_input('Please enter current in-game year: ', utils.check_number) - local idx, addr = zone.area.int32_t:find_one{yvalue} - if idx then - ms.found_offset('cur_year', addr) - return - end - - dfhack.printerr('Could not find cur_year') -end - --- --- cur_year_tick --- - -function stop_autosave() - if is_known 'd_init' then - local f = df.global.d_init.flags4 - if f.AUTOSAVE_SEASONAL or f.AUTOSAVE_YEARLY then - f.AUTOSAVE_SEASONAL = false - f.AUTOSAVE_YEARLY = false - print('Disabled seasonal and yearly autosave.') - end - else - dfhack.printerr('Could not disable autosave!') - end -end - -function step_n_frames(cnt, feed) - local world = df.global.world - local ctick = world.frame_counter - - if feed then - print(" Auto-stepping "..cnt.." frames.") - dwarfmode_step_frames(cnt) - return world.frame_counter-ctick - end - - local more = '' - while world.frame_counter-ctick < cnt do - print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") - more = ' more' - if not prompt_proceed(2) then - return nil - end - end - return world.frame_counter-ctick -end - -local function find_cur_year_tick() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('ui_unit_view_mode', 0x200) - else - zone = zoomed_searcher('cur_year', 128) - end - if not zone then - dfhack.printerr('Cannot search for cur_year_tick - prerequisites missing.') - return - end - - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = zone:find_interactive( - 'Searching for cur_year_tick.', - 'int32_t', - function(idx) - if idx > 0 then - if not step_n_frames(1, feed) then - return false - end - end - return true, nil, 1 - end, - 20 - ) - - ms.found_offset('cur_year_tick', addr) -end - -local function find_cur_year_tick_advmode() - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = searcher:find_interactive( - 'Searching for cur_year_tick_advmode.', - 'int32_t', - function(idx) - if idx > 0 then - if not step_n_frames(1, feed) then - return false - end - end - return true, nil, 144 - end, - 20 - ) - - ms.found_offset('cur_year_tick_advmode', addr) -end - --- --- cur_season_tick --- - -local function find_cur_season_tick() - if not (is_known 'cur_year_tick') then - dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.') - return - end - - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = searcher:find_interactive([[ -Searching for cur_season_tick. Please exit to main dwarfmode -menu, then do as instructed below:]], - 'int32_t', - function(ccursor) - if ccursor > 0 then - if not step_n_frames(10, feed) then - return false - end - end - return true, math.floor((df.global.cur_year_tick%100800)/10) - end - ) - ms.found_offset('cur_season_tick', addr) -end - --- --- cur_season --- - -local function find_cur_season() - if not (is_known 'cur_year_tick' and is_known 'cur_season_tick') then - dfhack.printerr('Cannot search for cur_season - prerequisites missing.') - return - end - - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = searcher:find_interactive([[ -Searching for cur_season. Please exit to main dwarfmode -menu, then do as instructed below:]], - 'int8_t', - function(ccursor) - if ccursor > 0 then - local cst = df.global.cur_season_tick - df.global.cur_season_tick = 10079 - df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10 - if not step_n_frames(10, feed) then - return false - end - end - return true, math.floor(df.global.cur_year_tick/100800)%4 - end - ) - ms.found_offset('cur_season', addr) -end - --- --- process_jobs --- - -local function get_process_zone() - if os_type == 'windows' then - return zoomed_searcher('ui_workshop_job_cursor', 'ui_building_in_resize') - elseif os_type == 'linux' or os_type == 'darwin' then - return zoomed_searcher('cur_year', 'cur_year_tick') - end -end - -local function find_process_jobs() - local zone = get_process_zone() or searcher - local addr - - stop_autosave() - - if dwarfmode_to_top() and dfhack.internal.getAddress('cursor') then - local cursor = df.global.T_cursor:new() - addr = zone:find_interactive([[ -Searching for process_jobs. Please position the cursor to the left -of at least 10 vacant natural floor tiles.]], - 'int8_t', - function(idx) - if idx == 0 then - dwarfmode_feed_input('D_LOOK') - if not prompt_proceed(2) then return false end - cursor:assign(df.global.cursor) - elseif idx == 6 then - print(' Please resize the game window.') - if not prompt_proceed(2) then return false end - end - dwarfmode_to_top() - dwarfmode_step_frames(1) - if idx % 2 == 0 then - dwarfmode_feed_input( - 'D_BUILDING', - 'HOTKEY_BUILDING_CONSTRUCTION', - 'HOTKEY_BUILDING_CONSTRUCTION_WALL' - ) - df.global.cursor:assign(cursor) - df.global.cursor.x = df.global.cursor.x + (idx / 2) - dwarfmode_feed_input('CURSOR_RIGHT', 'CURSOR_LEFT', 'SELECT', 'SELECT') - return true, 1 - else - return true, 0 - end - end, - 20) - end - - if not addr then - local addr = zone:find_menu_cursor([[ -Searching for process_jobs. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'designate a building to be constructed, e.g a bed or a wall', - [0] = 'step or unpause the game to reset the flag' } - ) - end - ms.found_offset('process_jobs', addr) -end - --- --- process_dig --- - -local function find_process_dig() - local zone = get_process_zone() or searcher - local addr - - stop_autosave() - - if dwarfmode_to_top() and dfhack.internal.getAddress('cursor') then - local cursor = df.global.T_cursor:new() - addr = zone:find_interactive([[ -Searching for process_dig. Please position the cursor to the left -of at least 10 unmined, unrevealed tiles.]], - 'int8_t', - function(idx) - if idx == 0 then - dwarfmode_feed_input('D_LOOK') - if not prompt_proceed(2) then return false end - cursor:assign(df.global.cursor) - elseif idx == 6 then - print(' Please resize the game window.') - if not prompt_proceed(2) then return false end - end - dwarfmode_to_top() - dwarfmode_step_frames(1) - if idx % 2 == 0 then - dwarfmode_feed_input('D_DESIGNATE', 'DESIGNATE_DIG') - df.global.cursor:assign(cursor) - df.global.cursor.x = df.global.cursor.x + (idx / 2) - dwarfmode_feed_input('SELECT', 'SELECT') - return true, 1 - else - return true, 0 - end - end, - 20) - end - - if not addr then - addr = zone:find_menu_cursor([[ -Searching for process_dig. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'designate a tile to be mined out', - [0] = 'step or unpause the game to reset the flag' } - ) - end - ms.found_offset('process_dig', addr) -end - --- --- pause_state --- - -local function find_pause_state() - local zone, addr - if os_type == 'linux' or os_type == 'darwin' then - zone = zoomed_searcher('ui_look_cursor', 32) - elseif os_type == 'windows' then - zone = zoomed_searcher('ui_workshop_job_cursor', 80) - end - zone = zone or searcher - - stop_autosave() - - if dwarfmode_to_top() then - addr = zone:find_interactive( - 'Auto-searching for pause_state', - 'int8_t', - function(idx) - if idx%2 == 0 then - dwarfmode_feed_input('D_ONESTEP') - return true, 0 - else - screen_dwarfmode():logic() - return true, 1 - end - end, - 20 - ) - end - - if not addr then - addr = zone:find_menu_cursor([[ -Searching for pause_state. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'PAUSE the game', - [0] = 'UNPAUSE the game' } - ) - end - - 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) - dwarfmode_feed_input('D_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 - if force_scan['standing_orders'] then - force_scan[gname] = true - end - exec_finder(function() - return find_standing_orders(gname, seq, depends) - end, gname) -end - --- --- MAIN FLOW --- - -print('\nInitial globals (need title screen):\n') - -exec_finder(find_gview, 'gview') -exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' }) -exec_finder(find_announcements, 'announcements') -exec_finder(find_d_init, 'd_init', is_valid_d_init) -exec_finder(find_enabler, 'enabler', is_valid_enabler) -exec_finder(find_gps, 'gps', is_valid_gps) - -print('\nCompound globals (need loaded world):\n') - -print('\nPlease load the save previously processed with prepare-save.') -if not prompt_proceed() then - searcher:reset() - return -end - -exec_finder(find_world, 'world', is_valid_world) -exec_finder(find_ui, 'ui', is_valid_ui) -exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus') -exec_finder(find_ui_build_selector, 'ui_build_selector') -exec_finder(find_init, 'init', is_valid_init) - -print('\nPrimitive globals:\n') - -exec_finder(find_current_weather, 'current_weather') -exec_finder(find_ui_menu_width, { 'ui_menu_width', 'ui_area_map_width' }) -exec_finder(find_ui_selected_unit, 'ui_selected_unit') -exec_finder(find_ui_unit_view_mode, 'ui_unit_view_mode') -exec_finder(find_ui_look_cursor, 'ui_look_cursor') -exec_finder(find_ui_building_item_cursor, 'ui_building_item_cursor') -exec_finder(find_ui_workshop_in_add, 'ui_workshop_in_add') -exec_finder(find_ui_workshop_job_cursor, 'ui_workshop_job_cursor') -exec_finder(find_ui_building_in_assign, 'ui_building_in_assign') -exec_finder(find_ui_building_in_resize, 'ui_building_in_resize') -exec_finder(find_ui_lever_target_type, 'ui_lever_target_type') -exec_finder(find_window_x, 'window_x') -exec_finder(find_window_y, 'window_y') -exec_finder(find_window_z, 'window_z') - -print('\nUnpausing globals:\n') - -exec_finder(find_cur_year, 'cur_year') -exec_finder(find_cur_year_tick, 'cur_year_tick') -exec_finder(find_cur_year_tick_advmode, 'cur_year_tick_advmode') -exec_finder(find_cur_season_tick, 'cur_season_tick') -exec_finder(find_cur_season, 'cur_season') -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') - -for _, global in ipairs(finder_searches) do - local addr = dfhack.internal.getAddress(global) - if addr ~= nil then - local ival = addr - dfhack.internal.getRebaseDelta() - print(string.format("", global, ival)) - end -end - -searcher:reset() diff --git a/scripts/devel/inject-raws.lua b/scripts/devel/inject-raws.lua deleted file mode 100644 index 048d4b560..000000000 --- a/scripts/devel/inject-raws.lua +++ /dev/null @@ -1,197 +0,0 @@ --- Inject new raw definitions into the world ---[[=begin - -devel/inject-raws -================= -WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE. - -This script attempts to inject new raw objects into your -world. If the injected references do not match the actual -edited raws, your save will refuse to load, or load but crash. - -This script can handle reaction, item and building definitions. - -The savegame contains a list of the relevant definition tokens in -the right order, but all details are read from raws every time. -This allows just adding stub definitions, and simply saving and -reloading the game. - -This is useful enough for modders and some users to justify the danger. - -Usage example:: - - devel/inject-raws trapcomp ITEM_TRAPCOMP_STEAM_PISTON workshop STEAM_ENGINE MAGMA_STEAM_ENGINE reaction STOKE_BOILER - -=end]] - -local utils = require 'utils' - -local raws = df.global.world.raws - -print[[ -WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE. - -This script attempts to inject new raw objects into your -world. If the injected references do not match the actual -edited raws, your save will refuse to load, or load but crash. -]] - -if not utils.prompt_yes_no('Did you make a backup?') then - qerror('Not backed up.') -end - -df.global.pause_state = true - -local changed = false - -function inject_reaction(name) - for _,v in ipairs(raws.reactions) do - if v.code == name then - print('Reaction '..name..' already exists.') - return - end - end - - print('Injecting reaction '..name) - changed = true - - raws.reactions:insert('#', { - new = true, - code = name, - name = 'Dummy reaction '..name, - index = #raws.reactions, - }) -end - -local building_types = { - workshop = { df.building_def_workshopst, raws.buildings.workshops }, - furnace = { df.building_def_furnacest, raws.buildings.furnaces }, -} - -function inject_building(btype, name) - for _,v in ipairs(raws.buildings.all) do - if v.code == name then - print('Building '..name..' already exists.') - return - end - end - - print('Injecting building '..name) - changed = true - - local typeinfo = building_types[btype] - - local id = raws.buildings.next_id - raws.buildings.next_id = id+1 - - raws.buildings.all:insert('#', { - new = typeinfo[1], - code = name, - name = 'Dummy '..btype..' '..name, - id = id, - }) - - typeinfo[2]:insert('#', raws.buildings.all[#raws.buildings.all-1]) -end - -local itemdefs = raws.itemdefs -local item_types = { - weapon = { df.itemdef_weaponst, itemdefs.weapons, 'weapon_type' }, - trainweapon = { df.itemdef_weaponst, itemdefs.weapons, 'training_weapon_type' }, - pick = { df.itemdef_weaponst, itemdefs.weapons, 'digger_type' }, - trapcomp = { df.itemdef_trapcompst, itemdefs.trapcomps, 'trapcomp_type' }, - toy = { df.itemdef_toyst, itemdefs.toys, 'toy_type' }, - tool = { df.itemdef_toolst, itemdefs.tools, 'tool_type' }, - instrument = { df.itemdef_instrumentst, itemdefs.instruments, 'instrument_type' }, - armor = { df.itemdef_armorst, itemdefs.armor, 'armor_type' }, - ammo = { df.itemdef_ammost, itemdefs.ammo, 'ammo_type' }, - siegeammo = { df.itemdef_siegeammost, itemdefs.siege_ammo, 'siegeammo_type' }, - gloves = { df.itemdef_glovest, itemdefs.gloves, 'gloves_type' }, - shoes = { df.itemdef_shoest, itemdefs.shoes, 'shoes_type' }, - shield = { df.itemdef_shieldst, itemdefs.shields, 'shield_type' }, - helm = { df.itemdef_helmst, itemdefs.helms, 'helm_type' }, - pants = { df.itemdef_pantsst, itemdefs.pants, 'pants_type' }, - food = { df.itemdef_foodst, itemdefs.food }, -} - -function add_to_civ(entity, bvec, id) - for _,v in ipairs(entity.resources[bvec]) do - if v == id then - return - end - end - - entity.resources[bvec]:insert('#', id) -end - -function add_to_dwarf_civs(btype, id) - local typeinfo = item_types[btype] - if not typeinfo[3] then - print('Not adding to civs.') - end - - for _,entity in ipairs(df.global.world.entities.all) do - if entity.race == df.global.ui.race_id then - add_to_civ(entity, typeinfo[3], id) - end - end -end - -function inject_item(btype, name) - for _,v in ipairs(itemdefs.all) do - if v.id == name then - print('Itemdef '..name..' already exists.') - return - end - end - - print('Injecting item '..name) - changed = true - - local typeinfo = item_types[btype] - local vec = typeinfo[2] - local id = #vec - - vec:insert('#', { - new = typeinfo[1], - id = name, - subtype = id, - name = name, - name_plural = name, - }) - - itemdefs.all:insert('#', vec[id]) - - add_to_dwarf_civs(btype, id) -end - -local args = {...} -local mode = nil -local ops = {} - -for _,kv in ipairs(args) do - if mode and string.match(kv, '^[%u_]+$') then - table.insert(ops, curry(mode, kv)) - elseif kv == 'reaction' then - mode = inject_reaction - elseif building_types[kv] then - mode = curry(inject_building, kv) - elseif item_types[kv] then - mode = curry(inject_item, kv) - else - qerror('Invalid option: '..kv) - end -end - -if #ops > 0 then - print('') - for _,v in ipairs(ops) do - v() - end -end - -if changed then - print('\nNow without unpausing save and reload the game to re-read raws.') -else - print('\nNo changes made.') -end diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua deleted file mode 100644 index 8f622756f..000000000 --- a/scripts/devel/inspect-screen.lua +++ /dev/null @@ -1,110 +0,0 @@ --- Read from the screen and display info about the tiles ---[[=begin - -devel/inspect-screen -==================== -Read the tiles from the screen and display info about them. - -=end]] - -local utils = require 'utils' -local gui = require 'gui' - -InspectScreen = defclass(InspectScreen, gui.Screen) - -function InspectScreen:init(args) - local w,h = dfhack.screen.getWindowSize() - self.cursor_x = math.floor(w/2) - self.cursor_y = math.floor(h/2) -end - -function InspectScreen:computeFrame(parent_rect) - local sw, sh = parent_rect.width, parent_rect.height - self.cursor_x = math.max(0, math.min(self.cursor_x, sw-1)) - self.cursor_y = math.max(0, math.min(self.cursor_y, sh-1)) - - local frame = { w = 14, r = 1, h = 10, t = 1 } - if self.cursor_x > sw/2 then - frame = { w = 14, l = 1, h = 10, t = 1 } - end - - return gui.compute_frame_body(sw, sh, frame, 1, 0, false) -end - -function InspectScreen:onRenderFrame(dc, rect) - self:renderParent() - self.cursor_pen = dfhack.screen.readTile(self.cursor_x, self.cursor_y) - if gui.blink_visible(100) then - dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y) - end - dc:fill(rect, {ch=' ',fg=COLOR_WHITE,bg=COLOR_CYAN}) -end - -local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true} -local BG_PEN = {fg=COLOR_BLACK,bg=COLOR_WHITE,tile_color=true} -local TXT_PEN = {fg=COLOR_WHITE} - -function InspectScreen:onRenderBody(dc) - dc:pen(COLOR_WHITE, COLOR_CYAN) - if self.cursor_pen then - local info = self.cursor_pen - dc:string('CH: '):char(info.ch, FG_PEN):char(info.ch, BG_PEN):string(' '):string(''..info.ch,TXT_PEN):newline() - local fgcolor = info.fg - local fgstr = info.fg - if info.bold then - fgcolor = (fgcolor+8)%16 - fgstr = fgstr..'+8' - end - dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN) - dc:seek(dc.width-1):char(info.ch,{fg=info.fg,bold=info.bold}):newline() - dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN) - dc:seek(dc.width-1):char(info.ch,{fg=COLOR_BLACK,bg=info.bg}):newline() - local bstring = 'false' - if info.bold then bstring = 'true' end - dc:string('Bold: '..bstring):newline():newline() - - if info.tile and gui.USE_GRAPHICS then - dc:string('TL: '):tile(' ', info.tile, FG_PEN):tile(' ', info.tile, BG_PEN):string(' '..info.tile):newline() - if info.tile_color then - dc:string('Color: true') - elseif info.tile_fg then - dc:string('FG: '):string('NN',{fg=info.tile_fg}):string(' '):string(''..info.tile_fg,TXT_PEN):newline() - dc:string('BG: '):string('NN',{fg=info.tile_bg}):string(' '):string(''..info.tile_bg,TXT_PEN):newline() - end - end - else - dc:string('Invalid', COLOR_LIGHTRED) - end -end - -local MOVEMENT_KEYS = { - CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 }, - CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, - CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 }, - CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 }, - CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, - CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, - CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, - CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true }, -} - -function InspectScreen:onInput(keys) - if keys.LEAVESCREEN then - self:dismiss() - else - for k,v in pairs(MOVEMENT_KEYS) do - if keys[k] then - local delta = 1 - if v[4] then - delta = 10 - end - self.cursor_x = self.cursor_x + delta*v[1] - self.cursor_y = self.cursor_y + delta*v[2] - self:updateLayout() - return - end - end - end -end - -InspectScreen{}:show() diff --git a/scripts/devel/light.lua b/scripts/devel/light.lua deleted file mode 100644 index e19591e11..000000000 --- a/scripts/devel/light.lua +++ /dev/null @@ -1,388 +0,0 @@ --- an experimental lighting engine ---[[=begin - -devel/light -=========== -An experimental lighting engine for DF, using the `rendermax` plugin. - -Call ``devel/light static`` to not recalculate lighting when in game. -Press :kbd:`~` to recalculate lighting. Press :kbd:`\`` to exit. - -=end]] - -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local render = require 'plugins.rendermax' - -local levelDim=0.05 -local tile_attrs = df.tiletype.attrs - -local args={...} - -function setCell(x,y,cell) - cell=cell or {} - cell.fm=cell.fm or {r=1,g=1,b=1} - cell.bm=cell.bm or {r=1,g=1,b=1} - cell.fo=cell.fo or {r=0,g=0,b=0} - cell.bo=cell.bo or {r=0,g=0,b=0} - render.setCell(x,y,cell) -end -function getCursorPos() - local g_cursor=df.global.cursor - if g_cursor.x ~= -30000 then - return copyall(g_cursor) - end -end -function falloff(color,sqDist,maxdist) - local v1=1/(sqDist/maxdist+1) - local v2=v1-1/(1+maxdist*maxdist) - local v=v2/(1-1/(1+maxdist*maxdist)) - return {r=v*color.r,g=v*color.g,b=v*color.b} -end -function blend(c1,c2) -return {r=math.max(c1.r,c2.r), - g=math.max(c1.g,c2.g), - b=math.max(c1.b,c2.b)} -end -LightOverlay=defclass(LightOverlay,guidm.DwarfOverlay) -LightOverlay.ATTRS { - lightMap={}, - dynamic=true, - dirty=false, -} -function LightOverlay:init(args) - - self.tick=df.global.cur_year_tick_advmode -end - -function lightPassable(shape) - if shape==df.tiletype_shape.WALL or - shape==df.tiletype_shape.BROOK_BED or - shape==df.tiletype_shape.TREE then - return false - else - return true - end -end -function circle(xm, ym,r,plot) - local x = -r - local y = 0 - local err = 2-2*r -- /* II. Quadrant */ - repeat - plot(xm-x, ym+y);--/* I. Quadrant */ - plot(xm-y, ym-x);--/* II. Quadrant */ - plot(xm+x, ym-y);--/* III. Quadrant */ - plot(xm+y, ym+x);--/* IV. Quadrant */ - r = err; - if (r <= y) then - y=y+1 - err =err+y*2+1; --/* e_xy+e_y < 0 */ - end - if (r > x or err > y) then - x=x+1 - err =err+x*2+1; --/* e_xy+e_x > 0 or no 2nd y-step */ - end - until (x >= 0); -end -function line(x0, y0, x1, y1,plot) - local dx = math.abs(x1-x0) - local dy = math.abs(y1-y0) - local sx,sy - if x0 < x1 then sx = 1 else sx = -1 end - if y0 < y1 then sy = 1 else sy = -1 end - local err = dx-dy - - while true do - if not plot(x0,y0) then - return - end - if x0 == x1 and y0 == y1 then - break - end - local e2 = 2*err - if e2 > -dy then - err = err - dy - x0 = x0 + sx - end - if x0 == x1 and y0 == y1 then - if not plot(x0,y0) then - return - end - break - end - if e2 < dx then - err = err + dx - y0 = y0 + sy - end - end -end -function LightOverlay:calculateFovs() - self.fovs=self.fovs or {} - self.precalc=self.precalc or {} - for k,v in ipairs(self.fovs) do - self:calculateFov(v.pos,v.radius,v.color) - end -end -function LightOverlay:calculateFov(pos,radius,color) - local vp=self:getViewport() - local map = self.df_layout.map - local ray=function(tx,ty) - local power=copyall(color) - local lx=pos.x - local ly=pos.y - local setTile=function(x,y) - if x>0 and y>0 and x<=map.width and y<=map.height then - local dtsq=(lx-x)*(lx-x)+(ly-y)*(ly-y) - local dt=math.sqrt(dtsq) - local tile=x+y*map.width - if self.precalc[tile] then - local tcol=blend(self.precalc[tile],power) - if tcol.r==self.precalc[tile].r and tcol.g==self.precalc[tile].g and self.precalc[tile].b==self.precalc[tile].b - and dtsq>0 then - return false - end - end - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - local ncol=blend(power,ocol) - - self.lightMap[tile]=ncol - local v=self.ocupancy[tile] - if dtsq>0 then - power.r=power.r*(v.r^dt) - power.g=power.g*(v.g^dt) - power.b=power.b*(v.b^dt) - end - lx=x - ly=y - local pwsq=power.r*power.r+power.g*power.g+power.b*power.b - return pwsq>levelDim*levelDim - end - return false - end - line(pos.x,pos.y,tx,ty,setTile) - end - circle(pos.x,pos.y,radius,ray) -end -function LightOverlay:placeLightFov(pos,radius,color) - local map = self.df_layout.map - local tile=pos.x+pos.y*map.width - local ocol=self.precalc[tile] or {r=0,g=0,b=0} - local ncol=blend(color,ocol) - self.precalc[tile]=ncol - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - local ncol=blend(color,ocol) - self.lightMap[tile]=ncol - table.insert(self.fovs,{pos=pos,radius=radius,color=color}) -end -function LightOverlay:placeLightFov2(pos,radius,color,f,rays) - f=f or falloff - local raycount=rays or 25 - local vp=self:getViewport() - local map = self.df_layout.map - local off=math.random(0,math.pi) - local done={} - for d=0,math.pi*2,math.pi*2/raycount do - local dx,dy - dx=math.cos(d+off) - dy=math.sin(d+off) - local cx=0 - local cy=0 - - for dt=0,radius,0.01 do - if math.abs(math.floor(dt*dx)-cx)>0 or math.abs(math.floor(dt*dy)-cy)> 0 then - local x=cx+pos.x - local y=cy+pos.y - - if x>0 and y>0 and x<=map.width and y<=map.height and not done[tile] then - local tile=x+y*map.width - done[tile]=true - local ncol=f(color,dt*dt,radius) - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - ncol=blend(ncol,ocol) - self.lightMap[tile]=ncol - - - if --(ncol.r==ocol.r and ncol.g==ocol.g and ncol.b==ocol.b) or - not self.ocupancy[tile] then - break - end - end - cx=math.floor(dt*dx) - cy=math.floor(dt*dy) - end - end - end -end -function LightOverlay:placeLight(pos,radius,color,f) - f=f or falloff - local vp=self:getViewport() - local map = self.df_layout.map - - for i=-radius,radius do - for j=-radius,radius do - local x=pos.x+i+1 - local y=pos.y+j+1 - if x>0 and y>0 and x<=map.width and y<=map.height then - local tile=x+y*map.width - local ncol=f(color,(i*i+j*j),radius) - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - self.lightMap[tile]=blend(ncol,ocol) - end - end - end -end -function LightOverlay:calculateLightLava() - local vp=self:getViewport() - local map = self.df_layout.map - for i=map.x1,map.x2 do - for j=map.y1,map.y2 do - local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} - local pos2={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z-1} - local t1=dfhack.maps.getTileFlags(pos) - local tt=dfhack.maps.getTileType(pos) - if tt then - local shape=tile_attrs[tt].shape - local t2=dfhack.maps.getTileFlags(pos2) - if (t1 and t1.liquid_type and t1.flow_size>0) or - (shape==df.tiletype_shape.EMPTY and t2 and t2.liquid_type and t2.flow_size>0) then - --self:placeLight({x=i,y=j},5,{r=0.8,g=0.2,b=0.2}) - self:placeLightFov({x=i,y=j},5,{r=0.8,g=0.2,b=0.2},nil) - end - end - end - end -end -function LightOverlay:calculateLightSun() - local vp=self:getViewport() - local map = self.df_layout.map - for i=map.x1,map.x2+1 do - for j=map.y1,map.y2+1 do - local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} - - local t1=dfhack.maps.getTileFlags(pos) - - if (t1 and t1.outside ) then - - self:placeLightFov({x=i,y=j},15,{r=1,g=1,b=1},nil) - end - end - end -end -function LightOverlay:calculateLightCursor() - local c=getCursorPos() - - if c then - - local vp=self:getViewport() - local pos=vp:tileToScreen(c) - --self:placeLight(pos,11,{r=0.96,g=0.84,b=0.03}) - self:placeLightFov({x=pos.x+1,y=pos.y+1},11,{r=0.96,g=0.84,b=0.03}) - - end -end -function LightOverlay:buildOcupancy() - self.ocupancy={} - local vp=self:getViewport() - local map = self.df_layout.map - for i=map.x1,map.x2+1 do - for j=map.y1,map.y2+1 do - local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} - local tile=i+j*map.width - local tt=dfhack.maps.getTileType(pos) - local t1=dfhack.maps.getTileFlags(pos) - if tt then - local shape=tile_attrs[tt].shape - if not lightPassable(shape) then - self.ocupancy[tile]={r=0,g=0,b=0} - else - if t1 and not t1.liquid_type and t1.flow_size>2 then - self.ocupancy[tile]={r=0.5,g=0.5,b=0.7} - else - self.ocupancy[tile]={r=0.8,g=0.8,b=0.8} - end - end - end - end - end -end -function LightOverlay:changed() - if self.dirty or self.tick~=df.global.cur_year_tick_advmode then - self.dirty=false - self.tick=df.global.cur_year_tick_advmode - return true - end - return false -end -function LightOverlay:makeLightMap() - if not self:changed() then - return - end - self.fovs={} - self.precalc={} - self.lightMap={} - - self:buildOcupancy() - self:calculateLightCursor() - self:calculateLightLava() - self:calculateLightSun() - - self:calculateFovs() -end -function LightOverlay:onIdle() - self._native.parent:logic() -end -function LightOverlay:render(dc) - if self.dynamic then - self:makeLightMap() - end - self:renderParent() - local vp=self:getViewport() - local map = self.df_layout.map - - self.lightMap=self.lightMap or {} - render.lockGrids() - render.invalidate({x=map.x1,y=map.y1,w=map.width,h=map.height}) - render.resetGrids() - for i=map.x1,map.x2 do - for j=map.y1,map.y2 do - local v=self.lightMap[i+j*map.width] - if v then - setCell(i,j,{fm=v,bm=v}) - else - local dimRgb={r=levelDim,g=levelDim,b=levelDim} - setCell(i,j,{fm=dimRgb,bm=dimRgb}) - end - end - end - render.unlockGrids() - -end -function LightOverlay:onDismiss() - render.lockGrids() - render.resetGrids() - render.invalidate() - render.unlockGrids() - -end -function LightOverlay:onInput(keys) - if keys.STRING_A096 then - self:dismiss() - else - self:sendInputToParent(keys) - - if keys.CHANGETAB then - self:updateLayout() - end - if keys.STRING_A126 and not self.dynamic then - self:makeLightMap() - end - self.dirty=true - end -end -if not render.isEnabled() then - qerror("Lua rendermode not enabled!") -end -local dyn=true -if #args>0 and args[1]=="static" then dyn=false end -local lview = LightOverlay{ dynamic=dyn} -lview:show() diff --git a/scripts/devel/list-filters.lua b/scripts/devel/list-filters.lua deleted file mode 100644 index 7eb2210a9..000000000 --- a/scripts/devel/list-filters.lua +++ /dev/null @@ -1,78 +0,0 @@ --- List input items for the building being built. ---[[=begin - -devel/list-filters -================== -List input items for the building currently being built. -This is where the filters in lua/dfhack/buildings.lua come from. - -=end]] - -local dumper = require 'dumper' -local utils = require 'utils' -local buildings = require 'dfhack.buildings' - -local function name_enum(tgt,name,ename,enum) - if tgt[name] ~= nil then - tgt[name] = ename..'.'..enum[tgt[name]] - end -end - -local lookup = {} -local items = df.global.world.items - -for i=df.job_item_vector_id._first_item,df.job_item_vector_id._last_item do - local id = df.job_item_vector_id.attrs[i].other - local ptr - if id == df.items_other_id.ANY then - ptr = items.all - elseif id == df.items_other_id.BAD then - ptr = items.bad - else - ptr = items.other[id] - end - if ptr then - local _,addr = df.sizeof(ptr) - lookup[addr] = 'df.job_item_vector_id.'..df.job_item_vector_id[i] - end -end - -local function clone_filter(src,quantity) - local tgt = utils.clone_with_default(src, buildings.input_filter_defaults, true) - if quantity ~= 1 then - tgt.quantity = quantity - end - name_enum(tgt, 'item_type', 'df.item_type', df.item_type) - name_enum(tgt, 'has_tool_use', 'df.tool_uses', df.tool_uses) - local ptr = src.item_vector - if ptr and ptr ~= df.global.world.items.other[0] then - local _,addr = df.sizeof(ptr) - tgt.vector_id = lookup[addr] - end - return tgt -end - -local function dump(name) - local out = {} - for i,v in ipairs(df.global.ui_build_selector.requirements) do - out[#out+1] = clone_filter(v.filter, v.count_required) - end - - local fmt = dumper.DataDumper(out,name,false,1,4) - fmt = string.gsub(fmt, '"(df%.[^"]+)"','%1') - fmt = string.gsub(fmt, '%s+$', '') - print(fmt) -end - -local itype = df.global.ui_build_selector.building_type -local stype = df.global.ui_build_selector.building_subtype - -if itype == df.building_type.Workshop then - dump(' [df.workshop_type.'..df.workshop_type[stype]..'] = ') -elseif itype == df.building_type.Furnace then - dump(' [df.furnace_type.'..df.furnace_type[stype]..'] = ') -elseif itype == df.building_type.Trap then - dump(' [df.trap_type.'..df.trap_type[stype]..'] = ') -else - dump(' [df.building_type.'..df.building_type[itype]..'] = ') -end diff --git a/scripts/devel/lsmem.lua b/scripts/devel/lsmem.lua deleted file mode 100644 index b390adc5c..000000000 --- a/scripts/devel/lsmem.lua +++ /dev/null @@ -1,21 +0,0 @@ --- Prints memory ranges of the process. ---[[=begin - -devel/lsmem -=========== -Prints memory ranges of the process. - -=end]] - -for _,v in ipairs(dfhack.internal.getMemRanges()) do - local access = { '-', '-', '-', 'p' } - if v.read then access[1] = 'r' end - if v.write then access[2] = 'w' end - if v.execute then access[3] = 'x' end - if not v.valid then - access[4] = '?' - elseif v.shared then - access[4] = 's' - end - print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name)) -end diff --git a/scripts/devel/lua-example.lua b/scripts/devel/lua-example.lua deleted file mode 100644 index bbdb04ed6..000000000 --- a/scripts/devel/lua-example.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Example of a lua script. ---[[=begin - -devel/lua-example -================= -An example lua script, which reports the number of times it has -been called. Useful for testing environment persistence. - -=end]] - -run_count = (run_count or 0) + 1 - -print('Arguments: ',...) -print('Command called '..run_count..' times.') diff --git a/scripts/devel/make-dt.pl b/scripts/devel/make-dt.pl deleted file mode 100644 index e3f390802..000000000 --- a/scripts/devel/make-dt.pl +++ /dev/null @@ -1,492 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -my ($version, $timestamp, $hash); - -open FH, 'version.lisp' or die "Cannot open version"; -while () { - if (/df-version-str.*\"(.*)\"/) { - $version = $1; - } elsif (/windows-timestamp.*#x([0-9a-f]+)/) { - $timestamp = $1; - } elsif (/linux-hash.*\"(.*)\"/) { - $hash = $1; - } -} -close FH; - -sub load_csv(\%$) { - my ($rhash, $fname) = @_; - - open FH, $fname or die "Cannot open $fname"; - while () { - next unless /^\"([^\"]*)\",\"(\d+)\",\"(?:0x([0-9a-fA-F]+))?\",\"[^\"]*\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\"/; - my ($top, $level, $addr, $type, $name, $target) = ($1,$2,$3,$4,$5,$6); - next if defined $rhash->{$top}{$name}; - $rhash->{$top}{$name} = ($type eq 'enum-item' ? $target : hex $addr); - } - close FH; -} - -our $complete; - -sub lookup_addr(\%$$;$) { - my ($rhash, $top, $name, $bias) = @_; - - my $val = $rhash->{$top}{$name}; - unless (defined $val) { - $complete = 0; - return 0; - } - return $val + ($bias||0); -} - -our @lines; - -sub emit_header($) { - my ($name) = @_; - push @lines, '' if @lines; - push @lines, "[$name]"; -} - -sub emit_addr($\%$$;$) { - my ($name, $rhash, $top, $var, $bias) = @_; - - my $val = $rhash->{$top}{$var}; - if (defined $val) { - $val += ($bias||0); - if ($val < 0x10000) { - push @lines, sprintf('%s=0x%04x', $name, $val); - } else { - push @lines, sprintf('%s=0x%08x', $name, $val); - } - } else { - $complete = 0; - push @lines, "$name=0x0"; - } -} - -sub generate_dt_ini($$$$) { - my ($subdir, $version, $checksum, $ssize) = @_; - - my %globals; - load_csv %globals, "$subdir/globals.csv"; - my %all; - load_csv %all, "$subdir/all.csv"; - - local $complete = 1; - local @lines; - - emit_header 'addresses'; - emit_addr 'translation_vector',%globals,'world','world.raws.language.translations'; - emit_addr 'language_vector',%globals,'world','world.raws.language.words'; - emit_addr 'creature_vector',%globals,'world','world.units.all'; - emit_addr 'active_creature_vector',%globals,'world','world.units.active'; - emit_addr 'dwarf_race_index',%globals,'ui','ui.race_id'; - emit_addr 'squad_vector',%globals,'world','world.squads.all'; - emit_addr 'current_year',%globals,'cur_year','cur_year'; - emit_addr 'cur_year_tick',%globals,'cur_year_tick','cur_year_tick'; - emit_addr 'dwarf_civ_index',%globals,'ui','ui.civ_id'; - emit_addr 'races_vector',%globals,'world','world.raws.creatures.all'; - emit_addr 'reactions_vector',%globals,'world','world.raws.reactions'; - emit_addr 'events_vector',%globals,'world','world.history.events'; - emit_addr 'historical_figures_vector',%globals,'world','world.history.figures'; - emit_addr 'fake_identities_vector',%globals,'world','world.identities.all'; - emit_addr 'fortress_entity',%globals,'ui','ui.main.fortress_entity'; - emit_addr 'historical_entities_vector',%globals,'world','world.entities.all'; - emit_addr 'itemdef_weapons_vector',%globals,'world','world.raws.itemdefs.weapons'; - emit_addr 'itemdef_trap_vector',%globals,'world','world.raws.itemdefs.trapcomps'; - emit_addr 'itemdef_toy_vector',%globals,'world','world.raws.itemdefs.toys'; - emit_addr 'itemdef_tool_vector',%globals,'world','world.raws.itemdefs.tools'; - emit_addr 'itemdef_instrument_vector',%globals,'world','world.raws.itemdefs.instruments'; - emit_addr 'itemdef_armor_vector',%globals,'world','world.raws.itemdefs.armor'; - emit_addr 'itemdef_ammo_vector',%globals,'world','world.raws.itemdefs.ammo'; - emit_addr 'itemdef_siegeammo_vector',%globals,'world','world.raws.itemdefs.siege_ammo'; - emit_addr 'itemdef_glove_vector',%globals,'world','world.raws.itemdefs.gloves'; - emit_addr 'itemdef_shoe_vector',%globals,'world','world.raws.itemdefs.shoes'; - emit_addr 'itemdef_shield_vector',%globals,'world','world.raws.itemdefs.shields'; - emit_addr 'itemdef_helm_vector',%globals,'world','world.raws.itemdefs.helms'; - emit_addr 'itemdef_pant_vector',%globals,'world','world.raws.itemdefs.pants'; - emit_addr 'itemdef_food_vector',%globals,'world','world.raws.itemdefs.food'; - emit_addr 'colors_vector',%globals,'world','world.raws.language.colors'; - emit_addr 'shapes_vector',%globals,'world','world.raws.language.shapes'; - emit_addr 'base_materials',%globals,'world','world.raws.mat_table.builtin'; - emit_addr 'inorganics_vector',%globals,'world','world.raws.inorganics'; - emit_addr 'plants_vector',%globals,'world','world.raws.plants.all'; - emit_addr 'material_templates_vector',%globals,'world','world.raws.material_templates'; - emit_addr 'all_syndromes_vector',%globals,'world','world.raws.syndromes.all'; - emit_addr 'world_data',%globals,'world','world.world_data'; - emit_addr 'active_sites_vector',%all,'world_data','active_site'; - emit_addr 'world_site_type',%all,'world_site','type'; - emit_addr 'weapons_vector',%globals,'world','world.items.other[WEAPON]'; - emit_addr 'shields_vector',%globals,'world','world.items.other[SHIELD]'; - emit_addr 'quivers_vector',%globals,'world','world.items.other[QUIVER]'; - emit_addr 'crutches_vector',%globals,'world','world.items.other[CRUTCH]'; - emit_addr 'backpacks_vector',%globals,'world','world.items.other[BACKPACK]'; - emit_addr 'ammo_vector',%globals,'world','world.items.other[AMMO]'; - emit_addr 'flasks_vector',%globals,'world','world.items.other[FLASK]'; - emit_addr 'pants_vector',%globals,'world','world.items.other[PANTS]'; - emit_addr 'armor_vector',%globals,'world','world.items.other[ARMOR]'; - emit_addr 'shoes_vector',%globals,'world','world.items.other[SHOES]'; - emit_addr 'helms_vector',%globals,'world','world.items.other[HELM]'; - emit_addr 'gloves_vector',%globals,'world','world.items.other[GLOVES]'; - emit_addr 'artifacts_vector',%globals,'world','world.artifacts.all'; - - emit_header 'offsets'; - emit_addr 'word_table',%all,'language_translation','words'; - push @lines, 'string_buffer_offset=0x0000'; - - emit_header 'word_offsets'; - emit_addr 'base',%all,'language_word','word'; - emit_addr 'noun_singular',%all,'language_word','forms[Noun]'; - emit_addr 'noun_plural',%all,'language_word','forms[NounPlural]'; - emit_addr 'adjective',%all,'language_word','forms[Adjective]'; - emit_addr 'verb',%all,'language_word','forms[Verb]'; - emit_addr 'present_simple_verb',%all,'language_word','forms[Verb3rdPerson]'; - emit_addr 'past_simple_verb',%all,'language_word','forms[VerbPast]'; - emit_addr 'past_participle_verb',%all,'language_word','forms[VerbPassive]'; - emit_addr 'present_participle_verb',%all,'language_word','forms[VerbGerund]'; - emit_addr 'words',%all,'language_name','words'; - emit_addr 'word_type',%all,'language_name','parts_of_speech'; - emit_addr 'language_id',%all,'language_name','language'; - - emit_header 'general_ref_offsets'; - emit_addr 'ref_type',%all,'general_ref::vtable','getType'; - emit_addr 'artifact_id',%all,'general_ref_artifact','artifact_id'; - emit_addr 'item_id',%all,'general_ref_item','item_id'; - - emit_header 'race_offsets'; - emit_addr 'name_singular',%all,'creature_raw','name'; - emit_addr 'name_plural',%all,'creature_raw','name',$ssize; - emit_addr 'adjective',%all,'creature_raw','name',$ssize*2; - emit_addr 'baby_name_singular',%all,'creature_raw','general_baby_name'; - emit_addr 'baby_name_plural',%all,'creature_raw','general_baby_name',$ssize; - emit_addr 'child_name_singular',%all,'creature_raw','general_child_name'; - emit_addr 'child_name_plural',%all,'creature_raw','general_child_name',$ssize; - emit_addr 'pref_string_vector',%all,'creature_raw','prefstring'; - emit_addr 'castes_vector',%all,'creature_raw','caste'; - emit_addr 'pop_ratio_vector',%all,'creature_raw','pop_ratio'; - emit_addr 'materials_vector',%all,'creature_raw','material'; - emit_addr 'flags',%all,'creature_raw','flags'; - emit_addr 'tissues_vector',%all,'creature_raw','tissue'; - - emit_header 'caste_offsets'; - emit_addr 'caste_name',%all,'caste_raw','caste_name'; - emit_addr 'caste_descr',%all,'caste_raw','description'; - emit_addr 'caste_trait_ranges',%all,'caste_raw','personality.a'; - emit_addr 'caste_phys_att_ranges',%all,'caste_raw','attributes.phys_att_range'; - emit_addr 'baby_age',%all,'caste_raw','misc.baby_age'; - emit_addr 'child_age',%all,'caste_raw','misc.child_age'; - emit_addr 'adult_size',%all,'caste_raw','misc.adult_size'; - emit_addr 'flags',%all,'caste_raw','flags'; - emit_addr 'body_info',%all,'caste_raw','body_info'; - emit_addr 'skill_rates',%all,'caste_raw','skill_rates'; - emit_addr 'caste_att_rates',%all,'caste_raw','attributes.phys_att_rates'; - emit_addr 'caste_att_caps',%all,'caste_raw','attributes.phys_att_cap_perc'; - emit_addr 'shearable_tissues_vector',%all,'caste_raw','shearable_tissue_layer'; - emit_addr 'extracts',%all,'caste_raw','extracts.extract_matidx'; - - emit_header 'hist_entity_offsets'; - emit_addr 'beliefs',%all,'historical_entity','resources.values'; - emit_addr 'squads',%all,'historical_entity','squads'; - emit_addr 'positions',%all,'historical_entity','positions.own'; - emit_addr 'assignments',%all,'historical_entity','positions.assignments'; - emit_addr 'assign_hist_id',%all,'entity_position_assignment','histfig'; - emit_addr 'assign_position_id',%all,'entity_position_assignment','position_id'; - emit_addr 'position_id',%all,'entity_position','id'; - emit_addr 'position_name',%all,'entity_position','name'; - emit_addr 'position_female_name',%all,'entity_position','name_female'; - emit_addr 'position_male_name',%all,'entity_position','name_male'; - - emit_header 'hist_figure_offsets'; - emit_addr 'hist_race',%all,'historical_figure','race'; - emit_addr 'hist_name',%all,'historical_figure','name'; - emit_addr 'id',%all,'historical_figure','id'; - emit_addr 'hist_fig_info',%all,'historical_figure','info'; - emit_addr 'reputation',%all,'historical_figure_info','reputation'; - emit_addr 'current_ident',%all,'historical_figure_info::anon13','cur_identity'; - emit_addr 'fake_name',%all,'identity','name'; - emit_addr 'fake_birth_year',%all,'identity','birth_year'; - emit_addr 'fake_birth_time',%all,'identity','birth_second'; - emit_addr 'kills',%all,'historical_figure_info','kills'; - emit_addr 'killed_race_vector',%all,'historical_kills','killed_race'; - emit_addr 'killed_undead_vector',%all,'historical_kills','killed_undead'; - emit_addr 'killed_counts_vector',%all,'historical_kills','killed_count'; - - emit_header 'hist_event_offsets'; - emit_addr 'event_year',%all,'history_event','year'; - emit_addr 'id',%all,'history_event','id'; - emit_addr 'killed_hist_id',%all,'history_event_hist_figure_diedst','victim_hf'; - - emit_header 'item_offsets'; - if ($subdir eq 'osx') { - push @lines, 'item_type=0x0004'; - } else { - push @lines, 'item_type=0x0001'; - } - emit_addr 'item_def',%all,'item_ammost','subtype'; #currently same for all - emit_addr 'id',%all,'item','id'; - emit_addr 'general_refs',%all,'item','general_refs'; - emit_addr 'stack_size',%all,'item_actual','stack_size'; - emit_addr 'wear',%all,'item_actual','wear'; - emit_addr 'mat_type',%all,'item_crafted','mat_type'; - emit_addr 'mat_index',%all,'item_crafted','mat_index'; - emit_addr 'quality',%all,'item_crafted','quality'; - - emit_header 'item_subtype_offsets'; - emit_addr 'sub_type',%all,'itemdef','subtype'; - emit_addr 'name',%all,'itemdef_armorst','name'; - emit_addr 'name_plural',%all,'itemdef_armorst','name_plural'; - emit_addr 'adjective',%all,'itemdef_armorst','name_preplural'; - - emit_header 'item_filter_offsets'; - emit_addr 'item_subtype',%all,'item_filter_spec','item_subtype'; - emit_addr 'mat_class',%all,'item_filter_spec','material_class'; - emit_addr 'mat_type',%all,'item_filter_spec','mattype'; - emit_addr 'mat_index',%all,'item_filter_spec','matindex'; - - emit_header 'weapon_subtype_offsets'; - emit_addr 'single_size',%all,'itemdef_weaponst','two_handed'; - emit_addr 'multi_size',%all,'itemdef_weaponst','minimum_size'; - emit_addr 'ammo',%all,'itemdef_weaponst','ranged_ammo'; - emit_addr 'melee_skill',%all,'itemdef_weaponst','skill_melee'; - emit_addr 'ranged_skill',%all,'itemdef_weaponst','skill_ranged'; - - emit_header 'armor_subtype_offsets'; - emit_addr 'layer',%all,'armor_properties','layer'; - emit_addr 'mat_name',%all,'itemdef_armorst','material_placeholder'; - emit_addr 'other_armor_level',%all,'itemdef_helmst','armorlevel'; - emit_addr 'armor_adjective',%all,'itemdef_armorst','adjective'; - emit_addr 'armor_level',%all,'itemdef_armorst','armorlevel'; - emit_addr 'chest_armor_properties',%all,'itemdef_armorst','props'; - emit_addr 'pants_armor_properties',%all,'itemdef_pantsst','props'; - emit_addr 'other_armor_properties',%all,'itemdef_helmst','props'; - - emit_header 'material_offsets'; - emit_addr 'solid_name',%all,'material_common','state_name[Solid]'; - emit_addr 'liquid_name',%all,'material_common','state_name[Liquid]'; - emit_addr 'gas_name',%all,'material_common','state_name[Gas]'; - emit_addr 'powder_name',%all,'material_common','state_name[Powder]'; - emit_addr 'paste_name',%all,'material_common','state_name[Paste]'; - emit_addr 'pressed_name',%all,'material_common','state_name[Pressed]'; - emit_addr 'flags',%all,'material_common','flags'; - emit_addr 'inorganic_materials_vector',%all,'inorganic_raw','material'; - emit_addr 'inorganic_flags',%all,'inorganic_raw','flags'; - - emit_header 'plant_offsets'; - emit_addr 'name',%all,'plant_raw','name'; - emit_addr 'name_plural',%all,'plant_raw','name_plural'; - emit_addr 'name_leaf_plural',%all,'plant_raw','leaves_plural'; - emit_addr 'name_seed_plural',%all,'plant_raw','seed_plural'; - emit_addr 'materials_vector',%all,'plant_raw','material'; - emit_addr 'flags',%all,'plant_raw','flags'; - - emit_header 'descriptor_offsets'; - emit_addr 'color_name',%all,'descriptor_color','name'; - emit_addr 'shape_name_plural',%all,'descriptor_shape','name_plural'; - - emit_header 'health_offsets'; - emit_addr 'parent_id',%all,'body_part_raw','con_part_id'; - emit_addr 'layers_vector',%all,'body_part_raw','layers'; - emit_addr 'number',%all,'body_part_raw','number'; - emit_addr 'names_vector',%all,'body_part_raw','name_singular'; - emit_addr 'names_plural_vector',%all,'body_part_raw','name_plural'; - emit_addr 'layer_tissue',%all,'body_part_layer_raw','tissue_id'; - emit_addr 'layer_global_id',%all,'body_part_layer_raw','layer_id'; - emit_addr 'tissue_name',%all,'tissue_template','tissue_name_singular'; - emit_addr 'tissue_flags',%all,'tissue_template','flags'; - - emit_header 'dwarf_offsets'; - emit_addr 'first_name',%all,'unit','name',lookup_addr(%all,'language_name','first_name'); - emit_addr 'nick_name',%all,'unit','name',lookup_addr(%all,'language_name','nickname'); - emit_addr 'last_name',%all,'unit','name',lookup_addr(%all,'language_name','words'); - emit_addr 'custom_profession',%all,'unit','custom_profession'; - emit_addr 'profession',%all,'unit','profession'; - emit_addr 'race',%all,'unit','race'; - emit_addr 'flags1',%all,'unit','flags1'; - emit_addr 'flags2',%all,'unit','flags2'; - emit_addr 'flags3',%all,'unit','flags3'; - emit_addr 'caste',%all,'unit','caste'; - emit_addr 'sex',%all,'unit','sex'; - emit_addr 'id',%all,'unit','id'; - emit_addr 'animal_type',%all,'unit','training_level'; - emit_addr 'civ',%all,'unit','civ_id'; - emit_addr 'specific_refs',%all,'unit','specific_refs'; - emit_addr 'squad_id',%all,'unit','military.squad_id'; - emit_addr 'squad_position',%all,'unit','military.squad_position'; - emit_addr 'recheck_equipment',%all,'unit','military.pickup_flags'; - emit_addr 'mood',%all,'unit','mood'; - emit_addr 'birth_year',%all,'unit','relations.birth_year'; - emit_addr 'birth_time',%all,'unit','relations.birth_time'; - emit_addr 'pet_owner_id',%all,'unit','relations.pet_owner_id'; - emit_addr 'current_job',%all,'unit','job.current_job'; - emit_addr 'physical_attrs',%all,'unit','body.physical_attrs'; - emit_addr 'body_size',%all,'unit','appearance.body_modifiers'; - emit_addr 'size_info',%all,'unit','body.size_info'; - emit_addr 'curse',%all,'unit','curse.name'; - emit_addr 'curse_add_flags1',%all,'unit','curse.add_tags1'; - emit_addr 'turn_count',%all,'unit','curse.time_on_site'; - emit_addr 'souls',%all,'unit','status.souls'; - emit_addr 'states',%all,'unit','status.misc_traits'; - emit_addr 'labors',%all,'unit','status.labors'; - emit_addr 'hist_id',%all,'unit','hist_figure_id'; - emit_addr 'artifact_name',%all,'unit','status.artifact_name'; - emit_addr 'active_syndrome_vector',%all,'unit','syndromes.active'; - emit_addr 'syn_sick_flag',%all,'unit_syndrome','flags.is_sick'; - emit_addr 'unit_health_info',%all,'unit','health'; - emit_addr 'temp_mood',%all,'unit','counters.soldier_mood'; - emit_addr 'counters1',%all,'unit','counters.winded'; - emit_addr 'counters2',%all,'unit','counters.pain'; - emit_addr 'counters3',%all,'unit','counters2.paralysis'; - emit_addr 'limb_counters',%all,'unit','status2.limbs_stand_max'; - emit_addr 'blood',%all,'unit','body.blood_max'; - emit_addr 'body_component_info',%all,'unit','body.components'; - emit_addr 'layer_status_vector',%all,'body_component_info','layer_status'; - emit_addr 'wounds_vector',%all,'unit','body.wounds'; - emit_addr 'mood_skill',%all,'unit','job.mood_skill'; - emit_addr 'used_items_vector',%all,'unit','used_items'; - emit_addr 'affection_level',%all,'unit_item_use','affection_level'; - emit_addr 'inventory',%all,'unit','inventory'; - emit_addr 'inventory_item_mode',%all,'unit_inventory_item','mode'; - emit_addr 'inventory_item_bodypart',%all,'unit_inventory_item','body_part_id'; - - emit_header 'syndrome_offsets'; - emit_addr 'cie_effects',%all,'syndrome','ce'; - emit_addr 'cie_end',%all,'creature_interaction_effect','end'; - emit_addr 'cie_first_perc',%all,'creature_interaction_effect_phys_att_changest','phys_att_perc'; #same for mental - emit_addr 'cie_phys',%all,'creature_interaction_effect_phys_att_changest','phys_att_add'; - emit_addr 'cie_ment',%all,'creature_interaction_effect_ment_att_changest','ment_att_add'; - emit_addr 'syn_classes_vector',%all,'syndrome','syn_class'; - emit_addr 'trans_race_id',%all,'creature_interaction_effect_body_transformationst','race'; - - emit_header 'unit_wound_offsets'; - emit_addr 'parts',%all,'unit_wound','parts'; - emit_addr 'id',%all,'unit_wound::anon2','body_part_id'; - emit_addr 'layer',%all,'unit_wound::anon2','layer_idx'; - emit_addr 'general_flags',%all,'unit_wound','flags'; - emit_addr 'flags1',%all,'unit_wound::anon2','flags1'; - emit_addr 'flags2',%all,'unit_wound::anon2','flags2'; - emit_addr 'effects_vector',%all,'unit_wound::anon2','effect_type'; - emit_addr 'bleeding',%all,'unit_wound::anon2','bleeding'; - emit_addr 'pain',%all,'unit_wound::anon2','pain'; - emit_addr 'cur_pen',%all,'unit_wound::anon2','cur_penetration_perc'; - emit_addr 'max_pen',%all,'unit_wound::anon2','max_penetration_perc'; - - emit_header 'soul_details'; - emit_addr 'name',%all,'unit_soul','name'; - emit_addr 'orientation',%all,'unit_soul','orientation_flags'; - emit_addr 'mental_attrs',%all,'unit_soul','mental_attrs'; - emit_addr 'skills',%all,'unit_soul','skills'; - emit_addr 'preferences',%all,'unit_soul','preferences'; - emit_addr 'personality',%all,'unit_soul','personality'; - emit_addr 'beliefs',%all,'unit_personality','values'; - emit_addr 'emotions',%all,'unit_personality','emotions'; - emit_addr 'goals',%all,'unit_personality','dreams'; - emit_addr 'goal_realized',%all,'unit_personality::anon5','unk8'; - emit_addr 'traits',%all,'unit_personality','traits'; - emit_addr 'stress_level',%all,'unit_personality','stress_level'; - - emit_header 'emotion_offsets'; - emit_addr 'emotion_type',%all,'unit_personality::anon4','type'; - emit_addr 'strength',%all,'unit_personality::anon4','strength'; - emit_addr 'thought_id',%all,'unit_personality::anon4','thought'; - emit_addr 'sub_id',%all,'unit_personality::anon4','subthought'; - emit_addr 'level',%all,'unit_personality::anon4','severity'; - emit_addr 'year',%all,'unit_personality::anon4','year'; - emit_addr 'year_tick',%all,'unit_personality::anon4','year_tick'; - - emit_header 'job_details'; - emit_addr 'id',%all,'job','job_type'; - emit_addr 'mat_type',%all,'job','mat_type'; - emit_addr 'mat_index',%all,'job','mat_index'; - emit_addr 'mat_category',%all,'job','material_category'; - emit_addr 'on_break_flag',%all,'misc_trait_type','OnBreak'; - emit_addr 'sub_job_id',%all,'job','reaction_name'; - emit_addr 'reaction',%all,'reaction','name'; - emit_addr 'reaction_skill',%all,'reaction','skill'; - - emit_header 'squad_offsets'; - emit_addr 'id',%all,'squad','id'; - emit_addr 'name',%all,'squad','name'; - emit_addr 'alias',%all,'squad','alias'; - emit_addr 'members',%all,'squad','positions'; - emit_addr 'carry_food',%all,'squad','carry_food'; - emit_addr 'carry_water',%all,'squad','carry_water'; - emit_addr 'ammunition',%all,'squad','ammunition'; - emit_addr 'quiver',%all,'squad_position','quiver'; - emit_addr 'backpack',%all,'squad_position','backpack'; - emit_addr 'flask',%all,'squad_position','flask'; - emit_addr 'armor_vector',%all,'squad_position','uniform[body]'; - emit_addr 'helm_vector',%all,'squad_position','uniform[head]'; - emit_addr 'pants_vector',%all,'squad_position','uniform[pants]'; - emit_addr 'gloves_vector',%all,'squad_position','uniform[gloves]'; - emit_addr 'shoes_vector',%all,'squad_position','uniform[shoes]'; - emit_addr 'shield_vector',%all,'squad_position','uniform[shield]'; - emit_addr 'weapon_vector',%all,'squad_position','uniform[weapon]'; - emit_addr 'uniform_item_filter',%all,'squad_uniform_spec','item_filter'; - emit_addr 'uniform_indiv_choice',%all,'squad_uniform_spec','indiv_choice'; - - my $body_str = join("\n",@lines); - my $complete_str = ($complete ? 'true' : 'false'); - - open OUT, ">$subdir/therapist.ini" or die "Cannot open output file"; - print OUT <<__END__; -[info] -checksum=0x$checksum -version_name=$version -complete=$complete_str - -$body_str - -[valid_flags_2] -size=0 - -[invalid_flags_1] -size=10 -1\\name=a zombie -1\\value=0x00001000 -2\\name=a skeleton -2\\value=0x00002000 -3\\name=a merchant -3\\value=0x00000040 -4\\name=outpost liason or diplomat -4\\value=0x00000800 -5\\name=an invader or hostile -5\\value=0x00020000 -6\\name=an invader or hostile -6\\value=0x00080000 -7\\name=resident, invader or ambusher -7\\value=0x00600000 -8\\name=part of a merchant caravan -8\\value=0x00000080 -9\\name="Dead, Jim." -9\\value=0x00000002 -10\\name=marauder -10\\value=0x00000010 - -[invalid_flags_2] -size=5 -1\\name="killed, Jim." -1\\value=0x00000080 -2\\name=from the Underworld. SPOOKY! -2\\value=0x00040000 -3\\name=resident -3\\value=0x00080000 -4\\name=uninvited visitor -4\\value=0x00400000 -5\\name=visitor -5\\value=0x00800000 - -[invalid_flags_3] -size=1 -1\\name=a ghost -1\\value=0x00001000 -__END__ - close OUT; -} - -generate_dt_ini 'linux', $version, substr($hash,0,8), 4; -generate_dt_ini 'windows', $version.' (graphics)', $timestamp, 0x1C; -generate_dt_ini 'osx', $version, substr($hash,0,8), 4; \ No newline at end of file diff --git a/scripts/devel/nuke-items.lua b/scripts/devel/nuke-items.lua deleted file mode 100644 index 6b67f81c8..000000000 --- a/scripts/devel/nuke-items.lua +++ /dev/null @@ -1,22 +0,0 @@ --- Delete ALL items not held by units, buildings or jobs ---[[=begin - -devel/nuke-items -================ -Deletes ALL items not held by units, buildings or jobs. -Intended solely for lag investigation. - -=end]] - -local count = 0 - -for _,v in ipairs(df.global.world.items.all) do - if not (v.flags.in_building or v.flags.construction or v.flags.in_job - or dfhack.items.getGeneralRef(v,df.general_ref_type.UNIT_HOLDER)) then - count = count + 1 - v.flags.forbid = true - v.flags.garbage_collect = true - end -end - -print('Deletion requested: '..count) diff --git a/scripts/devel/pop-screen.lua b/scripts/devel/pop-screen.lua deleted file mode 100644 index 8bc5e45c6..000000000 --- a/scripts/devel/pop-screen.lua +++ /dev/null @@ -1,10 +0,0 @@ --- For killing bugged out gui script screens. ---[[=begin - -devel/pop-screen -================ -For killing bugged out gui script screens. - -=end]] - -dfhack.screen.dismiss(dfhack.gui.getCurViewscreen()) diff --git a/scripts/devel/prepare-save.lua b/scripts/devel/prepare-save.lua deleted file mode 100644 index c1e71ef7f..000000000 --- a/scripts/devel/prepare-save.lua +++ /dev/null @@ -1,98 +0,0 @@ --- Prepare the current save for devel/find-offsets ---[[=begin - -devel/prepare-save -================== -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -This script prepares the current savegame to be used -with `devel/find-offsets`. It CHANGES THE GAME STATE -to predefined values, and initiates an immediate -`quicksave`, thus PERMANENTLY MODIFYING the save. - -=end]] - -local utils = require 'utils' - -df.global.pause_state = true - -print[[ -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -This script prepares the current savegame to be used -with devel/find-offsets. It CHANGES THE GAME STATE -to predefined values, and initiates an immediate -quicksave, thus PERMANENTLY MODIFYING the save. -]] - -if not utils.prompt_yes_no('Proceed?') then - return -end - ---[[print('Placing anchor...') - -do - local wp = df.global.ui.waypoints - - for _,pt in ipairs(wp.points) do - if pt.name == 'dfhack_anchor' then - print('Already placed.') - goto found - end - end - - local x,y,z = pos2xyz(df.global.cursor) - - if not x then - error("Place cursor at your preferred anchor point.") - end - - local id = wp.next_point_id - wp.next_point_id = id + 1 - - wp.points:insert('#',{ - new = true, id = id, name = 'dfhack_anchor', - comment=(x..','..y..','..z), - tile = string.byte('!'), fg_color = COLOR_LIGHTRED, bg_color = COLOR_BLUE, - pos = xyz2pos(x,y,z) - }) - -::found:: -end]] - -print('Nicknaming units...') - -for i,unit in ipairs(df.global.world.units.active) do - dfhack.units.setNickname(unit, i..':'..unit.id) -end - -print('Setting weather...') - -local wbytes = { - 2, 1, 0, 2, 0, - 1, 2, 1, 0, 0, - 2, 0, 2, 1, 2, - 1, 2, 0, 1, 1, - 2, 0, 1, 0, 2 -} - -for i=0,4 do - for j = 0,4 do - df.global.current_weather[i][j] = (wbytes[i*5+j+1] or 2) - end -end - -local yearstr = df.global.cur_year..','..df.global.cur_year_tick - -print('Cur year and tick: '..yearstr) - -dfhack.persistent.save{ - key='prepare-save/cur_year', - value=yearstr, - ints={df.global.cur_year, df.global.cur_year_tick} -} - --- Save - -dfhack.run_script('quicksave') - diff --git a/scripts/devel/print-args.lua b/scripts/devel/print-args.lua deleted file mode 100644 index 46024eaa4..000000000 --- a/scripts/devel/print-args.lua +++ /dev/null @@ -1,15 +0,0 @@ ---print-args.lua ---author expwnent ---[[=begin - -devel/print-args -================ -Prints all the arguments you supply to the script on their own line. -Useful for debugging other scripts. - -=end]] - -local args = {...} -for _,arg in ipairs(args) do - print(arg) -end diff --git a/scripts/devel/print-args2.lua b/scripts/devel/print-args2.lua deleted file mode 100644 index ad870623d..000000000 --- a/scripts/devel/print-args2.lua +++ /dev/null @@ -1,17 +0,0 @@ ---print-args2.lua ---author expwnent ---[[=begin - -devel/print-args2 -================= -Prints all the arguments you supply to the script on their own line -with quotes around them. - -=end]] - -local args = {...} -print("print-args") -for _,arg in ipairs(args) do - print("'"..arg.."'") -end - diff --git a/scripts/devel/save-version.lua b/scripts/devel/save-version.lua deleted file mode 100644 index 450aa04cf..000000000 --- a/scripts/devel/save-version.lua +++ /dev/null @@ -1,154 +0,0 @@ --- 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", - [1537] = "0.42.05", - [1542] = "0.42.06", - - [1551] = "0.43.01", - [1552] = "0.43.02", -} - -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 diff --git a/scripts/devel/scanitemother.rb b/scripts/devel/scanitemother.rb deleted file mode 100644 index 1bdf58946..000000000 --- a/scripts/devel/scanitemother.rb +++ /dev/null @@ -1,16 +0,0 @@ -# list selected item's indices in world.item.other[] -=begin - -devel/scanitemother -=================== -List indices in ``world.item.other[]`` where current selected item appears. - -=end -tg = df.item_find -raise 'select an item' if not tg - -o = df.world.items.other -# discard ANY/BAD -o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k| - puts k if o[k].find { |i| i == tg } -} diff --git a/scripts/devel/spawn-unit-helper.rb b/scripts/devel/spawn-unit-helper.rb deleted file mode 100644 index 77dadbcdd..000000000 --- a/scripts/devel/spawn-unit-helper.rb +++ /dev/null @@ -1,35 +0,0 @@ -# Allow arena creature spawn after a mode change - -df.world.arena_spawn.race.clear -df.world.arena_spawn.caste.clear - -df.world.raws.creatures.all.length.times { |r_idx| - df.world.raws.creatures.all[r_idx].caste.length.times { |c_idx| - df.world.arena_spawn.race << r_idx - df.world.arena_spawn.caste << c_idx - } -} - -df.world.arena_spawn.creature_cnt[df.world.arena_spawn.race.length-1] = 0 - -puts < [-xy ] [-z ]') -end - -local fname = table.remove(args,1) -local goal = tonumber(table.remove(args,1)) or qerror('Invalid density') -local expr = table.remove(args,1) or qerror('No expression') -local zscale = 2 -local xyscale = 1 - -for i = 1,#args,2 do - if args[i] == '-xy' then - xyscale = tonumber(args[i+1]) or qerror('Invalid xyscale') - end - if args[i] == '-z' then - zscale = tonumber(args[i+1]) or qerror('Invalid zscale') - end -end - -local fn_env = copyall(math) - -fn_env.rng = rng -fn_env.apow = function(x,y) return math.pow(math.abs(x),y) end -fn_env.spow = function(x,y) return x*math.pow(math.abs(x),y-1) end - --- Noise functions are referenced from expressions --- as variables of form like "x3a", where: --- 1) x is one of x/y/z/w independent functions in each octave --- 2) 3 is the octave number; 0 corresponds to the whole range --- 3) a is the subtype - --- Independent noise functions: support 4 -local ids = { 'x', 'y', 'z', 'w' } --- Subtype: provides an offset to the coordinates -local subs = { - [''] = { 0, 0, 0 }, - a = { 0.5, 0, 0 }, - b = { 0, 0.5, 0 }, - c = { 0.5, 0.5, 0 }, - d = { 0, 0, 0.5 }, - e = { 0.5, 0, 0.5 }, - f = { 0, 0.5, 0.5 }, - g = { 0.5, 0.5, 0.5 }, -} - -function mkdelta(v) - if v == 0 then - return '' - else - return '+'..v - end -end - -function mkexpr(expr) - -- Collect referenced variables - local max_octave = -1 - local vars = {} - - for var,id,octave,subtype in string.gmatch(expr,'%f[%w](([xyzw])(%d+)(%a*))%f[^%w]') do - if not vars[var] then - octave = tonumber(octave) - - if octave > max_octave then - max_octave = octave - end - - local sub = subs[subtype] or qerror('Invalid subtype: '..subtype) - - vars[var] = { id = id, octave = octave, subtype = subtype, sub = sub } - end - end - - if max_octave < 0 then - qerror('No noise function references in expression.') - end - - -- Allocate the noise functions - local code = '' - - for i = 0,max_octave do - for j,id in ipairs(ids) do - code = code .. 'local _fn_'..i..'_'..id..' = rng:perlin(3)\n'; - end - end - - -- Evaluate variables - code = code .. 'return function(x,y,z)\n' - - for var,info in pairs(vars) do - local fn = '_fn_'..info.octave..'_'..info.id - local mul = math.pow(2,info.octave) - mul = math.min(48*4, mul) - code = code .. ' local '..var - .. ' = _fn_'..info.octave..'_'..info.id - .. '(x*'..mul..mkdelta(info.sub[1]) - .. ',y*'..mul..mkdelta(info.sub[2]) - .. ',z*'..mul..mkdelta(info.sub[3]) - .. ')\n' - end - - -- Complete and compile the function - code = code .. ' return ('..expr..')\nend\n' - - local f,err = load(code, '=(expr)', 't', fn_env) - if not f then - qerror(err) - end - return f() -end - -local field_fn = mkexpr(expr) - -function render(thresh,file) - local area = 0 - local line, arr = '', {} - - for zy = 0,1 do - for y = 0,48*4-1 do - line = '' - for zx = 0,1 do - for x = 0,48*4-1 do - local tx = (0.5+x)/(48*4/xyscale) - local ty = (0.5+y)/(48*4/xyscale) - local tz = 0.3+(zx+zy*2)/(48*4/zscale) - local v1 = field_fn(tx, ty, tz) - local v = -1 - if v1 > thresh then - v = v1; - area = area + 1 - end - if file then - local c = math.max(0, math.min(255, v * 127 + 128)) - arr[2*x+1] = c - arr[2*x+2] = c - end - end - if file then - line = line..string.char(table.unpack(arr)) - end - end - if file then - file:write(line,line) - end - end - end - - return area/4/(48*4)/(48*4) -end - -function search(fn,min,max,goal,eps) - local center - for i = 1,32 do - center = (max+min)/2 - local cval = fn(center) - print('At '..center..': '..cval) - if math.abs(cval-goal) < eps then - break - end - if cval > goal then - min = center - else - max = center - end - end - return center -end - -local thresh = search(render, -2, 2, goal, math.min(0.001,goal/20)) - -local file,err = io.open(fname, 'wb') -if not file then - print('error: ',err) - return -end -file:write('P5\n') -file:write('# '..goal..' '..expr..' '..xyscale..' '..zscale..'\n') -file:write('768 768\n255\n') -local area = render(thresh, file) -file:close() - -print('Area fraction: '..area) diff --git a/scripts/devel/unforbidall.rb b/scripts/devel/unforbidall.rb deleted file mode 100644 index 176bff9c0..000000000 --- a/scripts/devel/unforbidall.rb +++ /dev/null @@ -1,10 +0,0 @@ -# unforbid all items -=begin - -devel/unforbidall -================= -Unforbid all items. - -=end - -df.world.items.all.each { |i| i.flags.forbid = false } diff --git a/scripts/devel/unit-path.lua b/scripts/devel/unit-path.lua deleted file mode 100644 index 2eff7ed2d..000000000 --- a/scripts/devel/unit-path.lua +++ /dev/null @@ -1,225 +0,0 @@ --- Show the internal path a unit is currently following. ---[[=begin - -devel/unit-path -=============== -Show the internal path a unit is currently following. - -=end]] - -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local tile_attrs = df.tiletype.attrs - -UnitPathUI = defclass(UnitPathUI, guidm.MenuOverlay) - -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() - -- with cursor, but without those ugly lines from native hauling mode - df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles -end - -function UnitPathUI:onDestroy() - self:moveCursorTo(copyall(self.unit.pos)) - df.global.ui.main.mode = self.saved_mode -end - -local function getTileType(cursor) - local block = dfhack.maps.getTileBlock(cursor) - if block then - return block.tiletype[cursor.x%16][cursor.y%16] - else - return 0 - end -end - -local function getTileWalkable(cursor) - local block = dfhack.maps.getTileBlock(cursor) - if block then - return block.walkable[cursor.x%16][cursor.y%16] - else - return 0 - end -end - -local function paintMapTile(dc, vp, cursor, pos, ...) - if not same_xyz(cursor, pos) then - local stile = vp:tileToScreen(pos) - if stile.z == 0 then - dc:seek(stile.x,stile.y):char(...) - end - end -end - -local function get_path_point(gpath,i) - return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) -end - -function UnitPathUI:renderPath(dc,vp,cursor) - local gpath = self.unit.path.path - local startp = self.unit.pos - local endp = self.unit.path.dest - local visible = gui.blink_visible(500) - - if visible then - paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) - end - - local ok = nil - local pcnt = #gpath.x - if pcnt > 0 then - ok = true - - for i = 0,pcnt-1 do - local pt = get_path_point(gpath, i) - if i == 0 and not same_xyz(startp,pt) then - ok = false - end - if i == pcnt-1 and not same_xyz(endp,pt) then - ok = false - end - --[[local tile = getTileType(pt) - if not isTrackTile(tile) then - ok = false - end]] - if visible then - local char = '+' - if i < pcnt-1 then - local npt = get_path_point(gpath, i+1) - if npt.z == pt.z+1 then - char = 30 - elseif npt.z == pt.z-1 then - char = 31 - elseif npt.x == pt.x+1 then - char = 26 - elseif npt.x == pt.x-1 then - char = 27 - elseif npt.y == pt.y+1 then - char = 25 - elseif npt.y == pt.y-1 then - char = 24 - end - end - local color = COLOR_LIGHTGREEN - if getTileWalkable(pt) == 0 then color = COLOR_LIGHTRED end - paintMapTile(dc, vp, cursor, pt, char, color) - end - end - end - - if gui.blink_visible(120) then - paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) - end - - return ok -end - -function UnitPathUI:onRenderBody(dc) - dc:clear():seek(1,1):pen(COLOR_WHITE):string("Unit Path") - - local prof = dfhack.units.getProfessionName(self.unit) - local name = dfhack.units.getVisibleName(self.unit) - - dc:seek(2,3):pen(COLOR_BLUE):string(prof) - if name and name.has_name then - dc:seek(2,4):pen(COLOR_BLUE):string(dfhack.TranslateName(name)) - end - - local cursor = guidm.getCursorPos() - - local vp = self:getViewport() - local mdc = gui.Painter.new(self.df_layout.map) - - 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 - - dc:seek(1,6):pen(COLOR_RED):string('Not following path') - else - self:renderPath(mdc,vp,cursor) - - dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal] or '?') - end - - dc:newline():pen(COLOR_GREY) - dc:newline(2):string('Speed: '..dfhack.units.computeMovementSpeed(self.unit)) - dc:newline(2):string('Slowdown: '..dfhack.units.computeSlowdownFactor(self.unit)) - - dc:newline():newline(1) - - local has_station = self.unit.idle_area_type >= 0 - - if has_station then - if gui.blink_visible(250) then - paintMapTile(mdc, vp, cursor, self.unit.idle_area, 21, COLOR_LIGHTCYAN) - end - - dc:pen(COLOR_GREEN):string(df.unit_station_type[self.unit.idle_area_type]) - dc:newline():newline(2):pen(COLOR_GREY):string('Threshold: '..self.unit.idle_area_threshold) - else - dc:pen(COLOR_RED):string('No station'):newline():newline(2) - end - - dc:newline():newline(1):string('At cursor:') - dc:newline():newline(2) - - local tile = getTileType(cursor) - dc:string(df.tiletype[tile],COLOR_CYAN) - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('CUSTOM_Z'):string(": Zoom unit, ") - 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) - dc:key('LEAVESCREEN'):string(": Back") -end - -function UnitPathUI:onInput(keys) - if keys.CUSTOM_Z then - self:moveCursorTo(copyall(self.unit.pos)) - elseif keys.CUSTOM_G then - if self.has_goal then - self:moveCursorTo(copyall(self.unit.path.dest)) - end - elseif keys.CUSTOM_N then - if self.unit.idle_area_type > 0 then - self:moveCursorTo(copyall(self.unit.idle_area)) - end - elseif keys.LEAVESCREEN then - self:dismiss() - elseif self:propagateMoveKeys(keys) then - return - 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 - qerror("This script requires the main dwarfmode view in 'v' mode with a unit selected") -end - -UnitPathUI{ unit = unit }:show() diff --git a/scripts/devel/watch-minecarts.lua b/scripts/devel/watch-minecarts.lua deleted file mode 100644 index f7eba0ca6..000000000 --- a/scripts/devel/watch-minecarts.lua +++ /dev/null @@ -1,83 +0,0 @@ --- Logs minecart coordinates and speeds to console. ---[[=begin - -devel/watch-minecarts -===================== -Logs minecart coordinates and speeds to console. - -Usage: ``devel/watch-minecarts start|stop`` - -=end]] - -last_stats = last_stats or {} - -function compare_one(vehicle) - local last = last_stats[vehicle.id] - local item = df.item.find(vehicle.item_id) - local ipos = item.pos - local new = { - ipos.x*100000 + vehicle.offset_x, vehicle.speed_x, - ipos.y*100000 + vehicle.offset_y, vehicle.speed_y, - ipos.z*100000 + vehicle.offset_z, vehicle.speed_z - } - - if (last == nil) or item.flags.on_ground then - local delta = { vehicle.id } - local show = (last == nil) - - for i=1,6 do - local rv = 0 - if last then - rv = last[i] - end - delta[i*2] = new[i]/100000 - local dv = new[i] - rv - delta[i*2+1] = dv/100000 - if dv ~= 0 then - show = true - end - end - - if show then - print(table.unpack(delta)) - end - end - - last_stats[vehicle.id] = new -end - -function compare_all() - local seen = {} - for _,v in ipairs(df.global.world.vehicles.all) do - seen[v.id] = true - compare_one(v) - end - for k,v in pairs(last_stats) do - if not seen[k] then - print(k,'DEAD') - end - end -end - -function start_timer() - if not dfhack.timeout_active(timeout_id) then - timeout_id = dfhack.timeout(1, 'ticks', function() - compare_all() - start_timer() - end); - if not timeout_id then - dfhack.printerr('Could not start timer in watch-minecarts') - end - end -end - -compare_all() - -local cmd = ... - -if cmd == 'start' then - start_timer() -elseif cmd == 'stop' then - dfhack.timeout_active(timeout_id, nil) -end - diff --git a/scripts/digfort.rb b/scripts/digfort.rb deleted file mode 100644 index d83928172..000000000 --- a/scripts/digfort.rb +++ /dev/null @@ -1,94 +0,0 @@ -# designate an area based on a '.csv' plan -=begin - -digfort -======= -A script to designate an area for digging according to a plan in csv format. - -This script, inspired from quickfort, can designate an area for digging. -Your plan should be stored in a .csv file like this:: - - # this is a comment - d;d;u;d;d;skip this tile;d - d;d;d;i - -Available tile shapes are named after the 'dig' menu shortcuts: -``d`` for dig, ``u`` for upstairs, ``j`` downstairs, ``i`` updown, -``h`` channel, ``r`` upward ramp, ``x`` remove designation. -Unrecognized characters are ignored (eg the 'skip this tile' in the sample). - -Empty lines and data after a ``#`` are ignored as comments. -To skip a row in your design, use a single ``;``. - -One comment in the file may contain the phrase ``start(3,5)``. It is interpreted -as an offset for the pattern: instead of starting at the cursor, it will start -3 tiles left and 5 tiles up from the cursor. - -The script takes the plan filename, starting from the root df folder (where -``Dwarf Fortress.exe`` is found). - -=end - -fname = $script_args[0].to_s - -if not $script_args[0] then - puts " Usage: digfort " - throw :script_finished -end -if not fname[-4..-1] == ".csv" then - puts " The plan file must be in .csv format." - throw :script_finished -end -if not File.file?(fname) then - puts " The specified file does not exist." - throw :script_finished -end - -planfile = File.read(fname) - -if df.cursor.x == -30000 - puts "place the game cursor to the top-left corner of the design and retry" - throw :script_finished -end - -offset = [0, 0] -tiles = [] -planfile.each_line { |l| - if l =~ /#.*start\s*\(\s*(-?\d+)\s*[,;]\s*(-?\d+)/ - raise "Error: multiple start() comments" if offset != [0, 0] - offset = [$1.to_i, $2.to_i] - end - - l = l.chomp.sub(/#.*/, '') - next if l == '' - tiles << l.split(/[;,]/).map { |t| - t = t.strip - (t[0] == ?") ? t[1..-2] : t - } -} - -x = df.cursor.x - offset[0] -y = df.cursor.y - offset[1] -z = df.cursor.z - -tiles.each { |line| - next if line.empty? or line == [''] - line.each { |tile| - t = df.map_tile_at(x, y, z) - s = t.shape_basic - case tile - when 'd'; t.dig(:Default) if s == :Wall - when 'u'; t.dig(:UpStair) if s == :Wall - when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor - when 'i'; t.dig(:UpDownStair) if s == :Wall - when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor - when 'r'; t.dig(:Ramp) if s == :Wall - when 'x'; t.dig(:No) - end - x += 1 - } - x = df.cursor.x - offset[0] - y += 1 -} - -puts ' done' diff --git a/scripts/drain-aquifer.lua b/scripts/drain-aquifer.lua deleted file mode 100644 index 6a79017bf..000000000 --- a/scripts/drain-aquifer.lua +++ /dev/null @@ -1,40 +0,0 @@ --- Remove all aquifers from the map ---[[=begin - -drain-aquifer -============= -Remove all 'aquifer' tag from the map blocks. Irreversible. - -=end]] - -local function drain() - local layers = {} - local layer_count = 0 - local tile_count = 0 - - for k, block in ipairs(df.global.world.map.map_blocks) do - if block.flags.has_aquifer then - block.flags.has_aquifer = false - block.flags.check_aquifer = false - - for x, row in ipairs(block.designation) do - for y, tile in ipairs(row) do - if tile.water_table then - tile.water_table = false - tile_count = tile_count + 1 - end - end - end - - if not layers[block.map_pos.z] then - layers[block.map_pos.z] = true - layer_count = layer_count + 1 - end - end - end - - print("Cleared "..tile_count.." aquifer tile"..((tile_count ~= 1) and "s" or "").. - " in "..layer_count.." layer"..((layer_count ~= 1) and "s" or "")..".") -end - -drain(...) diff --git a/scripts/elevate-mental.lua b/scripts/elevate-mental.lua deleted file mode 100644 index 9db61ed79..000000000 --- a/scripts/elevate-mental.lua +++ /dev/null @@ -1,52 +0,0 @@ --- Elevate all the mental attributes of a unit --- by vjek ---[[=begin - -elevate-mental -============== -Set all mental attributes of the selected dwarf to 2600, which is very high. -Numbers between 0 and 5000 can be passed as an argument: ``elevate-mental 100`` -for example would make the dwarf very stupid indeed. - -=end]] - -function ElevateMentalAttributes(value) - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - --print name of dwarf - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(unit))) - --walk through available attributes, adjust current to max - local ok,f,t,k = pcall(pairs,unit.status.current_soul.mental_attrs) - if ok then - for k,v in f,t,k do - if value ~= nil then - print("Adjusting current value for "..tostring(k).." of "..v.value.." to the value of "..value) - v.value=value - else - print("Adjusting current value for "..tostring(k).." of "..v.value.." to max value of "..v.max_value) - v.value=v.max_value - --below will reset values back to "normal" - --v.value=v.max_value/2 - end - end - end -end ---script execution starts here -local opt = ... -opt = tonumber(opt) - -if opt ~= nil then - if opt >=0 and opt <=5000 then - ElevateMentalAttributes(opt) - end - if opt <0 or opt >5000 then - print("Invalid Range or argument. This script accepts either no argument, in which case it will increase the attribute to the max_value for the unit, or an argument between 0 and 5000, which will set all attributes to that value.") - end -end - -if opt == nil then - ElevateMentalAttributes() -end diff --git a/scripts/elevate-physical.lua b/scripts/elevate-physical.lua deleted file mode 100644 index c210b4ffa..000000000 --- a/scripts/elevate-physical.lua +++ /dev/null @@ -1,51 +0,0 @@ --- Elevate all the physical attributes of a unit --- by vjek ---[[=begin - -elevate-physical -================ -As for elevate-mental, but for physical traits. High is good for soldiers, -while having an ineffective hammerer can be useful too... - -=end]] - -function ElevatePhysicalAttributes(value) - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - --print name of dwarf - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(unit))) - --walk through available attributes, adjust current to max - local ok,f,t,k = pcall(pairs,unit.body.physical_attrs) - if ok then - for k,v in f,t,k do - if value ~= nil then - print("Adjusting current value for "..tostring(k).." of "..v.value.." to the value of "..value) - v.value=value - else - print("Adjusting current value for "..tostring(k).." of "..v.value.." to max value of "..v.max_value) - v.value=v.max_value - --below will reset values back to "normal" - --v.value=v.max_value/2 - end - end - end -end ---script execution starts here -local opt = ... -opt = tonumber(opt) - -if opt ~= nil then - if opt >=0 and opt <=5000 then - ElevatePhysicalAttributes(opt) - end - if opt <0 or opt >5000 then - print("Invalid Range or argument. This script accepts either no argument, in which case it will increase the attribute to the max_value for the unit, or an argument between 0 and 5000, which will set all attributes to that value.") - end -end - -if opt == nil then - ElevatePhysicalAttributes() -end diff --git a/scripts/emigration.lua b/scripts/emigration.lua deleted file mode 100644 index aba62fa2b..000000000 --- a/scripts/emigration.lua +++ /dev/null @@ -1,132 +0,0 @@ ---Allow stressed dwarves to emigrate from the fortress --- For 34.11 by IndigoFenix; update and cleanup by PeridexisErrant --- old version: http://dffd.bay12games.com/file.php?id=8404 ---[[=begin - -emigration -========== -Allows dwarves to emigrate from the fortress when stressed, -in proportion to how badly stressed they are and adjusted -for who they would have to leave with - a dwarven merchant -being more attractive than leaving alone (or with an elf). -The check is made monthly. - -A happy dwarf (ie with negative stress) will never emigrate. - -Usage: ``emigration enable|disable`` - -=end]] - -local args = {...} -if args[1] == "enable" then - enabled = true -elseif args[1] == "disable" then - enabled = false -end - -function desireToStay(unit,method,civ_id) - -- on a percentage scale - local value = 100 - unit.status.current_soul.personality.stress_level / 5000 - if method == 'merchant' or method == 'diplomat' then - if civ_id ~= unit.civ_id then value = value*2 end end - if method == 'wild' then - value = value*5 end - return value -end - -function desert(u,method,civ) - u.relations.following = nil - local line = dfhack.TranslateName(dfhack.units.getVisibleName(u)) .. " has " - if method == 'merchant' then - line = line.."joined the merchants" - u.flags1.merchant = true - u.civ_id = civ - elseif method == 'diplomat' then - line = line.."followed the diplomat" - u.flags1.diplomat = true - u.civ_id = civ - else - line = line.."abandoned the settlement in search of a better life." - u.civ_id = -1 - u.flags1.forest = true - u.animal.leave_countdown = 2 - end - print(line) - dfhack.gui.showAnnouncement(line, COLOR_WHITE) -end - -function canLeave(unit) - for _, skill in pairs(unit.status.current_soul.skills) do - if skill.rating > 14 then return false end - end - if unit.flags1.caged - or unit.race ~= df.global.ui.race_id - or unit.civ_id ~= df.global.ui.civ_id - or dfhack.units.isDead(unit) - or dfhack.units.isOpposedToLife(unit) - or unit.flags1.merchant - or unit.flags1.diplomat - or unit.flags1.chained - or dfhack.units.getNoblePositions(unit) ~= nil - or unit.military.squad_id ~= -1 - or dfhack.units.isCitizen(unit) - or dfhack.units.isSane(unit) - or unit.profession ~= 103 - or not dfhack.units.isDead(unit) - then return false end - return true -end - -function checkForDeserters(method,civ_id) - local allUnits = df.global.world.units.active - for i=#allUnits-1,0,-1 do -- search list in reverse - local u = allUnits[i] - if canLeave(u) and math.random(100) < desireToStay(u,method,civ_id) then - desert(u,method,civ_id) - end - end -end - -function checkmigrationnow() - local merchant_civ_ids = {} - local diplomat_civ_ids = {} - local allUnits = df.global.world.units.active - for i=0, #allUnits-1 do - local unit = allUnits[i] - if dfhack.units.isSane(unit) - and not dfhack.units.isDead(unit) - and not dfhack.units.isOpposedToLife(unit) - and not unit.flags1.tame - then - if unit.flags1.merchant then table.insert(merchant_civ_ids, unit.civ_id) end - if unit.flags1.diplomat then table.insert(diplomat_civ_ids, unit.civ_id) end - end - end - - for _, civ_id in pairs(merchant_civ_ids) do checkForDeserters('merchant', civ_id) end - for _, civ_id in pairs(diplomat_civ_ids) do checkForDeserters('diplomat', civ_id) end - checkForDeserters('wild', -1) -end - -local function event_loop() - if enabled then - checkmigrationnow() - dfhack.timeout(1, 'months', event_loop) - end -end - -dfhack.onStateChange.loadEmigration = function(code) - if code==SC_MAP_LOADED then - if enabled then - print("Emigration enabled.") - event_loop() - else - print("Emigration disabled.") - end - end -end - -if dfhack.isMapLoaded() then - dfhack.onStateChange.loadEmigration(SC_MAP_LOADED) -end - diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua deleted file mode 100644 index 86542f930..000000000 --- a/scripts/exportlegends.lua +++ /dev/null @@ -1,836 +0,0 @@ --- Export everything from legends mode ---[[=begin - -exportlegends -============= -Controls legends mode to export data - especially useful to set-and-forget large -worlds, or when you want a map of every site when there are several hundred. - -The 'info' option exports more data than is possible in vanilla, to a -:file:`region-date-legends_plus.xml` file developed to extend -:forums:`World Viewer <128932>` and other legends utilities. - -Options: - -:info: Exports the world/gen info, the legends XML, and a custom XML with more information -:custom: Exports a custom XML with more information -:sites: Exports all available site maps -:maps: Exports all seventeen detailed maps -:all: Equivalent to calling all of the above, in that order - -=end]] - -gui = require 'gui' -local args = {...} -local vs = dfhack.gui.getCurViewscreen() -local i = 1 - -local MAPS = { - "Standard biome+site map", - "Elevations including lake and ocean floors", - "Elevations respecting water level", - "Biome", - "Hydrosphere", - "Temperature", - "Rainfall", - "Drainage", - "Savagery", - "Volcanism", - "Current vegetation", - "Evil", - "Salinity", - "Structures/fields/roads/etc.", - "Trade", - "Nobility and Holdings", - "Diplomacy", -} - -function getItemSubTypeName(itemType, subType) - if (dfhack.items.getSubtypeCount(itemType)) <= 0 then - return tostring(-1) - end - local subtypename = dfhack.items.getSubtypeDef(itemType, subType) - if (subtypename == nil) then - return tostring(-1) - else - return tostring(subtypename.name):lower() - end -end - -function findEntity(id) - for k,v in ipairs(df.global.world.entities.all) do - if (v.id == id) then - return v - end - end - return nil -end - -function table.contains(table, element) - for _, value in pairs(table) do - if value == element then - return true - end - end - return false -end - -function table.containskey(table, key) - for value, _ in pairs(table) do - if value == key then - return true - end - end - return false -end - --- wrapper that returns "unknown N" for df.enum_type[BAD_VALUE], --- instead of returning nil or causing an error -df_enums = {} -setmetatable(df_enums, { - __index = function(self, enum) - if not df[enum] or df[enum]._kind ~= 'enum-type' then - error('invalid enum: ' .. enum) - end - local t = {} - setmetatable(t, { - __index = function(self, k) - return df[enum][k] or 'unknown ' .. k - end - }) - return t - end, - __newindex = function() error('read-only') end -}) - ---create an extra legends xml with extra data, by Mason11987 for World Viewer -function export_more_legends_xml() - 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) - - 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 - - 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") - - file:write("\n") - for landmassK, landmassV in ipairs(df.global.world.world_data.landmasses) do - file:write("\t\n") - file:write("\t\t"..landmassV.index.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(landmassV.name,1)).."\n") - file:write("\t\t"..landmassV.min_x..","..landmassV.min_y.."\n") - file:write("\t\t"..landmassV.max_x..","..landmassV.max_y.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for mountainK, mountainV in ipairs(df.global.world.world_data.mountain_peaks) do - file:write("\t\n") - file:write("\t\t"..mountainK.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(mountainV.name,1)).."\n") - file:write("\t\t"..mountainV.pos.x..","..mountainV.pos.y.."\n") - file:write("\t\t"..mountainV.height.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for regionK, regionV in ipairs(df.global.world.world_data.regions) do - 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 - file:write(xVal..","..regionV.region_coords.y[xK].."|") - end - file:write("\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do - 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 - file:write(xVal..","..regionV.region_coords.y[xK].."|") - end - file:write("\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for siteK, siteV in ipairs(df.global.world.world_data.sites) do - file:write("\t\n") - for k,v in pairs(siteV) do - if (k == "id" or k == "civ_id" or k == "cur_owner_id") then - file:write("\t\t<"..k..">"..tostring(v).."\n") - elseif (k == "buildings") then - if (#siteV.buildings > 0) then - file:write("\t\t\n") - for buildingK, buildingV in ipairs(siteV.buildings) do - file:write("\t\t\t\n") - file:write("\t\t\t\t"..buildingV.id.."\n") - file:write("\t\t\t\t"..df_enums.abstract_building_type[buildingV:getType()]:lower().."\n") - if (df_enums.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire" or table.containskey(buildingV,"name")) then - 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 - if (buildingV:getType() == df.abstract_building_type.TEMPLE) then - file:write("\t\t\t\t"..buildingV.deity.."\n") - file:write("\t\t\t\t"..buildingV.religion.."\n") - end - if (buildingV:getType() == df.abstract_building_type.DUNGEON) then - file:write("\t\t\t\t"..buildingV.dungeon_type.."\n") - end - for inhabitabntK,inhabitabntV in pairs(buildingV.inhabitants) do - file:write("\t\t\t\t"..inhabitabntV.anon_2.."\n") - end - file:write("\t\t\t\n") - end - file:write("\t\t\n") - end - end - end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do - 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_enums.world_construction_type[wcV:getType()]):lower().."\n") - file:write("\t\t") - for xK, xVal in ipairs(wcV.square_pos.x) do - file:write(xVal..","..wcV.square_pos.y[xK].."|") - end - file:write("\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do - file:write("\t\n") - file:write("\t\t"..artifactV.id.."\n") - if (artifactV.item:getType() ~= -1) then - file:write("\t\t"..tostring(df_enums.item_type[artifactV.item:getType()]):lower().."\n") - if (artifactV.item:getSubtype() ~= -1) then - file:write("\t\t"..artifactV.item.subtype.name.."\n") - end - for improvementK,impovementV in pairs(artifactV.item.improvements) do - if impovementV:getType() == df.improvement_type.WRITING then - for writingk,writingV in pairs(impovementV["itemimprovement_writingst.anon_1"]) do - file:write("\t\t"..writingV.."\n") - end - elseif impovementV:getType() == df.improvement_type.PAGES then - file:write("\t\t"..impovementV.count.."\n") - for writingk,writingV in pairs(impovementV.contents) do - file:write("\t\t"..writingV.."\n") - end - end - end - end - if (table.containskey(artifactV.item,"description")) then - file:write("\t\t"..dfhack.df2utf(artifactV.item.description:lower()).."\n") - end - if artifactV.item:getMaterial() ~= -1 then - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."\n") - end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for hfK, hfV in ipairs(df.global.world.history.figures) do - file:write("\t\n") - file:write("\t\t"..hfV.id.."\n") - file:write("\t\t"..hfV.sex.."\n") - if hfV.race >= 0 then file:write("\t\t"..df.global.world.raws.creatures.all[hfV.race].name[0].."\n") end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do - 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() - file:write("\t\t"..raceName..":"..entityPopV.counts[raceK].."\n") - end - file:write("\t\t"..entityPopV.civ_id.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for entityK, entityV in ipairs(df.global.world.entities.all) do - file:write("\t\n") - file:write("\t\t"..entityV.id.."\n") - if entityV.race >= 0 then - file:write("\t\t"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."\n") - end - file:write("\t\t"..(df_enums.historical_entity_type[entityV.type]):lower().."\n") - if entityV.type == df.historical_entity_type.Religion then -- Get worshipped figure - if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nil) then - for k,v in pairs(entityV.unknown1b.worship) do - file:write("\t\t"..v.."\n") - end - end - end - for id, link in pairs(entityV.entity_links) do - file:write("\t\t\n") - for k, v in pairs(link) do - if (k == "type") then - file:write("\t\t\t<"..k..">"..tostring(df_enums.entity_entity_link_type[v]).."\n") - else - file:write("\t\t\t<"..k..">"..v.."\n") - end - end - file:write("\t\t\n") - end - for positionK,positionV in pairs(entityV.positions.own) do - file:write("\t\t\n") - file:write("\t\t\t"..positionV.id.."\n") - if positionV.name[0] ~= "" then file:write("\t\t\t"..positionV.name[0].."\n") end - if positionV.name_male[0] ~= "" then file:write("\t\t\t"..positionV.name_male[0].."\n") end - if positionV.name_female[0] ~= "" then file:write("\t\t\t"..positionV.name_female[0].."\n") end - if positionV.spouse[0] ~= "" then file:write("\t\t\t"..positionV.spouse[0].."\n") end - if positionV.spouse_male[0] ~= "" then file:write("\t\t\t"..positionV.spouse_male[0].."\n") end - if positionV.spouse_female[0] ~= "" then file:write("\t\t\t"..positionV.spouse_female[0].."\n") end - file:write("\t\t\n") - end - for assignmentK,assignmentV in pairs(entityV.positions.assignments) do - file:write("\t\t\n") - for k, v in pairs(assignmentV) do - if (k == "id" or k == "histfig" or k == "position_id" or k == "squad_id") then - file:write("\t\t\t<"..k..">"..v.."\n") - end - end - file:write("\t\t\n") - end - for idx,id in pairs(entityV.histfig_ids) do - file:write("\t\t"..id.."\n") - end - for id, link in ipairs(entityV.children) do - file:write("\t\t"..link.."\n") - end - file:write("\t\t") - for xK, xVal in ipairs(entityV.claims.unk2.x) do - file:write(xVal..","..entityV.claims.unk2.y[xK].."|") - end - file:write("\t\t\n") - if (table.containskey(entityV,"occasion_info") and entityV.occasion_info ~= nil) then - for occasionK, occasionV in pairs(entityV.occasion_info.occasions) do - file:write("\t\t\n") - file:write("\t\t\t"..occasionV.id.."\n") - file:write("\t\t\t"..dfhack.df2utf(dfhack.TranslateName(occasionV.name,1)).."\n") - file:write("\t\t\t"..occasionV.event.."\n") - for scheduleK, scheduleV in pairs(occasionV.schedule) do - file:write("\t\t\t\n") - file:write("\t\t\t\t"..scheduleK.."\n") - file:write("\t\t\t\t"..df_enums.occasion_schedule_type[scheduleV.type]:lower().."\n") - if(scheduleV.type == df.occasion_schedule_type.THROWING_COMPETITION) then - file:write("\t\t\t\t"..df_enums.item_type[scheduleV.reference]:lower().."\n") - file:write("\t\t\t\t"..getItemSubTypeName(scheduleV.reference,scheduleV.reference2).."\n") - else - file:write("\t\t\t\t"..scheduleV.reference.."\n") - file:write("\t\t\t\t"..scheduleV.reference2.."\n") - end - for featureK, featureV in pairs(scheduleV.features) do - file:write("\t\t\t\t\n") - if(df_enums.occasion_schedule_feature[featureV.feature] ~= nil) then - file:write("\t\t\t\t\t"..df_enums.occasion_schedule_feature[featureV.feature]:lower().."\n") - else - file:write("\t\t\t\t\t"..featureV.feature.."\n") - end - file:write("\t\t\t\t\t"..featureV.reference.."\n") - file:write("\t\t\t\t\n") - end - file:write("\t\t\t\n") - end - file:write("\t\t\n") - end - end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for formK, formV in ipairs(df.global.world.poetic_forms.all) do - file:write("\t\n") - file:write("\t\t"..formV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for formK, formV in ipairs(df.global.world.musical_forms.all) do - file:write("\t\n") - file:write("\t\t"..formV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for formK, formV in ipairs(df.global.world.dance_forms.all) do - file:write("\t\n") - file:write("\t\t"..formV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for wcK, wcV in ipairs(df.global.world.written_contents.all) do - file:write("\t\n") - file:write("\t\t"..wcV.id.."\n") - file:write("\t\t"..wcV.title.."\n") - file:write("\t\t"..wcV.page_start.."\n") - file:write("\t\t"..wcV.page_end.."\n") - for refK, refV in pairs(wcV.refs) do - file:write("\t\t\n") - file:write("\t\t\t"..df_enums.general_ref_type[refV:getType()].."\n") - if refV:getType() == df.general_ref_type.ARTIFACT then file:write("\t\t\t"..refV.artifact_id.."\n") -- artifact - elseif refV:getType() == df.general_ref_type.ENTITY then file:write("\t\t\t"..refV.entity_id.."\n") -- entity - elseif refV:getType() == df.general_ref_type.HISTORICAL_EVENT then file:write("\t\t\t"..refV.event_id.."\n") -- event - elseif refV:getType() == df.general_ref_type.SITE then file:write("\t\t\t"..refV.site_id.."\n") -- site - elseif refV:getType() == df.general_ref_type.SUBREGION then file:write("\t\t\t"..refV.region_id.."\n") -- region - elseif refV:getType() == df.general_ref_type.HISTORICAL_FIGURE then file:write("\t\t\t"..refV.hist_figure_id.."\n") -- hist figure - elseif refV:getType() == df.general_ref_type.WRITTEN_CONTENT then file:write("\t\t\t"..refV.anon_1.."\n") - elseif refV:getType() == df.general_ref_type.POETIC_FORM then file:write("\t\t\t"..refV.poetic_form_id.."\n") -- poetic form - elseif refV:getType() == df.general_ref_type.MUSICAL_FORM then file:write("\t\t\t"..refV.musical_form_id.."\n") -- musical form - elseif refV:getType() == df.general_ref_type.DANCE_FORM then file:write("\t\t\t"..refV.dance_form_id.."\n") -- dance form - elseif refV:getType() == df.general_ref_type.INTERACTION then -- TODO INTERACTION - elseif refV:getType() == df.general_ref_type.KNOWLEDGE_SCHOLAR_FLAG then -- TODO KNOWLEDGE_SCHOLAR_FLAG - elseif refV:getType() == df.general_ref_type.VALUE_LEVEL then -- TODO VALUE_LEVEL - elseif refV:getType() == df.general_ref_type.LANGUAGE then -- TODO LANGUAGE - else - print("unknown reference",refV:getType(),df_enums.general_ref_type[refV:getType()]) - --for k,v in pairs(refV) do print(k,v) end - end - file:write("\t\t\n") - end - file:write("\t\t"..(df_enums.written_content_type[wcV.type] or wcV.type).."\n") - for styleK, styleV in pairs(wcV.styles) do - file:write("\t\t\n") - end - file:write("\t\t"..wcV.author.."\n") - file:write("\t\n") - end - file: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 - or event:getType() == df.history_event_type.ADD_HF_HF_LINK - or event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK - or event:getType() == df.history_event_type.TOPICAGREEMENT_CONCLUDED - or event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED - or event:getType() == df.history_event_type.TOPICAGREEMENT_MADE - or event:getType() == df.history_event_type.BODY_ABUSED - or event:getType() == df.history_event_type.CHANGE_CREATURE_TYPE - or event:getType() == df.history_event_type.CHANGE_HF_JOB - or event:getType() == df.history_event_type.CHANGE_HF_STATE - or event:getType() == df.history_event_type.CREATED_BUILDING - or event:getType() == df.history_event_type.CREATURE_DEVOURED - or event:getType() == df.history_event_type.HF_DOES_INTERACTION - or event:getType() == df.history_event_type.HF_LEARNS_SECRET - or event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET - or event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT - or event:getType() == df.history_event_type.ITEM_STOLEN - or event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK - or event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK - or event:getType() == df.history_event_type.REPLACED_BUILDING - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_DESIGN - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ENGRAVING - or event:getType() == df.history_event_type.MASTERPIECE_LOST - or event:getType() == df.history_event_type.ENTITY_ACTION - or event:getType() == df.history_event_type.HF_ACT_ON_BUILDING - or event:getType() == df.history_event_type.ARTIFACT_CREATED - or event:getType() == df.history_event_type.ASSUME_IDENTITY - or event:getType() == df.history_event_type.CREATE_ENTITY_POSITION - or event:getType() == df.history_event_type.DIPLOMAT_LOST - or event:getType() == df.history_event_type.MERCHANT - or event:getType() == df.history_event_type.WAR_PEACE_ACCEPTED - or event:getType() == df.history_event_type.WAR_PEACE_REJECTED - or event:getType() == df.history_event_type.HIST_FIGURE_WOUNDED - or event:getType() == df.history_event_type.HIST_FIGURE_DIED - then - file:write("\t\n") - file:write("\t\t"..event.id.."\n") - file:write("\t\t"..tostring(df_enums.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) - or k == "region_pos" or k == "layer" or k == "feature_layer" or k == "subregion" - 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 - file:write("\t\t<"..k..">"..df_enums.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 - file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") - break - end - end - else - 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 - file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") - break - end - end - else - file:write("\t\t-1\n") - end - elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "link_type" then - file:write("\t\t<"..k..">"..df_enums.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 - file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") - break - end - end - else - file:write("\t\t-1\n") - end - elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then - file:write("\t\t"..df_enums.histfig_hf_link_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then - file:write("\t\t"..df_enums.histfig_site_link_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then - file:write("\t\t"..df_enums.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 or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - ) and k == "item_type" then - file:write("\t\t"..df_enums.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 or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - ) and k == "item_subtype" then - --if event.item_type > -1 and v > -1 then - file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,v).."\n") - --end - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD and k == "item_subtype" then - --if event.item_type > -1 and v > -1 then - file:write("\t\tfood\n") - file:write("\t\t<"..k..">"..getItemSubTypeName(df.item_type.FOOD,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 - file:write("\t\t"..event.mattype.."\n") - file:write("\t\t"..event.matindex.."\n") - else - 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 - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - ) and k == "mat_type" then - if (v > -1) then - if (dfhack.matinfo.decode(event.mat_type, event.mat_index) == nil) then - file:write("\t\t"..event.mat_type.."\n") - file:write("\t\t"..event.mat_index.."\n") - else - 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 - file:write("\t\t"..event.imp_mat_type.."\n") - file:write("\t\t"..event.imp_mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."\n") - end - end - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM and k == "dye_mat_type" then - if (v > -1) then - if (dfhack.matinfo.decode(event.dye_mat_type, event.dye_mat_index) == nil) then - file:write("\t\t"..event.dye_mat_type.."\n") - file:write("\t\t"..event.dye_mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.dye_mat_type, event.dye_mat_index)).."\n") - end - end - - elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "matindex" then - --skip - elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "item" and v == -1 then - --skip - elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT - ) and k == "mat_index" then - --skip - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_index" then - --skip - elseif (event:getType() == df.history_event_type.WAR_PEACE_ACCEPTED or - event:getType() == df.history_event_type.WAR_PEACE_REJECTED or - event:getType() == df.history_event_type.TOPICAGREEMENT_CONCLUDED or - event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED or - event:getType() == df.history_event_type.TOPICAGREEMENT_MADE - ) and k == "topic" then - file:write("\t\t"..tostring(df_enums.meeting_topic[v]):lower().."\n") - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then - file:write("\t\t"..df_enums.improvement_type[v]:lower().."\n") - elseif ((event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT and k == "group") - 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 - 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 - file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[detailV].name[0].."\n") - end - elseif event:getType() == df.history_event_type.BODY_ABUSED and (k == "props") then - file:write("\t\t"..tostring(df_enums.item_type[event.props.item.item_type]):lower().."\n") - file:write("\t\t"..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 - file:write("\t\t"..event.props.item.mat_type.."\n") - file:write("\t\t"..event.props.item.mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."\n") - end - end - --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] - 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 - 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 - file:write("\t\t"..df_enums.building_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_subtype" then - if (df_enums.building_type[event.building_type]:lower() == "furnace") then - file:write("\t\t"..df_enums.furnace_type[v]:lower().."\n") - elseif v > -1 then - file:write("\t\t"..tostring(v).."\n") - end - elseif k == "race" then - if v > -1 then - file:write("\t\t"..df.global.world.raws.creatures.all[v].name[0].."\n") - end - elseif k == "caste" then - if v > -1 then - 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 - 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 - 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 - 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() == df.general_ref_type.IS_ARTIFACT) then - file:write("\t\t"..refv.artifact_id.."\n") - break - end - end - end - end - - end - elseif (detailK == "item_type") then - if event.weapon.item > -1 then - file:write("\t\t<"..detailK..">"..tostring(df_enums.item_type[detailV]):lower().."\n") - end - elseif (detailK == "item_subtype") then - if event.weapon.item > -1 and detailV > -1 then - file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."\n") - end - elseif (detailK == "mattype") then - if (detailV > -1) then - 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 - 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() == df.general_ref_type.IS_ARTIFACT) then - file:write("\t\t"..refv.artifact_id.."\n") - break - end - end - end - end - end - elseif (detailK == "shooter_item_type") then - if event.weapon.shooter_item > -1 then - file:write("\t\t<"..detailK..">"..tostring(df_enums.item_type[detailV]):lower().."\n") - end - elseif (detailK == "shooter_item_subtype") then - if event.weapon.shooter_item > -1 and detailV > -1 then - file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."\n") - end - elseif (detailK == "shooter_mattype") then - if (detailV > -1) then - 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 - file:write("\t\t<"..detailK..">"..detailV.."\n") - end - end - elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "death_cause" then - file:write("\t\t<"..k..">"..df_enums.death_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.CHANGE_HF_JOB and (k == "new_job" or k == "old_job") then - file:write("\t\t<"..k..">"..df_enums.profession[v]:lower().."\n") - elseif event:getType() == df.history_event_type.CHANGE_CREATURE_TYPE and (k == "old_race" or k == "new_race") and v >= 0 then - file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[v].name[0].."\n") - else - file:write("\t\t<"..k..">"..tostring(v).."\n") - end - end - file:write("\t\n") - end - end - 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') -function export_legends_info() - print(' Exporting: World map/gen info') - gui.simulateInput(vs, 'LEGENDS_EXPORT_MAP') - print(' Exporting: Legends xml') - gui.simulateInput(vs, 'LEGENDS_EXPORT_XML') - print(" Exporting: Extra legends_plus xml") - export_more_legends_xml() -end - ---- presses 'd' for detailed maps -function wait_for_legends_vs() - local vs = dfhack.gui.getCurViewscreen() - if i <= #MAPS then - if df.viewscreen_legendsst:is_instance(vs.parent) then - vs = vs.parent - end - if df.viewscreen_legendsst:is_instance(vs) then - gui.simulateInput(vs, 'LEGENDS_EXPORT_DETAILED_MAP') - dfhack.timeout(10,'frames',wait_for_export_maps_vs) - else - dfhack.timeout(10,'frames',wait_for_legends_vs) - end - end -end - --- selects detailed map and export it -function wait_for_export_maps_vs() - local vs = dfhack.gui.getCurViewscreen() - if dfhack.gui.getCurFocus() == "export_graphical_map" then - vs.sel_idx = i-1 - print(' Exporting: '..MAPS[i]..' map') - gui.simulateInput(vs, 'SELECT') - i = i + 1 - dfhack.timeout(10,'frames',wait_for_legends_vs) - else - dfhack.timeout(10,'frames',wait_for_export_maps_vs) - end -end - --- export site maps -function export_site_maps() - local vs = dfhack.gui.getCurViewscreen() - if ((dfhack.gui.getCurFocus() ~= "legends" ) and (not table.contains(vs, "main_cursor"))) then -- Using open-legends - vs = vs.parent - end - print(' Exporting: All possible site maps') - vs.main_cursor = 1 - gui.simulateInput(vs, 'SELECT') - for i=1, #vs.sites do - gui.simulateInput(vs, 'LEGENDS_EXPORT_MAP') - gui.simulateInput(vs, 'STANDARDSCROLL_DOWN') - end - gui.simulateInput(vs, 'LEAVESCREEN') -end - --- main() -if dfhack.gui.getCurFocus() == "legends" or dfhack.gui.getCurFocus() == "dfhack/lua/legends" then - -- either native legends mode, or using the open-legends.lua script - if args[1] == "all" then - export_legends_info() - export_site_maps() - wait_for_legends_vs() - elseif args[1] == "info" then - export_legends_info() - elseif args[1] == "custom" then - export_more_legends_xml() - elseif args[1] == "maps" then - wait_for_legends_vs() - elseif args[1] == "sites" then - export_site_maps() - else dfhack.printerr('Valid arguments are "all", "info", "maps" or "sites"') - end -elseif args[1] == "maps" and - dfhack.gui.getCurFocus() == "export_graphical_map" then - wait_for_export_maps_vs() -else - dfhack.printerr('Exportlegends must be run from the main legends view') -end diff --git a/scripts/exterminate.rb b/scripts/exterminate.rb deleted file mode 100644 index bc25a95b8..000000000 --- a/scripts/exterminate.rb +++ /dev/null @@ -1,192 +0,0 @@ -# exterminate creatures -=begin - -exterminate -=========== -Kills any unit of a given race. - -With no argument, lists the available races and count eligible targets. - -With the special argument ``him``, targets only the selected creature. - -With the special argument ``undead``, targets all undeads on the map, -regardless of their race. - -When specifying a race, a caste can be specified to further restrict the -targeting. To do that, append and colon and the caste name after the race. - -Any non-dead non-caged unit of the specified race gets its ``blood_count`` -set to 0, which means immediate death at the next game tick. For creatures -such as vampires, it also sets animal.vanish_countdown to 2. - -An alternate mode is selected by adding a 2nd argument to the command, -``magma``. In this case, a column of 7/7 magma is generated on top of the -targets until they die (Warning: do not call on magma-safe creatures. Also, -using this mode on birds is not recommended.) The final alternate mode -is ``butcher``, which marks them for butchering but does not kill. - -Will target any unit on a revealed tile of the map, including ambushers, -but ignore caged/chained creatures. - -Ex:: - - exterminate gob - exterminate gob:male - -To kill a single creature, select the unit with the 'v' cursor and:: - - exterminate him - -To purify all elves on the map with fire (may have side-effects):: - - exterminate elve magma - -=end - -race = $script_args[0] - -# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death -# if it is 'butcher' mark all units for butchering (wont work with hostiles) -kill_by = $script_args[1] - -case kill_by -when 'magma' - slain = 'burning' -when 'slaughter', 'butcher' - slain = 'marked for butcher' -when nil - slain = 'slain' -else - race = 'help' -end - -checkunit = lambda { |u| - (u.body.blood_count != 0 or u.body.blood_max == 0) and - not u.flags1.dead and - not u.flags1.caged and not u.flags1.chained and - #not u.flags1.hidden_in_ambush and - not df.map_designation_at(u).hidden -} - -slayit = lambda { |u| - case kill_by - when 'magma' - # it's getting hot around here - # !!WARNING!! do not call on a magma-safe creature - ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) { - if u.flags1.dead - df.onupdate_unregister(ouh) - else - x, y, z = u.pos.x, u.pos.y, u.pos.z - z += 1 while tile = df.map_tile_at(x, y, z+1) and - tile.shape_passableflow and tile.shape_passablelow - df.map_tile_at(x, y, z).spawn_magma(7) - end - } - when 'butcher', 'slaughter' - # mark for slaughter at butcher's shop - u.flags2.slaughter = true - else - # just make them drop dead - u.body.blood_count = 0 - # some races dont mind having no blood, ensure they are still taken care of. - u.animal.vanish_countdown = 2 - end -} - -all_races = Hash.new(0) - -df.world.units.active.map { |u| - if checkunit[u] - if (u.enemy.undead or - (u.curse.add_tags1.OPPOSED_TO_LIFE and not - u.curse.rem_tags1.OPPOSED_TO_LIFE)) - all_races['Undead'] += 1 - else - all_races[u.race_tg.creature_id] += 1 - end - end -} - -case race -when nil - all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } - -when 'help', '?' - puts <= #map_features then - qerror('Invalid feature ID') - end - map_features[tonumber(idx)].flags.Discovered = discovered -end - -function list_features() - local name = df.new('string') - for idx, feat in ipairs(map_features) do - local tags = '' - for _, t in pairs({'water', 'magma', 'subterranean', 'chasm', 'layer'}) do - if feat['is' .. t:sub(1, 1):upper() .. t:sub(2)](feat) then - tags = tags .. (' [%s]'):format(t) - end - end - feat:getName(name) - print(('Feature #%i is %s: "%s", type %s%s'):format( - idx, - feat.flags.Discovered and 'shown' or 'hidden', - name.value, - df.feature_type[feat:getType()], - tags - )) - end - df.delete(name) -end - -function enable_magma_funaces() - for idx, feat in ipairs(map_features) do - if tostring(feat):find('magma') ~= nil then - toggle_feature(idx, true) - print('Enabled magma furnaces.') - return - end - end - dfhack.printerr('Could not find a magma-bearing feature.') -end - -local args = {...} -if args[1] == 'list' then - list_features() -elseif args[1] == 'magma' then - enable_magma_funaces() -elseif args[1] == 'show' then - toggle_feature(args[2], true) -elseif args[1] == 'hide' then - toggle_feature(args[2], false) -else - print((help:gsub('=[a-z]+', ''))) -end diff --git a/scripts/fix-ster.lua b/scripts/fix-ster.lua deleted file mode 100644 index 66b4f6765..000000000 --- a/scripts/fix-ster.lua +++ /dev/null @@ -1,122 +0,0 @@ --- makes creatures [in]fertile, by modifying orientation --- usage: fix-ster [fert|ster] [all|animals|only:] --- original author: Tacomagic --- minor fixes by PeridexisErrant, Lethosor ---@ module = true ---[[=begin - -fix-ster -======== -Utilizes the orientation tag to either fix infertile creatures or inflict -infertility on creatures that you do not want to breed. Usage:: - - fix-ster [fert|ster] [all|animals|only:] - -``fert`` or ``ster`` is a required argument; whether to make the target fertile -or sterile. Optional arguments specify the target: no argument for the -selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf -creatures, or ``only:`` to only process matching creatures. - -=end]] - -function changeorient(unit, ori) - --Sets the fertility flag based on gender. - if not unit.status.current_soul then - return - end - if unit.sex == 0 then - unit.status.current_soul.orientation_flags.marry_male=ori - else - unit.status.current_soul.orientation_flags.marry_female=ori - end -end - -function changelots(creatures, ori, silent) - local v, unit - local c = 0 - --loops through indexes in creatures table and changes orientation flags - for _, v in ipairs(creatures) do - unit = df.global.world.units.active[v] - changeorient(unit,ori) - c = c + 1 - end - if not silent then - print("Changed " .. c .. " creatures.") - end -end - -function process_args(args) - local n, v, ori, crename, crenum - local creatures = {} - --Checks for any arguments at all. - if args == nil or #args == 0 then - print("No arguments. Usage is: fixster [all|animals|only:]") - return - end - for i, a in pairs(args) do - args[i] = tostring(a):lower() - end - if args[1]:sub(1, 1) == "s" then -- sterile - ori = false - elseif args[1]:sub(1, 1) == "f" then -- fertile - ori = true - else - qerror("Unrecognised first argument: " .. args[1] .. ". Aborting.") - end - - --Checks for the existence of the second argument. If it's missing, uses selected unit (if any) - if args[2] == nil then - unit = dfhack.gui.getSelectedUnit() - if not unit then return end - changeorient(unit, ori) - print('Changed selected creature.') - - --ALL arg processing - elseif args[2] == "all" then - --Create table of all current unit indexes - for n,v in ipairs(df.global.world.units.active) do - table.insert(creatures,n) - end - changelots(creatures,ori) - - --ANIMALS arg processing - elseif args[2] == "animals" then - --Create a table of all creature indexes except dwarves on the current map - for n,v in ipairs(df.global.world.units.active) do - if v.race ~= df.global.ui.race_id then - table.insert(creatures,n) - end - end - changelots(creatures,ori) - - -- ONLY: arg processing - elseif args[2]:sub(1,4) == "only" then - crename = args[2]:sub(6):upper() - - --Search raws for creature - for k,v in pairs(df.global.world.raws.creatures.all) do - if v.creature_id == crename then - crenum = k - end - end - --If no match, abort - if crenum == nil then - qerror("Creature not found. Check spelling.") - end - - --create a table of all the matching creature indexes on the map for processing - for n,v in ipairs(df.global.world.units.active) do - if v.race == crenum then - table.insert(creatures,n) - end - end - changelots(creatures,ori) - else - qerror("Unrecognised optional argument. Aborting") - end -end - -if not moduleMode then - local args = table.pack(...) - process_args(args) -end diff --git a/scripts/fix/about.txt b/scripts/fix/about.txt deleted file mode 100644 index 6a2d35c7e..000000000 --- a/scripts/fix/about.txt +++ /dev/null @@ -1 +0,0 @@ -``fix/*`` scripts fix various bugs and issues, some of them obscure. diff --git a/scripts/fix/blood-del.lua b/scripts/fix/blood-del.lua deleted file mode 100644 index ba32d4ab9..000000000 --- a/scripts/fix/blood-del.lua +++ /dev/null @@ -1,62 +0,0 @@ --- Stop traders bringing blood, ichor, or goo ---author Urist Da Vinci; edited by expwnent, scamtank ---[[=begin - -fix/blood-del -============= -Makes it so that future caravans won't bring barrels full of blood, ichor, or goo. - -=end]] -local my_entity=df.historical_entity.find(df.global.ui.civ_id) -local sText=" " -local k=0 -local v=1 - -for x,y in pairs(df.global.world.entities.all) do - my_entity=y - k=0 - while k < #my_entity.resources.misc_mat.extracts.mat_index do - v=my_entity.resources.misc_mat.extracts.mat_type[k] - sText=dfhack.matinfo.decode(v,my_entity.resources.misc_mat.extracts.mat_index[k]) - if (sText==nil) then - --LIQUID barrels - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - else - if(sText.material.id=="BLOOD") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="ICHOR") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="GOO") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="SWEAT") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="TEARS") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - --VENOM - --POISON - --FLUID - --MILK - --EXTRACT - - end - k=k+1 - end -end - diff --git a/scripts/fix/build-location.lua b/scripts/fix/build-location.lua deleted file mode 100644 index 9df518ec3..000000000 --- a/scripts/fix/build-location.lua +++ /dev/null @@ -1,45 +0,0 @@ --- Lets constructions reconsider the build location. - --- Partial work-around for http://www.bay12games.com/dwarves/mantisbt/view.php?id=5991 ---[[=begin - -fix/build-location -================== -Fixes construction jobs that are stuck trying to build a wall while standing -on the same exact tile (:bug:`5991`), designates the tile restricted traffic to -hopefully avoid jamming it again, and unsuspends them. - -=end]] -local utils = require('utils') - -local count = 0 - -for link,job in utils.listpairs(df.global.world.job_list) do - local job = link.item - local place = dfhack.job.getHolder(job) - - if job.job_type == df.job_type.ConstructBuilding - and place and place:isImpassableAtCreation() - and job.item_category[0] - then - local cpos = utils.getBuildingCenter(place) - - if same_xyz(cpos, job.pos) then - -- Reset the flag - job.item_category[0] = false - job.flags.suspend = false - - -- Mark the tile restricted traffic - local dsgn,occ = dfhack.maps.getTileFlags(cpos) - dsgn.traffic = df.tile_traffic.Restricted - - count = count + 1 - end - end -end - -print('Found and unstuck '..count..' construct building jobs.') - -if count > 0 then - df.global.process_jobs = true -end diff --git a/scripts/fix/dead-units.lua b/scripts/fix/dead-units.lua deleted file mode 100644 index 7687a75cc..000000000 --- a/scripts/fix/dead-units.lua +++ /dev/null @@ -1,37 +0,0 @@ --- Remove uninteresting dead units from the unit list. ---[[=begin - -fix/dead-units -============== -Removes uninteresting dead units from the unit list. Doesn't seem to give any -noticeable performance gain, but migrants normally stop if the unit list grows -to around 3000 units, and this script reduces it back. - -=end]] -local units = df.global.world.units.active -local dwarf_race = df.global.ui.race_id -local dwarf_civ = df.global.ui.civ_id -local count = 0 - -for i=#units-1,0,-1 do - local unit = units[i] - local flags1 = unit.flags1 - local flags2 = unit.flags2 - if flags1.dead and unit.race ~= dwarf_race then - local remove = false - if flags2.slaughter then - remove = true - elseif not unit.name.has_name then - remove = true - elseif unit.civ_id ~= dwarf_civ and - not (flags1.merchant or flags1.diplomat) then - remove = true - end - if remove then - count = count + 1 - units:erase(i) - end - end -end - -print('Units removed from active: '..count) diff --git a/scripts/fix/diplomats.lua b/scripts/fix/diplomats.lua deleted file mode 100644 index 4e8c6daf7..000000000 --- a/scripts/fix/diplomats.lua +++ /dev/null @@ -1,106 +0,0 @@ --- Add Elven diplomats to negotiate tree caps ---[[=begin - -fix/diplomats -============= -Adds a Diplomat position to all Elven civilizations, allowing them to negotiate -tree cutting quotas - and you to violate them and start wars. -This was vanilla behaviour until ``0.31.12``, in which the "bug" was "fixed". - -=end]] - - -function update_pos(ent) - local pos = df.entity_position:new() - ent.positions.own:insert('#', pos) - - pos.code = "DIPLOMAT" - pos.id = ent.positions.next_position_id + 1 - ent.positions.next_position_id = ent.positions.next_position_id + 1 - - pos.flags.DO_NOT_CULL = true - pos.flags.MENIAL_WORK_EXEMPTION = true - pos.flags.SLEEP_PRETENSION = true - pos.flags.PUNISHMENT_EXEMPTION = true - pos.flags.ACCOUNT_EXEMPT = true - pos.flags.DUTY_BOUND = true - pos.flags.COLOR = true - pos.flags.HAS_RESPONSIBILITIES = true - pos.flags.IS_DIPLOMAT = true - pos.flags.IS_LEADER = true - -- not sure what these flags do, but the game sets them for a valid diplomat - pos.flags.unk_12 = true - pos.flags.unk_1a = true - pos.flags.unk_1b = true - pos.name[0] = "Diplomat" - pos.name[1] = "Diplomats" - pos.precedence = 70 - pos.color[0] = 7 - pos.color[1] = 0 - pos.color[2] = 1 - - return pos -end - - - -local checked = 0 -local fixed = 0 - -for _,ent in pairs(df.global.world.entities.all) do - if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.TREE_CAP_DIPLOMACY then - checked = checked + 1 - - update = true - local found_position - -- see if we need to add a new position or modify an existing one - for _,pos in pairs(ent.positions.own) do - if pos.responsibilities.MAKE_INTRODUCTIONS and - pos.responsibilities.MAKE_PEACE_AGREEMENTS and - pos.responsibilities.MAKE_TOPIC_AGREEMENTS then - -- a diplomat position exists with the proper responsibilities - skip to the end - update = false - found_position=pos - break - end - -- Diplomat position already exists, but has the wrong options - modify it instead of creating a new one - if pos.code == "DIPLOMAT" then - found_position=pos - break - end - end - if update then - -- either there's no diplomat, or there is one and it's got the wrong responsibilities - if not found_position then - found_position = add_guild_rep( ent ) - end - -- assign responsibilities - found_position.responsibilities.MAKE_INTRODUCTIONS = true - found_position.responsibilities.MAKE_PEACE_AGREEMENTS = true - found_position.responsibilities.MAKE_TOPIC_AGREEMENTS = true - end - -- make sure the diplomat position, whether we created it or not, is set up for proper assignment - local assign = true - for _,p in pairs(ent.positions.assignments) do - if p.position_id == found_position.id then -- it is - nothing more to do here - assign = false - break - end - end - if assign then -- it isn't - set it up - local asn = df.entity_position_assignment:new() - ent.positions.assignments:insert('#', asn); - - asn.id = ent.positions.next_assignment_id - ent.positions.next_assignment_id = asn.id + 1 - asn.position_id = found_position.id - asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags - asn.flags[0] = true -- and set the first one - end - if update or assign then - fixed = fixed + 1 - end - end -end - -print("Enabled tree cap diplomacy for "..fixed.." of "..checked.." civilizations.") diff --git a/scripts/fix/dry-buckets.lua b/scripts/fix/dry-buckets.lua deleted file mode 100644 index 91cff2cd1..000000000 --- a/scripts/fix/dry-buckets.lua +++ /dev/null @@ -1,28 +0,0 @@ --- Removes water from buckets (for lye-making). ---[[=begin - -fix/dry-buckets -=============== -Removes water from all buckets in your fortress, allowing them -to be used for making lye. Skips buckets in buildings (eg a well), -being carried, or currently used by a job. - -=end]] - -local emptied = 0 -local water_type = dfhack.matinfo.find('WATER').type - -for _,item in ipairs(df.global.world.items.all) do - container = dfhack.items.getContainer(item) - if container ~= nil - and container:getType() == df.item_type.BUCKET - and not (container.flags.in_job or container.flags.in_building) - and item:getMaterial() == water_type - and item:getType() == df.item_type.LIQUID_MISC - and not (item.flags.in_job or item.flags.in_building) then - dfhack.items.remove(item) - emptied = emptied + 1 - end -end - -print('Emptied '..emptied..' buckets.') diff --git a/scripts/fix/fat-dwarves.lua b/scripts/fix/fat-dwarves.lua deleted file mode 100644 index 09b5eb28e..000000000 --- a/scripts/fix/fat-dwarves.lua +++ /dev/null @@ -1,33 +0,0 @@ --- Makes fat dwarves non-fat. --- --- See for more info: --- http://www.bay12games.com/dwarves/mantisbt/view.php?id=5971 ---[[=begin - -fix/fat-dwarves -=============== -Avoids 5-10% FPS loss due to constant recalculation of insulation for dwarves at -maximum fatness, by reducing the cap from 1,000,000 to 999,999. -Recalculation is triggered in steps of 250 units, and very fat dwarves -constantly bounce off the maximum value while eating. - -=end]] -local num_fat = 0 -local num_lagging = 0 - -for _,v in ipairs(df.global.world.units.all) do - local fat = v.counters2.stored_fat - if fat > 850000 then - v.counters2.stored_fat = 500000 - if v.race == df.global.ui.race_id then - print(fat,dfhack.TranslateName(dfhack.units.getVisibleName(v))) - num_fat = num_fat + 1 - if fat > 999990 then - num_lagging = num_lagging + 1 - end - end - end -end - -print("Fat dwarves cured: "..num_fat) -print("Lag sources: "..num_lagging) diff --git a/scripts/fix/feeding-timers.lua b/scripts/fix/feeding-timers.lua deleted file mode 100644 index 84fab7086..000000000 --- a/scripts/fix/feeding-timers.lua +++ /dev/null @@ -1,43 +0,0 @@ --- feeding-timers.lua --- original author: tejón --- rewritten by expwnent --- see repeat.lua for how to run this every so often automatically ---[[=begin - -fix/feeding-timers -================== -Reset the GiveWater and GiveFood timers of all units as appropriate. - -=end]] -local args = {...} -if args[1] ~= nil then - print("fix/feeding-timers usage") - print(" fix/feeding-timers") - print(" reset the feeding timers of all units as appropriate") - print(" fix/feeding-timers help") - print(" print this help message") - print(" repeat -time [n] [years/months/ticks/days/etc] -command fix/feeding-timers") - print(" run this script every n time units") - print(" repeat -cancel fix/feeding-timers") - print(" stop automatically running this script") - return -end - -local fixcount = 0 -for _,unit in ipairs(df.global.world.units.all) do - if dfhack.units.isCitizen(unit) and not (unit.flags1.dead) then - for _,v in pairs(unit.status.misc_traits) do - local didfix = 0 - if v.id == 0 then -- I think this should have additional conditions... - v.value = 0 -- GiveWater cooldown set to zero - didfix = 1 - end - if v.id == 1 then -- I think this should have additional conditions... - v.value = 0 -- GiveFood cooldown set to zero - didfix = 1 - end - fixcount = fixcount + didfix - end - end -end -print("Fixed feeding timers for " .. fixcount .. " citizens.") diff --git a/scripts/fix/item-occupancy.lua b/scripts/fix/item-occupancy.lua deleted file mode 100644 index bcf1b8903..000000000 --- a/scripts/fix/item-occupancy.lua +++ /dev/null @@ -1,131 +0,0 @@ --- Verify item occupancy and block item list integrity. - ---[[=begin - -fix/item-occupancy -================== -Diagnoses and fixes issues with nonexistant 'items occupying site', usually -caused by `autodump` bugs or other hacking mishaps. Checks that: - -#. Item has ``flags.on_ground`` <=> it is in the correct block item list -#. A tile has items in block item list <=> it has ``occupancy.item`` -#. The block item lists are sorted - -=end]] -local utils = require 'utils' - -function check_block_items(fix) - local cnt = 0 - local icnt = 0 - local found = {} - local found_somewhere = {} - - local should_fix = false - local can_fix = true - - for _,block in ipairs(df.global.world.map.map_blocks) do - local itable = {} - local bx,by,bz = pos2xyz(block.map_pos) - - -- Scan the block item vector - local last_id = nil - local resort = false - - for _,id in ipairs(block.items) do - local item = df.item.find(id) - local ix,iy,iz = pos2xyz(item.pos) - local dx,dy,dz = ix-bx,iy-by,iz-bz - - -- Check sorted order - if last_id and last_id >= id then - print(bx,by,bz,last_id,id,'block items not sorted') - resort = true - else - last_id = id - end - - -- Check valid coordinates and flags - if not item.flags.on_ground then - print(bx,by,bz,id,dx,dy,'in block & not on ground') - elseif dx < 0 or dx >= 16 or dy < 0 or dy >= 16 or dz ~= 0 then - found_somewhere[id] = true - print(bx,by,bz,id,dx,dy,dz,'invalid pos') - can_fix = false - else - found[id] = true - itable[dx + dy*16] = true; - - -- Check missing occupancy - if not block.occupancy[dx][dy].item then - print(bx,by,bz,dx,dy,'item & not occupied') - if fix then - block.occupancy[dx][dy].item = true - else - should_fix = true - end - end - end - end - - -- Sort the vector if needed - if resort then - if fix then - utils.sort_vector(block.items) - else - should_fix = true - end - end - - icnt = icnt + #block.items - - -- Scan occupancy for spurious marks - for x=0,15 do - local ocx = block.occupancy[x] - for y=0,15 do - if ocx[y].item and not itable[x + y*16] then - print(bx,by,bz,x,y,'occupied & no item') - if fix then - ocx[y].item = false - else - should_fix = true - end - end - end - end - - cnt = cnt + 256 - end - - -- Check if any items are missing from blocks - for _,item in ipairs(df.global.world.items.all) do - if item.flags.on_ground and not found[item.id] then - can_fix = false - if not found_somewhere[item.id] then - print(id,item.pos.x,item.pos.y,item.pos.z,'on ground & not in block') - end - end - end - - -- Report - print(cnt.." tiles and "..icnt.." items checked.") - - if should_fix and can_fix then - print("Use 'fix/item-occupancy --fix' to fix the listed problems.") - elseif should_fix then - print("The problems are too severe to be fixed by this script.") - end -end - -local opt = ... -local fix = false - -if opt then - if opt == '--fix' then - fix = true - else - qerror('Invalid option: '..opt) - end -end - -print("Checking item occupancy - this will take a few seconds.") -check_block_items(fix) diff --git a/scripts/fix/loyaltycascade.rb b/scripts/fix/loyaltycascade.rb deleted file mode 100644 index 561e8b9e4..000000000 --- a/scripts/fix/loyaltycascade.rb +++ /dev/null @@ -1,70 +0,0 @@ -# Cancels a 'loyalty cascade' when citizens are killed -=begin - -fix/loyaltycascade -================== -Aborts loyalty cascades by fixing units whose own civ is the enemy. - -=end -def fixunit(unit) - return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id - links = unit.hist_figure_tg.entity_links - fixed = false - - # check if the unit is a civ renegade - if i1 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and - l.entity_id == df.ui.civ_id - } and i2 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and - l.entity_id == df.ui.civ_id - } - fixed = true - i1, i2 = i2, i1 if i1 > i2 - links.delete_at i2 - links.delete_at i1 - links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100) - df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again" - end - - # check if the unit is a group renegade - if i1 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and - l.entity_id == df.ui.group_id - } and i2 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and - l.entity_id == df.ui.group_id - } - fixed = true - i1, i2 = i2, i1 if i1 > i2 - links.delete_at i2 - links.delete_at i1 - links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100) - df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again" - end - - # fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed) - if fixed and unit.enemy.enemy_status_slot != -1 - i = unit.enemy.enemy_status_slot - unit.enemy.enemy_status_slot = -1 - cache = df.world.enemy_status_cache - cache.slot_used[i] = false - cache.rel_map[i].map! { -1 } - cache.rel_map.each { |a| a[i] = -1 } - cache.next_slot = i if cache.next_slot > i - end - - # return true if we actually fixed the unit - fixed -end - -count = 0 -df.unit_citizens.each { |u| - count += 1 if fixunit(u) -} - -if count > 0 - puts "loyalty cascade fixed (#{count} dwarves)" -else - puts "no loyalty cascade found" -end diff --git a/scripts/fix/merchants.lua b/scripts/fix/merchants.lua deleted file mode 100644 index ae3ef4233..000000000 --- a/scripts/fix/merchants.lua +++ /dev/null @@ -1,104 +0,0 @@ --- Allow humans to make trade agreements ---[[=begin - -fix/merchants -============= -Adds the Guild Representative position to all Human civilizations, -allowing them to make trade agreements. This was the default behaviour in -``0.28.181.40d`` and earlier. - -=end]] - - -function add_guild_rep(ent) - -- there was no guild rep - create it - local pos = df.entity_position:new() - ent.positions.own:insert('#', pos) - - pos.code = "GUILD_REPRESENTATIVE" - pos.id = ent.positions.next_position_id + 1 - ent.positions.next_position_id = ent.positions.next_position_id + 1 - - pos.flags.DO_NOT_CULL = true - pos.flags.MENIAL_WORK_EXEMPTION = true - pos.flags.SLEEP_PRETENSION = true - pos.flags.PUNISHMENT_EXEMPTION = true - pos.flags.ACCOUNT_EXEMPT = true - pos.flags.DUTY_BOUND = true - pos.flags.COLOR = true - pos.flags.HAS_RESPONSIBILITIES = true - pos.flags.IS_DIPLOMAT = true - pos.flags.IS_LEADER = true - -- not sure what these flags do, but the game sets them for a valid guild rep - pos.flags.unk_12 = true - pos.flags.unk_1a = true - pos.flags.unk_1b = true - pos.name[0] = "Guild Representative" - pos.name[1] = "Guild Representatives" - pos.precedence = 40 - pos.color[0] = 7 - pos.color[1] = 0 - pos.color[2] = 1 - - return pos -end - -local checked = 0 -local fixed = 0 - -for _,ent in pairs(df.global.world.entities.all) do - if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.MERCHANT_NOBILITY then - checked = checked + 1 - - update = true - -- see if we need to add a new position or modify an existing one - local found_position - for _,pos in pairs(ent.positions.own) do - if pos.responsibilities.TRADE and pos.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS then - -- a guild rep exists with the proper responsibilities - skip to the end - update = false - found_position=pos - break - end - -- Guild Representative position already exists, but has the wrong options - modify it instead of creating a new one - if pos.code == "GUILD_REPRESENTATIVE" then - found_position=pos - break - end - end - if update then - -- either there's no guild rep, or there is one and it's got the wrong responsibilities - if not found_position then - found_position = add_guild_rep(ent) - end - -- assign responsibilities - found_position.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS = true - found_position.responsibilities.TRADE=true - end - - -- make sure the guild rep position, whether we created it or not, is set up for proper assignment - local assign = true - for _,p in pairs(ent.positions.assignments) do - if p.position_id == found_position.id then -- it is - nothing more to do here - assign = false - break - end - end - if assign then - -- it isn't - set it up - local asn = df.entity_position_assignment:new() - ent.positions.assignments:insert('#', asn) - - asn.id = ent.positions.next_assignment_id - ent.positions.next_assignment_id = asn.id + 1 - asn.position_id = found_position.id - asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags - asn.flags[0] = true -- and set the first one - end - if update or assign then - fixed = fixed + 1 - end - end -end - -print("Added merchant nobility for "..fixed.." of "..checked.." civilizations.") diff --git a/scripts/fix/population-cap.lua b/scripts/fix/population-cap.lua deleted file mode 100644 index 1261fb504..000000000 --- a/scripts/fix/population-cap.lua +++ /dev/null @@ -1,47 +0,0 @@ --- Tells mountainhomes your pop. to avoid overshoot - ---[[=begin - -fix/population-cap -================== -Run this after every migrant wave to ensure your population cap is not exceeded. - -The reason for population cap problems is that the population value it -is compared against comes from the last dwarven caravan that successfully -left for mountainhomes. This script instantly updates it. -Note that a migration wave can still overshoot the limit by 1-2 dwarves because -of the last migrant bringing his family. Likewise, king arrival ignores cap. - -=end]] -local args = {...} - -local ui = df.global.ui -local ui_stats = ui.tasks -local civ = df.historical_entity.find(ui.civ_id) - -if not civ then - qerror('No active fortress.') -end - -local civ_stats = civ.activity_stats - -if not civ_stats then - if args[1] ~= 'force' then - qerror('No caravan report object; use "fix/population-cap force" to create one') - end - print('Creating an empty statistics structure...') - civ.activity_stats = { - new = true, - created_weapons = { resize = #ui_stats.created_weapons }, - discovered_creature_foods = { resize = #ui_stats.discovered_creature_foods }, - discovered_creatures = { resize = #ui_stats.discovered_creatures }, - discovered_plant_foods = { resize = #ui_stats.discovered_plant_foods }, - discovered_plants = { resize = #ui_stats.discovered_plants }, - } - civ_stats = civ.activity_stats -end - --- Use max to keep at least some of the original caravan communication idea -civ_stats.population = math.max(civ_stats.population, ui_stats.population) - -print('Home civ notified about current population.') diff --git a/scripts/fix/stable-temp.lua b/scripts/fix/stable-temp.lua deleted file mode 100644 index 7238d88d9..000000000 --- a/scripts/fix/stable-temp.lua +++ /dev/null @@ -1,71 +0,0 @@ --- Reset item temperature to the value of their tile. ---[[=begin - -fix/stable-temp -=============== -Instantly sets the temperature of all free-lying items to be in equilibrium with -the environment, which stops temperature updates until something changes. -To maintain this efficient state, use `tweak fast-heat `. - -=end]] -local args = {...} - -local apply = (args[1] == 'apply') - -local count = 0 -local types = {} - -local function update_temp(item,btemp) - if item.temperature.whole ~= btemp then - count = count + 1 - local tid = item:getType() - types[tid] = (types[tid] or 0) + 1 - end - - if apply then - item.temperature.whole = btemp - item.temperature.fraction = 0 - - if item.contaminants then - for _,c in ipairs(item.contaminants) do - c.temperature.whole = btemp - c.temperature.fraction = 0 - end - end - end - - for _,sub in ipairs(dfhack.items.getContainedItems(item)) do - update_temp(sub,btemp) - end - - if apply then - item:checkTemperatureDamage() - end -end - -local last_frame = df.global.world.frame_counter-1 - -for _,item in ipairs(df.global.world.items.all) do - if item.flags.on_ground and df.item_actual:is_instance(item) and - item.temp_updated_frame == last_frame then - local pos = item.pos - local block = dfhack.maps.getTileBlock(pos) - if block then - update_temp(item, block.temperature_1[pos.x%16][pos.y%16]) - end - end -end - -if apply then - print('Items updated: '..count) -else - print("Use 'fix/stable-temp apply' to force-change temperature.") - print('Items not in equilibrium: '..count) -end - -local tlist = {} -for k,_ in pairs(types) do tlist[#tlist+1] = k end -table.sort(tlist, function(a,b) return types[a] > types[b] end) -for _,k in ipairs(tlist) do - print(' '..df.item_type[k]..':', types[k]) -end diff --git a/scripts/fix/stuckdoors.rb b/scripts/fix/stuckdoors.rb deleted file mode 100644 index f99d0f263..000000000 --- a/scripts/fix/stuckdoors.rb +++ /dev/null @@ -1,32 +0,0 @@ -# fix doors that are frozen in 'open' state - -# this may happen after people mess with the game by (incorrectly) teleporting units or items -# a door may stick open if the map occupancy flags are wrong -=begin - -fix/stuckdoors -============== -Fix doors that are stuck open due to incorrect map occupancy flags, eg due to -incorrect use of `teleport`. - -=end -count = 0 -df.world.buildings.all.each { |bld| - # for all doors - next if bld._rtti_classname != :building_doorst - # check if it is open - next if bld.close_timer == 0 - # check if occupancy is set - occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) - if (occ.unit or occ.unit_grounded) and not - # check if an unit is present - df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z } - count += 1 - occ.unit = occ.unit_grounded = false - end - if occ.item and not df.world.items.all.find { |i| i.pos.x == bld.x1 and i.pos.y == bld.y1 and i.pos.z == bld.z } - count += 1 - occ.item = false - end -} -puts "unstuck #{count} doors" diff --git a/scripts/fixnaked.lua b/scripts/fixnaked.lua deleted file mode 100644 index 39cfd2b77..000000000 --- a/scripts/fixnaked.lua +++ /dev/null @@ -1,50 +0,0 @@ ---removes unhappy thoughts due to lack of clothing ---[[=begin - -fixnaked -======== -Removes all unhappy thoughts due to lack of clothing. - -=end]] - -function fixnaked() -local total_fixed = 0 -local total_removed = 0 - -for fnUnitCount,fnUnit in ipairs(df.global.world.units.all) do - if fnUnit.race == df.global.ui.race_id then - local listEvents = fnUnit.status.recent_events - --for lkey,lvalue in pairs(listEvents) do - -- print(df.unit_thought_type[lvalue.type],lvalue.type,lvalue.age,lvalue.subtype,lvalue.severity) - --end - - local found = 1 - local fixed = 0 - while found == 1 do - local events = fnUnit.status.recent_events - found = 0 - for k,v in pairs(events) do - if v.type == df.unit_thought_type.Uncovered - or v.type == df.unit_thought_type.NoShirt - or v.type == df.unit_thought_type.NoShoes - or v.type == df.unit_thought_type.NoCloak - or v.type == df.unit_thought_type.OldClothing - or v.type == df.unit_thought_type.TatteredClothing - or v.type == df.unit_thought_type.RottedClothing then - events:erase(k) - found = 1 - total_removed = total_removed + 1 - fixed = 1 - break - end - end - end - if fixed == 1 then - total_fixed = total_fixed + 1 - print(total_fixed, total_removed, dfhack.TranslateName(dfhack.units.getVisibleName(fnUnit))) - end - end -end -print("Total Fixed: "..total_fixed) -end -fixnaked() diff --git a/scripts/forum-dwarves.lua b/scripts/forum-dwarves.lua deleted file mode 100644 index e6b98395a..000000000 --- a/scripts/forum-dwarves.lua +++ /dev/null @@ -1,133 +0,0 @@ --- Save a copy of a text screen for the DF forums --- original author: Caldfir; edited by expwnent, Mchl ---[[=begin - -forum-dwarves -============= -Saves a copy of a text screen, formatted in bbcode for posting to the Bay12 Forums. -Use ``forum-dwarves help`` for more information. - -=end]] - -local args = {...} - -if args[1] == 'help' then - print([[ -description: - This script will attempt to read the current df-screen, and if it is a - text-viewscreen (such as the dwarf 'thoughts' screen or an item - 'description') then append a marked-up version of this text to the - target file. Previous entries in the file are not overwritten, so you - may use the 'forumdwarves' command multiple times to create a single - document containing the text from multiple screens (eg: text screens - from several dwarves, or text screens from multiple artifacts/items, - or some combination). -known screens: - The screens which have been tested and known to function properly with - this script are: - 1: dwarf/unit 'thoughts' screen - 2: item/art 'description' screen - 3: individual 'historical item/figure' screens - There may be other screens to which the script applies. It should be - safe to attempt running the script with any screen active, with an - error message to inform you when the selected screen is not appropriate - for this script. -target file: - The target file's name is 'forumdwarves.txt'. A remider to this effect - will be displayed if the script is successful. -character encoding: - The text will likely be using system-default encoding, and as such - will likely NOT display special characters (eg:È,ı,Ã) correctly. To - fix this, you need to modify the character set that you are reading - the document with. 'Notepad++' is a freely available program which - can do this using the following steps: - 1: open the document in Notepad++ - 2: in the menu-bar, select - Encoding->Character Sets->Western European->OEM-US - 3: copy the text normally to wherever you want to use it -]]) - return -end -local utils = require 'utils' -local gui = require 'gui' -local dialog = require 'gui.dialogs' -local colors_css = { - [0] = 'black', - [1] = 'navy', - [2] = 'green', - [3] = 'teal', - [4] = 'maroon', - [5] = 'purple', - [6] = 'olive', - [7] = 'silver', - [8] = 'gray', - [9] = 'blue', - [10] = 'lime', - [11] = 'cyan', - [12] = 'red', - [13] = 'magenta', - [14] = 'yellow', - [15] = 'white' -} - -local scrn = dfhack.gui.getCurViewscreen() -local flerb = dfhack.gui.getFocusString(scrn) - -local function format_for_forum(strin) - local strout = strin - - local newline_idx = string.find(strout, '[P]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3) - newline_idx = string.find(strout, '[P]', 1, true) - end - - newline_idx = string.find(strout, '[B]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3) - newline_idx = string.find(strout, '[B]', 1, true) - end - - newline_idx = string.find(strout, '[R]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3) - newline_idx = string.find(strout, '[R]', 1, true) - end - - local color_idx = string.find(strout, '[C:', 1, true) - while color_idx ~= nil do - local colormatch = (string.byte(strout, color_idx+3)-48)+((string.byte(strout, color_idx+7)-48)*8) - strout = string.sub(strout,1, color_idx-1)..'[/color][color='..colors_css[colormatch]..']'..string.sub(strout,color_idx+9) - color_idx = string.find(strout, '[C:', 1, true) - end - - return strout -end - -if flerb == 'textviewer' then - print(scrn) - printall(scrn) - local lines = scrn.src_text - local line = "" - - if lines ~= nil then - local log = io.open('forumdwarves.txt', 'a') - log:write("[color=silver]") - log:write(scrn.title) - for n,x in ipairs(lines) do - print(x) - printall(x) - print(x.value) - printall(x.value) - if (x ~= nil) and (x.value ~= nil) then - log:write(format_for_forum(x.value), ' ') - --log:write(x[0],'\n') - end - end - log:write("[/color]\n") - log:close() - end - print 'data prepared for forum in \"forumdwarves.txt\"' -else - print 'this is not a textview screen' -end diff --git a/scripts/full-heal.lua b/scripts/full-heal.lua deleted file mode 100644 index f9388058c..000000000 --- a/scripts/full-heal.lua +++ /dev/null @@ -1,153 +0,0 @@ --- Attempts to fully heal the selected unit ---author Kurik Amudnil, Urist DaVinci ---edited by expwnent - ---[[=begin - -full-heal -========= -Attempts to fully heal the selected unit. ``full-heal -r`` attempts to resurrect the unit. - -=end]] - -local utils=require('utils') - -validArgs = validArgs or utils.invert({ - 'r', - 'help', - 'unit', - 'keep_corpse' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print('full-heal: heal a unit completely from anything, optionally including death.') - print(' full-heal -unit [unitId]') - print(' heal the unit with the given id') - print(' full-heal -r -unit [unitId]') - print(' heal the unit with the given id and bring them back from death if they are dead') - print(' full-heal -r -keep_corpse -unit [unitId]') - print(' heal the unit with the given id and bring them back from death if they are dead, without removing their corpse') - print(' full-heal') - print(' heal the currently selected unit') - print(' full-heal -r') - print(' heal the currently selected unit and bring them back from death if they are dead') - print(' full-heal -help') - print(' print this help message') - return -end - -if(args.unit) then - unit = df.unit.find(args.unit) -else - unit = dfhack.gui.getSelectedUnit() -end - -if not unit then - qerror('Error: please select a unit or pass its id as an argument.') -end - -if unit then - if args.r then - if unit.flags1.dead then - --print("Resurrecting...") - unit.flags2.slaughter = false - unit.flags3.scuttle = false - end - unit.flags1.dead = false - unit.flags2.killed = false - unit.flags3.ghostly = false - if not args.keep_corpse then - for _,corpse in ipairs(df.global.world.items.other.CORPSE) do - if corpse.unit_id==unit.id then - corpse.flags.garbage_collect=true - corpse.flags.forbid=true - corpse.flags.hidden=true - end - end - end - --unit.unk_100 = 3 - end - - --print("Erasing wounds...") - while #unit.body.wounds > 0 do - unit.body.wounds:erase(#unit.body.wounds-1) - end - unit.body.wound_next_id=1 - - --print("Refilling blood...") - unit.body.blood_count=unit.body.blood_max - - --print("Resetting grasp/stand status...") - unit.status2.limbs_stand_count=unit.status2.limbs_stand_max - unit.status2.limbs_grasp_count=unit.status2.limbs_grasp_max - - --print("Resetting status flags...") - unit.flags2.has_breaks=false - unit.flags2.gutted=false - unit.flags2.circulatory_spray=false - unit.flags2.vision_good=true - unit.flags2.vision_damaged=false - unit.flags2.vision_missing=false - unit.flags2.breathing_good=true - unit.flags2.breathing_problem=false - - unit.flags2.calculated_nerves=false - unit.flags2.calculated_bodyparts=false - unit.flags2.calculated_insulation=false - unit.flags3.compute_health=true - - --print("Resetting counters...") - unit.counters.winded=0 - unit.counters.stunned=0 - unit.counters.unconscious=0 - unit.counters.webbed=0 - unit.counters.pain=0 - unit.counters.nausea=0 - unit.counters.dizziness=0 - - unit.counters2.paralysis=0 - unit.counters2.fever=0 - unit.counters2.exhaustion=0 - unit.counters2.hunger_timer=0 - unit.counters2.thirst_timer=0 - unit.counters2.sleepiness_timer=0 - unit.counters2.vomit_timeout=0 - - --print("Resetting body part status...") - local v=unit.body.components - for i=0,#v.nonsolid_remaining - 1,1 do - v.nonsolid_remaining[i] = 100 -- percent remaining of fluid layers (Urist Da Vinci) - end - - v=unit.body.components - for i=0,#v.layer_wound_area - 1,1 do - v.layer_status[i].whole = 0 -- severed, leaking layers (Urist Da Vinci) - v.layer_wound_area[i] = 0 -- wound contact areas (Urist Da Vinci) - v.layer_cut_fraction[i] = 0 -- 100*surface percentage of cuts/fractures on the body part layer (Urist Da Vinci) - v.layer_dent_fraction[i] = 0 -- 100*surface percentage of dents on the body part layer (Urist Da Vinci) - v.layer_effect_fraction[i] = 0 -- 100*surface percentage of "effects" on the body part layer (Urist Da Vinci) - end - - v=unit.body.components.body_part_status - for i=0,#v-1,1 do - v[i].on_fire = false - v[i].missing = false - v[i].organ_loss = false - v[i].organ_damage = false - v[i].muscle_loss = false - v[i].muscle_damage = false - v[i].bone_loss = false - v[i].bone_damage = false - v[i].skin_damage = false - v[i].motor_nerve_severed = false - v[i].sensory_nerve_severed = false - end - - if unit.job.current_job and unit.job.current_job.job_type == df.job_type.Rest then - --print("Wake from rest -> clean self...") - unit.job.current_job = df.job_type.CleanSelf - end -end - diff --git a/scripts/gaydar.lua b/scripts/gaydar.lua deleted file mode 100644 index 66aec2c12..000000000 --- a/scripts/gaydar.lua +++ /dev/null @@ -1,208 +0,0 @@ --- Shows the sexual orientation of units ---[[=begin - -gaydar -====== -Shows the sexual orientation of units, useful for social engineering or checking -the viability of livestock breeding programs. Use ``gaydar -help`` for information -on available filters for orientation, citizenship, species, etc. - -=end]] -local utils = require('utils') - -validArgs = utils.invert({ - 'all', - 'citizens', - 'named', - 'notStraight', - 'gayOnly', - 'biOnly', - 'straightOnly', - 'asexualOnly', - 'help' -}) - - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print( -[[gaydar.lua -arguments: - -help - print this help message -unit filters: - -all - shows orientation of every creature - -citizens - shows only orientation of citizens in fort mode - -named - shows orientation of all named units on map -orientation filters: - -notStraight - shows only creatures who are not strictly straight - -gayOnly - shows only creatures who are strictly gay - -biOnly - shows only creatures who can get into romances with - both sexes - -straightOnly - shows only creatures who are strictly straight. - -asexualOnly - shows only creatures who are strictly asexual. - - No argument will show the orientation of the unit - under the cursor. -]]) - return -end - -function dfprint(s) - print(dfhack.df2console(s)) -end - -function getSexString(sex) - local sexStr - if sex==0 then - sexStr=string.char(12) - elseif sex==1 then - sexStr=string.char(11) - else - return "" - end - return string.char(40)..sexStr..string.char(41) -end - -local function determineorientation(unit) - if unit.sex~=-1 and unit.status.current_soul then - local return_string='' - local orientation=unit.status.current_soul.orientation_flags - if orientation.indeterminate then - return ' indeterminate (probably adventurer)' - end - local male_interested,asexual=false,true - if orientation.romance_male then - return_string=return_string..' likes males' - male_interested=true - asexual=false - elseif orientation.marry_male then - return_string=return_string..' will marry males' - male_interested=true - asexual=false - end - if orientation.romance_female then - if male_interested then - return_string=return_string..' and likes females' - else - return_string=return_string..' likes females' - end - asexual=false - elseif orientation.marry_female then - if male_interested then - return_string=return_string..' and will marry females' - else - return_string=return_string..' will marry females' - end - asexual=false - end - if asexual then - return_string=' is asexual' - end - return return_string - else - return " is not biologically capable of sex" - end -end - -local function nameOrSpeciesAndNumber(unit) - if unit.name.has_name then - return dfhack.TranslateName(dfhack.units.getVisibleName(unit))..' '..getSexString(unit.sex),true - else - return 'Unit #'..unit.id..' ('..df.creature_raw.find(unit.race).caste[unit.caste].caste_name[0]..' '..getSexString(unit.sex)..')',false - end -end - -local orientations={} - -if args.citizens then - for k,v in ipairs(df.global.world.units.active) do - if dfhack.units.isCitizen(v) then - table.insert(orientations,nameOrSpeciesAndNumber(v) .. determineorientation(v)) - end - end -elseif args.all then - for k,v in ipairs(df.global.world.units.active) do - table.insert(orientations,nameOrSpeciesAndNumber(v)..determineorientation(v)) - end -elseif args.named then - for k,v in ipairs(df.global.world.units.active) do - local name,ok=nameOrSpeciesAndNumber(v) - if ok then - table.insert(orientations,name..determineorientation(v)) - end - end -else - local unit=dfhack.gui.getSelectedUnit(true) - local name,ok=nameOrSpeciesAndNumber(unit) - dfprint(name..determineorientation(unit)) - return -end - -function isNotStraight(v) - if v:find(string.char(12)) and v:find(' female') then return true end - if v:find(string.char(11)) and v:find(' male') then return true end - if v:find('asexual') then return true end - if v:find('indeterminate') then return true end - return false -end - -function isGay(v) - if v:find('asexual') then return false end - if v:find(string.char(12)) and not v:find(' male') then return true end - if v:find(string.char(11)) and not v:find(' female') then return true end - return false -end - -function isAsexual(v) - if v:find('asexual') or v:find('indeterminate') then return true else return false end -end - -function isBi(v) - if v:find(' female') and v:find(' male') then return true else return false end -end - -if args.notStraight then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isNotStraight(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.gayOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isGay(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.asexualOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isAsexual(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.straightOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if not isNotStraight(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.biOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isBi(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -else - for k,v in ipairs(orientations) do - dfprint(v) - end -end diff --git a/scripts/growcrops.rb b/scripts/growcrops.rb deleted file mode 100644 index c3c143254..000000000 --- a/scripts/growcrops.rb +++ /dev/null @@ -1,64 +0,0 @@ -# Instantly grow crops in farm plots -=begin - -growcrops -========= -Instantly grow seeds inside farming plots. - -With no argument, this command list the various seed types currently in -use in your farming plots. With a seed type, the script will grow 100 of -these seeds, ready to be harvested. Set the number with a 2nd argument. - -For example, to grow 40 plump helmet spawn:: - - growcrops plump 40 - -=end - -material = $script_args[0] -count_max = $script_args[1].to_i -count_max = 100 if count_max == 0 - -# cache information from the raws -@raws_plant_name ||= {} -@raws_plant_growdur ||= {} -if @raws_plant_name.empty? - df.world.raws.plants.all.each_with_index { |p, idx| - @raws_plant_name[idx] = p.id - @raws_plant_growdur[idx] = p.growdur - } -end - -inventory = Hash.new(0) -df.world.items.other[:SEEDS].each { |seed| - next if not seed.flags.in_building - next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } - next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] - inventory[seed.mat_index] += 1 -} - -if !material or material == 'help' or material == 'list' - # show a list of available crop types - inventory.sort_by { |mat, c| c }.each { |mat, c| - name = df.world.raws.plants.all[mat].id - puts " #{name} #{c}" - } - -else - - mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] }) - unless wantmat = @raws_plant_name.index(mat) - raise "invalid plant material #{material}" - end - - count = 0 - df.world.items.other[:SEEDS].each { |seed| - next if seed.mat_index != wantmat - next if not seed.flags.in_building - next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } - next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] - seed.grow_counter = @raws_plant_growdur[seed.mat_index] - count += 1 - } - puts "Grown #{count} #{mat}" -end diff --git a/scripts/gui/about.txt b/scripts/gui/about.txt deleted file mode 100644 index 1c83e50b2..000000000 --- a/scripts/gui/about.txt +++ /dev/null @@ -1,6 +0,0 @@ -``gui/*`` scripts implement dialogs in the main game window. - -In order to avoid user confusion, as a matter of policy all these tools -display the word "DFHack" on the screen somewhere while active. -When that is not appropriate because they merely add keybinding hints to -existing DF screens, they deliberately use red instead of green for the key. diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua deleted file mode 100644 index 67a0f4bb3..000000000 --- a/scripts/gui/advfort.lua +++ /dev/null @@ -1,1894 +0,0 @@ --- allows to do jobs in adv. mode. - ---[[=begin - -gui/advfort -=========== -This script allows to perform jobs in adventure mode. For more complete help -press :kbd:`?` while script is running. It's most comfortable to use this as a -keybinding. (e.g. ``keybinding set Ctrl-T gui/advfort``). Possible arguments: - -:-a, --nodfassign: uses different method to assign items. -:-i, --inventory: checks inventory for possible items to use in the job. -:-c, --cheat: relaxes item requirements for buildings (e.g. walls from bones). Implies -a -:-u, --unsafe: ignores dangerous conditions. -:-s, --safe: only allow building and etc. only if in site -:-q, --quick: quick item select mode -:job: selects that job (e.g. Dig or FellTree) - -An example of player digging in adventure mode: - -.. image:: /docs/images/advfort.png - -**WARNING:** changes only persist in non procedural sites, namely: player forts, caves, and camps. - -=end]] - ---[==[ - version: 0.05 - changelog: - *0.05 - - fixed some reactions not showing. Now there is a '[fallback]' choice to choose from other way of getting reactions. - - fixed brewing accepting too many items instead of barrel - - fixed tallow making to accept fat - - display filters - *0.044 - - added output to clear_jobs of number of cleared jobs - - another failed attempt at gather plants fix - - added track stop configuration window - *0.043 - - fixed track carving: up/down was reversed and removed (temp) requirements because they were not working correctly - - added checks for unsafe conditions (currently quite stupid). Should save few adventurers that are trying to work in dangerous conditions (e.g. fishing) - - unsafe checks disabled by "-u" ir "--unsafe" - *0.042 - - fixed (probably for sure now) the crash bug. - - added --clear_jobs debug option. Will delete ALL JOBS! - *0.041 - - fixed cooking allowing already cooked meals - *0.04 - - add (-q)uick mode. Autoselects materials. - - fixed few(?) crash bugs - - fixed job errors not being shown in df - *0.031 - - make forbiding optional (-s)afe mode - *0.03 - - forbid doing anything in non-sites unless you are (-c)heating - - a bit more documentation and tidying - - add a deadlock fix - *0.021 - - advfort_items now autofills items - - tried out few things to fix gather plants - *0.02 - - fixed axles not being able to be placed in other direction (thanks SyrusLD) - - added lever linking - - restructured advfort_items, don't forget to update that too! - - Added clutter view if shop is cluttered. - *0.013 - - fixed siege weapons and traps (somewhat). Now you can load them with new menu :) - *0.012 - - fix for some jobs not finding correct building. - *0.011 - - fixed crash with building jobs (other jobs might have been crashing too!) - - fixed bug with building asking twice to input items - *0.01 - - instant job startation - - item selection screen (!) - - BUG:custom jobs need stuff on ground to work - *0.003 - - fixed farms (i think...) - - added faster time pasing (yay for random deaths from local wildlife) - - still hasn't fixed gather plants. but you can visit local market, buy a few local fruits/vegetables eat them and use seeds - - other stuff - *0.002 - - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) - - gather plants still not working... Other jobs seem to work. - - added new-and-improved waiting. Interestingly it could be improved to be interuptable. - todo list: - - document everything! Maybe somebody would understand what is happening then and help me :< - - when building trap add to known traps (or known adventurers?) so that it does not trigger on adventurer - bugs list: - - items blocking construction stuck the game - - burning charcoal crashed game - - gem thingies probably broken - - custom reactions semibroken - - gathering plants still broken - ---]==] - ---keybinding, change to your hearts content. Only the key part. -keybinds={ -nextJob={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, -prevJob={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, -continue={key="A_WAIT",desc="Continue job if available"}, -down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, -down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, -up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, -up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, -use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, -workshop={key="CHANGETAB",desc="Show building menu"}, -quick={key="CUSTOM_Q",desc="Toggle quick item select"}, -} --- building filters -build_filter={ - forbid_all=false, --this forbits all except the "allow" - allow={"MetalSmithsForge"}, --ignored if forbit_all=false - forbid={} --ignored if forbit_all==true -} -build_filter.HUMANish={ - forbid_all=true, - allow={"Masons"}, - forbid={} -} - ---economic stone fix: just disable all of them ---[[ FIXME: maybe let player select which to disable?]] -for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end - -local gui = require 'gui' -local wid=require 'gui.widgets' -local dialog=require 'gui.dialogs' -local buildings=require 'dfhack.buildings' -local bdialog=require 'gui.buildings' -local workshopJobs=require 'dfhack.workshops' -local utils=require 'utils' -local gscript=require 'gui.script' - -local tile_attrs = df.tiletype.attrs - -settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false,gui_item_select=true,only_in_sites=false} - -function hasValue(tbl,val) - for k,v in pairs(tbl) do - if v==val then - return true - end - end - return false -end -function reverseRaceLookup(id) - return df.global.world.raws.creatures.all[id].creature_id -end -function deon_filter(name,type_id,subtype_id,custom_id, parent) - --print(name) - local adv=df.global.world.units.active[0] - local race_filter=build_filter[reverseRaceLookup(adv.race)] - if race_filter then - if race_filter.forbid_all then - return hasValue(race_filter.allow,name) - else - return not hasValue(race_filter.forbid,name) - end - else - if build_filter.forbid_all then - return hasValue(build_filter.allow,name) - else - return not hasValue(build_filter.forbid,name) - end - end -end -local mode_name -for k,v in ipairs({...}) do --setting parsing - if v=="-c" or v=="--cheat" then - settings.build_by_items=true - settings.df_assign=false - elseif v=="-q" or v=="--quick" then - settings.quick=true - elseif v=="-u" or v=="--unsafe" then --ignore pain and etc - settings.unsafe=true - elseif v=="-s" or v=="--safe" then - settings.safe=true - elseif v=="-i" or v=="--inventory" then - settings.check_inv=true - settings.df_assign=false - elseif v=="-a" or v=="--nodfassign" then - settings.df_assign=false - elseif v=="-h" or v=="--help" then - settings.help=true - elseif v=="--clear_jobs" then - settings.clear_jobs=true - else - mode_name=v - end -end - -mode=mode or 0 -last_building=last_building or {} - -function Disclaimer(tlb) - local dsc={"The Gathering Against ",{text="Goblin ",pen=dfhack.pen.parse{fg=COLOR_GREEN,bg=0}}, "Oppresion ", - "(TGAGO) is not responsible for all ",NEWLINE,"the damage that this tool can (and will) cause to you and your loved worlds",NEWLINE,"and/or sanity.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} - if tlb then - for _,v in ipairs(dsc) do - table.insert(tlb,v) - end - end - return dsc -end -function showHelp() - local helptext={ - "This tool allow you to perform jobs as a dwarf would in dwarf mode. When ",NEWLINE, - "cursor is available you can press ",{key="SELECT", text="select",key_sep="()"}, - " to enqueue a job from",NEWLINE,"pointer location. If job is 'Build' and there is no planed construction",NEWLINE, - "at cursor this tool show possible building choices.",NEWLINE,NEWLINE,{text="Keybindings:",pen=dfhack.pen.parse{fg=COLOR_CYAN,bg=0}},NEWLINE - } - for k,v in pairs(keybinds) do - table.insert(helptext,{key=v.key,text=v.desc,key_sep=":"}) - table.insert(helptext,NEWLINE) - end - table.insert(helptext,{text="CAREFULL MOVE",pen=dfhack.pen.parse{fg=COLOR_LIGHTGREEN,bg=0}}) - table.insert(helptext,": use job in that direction") - table.insert(helptext,NEWLINE) - table.insert(helptext,NEWLINE) - Disclaimer(helptext) - dialog.showMessage("Help!?!",helptext) -end - -if settings.help then - showHelp() - return -end - ---[[ Util functions ]]-- -function advGlobalPos() - local map=df.global.world.map - local wd=df.global.world.world_data - local adv=df.global.world.units.active[0] - --wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y - --return wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y - --return wd.adv_region_x*16+wd.adv_emb_x+adv.pos.x/16,wd.adv_region_y*16+wd.adv_emb_y+adv.pos.y/16 - --print(map.region_x,map.region_y,adv.pos.x,adv.pos.y) - --print(map.region_x+adv.pos.x/48, map.region_y+adv.pos.y/48,wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y) - return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48) -end -function inSite() - - local tx,ty=advGlobalPos() - - for k,v in pairs(df.global.world.world_data.sites) do - local tp={v.pos.x,v.pos.y} - if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and - ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then - return v - end - end -end ---[[ low level job management ]]-- -function findAction(unit,ltype) - ltype=ltype or df.unit_action_type.None - for i,v in ipairs(unit.actions) do - if v.type==ltype then - return v - end - end -end -function add_action(unit,action_data) - local action=findAction(unit) --find empty action - if action then - action:assign(action_data) - action.id=unit.next_action_id - unit.next_action_id=unit.next_action_id+1 - else - local tbl=copyall(action_data) - tbl.new=true - tbl.id=unit.next_action_id - unit.actions:insert("#",tbl) - unit.next_action_id=unit.next_action_id+1 - end -end -function addJobAction(job,unit) --what about job2? - if job==nil then - error("invalid job") - end - if findAction(unit,df.unit_action_type.Job) or findAction(unit,df.unit_action_type.Job2) then - print("Already has job action") - return - end - local action=findAction(unit) - local pos=copyall(unit.pos) - --local pos=copyall(job.pos) - unit.path.dest:assign(pos) - --job - local data={type=df.unit_action_type.Job,data={job={x=pos.x,y=pos.y,z=pos.z,timer=10}}} - --job2: - --local data={type=df.unit_action_type.Job2,data={job2={timer=10}}} - add_action(unit,data) - --add_action(unit,{type=df.unit_action_type.Unsteady,data={unsteady={timer=5}}}) -end - -function make_native_job(args) - if args.job == nil then - local newJob=df.job:new() - newJob.id=df.global.job_next_id - df.global.job_next_id=df.global.job_next_id+1 - newJob.flags.special=true - newJob.job_type=args.job_type - newJob.completion_timer=-1 - - newJob.pos:assign(args.pos) - --newJob.pos:assign(args.unit.pos) - args.job=newJob - args.unlinked=true - end -end -function smart_job_delete( job ) - local gref_types=df.general_ref_type - --TODO: unmark items as in job - for i,v in ipairs(job.general_refs) do - if v:getType()==gref_types.BUILDING_HOLDER then - local b=v:getBuilding() - if b then - --remove from building - for i,v in ipairs(b.jobs) do - if v==job then - b.jobs:erase(i) - break - end - end - else - print("Warning: building holder ref was invalid while deleting job") - end - elseif v:getType()==gref_types.UNIT_WORKER then - local u=v:getUnit() - if u then - u.job.current_job =nil - else - print("Warning: unit worker ref was invalid while deleting job") - end - else - print("Warning: failed to remove link from job with type:",gref_types[v:getType()]) - end - end - --unlink job - local link=job.list_link - if link.prev then - link.prev.next=link.next - end - if link.next then - link.next.prev=link.prev - end - link:delete() - --finally delete the job - job:delete() -end ---TODO: this logic might be better with other --starting logic-- -if settings.clear_jobs then - print("Clearing job list!") - local counter=0 - local job_link=df.global.world.job_list.next - while job_link and job_link.item do - local job=job_link.item - job_link=job_link.next - smart_job_delete(job) - counter=counter+1 - end - print("Deleted: "..counter.." jobs") - return -end -function makeJob(args) - gscript.start(function () - make_native_job(args) - local failed - for k,v in ipairs(args.pre_actions or {}) do - local ok,msg=v(args) - if not ok then - failed=msg - break - end - end - if failed==nil then - AssignUnitToJob(args.job,args.unit,args.from_pos) - for k,v in ipairs(args.post_actions or {}) do - local ok,msg=v(args) - if not ok then - failed=msg - break - end - end - if failed then - UnassignJob(args.job,args.unit) - end - end - if failed==nil then - if args.unlinked then - dfhack.job.linkIntoWorld(args.job,true) - args.unlinked=false - end - addJobAction(args.job,args.unit) - args.screen:wait_tick() - else - if not args.no_job_delete then - smart_job_delete(args.job) - end - dfhack.gui.showAnnouncement("Job failed:"..failed,5,1) - end - end) -end - -function UnassignJob(job,unit,unit_pos) - unit.job.current_job=nil -end -function AssignUnitToJob(job,unit,unit_pos) - job.general_refs:insert("#",{new=df.general_ref_unit_workerst,unit_id=unit.id}) - unit.job.current_job=job - unit_pos=unit_pos or {x=job.pos.x,y=job.pos.y,z=job.pos.z} - unit.path.dest:assign(unit_pos) - return true -end -function SetCreatureRef(args) - local job=args.job - local pos=args.pos - for k,v in pairs(df.global.world.units.active) do - if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - job.general_refs:insert("#",{new=df.general_ref_unit_cageest,unit_id=v.id}) - return - end - end -end - -function SetWebRef(args) - local pos=args.pos - for k,v in pairs(df.global.world.items.other.ANY_WEBS) do - if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - args.job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) - return - end - end -end -function SetPatientRef(args) - local job=args.job - local pos=args.pos - for k,v in pairs(df.global.world.units.active) do - if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - job.general_refs:insert("#",{new=df.general_ref_unit_patientst,unit_id=v.id}) - return - end - end -end -function SetCarveDir(args) - local job=args.job - local pos=args.pos - local from_pos=args.from_pos - local dirs={up=18,down=19,right=20,left=21} - if pos.x>from_pos.x then - job.item_category[dirs.right]=true - elseif pos.xfrom_pos.y then - job.item_category[dirs.down]=true - elseif pos.y0 and item_suitable) or settings.build_by_items then - --cur_item.flags.in_job=true - job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) - item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() - --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) - used_item_id[cur_item.id]=true - end - end - end - end - end - - return item_suitability,item_counts -end -function AssignJobItems(args) - if settings.df_assign then --use df default logic and hope that it would work - return true - end - -- first find items that you want to use for the job - local job=args.job - local its=EnumItems_with_settings(args) - - local item_suitability,item_counts=find_suitable_items(job,its) - --[[while(#job.items>0) do --clear old job items - job.items[#job.items-1]:delete() - job.items:erase(#job.items-1) - end]] - - if settings.gui_item_select and #job.job_items>0 then - local item_dialog=require('hack.scripts.gui.advfort_items') - - if settings.quick then --TODO not so nice hack. instead of rewriting logic for job item filling i'm using one in gui dialog... - local item_editor=item_dialog.jobitemEditor{ - job = job, - items = item_suitability, - } - if item_editor:jobValid() then - item_editor:commit() - finish_item_assign(args) - return true - else - return false, "Quick select items" - end - else - local ret=item_dialog.showItemEditor(job,item_suitability) - if ret then - finish_item_assign(args) - return true - else - print("Failed job, i'm confused...") - end - --end) - return false,"Selecting items" - end - else - if not settings.build_by_items then - for job_id, trg_job_item in ipairs(job.job_items) do - if item_counts[job_id]>0 then - print("Not enough items for this job") - return false, "Not enough items for this job" - end - end - end - finish_item_assign(args) - return true - end - -end - -CheckAndFinishBuilding=function (args,bld) - args.building=args.building or bld - for idx,job in pairs(bld.jobs) do - if job.job_type==df.job_type.ConstructBuilding then - args.job=job - args.no_job_delete=true - break - end - end - - if args.job~=nil then - args.pre_actions={AssignJobItems} - else - local t={items=buildings.getFiltersByType({},bld:getType(),bld:getSubtype(),bld:getCustomType())} - args.pre_actions={dfhack.curry(setFiltersUp,t),AssignBuildingRef}--,AssignJobItems - end - args.no_job_delete=true - makeJob(args) -end -function AssignJobToBuild(args) - local bld=args.building or dfhack.buildings.findAtTile(args.pos) - args.building=bld - args.job_type=df.job_type.ConstructBuilding - if bld~=nil then - CheckAndFinishBuilding(args,bld) - else - bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true,building_filter=deon_filter}:show() - end - return true -end -function BuildLast(args) - local bld=dfhack.buildings.findAtTile(args.pos) - args.job_type=df.job_type.ConstructBuilding - if bld~=nil then - CheckAndFinishBuilding(args,bld) - else - --bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true}:show() - if last_building and last_building.type then - BuildingChosen(args,last_building.type,last_building.subtype,last_building.custom) - end - end - return true -end -function CancelJob(unit) - local c_job=unit.job.current_job - if c_job then - unit.job.current_job =nil --todo add real cancelation - for k,v in pairs(c_job.general_refs) do - if df.general_ref_unit_workerst:is_instance(v) then - v:delete() - c_job.general_refs:erase(k) - return - end - end - end -end -function ContinueJob(unit) - local c_job=unit.job.current_job - --no job to continue - if not c_job then return end - --reset suspends... - c_job.flags.suspend=false - for k,v in pairs(c_job.items) do --try fetching missing items - if v.is_fetching==1 then - unit.path.dest:assign(v.item.pos) - return - end - end - - --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! - addJobAction(c_job,unit) -end ---TODO: in far far future maybe add real linking? --- function assign_link_refs(args ) --- local job=args.job --- --job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=args.building.id}) --- job.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=args.triggertarget.id}) --- printall(job) --- end --- function assign_link_roles( args ) --- if #args.job.items~=2 then --- print("AAA FAILED!") --- return false --- end --- args.job.items[0].role=df.job_item_ref.T_role.LinkToTarget --- args.job.items[1].role=df.job_item_ref.T_role.LinkToTrigger --- end -function fake_linking(lever,building,slots) - local item1=slots[1].items[1] - local item2=slots[2].items[1] - if not dfhack.items.moveToBuilding(item1,lever,2) then - qerror("failed to move item to building") - end - if not dfhack.items.moveToBuilding(item2,building,2) then - qerror("failed to move item2 to building") - end - item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id}) - item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id}) - - lever.linked_mechanisms:insert("#",item2) - --fixes... - if building:getType()==df.building_type.Door then - building.door_flags.operated_by_mechanisms=true - end - - dfhack.gui.showAnnouncement("Linked!",COLOR_YELLOW,true) -end -function LinkBuilding(args) - local bld=args.building or dfhack.buildings.findAtTile(args.pos) - args.building=bld - - local lever_bld - if lever_id then --intentionally global! - lever_bld=df.building.find(lever_id) - if lever_bld==nil then - lever_id=nil - end - end - if lever_bld==nil then - if bld:getType()==df.building_type.Trap and bld:getSubtype()==df.trap_type.Lever then - lever_id=bld.id - dfhack.gui.showAnnouncement("Selected lever for linking",COLOR_YELLOW,true) - return - else - dfhack.gui.showAnnouncement("You first need a lever",COLOR_RED,true) - end - else - if lever_bld==bld then - dfhack.gui.showAnnouncement("Invalid target",COLOR_RED,true) --todo more invalid targets - return - end - -- args.job_type=df.job_type.LinkBuildingToTrigger - -- args.building=lever_bld - -- args.triggertarget=bld - -- args.pre_actions={ - -- dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.item_type.TRAPPARTS},{quantity=1,item_type=df.item_type.TRAPPARTS}}}), - -- AssignJobItems, - -- assign_link_refs,} - -- args.post_actions={AssignBuildingRef,assign_link_roles} - -- makeJob(args) - local input_filter_defaults = { --stolen from buildings lua to better customize... - item_type = df.item_type.TRAPPARTS, - item_subtype = -1, - mat_type = -1, - mat_index = -1, - flags1 = {}, - flags2 = { allow_artifact = true }, - flags3 = {}, - flags4 = 0, - flags5 = 0, - reaction_class = '', - has_material_reaction_product = '', - metal_ore = -1, - min_dimension = -1, - has_tool_use = -1, - quantity = 1 - } - local job_items={copyall(input_filter_defaults),copyall(input_filter_defaults)} - local its=EnumItems_with_settings(args) - local suitability=find_suitable_items(nil,its,job_items) - require('hack.scripts.gui.advfort_items').jobitemEditor{items=suitability,job_items=job_items,on_okay=dfhack.curry(fake_linking,lever_bld,bld)}:show() - lever_id=nil - end - --one item as LinkToTrigger role - --one item as LinkToTarget - --genref for holder(lever) - --genref for triggertarget - -end ---[[ Plant gathering attemped fix No. 35]] --[=[ still did not work!]=] -function get_design_block_ev(blk) - for i,v in ipairs(blk.block_events) do - if v:getType()==df.block_square_event_type.designation_priority then - return v - end - end -end -function PlantGatherFix(args) - local pos=args.pos - --[[args.job.flags[17]=false --?? - - - local block=dfhack.maps.getTileBlock(pos) - local ev=get_design_block_ev(block) - if ev==nil then - block.block_events:insert("#",{new=df.block_square_event_designation_priorityst}) - ev=block.block_events[#block.block_events-1] - end - ev.priority[pos.x % 16][pos.y % 16]=bit32.bor(ev.priority[pos.x % 16][pos.y % 16],4000) - - args.job.item_category:assign{furniture=true,corpses=true,ammo=true} --this is actually required in fort mode - ]] - local path=args.unit.path - path.dest=pos - path.goal=df.unit_path_goal.GatherPlant - path.path.x:insert("#",pos.x) - path.path.y:insert("#",pos.y) - path.path.z:insert("#",pos.z) - printall(path) -end -actions={ - {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, - {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, - {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, - {"CarveTrack" ,df.job_type.CarveTrack,{} --TODO: check this- carving modifies standing tile but depends on direction! - ,{SetCarveDir}}, - {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, - {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, - {"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, - {"CarveUpDownStaircase" ,df.job_type.CarveUpDownStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, - {"CarveRamp" ,df.job_type.CarveRamp,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, - {"DigChannel" ,df.job_type.DigChannel,{MakePredicateWieldsItem(df.job_skill.MINING)}}, - {"FellTree" ,df.job_type.FellTree,{MakePredicateWieldsItem(df.job_skill.AXE),IsTree}}, - {"Fish" ,df.job_type.Fish,{IsWater}}, - --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, - --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, - {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, - {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare},{PlantGatherFix}}, - {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, - {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, - {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, - --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, - {"Build" ,AssignJobToBuild,{NoConstructedBuilding}}, - {"BuildLast" ,BuildLast,{NoConstructedBuilding}}, - {"Clean" ,df.job_type.Clean,{}}, - {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, - {"Link Buildings" ,LinkBuilding,{IsBuilding}}, -} - -for id,action in pairs(actions) do - if action[1]==mode_name then - mode=id-1 - break - end -end -usetool=defclass(usetool,gui.Screen) -usetool.focus_path = 'advfort' -function usetool:getModeName() - local adv=df.global.world.units.active[0] - local ret - if adv.job.current_job then - ret= string.format("%s working(%d) ",(actions[(mode or 0)+1][1] or ""),adv.job.current_job.completion_timer) - else - ret= actions[(mode or 0)+1][1] or " " - end - if settings.quick then - ret=ret.."*" - end - return ret -end - -function usetool:update_site() - local site=inSite() - self.current_site=site - local site_label=self.subviews.siteLabel - if site then - - site_label:itemById("site").text=dfhack.TranslateName(site.name) - else - if settings.safe then - site_label:itemById("site").text="" - else - site_label:itemById("site").text="" - end - end -end - -function usetool:init(args) - self:addviews{ - wid.Label{ - view_id="mainLabel", - frame = {xalign=0,yalign=0}, - text={{key=keybinds.prevJob.key},{gap=1,text=self:callback("getModeName")},{gap=1,key=keybinds.nextJob.key}, - } - }, - - wid.Label{ - view_id="shopLabel", - frame = {l=35,xalign=0,yalign=0}, - visible=false, - text={ - {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{id="clutter"}} - }, - - wid.Label{ - view_id="siteLabel", - frame = {t=1,xalign=-1,yalign=0}, - text={ - {id="text1", text="Site:"},{id="site", text="name"} - } - } - } - local labors=df.global.world.units.active[0].status.labors - for i,v in ipairs(labors) do - labors[i]=true - end - self:update_site() -end -MOVEMENT_KEYS = { - A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, - A_CARE_MOVE_W = { -1, 0, 0 }, A_CARE_MOVE_E = { 1, 0, 0 }, - A_CARE_MOVE_NW = { -1, -1, 0 }, A_CARE_MOVE_NE = { 1, -1, 0 }, - A_CARE_MOVE_SW = { -1, 1, 0 }, A_CARE_MOVE_SE = { 1, 1, 0 }, - --[[A_MOVE_N = { 0, -1, 0 }, A_MOVE_S = { 0, 1, 0 }, - A_MOVE_W = { -1, 0, 0 }, A_MOVE_E = { 1, 0, 0 }, - A_MOVE_NW = { -1, -1, 0 }, A_MOVE_NE = { 1, -1, 0 }, - A_MOVE_SW = { -1, 1, 0 }, A_MOVE_SE = { 1, 1, 0 },--]] - A_CUSTOM_CTRL_D = { 0, 0, -1 }, - A_CUSTOM_CTRL_E = { 0, 0, 1 }, - CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, - A_MOVE_SAME_SQUARE={0,0,0}, - SELECT={0,0,0}, -} -ALLOWED_KEYS={ - A_MOVE_N=true,A_MOVE_S=true,A_MOVE_W=true,A_MOVE_E=true,A_MOVE_NW=true, - A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true,A_MOVE_DOWN_AUX=true, - A_MOVE_UP_AUX=true,A_LOOK=true,CURSOR_DOWN=true,CURSOR_UP=true,CURSOR_LEFT=true,CURSOR_RIGHT=true, - CURSOR_UPLEFT=true,CURSOR_UPRIGHT=true,CURSOR_DOWNLEFT=true,CURSOR_DOWNRIGHT=true,A_CLEAR_ANNOUNCEMENTS=true, - CURSOR_UP_Z=true,CURSOR_DOWN_Z=true, -} -function moddedpos(pos,delta) - return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} -end -function usetool:onHelp() - showHelp() -end -function setFiltersUp(specific,args) - local job=args.job - if specific.job_fields~=nil then - job:assign(specific.job_fields) - end - --printall(specific) - for _,v in ipairs(specific.items) do - --printall(v) - local filter=v - filter.new=true - job.job_items:insert("#",filter) - end - return true -end -function onWorkShopJobChosen(args,idx,choice) - args.pos=args.from_pos - args.building=args.building or dfhack.buildings.findAtTile(args.pos) - args.job_type=choice.job_id - args.post_actions={AssignBuildingRef} - args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} - makeJob(args) -end -function siegeWeaponActionChosen(args,actionid) - local building=args.building - if actionid==1 then --Turn - building.facing=(args.building.facing+1)%4 - return - elseif actionid==2 then --Load - local action=df.job_type.LoadBallista - if building:getSubtype()==df.siegeengine_type.Catapult then - action=df.job_type.LoadCatapult - args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1}}}),AssignJobItems} --TODO just boulders here - else - args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.SIEGEAMMO}}}),AssignJobItems} - end - args.job_type=action - args.unit=df.global.world.units.active[0] - local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} - args.from_pos=from_pos - args.pos=from_pos - elseif actionid==3 then --Fire - local action=df.job_type.FireBallista - if building:getSubtype()==df.siegeengine_type.Catapult then - action=df.job_type.FireCatapult - end - args.job_type=action - args.unit=df.global.world.units.active[0] - local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} - args.from_pos=from_pos - args.pos=from_pos - end - args.post_actions={AssignBuildingRef} - makeJob(args) -end -function putItemToBuilding(building,item) - if building:getType()==df.building_type.Table then - dfhack.items.moveToBuilding(item,building,0) - else - local container=building.contained_items[0].item --todo maybe iterate over all, add if usemode==2? - dfhack.items.moveToContainer(item,container) - end -end -function usetool:openPutWindow(building) - local adv=df.global.world.units.active[0] - local items=EnumItems{pos=adv.pos,unit=adv, - inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true, - [df.unit_inventory_item.T_mode.Weapon]=true,},deep=true} - local choices={} - for k,v in pairs(items) do - table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) - end - dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) -end -function usetool:openSiegeWindow(building) - local args={building=building,screen=self} - dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, - dfhack.curry(siegeWeaponActionChosen,args)) -end -function usetool:onWorkShopButtonClicked(building,index,choice) - local adv=df.global.world.units.active[0] - local args={unit=adv,building=building} - if df.interface_button_building_new_jobst:is_instance(choice.button) then - choice.button:click() - if #building.jobs>0 then - local job=building.jobs[#building.jobs-1] - args.job=job - args.pos=adv.pos - args.from_pos=adv.pos - args.pre_actions={AssignJobItems} - args.screen=self - makeJob(args) - end - elseif df.interface_button_building_category_selectorst:is_instance(choice.button) or - df.interface_button_building_material_selectorst:is_instance(choice.button) then - choice.button:click() - self:openShopWindowButtoned(building,true) - end -end -function usetool:openShopWindowFallback( building,list) - local open_window=false - if not list then --if list is not passed we are responsible for showing the menu - list={} - open_window=true - end - - local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) - local adv=df.global.world.units.active[0] - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building - ,screen=self,bld=building} - if filter_pile then - local count=0 - state.common=filter_pile.common - for i,v in ipairs(filter_pile) do - local label=v.name:lower() - table.insert(list,{job_id=0,text=label,filter=v}) - count=count+1 - end - end - - if open_window then - dialog.showListPrompt("Workshop job choice", "Choose what to make", - COLOR_WHITE,list, - function (index,choice) - onWorkShopJobChosen(state,index,choice) - end - ,nil, nil,true) - end -end ---no reset here means that the button opens submenu -function usetool:openShopWindowButtoned(building,no_reset) - self:setupFields() - local wui=df.global.ui_sidebar_menus.workshop_job - if not no_reset then - -- [[ manual reset incase the df-one does not exist? - wui:assign{category_id=-1,mat_type=-1,mat_index=-1} - for k,v in pairs(wui.material_category) do - wui.material_category[k]=false - end - end - building:fillSidebarMenu() - - local list={} - local names_already_in={} - for id,choice in pairs(wui.choices_visible) do - local label=string.lower(utils.call_with_string(choice,"getLabel")) - table.insert(list,{text=label,button=choice,is_button=true}) - names_already_in[label]=true - end - local adv=df.global.world.units.active[0] - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building - ,screen=self,bld=building} - if #list==0 then - --we couldn't use the df hack so let's fill the list from fallback - self:openShopWindowFallback(building,list) - else - --the hack worked. Though we are not sure how well so let's add a button for fallback - table.insert(list,{text='[fallback]'}) - end - - if #list==0 then - qerror("no jobs for this shop") - end - - dialog.showListPrompt("Workshop job choice", "Choose what to make", - COLOR_WHITE,list, - function (index,choice) - if choice.text=="[fallback]" then - self:openShopWindowFallback(building) - return - end - if choice.is_button then - self:onWorkShopButtonClicked(building,index,choice) - else - onWorkShopJobChosen(state,index,choice) - end - end - ,nil, nil,true) -end - -function track_stop_configure(bld) --TODO: dedicated widget with nice interface and current setting display - local dump_choices={ - {text="no dumping"}, - {text="N",x=0,y=-1},--{t="NE",x=1,y=-1}, - {text="E",x=1,y=0},--{t="SE",x=1,y=1}, - {text="S",x=0,y=1},--{t="SW",x=-1,y=1}, - {text="W",x=-1,y=0},--{t="NW",x=-1,y=-1} - } - local choices={"Friction","Dumping"} - local function chosen(index,choice) - if choice.text=="Friction" then - dialog.showInputPrompt("Choose friction","Friction",nil,tostring(bld.friction),function ( txt ) - local num=tonumber(txt) --TODO allow only vanilla friction settings - if num then - bld.friction=num - end - end) - else - dialog.showListPrompt("Dumping direction", "Choose dumping:",COLOR_WHITE,dump_choices,function ( index,choice) - if choice.x then - bld.use_dump=1 --?? - bld.dump_x_shift=choice.x - bld.dump_y_shift=choice.y - else - bld.use_dump=0 - end - end) - end - end - dialog.showListPrompt("Track stop configure", "Choose what to change:",COLOR_WHITE,choices,chosen) -end -function usetool:armCleanTrap(building) - local adv=df.global.world.units.active[0] - --[[ - Lever, - PressurePlate, - CageTrap, - StoneFallTrap, - WeaponTrap, - TrackStop - --]] - if building.state==0 then - --CleanTrap - --[[ LoadCageTrap, - LoadStoneTrap, - LoadWeaponTrap, - ]] - if building.trap_type==df.trap_type.Lever then - --link - return - end - --building.trap_type==df.trap_type.PressurePlate then - --settings/link - local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos, - building=building,job_type=df.job_type.CleanTrap} - if building.trap_type==df.trap_type.CageTrap then - args.job_type=df.job_type.LoadCageTrap - local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} - - elseif building.trap_type==df.trap_type.StoneFallTrap then - args.job_type=df.job_type.LoadStoneTrap - local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} - elseif building.trap_type==df.trap_type.TrackStop then - --set dump and friction - track_stop_configure(building) - return - else - print("TODO: trap type:"..df.trap_type[building.trap_type]) - return - end - args.screen=self - makeJob(args) - end -end -function usetool:hiveActions(building) - local adv=df.global.world.units.active[0] - local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos, - from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self} - local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} - makeJob(args) - --InstallColonyInHive, - --CollectHiveProducts, -end -function usetool:operatePump(building) - - local adv=df.global.world.units.active[0] - makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self} -end -function usetool:farmPlot(building) - local adv=df.global.world.units.active[0] - local do_harvest=false - for id, con_item in pairs(building.contained_items) do - if con_item.use_mode==2 and con_item.item:getType()==df.item_type.PLANT then - if same_xyz(adv.pos,con_item.item.pos) then - do_harvest=true - end - end - end - --check if there tile is without plantseeds,add job - - local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self} - if do_harvest then - args.job_type=df.job_type.HarvestPlants - args.post_actions={AssignBuildingRef} - else - local seedjob={items={{quantity=1,item_type=df.item_type.SEEDS}}} - args.job_type=df.job_type.PlantSeeds - args.pre_actions={dfhack.curry(setFiltersUp,seedjob)} - args.post_actions={AssignBuildingRef,AssignJobItems} - end - - makeJob(args) -end -function usetool:bedActions(building) - local adv=df.global.world.units.active[0] - local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self,building=building, - job_type=df.job_type.Sleep,post_actions={AssignBuildingRef}} - makeJob(args) -end -function usetool:chairActions(building) - local adv=df.global.world.units.active[0] - local eatjob={items={{quantity=1,item_type=df.item_type.FOOD}}} - local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self,job_type=df.job_type.Eat,building=building, - pre_actions={dfhack.curry(setFiltersUp,eatjob),AssignJobItems},post_actions={AssignBuildingRef}} - makeJob(args) -end -MODES={ - [df.building_type.Table]={ --todo filters... - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Coffin]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Box]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Weaponrack]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Armorstand]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Cabinet]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Workshop]={ - name="Workshop menu", - input=usetool.openShopWindowButtoned, - }, - [df.building_type.Furnace]={ - name="Workshop menu", - input=usetool.openShopWindowButtoned, - }, - [df.building_type.SiegeEngine]={ - name="Siege menu", - input=usetool.openSiegeWindow, - }, - [df.building_type.FarmPlot]={ - name="Plant/Harvest", - input=usetool.farmPlot, - }, - [df.building_type.ScrewPump]={ - name="Operate Pump", - input=usetool.operatePump, - }, - [df.building_type.Trap]={ - name="Interact", - input=usetool.armCleanTrap, - }, - [df.building_type.Hive]={ - name="Hive actions", - input=usetool.hiveActions, - }, - [df.building_type.Bed]={ - name="Rest", - input=usetool.bedActions, - }, - [df.building_type.Chair]={ - name="Eat", - input=usetool.chairActions, - }, -} -function usetool:shopMode(enable,mode,building) - self.subviews.shopLabel.visible=enable - if mode then - self.subviews.shopLabel:itemById("text1").text=mode.name - if building:getClutterLevel()<=1 then - self.subviews.shopLabel:itemById("clutter").text="" - else - self.subviews.shopLabel:itemById("clutter").text=" Clutter:"..tostring(building:getClutterLevel()) - end - self.building=building - end - self.mode=mode -end -function usetool:shopInput(keys) - if keys[keybinds.workshop.key] then - self:openShopWindowButtoned(self.in_shop) - end -end -function usetool:wait_tick() - self:sendInputToParent("A_SHORT_WAIT") -end -function usetool:setupFields() - local adv=df.global.world.units.active[0] - local civ_id=df.global.world.units.active[0].civ_id - local ui=df.global.ui - ui.civ_id = civ_id - ui.main.fortress_entity=df.historical_entity.find(civ_id) - ui.race_id=adv.race - local nem=dfhack.units.getNemesis(adv) - if nem then - local links=nem.figure.entity_links - for _,link in ipairs(links) do - local hist_entity=df.historical_entity.find(link.entity_id) - if hist_entity and hist_entity.type==df.historical_entity_type.SiteGovernment then - ui.group_id=link.entity_id - break - end - end - end - local site= inSite() - if site then - ui.site_id=site.id - end -end -function usetool:siteCheck() - if self.site ~= nil or not settings.safe then --TODO: add check if it's correct site (the persistant ones) - return true - end - return false, "You are not on site" -end ---movement and co... Also passes on allowed keys -function usetool:fieldInput(keys) - local adv=df.global.world.units.active[0] - local cur_mode=actions[(mode or 0)+1] - local failed=false - for code,_ in pairs(keys) do - --print(code) - if MOVEMENT_KEYS[code] then - - local state={ - unit=adv, - pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]), - dir=MOVEMENT_KEYS[code], - from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}, - post_actions=cur_mode[4], - pre_actions=cur_mode[5], - job_type=cur_mode[2], - screen=self} - - if code=="SELECT" then --do job in the distance, TODO: check if you can still cheat-mine (and co.) remotely - if df.global.cursor.x~=-30000 then - state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} - else - break - end - end - - --First check site - local ok,msg=self:siteCheck() --TODO: some jobs might be possible without a site? - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - failed=true - else - for _,p in pairs(cur_mode[3] or {}) do --then check predicates - local ok,msg=p(state) - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - failed=true - end - end - end - - if not failed then - local ok,msg - if type(cur_mode[2])=="function" then - ok,msg=cur_mode[2](state) - else - makeJob(state) - --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) - - end - - if code=="SELECT" then - self:sendInputToParent("LEAVESCREEN") - end - self.long_wait=true - end - return code - end - if code~="_STRING" and code~="_MOUSE_L" and code~="_MOUSE_R" then - if ALLOWED_KEYS[code] then - self:sendInputToParent(code) - end - end - end - -end - -function usetool:onInput(keys) - - self:update_site() - - local adv=df.global.world.units.active[0] - - if keys.LEAVESCREEN then - if df.global.cursor.x~=-30000 then --if not poiting at anything - self:sendInputToParent("LEAVESCREEN") --leave poiting - else - self:dismiss() --leave the adv-tools all together - CancelJob(adv) - end - elseif keys[keybinds.nextJob.key] then --next job with looping - mode=(mode+1)%#actions - elseif keys[keybinds.prevJob.key] then --prev job with looping - mode=mode-1 - if mode<0 then mode=#actions-1 end - elseif keys["A_SHORT_WAIT"] then - --ContinueJob(adv) - self:sendInputToParent("A_SHORT_WAIT") - elseif keys[keybinds.quick.key] then - settings.quick=not settings.quick - elseif keys[keybinds.continue.key] then - --ContinueJob(adv) - --self:sendInputToParent("A_SHORT_WAIT") - self.long_wait=true - self.long_wait_timer=nil - else - if self.mode~=nil then - if keys[keybinds.workshop.key] then - self.mode.input(self,self.building) - end - self:fieldInput(keys) - else - self:fieldInput(keys) - end - end - -end -function usetool:cancel_wait() - self.long_wait_timer=nil - self.long_wait=false -end -function usetool:onIdle() - local adv=df.global.world.units.active[0] - local job_ptr=adv.job.current_job - local job_action=findAction(adv,df.unit_action_type.Job) - - --some heuristics for unsafe conditions - if self.long_wait and not settings.unsafe then --check if player wants for canceling to happen - local counters=adv.counters - local checked_counters={pain=true,winded=true,stunned=true,unconscious=true,suffocation=true,webbed=true,nausea=true,dizziness=true} - for k,v in pairs(checked_counters) do - if counters[k]>0 then - dfhack.gui.showAnnouncement("Job: canceled waiting because unsafe -"..k,5,1) - self:cancel_wait() - return - end - end - end - - if self.long_wait and self.long_wait_timer==nil then - self.long_wait_timer=1000 --TODO tweak this - end - - if job_ptr and self.long_wait and not job_action then - - if self.long_wait_timer<=0 then --fix deadlocks with force-canceling of waiting - self:cancel_wait() - return - else - self.long_wait_timer=self.long_wait_timer-1 - end - - if adv.job.current_job.completion_timer==-1 then - self.long_wait=false - end - ContinueJob(adv) - self:sendInputToParent("A_SHORT_WAIT") --todo continue till finished - end - self._native.parent:logic() -end -function usetool:isOnBuilding() - local adv=df.global.world.units.active[0] - local bld=dfhack.buildings.findAtTile(adv.pos) - if bld and MODES[bld:getType()]~=nil and bld:getBuildStage()==bld:getMaxBuildStage() then - return true,MODES[bld:getType()],bld - else - return false - end -end -function usetool:onRenderBody(dc) - self:shopMode(self:isOnBuilding()) - self:renderParent() -end -if not (dfhack.gui.getCurFocus()=="dungeonmode/Look" or dfhack.gui.getCurFocus()=="dungeonmode/Default") then - qerror("This script requires an adventurer mode with (l)ook or default mode.") -end -usetool():show() diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua deleted file mode 100644 index 4cc05da72..000000000 --- a/scripts/gui/advfort_items.lua +++ /dev/null @@ -1,226 +0,0 @@ ---Does something with items in adventure mode jobs ---[[=begin - -gui/advfort_items -================= -Does something with items in adventure mode jobs. - -=end]] -local _ENV = mkmodule('hack.scripts.gui.advfort_items') - -local gui=require('gui') -local wid=require('gui.widgets') -local gscript=require('gui.script') - -jobitemEditor=defclass(jobitemEditor,gui.FramedScreen) -jobitemEditor.ATTRS{ - frame_style = gui.GREY_LINE_FRAME, - frame_inset = 1, - allow_add=false, - allow_remove=false, - allow_any_item=false, - job=DEFAULT_NIL, - job_items=DEFAULT_NIL, - items=DEFAULT_NIL, - on_okay=DEFAULT_NIL, - autofill=true, -} -function update_slot_text(slot) - local items="" - for i,v in ipairs(slot.items) do - items=items.." "..dfhack.items.getDescription(v,0) - if i~=#slot.items then - items=items.."," - end - end - - slot.text=string.format("%02d. Filled(%d/%d):%s",slot.id+1,slot.filled_amount,slot.job_item.quantity,items) -end ---items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref) -function jobitemEditor:init(args) - --self.job=args.job - if self.job==nil and self.job_items==nil then qerror("This screen must have job target or job_items list") end - if self.items==nil then qerror("This screen must have item list") end - - self:addviews{ - wid.Label{ - view_id = 'label', - text = args.prompt, - text_pen = args.text_pen, - frame = { l = 0, t = 0 }, - }, - wid.List{ - view_id = 'itemList', - frame = { l = 0, t = 2 ,b=2}, - }, - wid.Label{ - frame = { b=1,l=1}, - text ={{text= ": cancel", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - { - gap=3, - text= ": accept", - key = "SEC_SELECT", - on_activate= self:callback("commit"), - enabled=self:callback("jobValid") - }, - { - gap=3, - text= ": add", - key = "CUSTOM_A", - enabled=self:callback("can_add"), - on_activate= self:callback("add_item") - }, - { - gap=3, - text= ": remove", - key = "CUSTOM_R", - enabled=self:callback("can_remove"), - on_activate= self:callback("remove_item") - },} - }, - } - self.assigned={} - self:fill() - if self.autofill then - self:fill_slots() - end -end -function jobitemEditor:get_slot() - local idx,choice=self.subviews.itemList:getSelected() - return choice -end -function jobitemEditor:can_add() - local slot=self:get_slot() - return slot.filled_amount0 -end -function jobitemEditor:get_item_filters( job_item ) - local true_flags={} - for k,v in pairs(job_item.flags1) do - if v then - table.insert(true_flags,k) - end - end - for k,v in pairs(job_item.flags2) do - if v then - table.insert(true_flags,k) - end - end - for k,v in pairs(job_item.flags3) do - if v then - table.insert(true_flags,k) - end - end - return table.concat(true_flags,"\n") -end -function jobitemEditor:add_item() - local cur_slot=self:get_slot() - local choices={} - table.insert(choices,{text=""}) - for k,v in pairs(cur_slot.choices) do - if not self.assigned[v.id] then - table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) - end - end - gscript.start(function () - local _,_2,choice=gscript.showListPrompt("which item?", "Select item\nItem filters:\n"..self:get_item_filters(cur_slot.job_item), COLOR_WHITE, choices) - if choice ~= nil and choice.item~=nil then - self:add_item_to_slot(cur_slot,choice.item) - end - end - ) -end -function jobitemEditor:fill_slots() - for i,v in ipairs(self.slots) do - while v.filled_amount`, which has not -been available since DF 0.34.11 - -See :bug:`1445` for more info about the patches. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' -local bp = require 'binpatch' - -AssignRack = defclass(AssignRack, guidm.MenuOverlay) - -AssignRack.focus_path = 'assign-rack' - -AssignRack.ATTRS { - building = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function list_squads(building,squad_table,squad_list) - local sqlist = building:getSquads() - if not sqlist then - return - end - - for i,v in ipairs(sqlist) do - local obj = df.squad.find(v.squad_id) - if obj then - if not squad_table[v.squad_id] then - squad_table[v.squad_id] = { id = v.squad_id, obj = obj } - table.insert(squad_list, squad_table[v.squad_id]) - end - - -- Set specific use flags - for n,ok in pairs(v.mode) do - if ok then - squad_table[v.squad_id][n] = true - end - end - - -- Check if any use is possible - local btype = building:getType() - if btype == df.building_type.Bed then - if v.mode.sleep then - squad_table[v.squad_id].any = true - end - elseif btype == df.building.Weaponrack then - if v.mode.train or v.mode.indiv_eq then - squad_table[v.squad_id].any = true - end - else - if v.mode.indiv_eq then - squad_table[v.squad_id].any = true - end - end - end - end - - for i,v in ipairs(building.parents) do - list_squads(v, squad_table, squad_list) - end -end - -function filter_invalid(list, id) - for i=#list-1,0,-1 do - local bld = df.building.find(list[i]) - if not bld or bld:getSpecificSquad() ~= id then - list:erase(i) - end - end -end - -function AssignRack:init(args) - self.squad_table = {} - self.squad_list = {} - list_squads(self.building, self.squad_table, self.squad_list) - table.sort(self.squad_list, function(a,b) return a.id < b.id end) - - self.choices = {} - for i,v in ipairs(self.squad_list) do - if v.any and (v.train or v.indiv_eq) then - local name = v.obj.alias - if name == '' then - name = dfhack.TranslateName(v.obj.name, true) - end - - filter_invalid(v.obj.rack_combat, v.id) - filter_invalid(v.obj.rack_training, v.id) - - table.insert(self.choices, { - icon = self:callback('isSelected', v), - icon_pen = COLOR_LIGHTGREEN, - obj = v, - text = { - name, NEWLINE, ' ', - { text = function() - return string.format('%d combat, %d training', #v.obj.rack_combat, #v.obj.rack_training) - end } - } - }) - end - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - 'Assign Weapon Rack' - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 2, b = 2 }, - icon_width = 2, row_height = 2, - scroll_keys = widgets.SECONDSCROLL, - choices = self.choices, - on_submit = self:callback('onSubmit'), - }, - widgets.Label{ - frame = { l = 0, t = 2 }, - text_pen = COLOR_LIGHTRED, - text = 'No appropriate barracks\n\nNote: weapon racks use the\nIndividual equipment flag', - visible = (#self.choices == 0), - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } - } - }, - } -end - -function AssignRack:isSelected(info) - if self.building.specific_squad == info.id then - return '\xfb' - else - return nil - end -end - -function AssignRack:onSubmit(idx, choice) - local rid = self.building.id - local curid = self.building.specific_squad - - local cur = df.squad.find(curid) - if cur then - utils.erase_sorted(cur.rack_combat, rid) - utils.erase_sorted(cur.rack_training, rid) - end - - self.building.specific_squad = -1 - df.global.ui.equipment.update.buildings = true - - local new = df.squad.find(choice.obj.id) - if new and choice.obj.id ~= curid then - self.building.specific_squad = choice.obj.id - - if choice.obj.indiv_eq then - utils.insert_sorted(new.rack_combat, rid) - end - if choice.obj.train then - utils.insert_sorted(new.rack_training, rid) - end - end -end - -function AssignRack:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - AssignRack.super.onInput(self, keys) - end -end - -if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some/Weaponrack' then - qerror("This script requires a weapon rack selected in the 'q' mode") -end - -AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show() - -if not already_patched then - local patch = bp.load_dif_file('weaponrack-unassign') - if patch and patch:isApplied() then - already_patched = true - end -end - -if not already_patched then - dlg.showMessage( - 'BUG ALERT', - { 'This script requires applying the binary patch', NEWLINE, - 'named weaponrack-unassign. Otherwise the game', NEWLINE, - 'will lose your settings due to a bug.' }, - COLOR_YELLOW - ) -end diff --git a/scripts/gui/autobutcher.lua b/scripts/gui/autobutcher.lua deleted file mode 100644 index 59f3733c1..000000000 --- a/scripts/gui/autobutcher.lua +++ /dev/null @@ -1,662 +0,0 @@ --- A GUI front-end for the autobutcher plugin. ---[[=begin - -gui/autobutcher -=============== -An in-game interface for `autobutcher`. - -=end]] -local gui = require 'gui' -local utils = require 'utils' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -local plugin = require 'plugins.zone' - -WatchList = defclass(WatchList, gui.FramedScreen) - -WatchList.ATTRS { - frame_title = 'Autobutcher Watchlist', - frame_inset = 0, -- cover full DF window - frame_background = COLOR_BLACK, - frame_style = gui.BOUNDARY_FRAME, -} - --- width of the race name column in the UI -local racewidth = 25 - -function nextAutowatchState() - if(plugin.autowatch_isEnabled()) then - return 'Stop ' - end - return 'Start' -end - -function nextAutobutcherState() - if(plugin.autobutcher_isEnabled()) then - return 'Stop ' - end - return 'Start' -end - -function getSleepTimer() - return plugin.autobutcher_getSleep() -end - -function setSleepTimer(ticks) - plugin.autobutcher_setSleep(ticks) -end - -function WatchList:init(args) - local colwidth = 7 - self:addviews{ - widgets.Panel{ - frame = { l = 0, r = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - text_pen = COLOR_CYAN, - text = { - { text = 'Race', width = racewidth }, ' ', - { text = 'female', width = colwidth }, ' ', - { text = ' male', width = colwidth }, ' ', - { text = 'Female', width = colwidth }, ' ', - { text = ' Male', width = colwidth }, ' ', - { text = 'watch? ' }, - { text = ' butchering' }, - NEWLINE, - { text = '', width = racewidth }, ' ', - { text = ' kids', width = colwidth }, ' ', - { text = ' kids', width = colwidth }, ' ', - { text = 'adults', width = colwidth }, ' ', - { text = 'adults', width = colwidth }, ' ', - { text = ' ' }, - { text = ' ordered' }, - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 3, b = 5 }, - not_found_label = 'Watchlist is empty.', - edit_pen = COLOR_LIGHTCYAN, - text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, - cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, - --on_select = self:callback('onSelectEntry'), - }, - widgets.Label{ - view_id = 'bottom_ui', - frame = { b = 0, h = 1 }, - text = 'filled by updateBottom()' - } - } - }, - } - - self:initListChoices() - self:updateBottom() -end - --- change the viewmode for stock data displayed in left section of columns -local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' } -local viewmode = 1 -function WatchList:onToggleView() - if viewmode < #viewmodes then - viewmode = viewmode + 1 - else - viewmode = 1 - end - self:initListChoices() - self:updateBottom() -end - --- update the bottom part of the UI (after sleep timer changed etc) -function WatchList:updateBottom() - self.subviews.bottom_ui:setText( - { - { key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max', - on_activate = self:callback('onToggleView') }, NEWLINE, - { key = 'CUSTOM_F', text = ': f kids', - on_activate = self:callback('onEditFK') }, ', ', - { key = 'CUSTOM_M', text = ': m kids', - on_activate = self:callback('onEditMK') }, ', ', - { key = 'CUSTOM_SHIFT_F', text = ': f adults', - on_activate = self:callback('onEditFA') }, ', ', - { key = 'CUSTOM_SHIFT_M', text = ': m adults', - on_activate = self:callback('onEditMA') }, '. ', - { key = 'CUSTOM_W', text = ': Toggle watch', - on_activate = self:callback('onToggleWatching') }, '. ', - { key = 'CUSTOM_X', text = ': Delete', - on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE, - --{ key = 'CUSTOM_A', text = ': Add race', - -- on_activate = self:callback('onAddRace') }, ', ', - { key = 'CUSTOM_SHIFT_R', text = ': Set whole row', - on_activate = self:callback('onSetRow') }, '. ', - { key = 'CUSTOM_B', text = ': Remove butcher orders', - on_activate = self:callback('onUnbutcherRace') }, '. ', - { key = 'CUSTOM_SHIFT_B', text = ': Butcher race', - on_activate = self:callback('onButcherRace') }, '. ', NEWLINE, - { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', - on_activate = self:callback('onToggleAutobutcher') }, '. ', - { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', - on_activate = self:callback('onToggleAutowatch') }, '. ', - { key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)', - on_activate = self:callback('onEditSleepTimer') }, '. ', - }) -end - -function stringify(number) - -- cap displayed number to 3 digits - -- after population of 50 per race is reached pets stop breeding anyways - -- so probably this could safely be reduced to 99 - local max = 999 - if number > max then number = max end - return tostring(number) -end - -function WatchList:initListChoices() - - local choices = {} - - -- first two rows are for "edit all races" and "edit new races" - local settings = plugin.autobutcher_getSettings() - local fk = stringify(settings.fk) - local fa = stringify(settings.fa) - local mk = stringify(settings.mk) - local ma = stringify(settings.ma) - - local watched = '' - - local colwidth = 7 - - table.insert (choices, { - text = { - { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = watched, width = 6, rjustify = true } - } - }) - - table.insert (choices, { - text = { - { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = watched, width = 6, rjustify = true } - } - }) - - local watchlist = plugin.autobutcher_getWatchList() - - for i,entry in ipairs(watchlist) do - fk = stringify(entry.fk) - fa = stringify(entry.fa) - mk = stringify(entry.mk) - ma = stringify(entry.ma) - if viewmode == 1 then - fkc = stringify(entry.fk_total) - fac = stringify(entry.fa_total) - mkc = stringify(entry.mk_total) - mac = stringify(entry.ma_total) - end - if viewmode == 2 then - fkc = stringify(entry.fk_protected) - fac = stringify(entry.fa_protected) - mkc = stringify(entry.mk_protected) - mac = stringify(entry.ma_protected) - end - if viewmode == 3 then - fkc = stringify(entry.fk_butcherable) - fac = stringify(entry.fa_butcherable) - mkc = stringify(entry.mk_butcherable) - mac = stringify(entry.ma_butcherable) - end - if viewmode == 4 then - fkc = stringify(entry.fk_butcherflag) - fac = stringify(entry.fa_butcherflag) - mkc = stringify(entry.mk_butcherflag) - mac = stringify(entry.ma_butcherflag) - end - local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag - local bo = ' ' - if butcher_ordered > 0 then bo = stringify(butcher_ordered) end - - local watched = 'no' - if entry.watched then watched = 'yes' end - - local racestr = entry.name - - -- highlight entries where the target quota can't be met because too many are protected - bad_pen = COLOR_LIGHTRED - good_pen = NONE -- this is stupid, but it works. sue me - fk_pen = good_pen - fa_pen = good_pen - mk_pen = good_pen - ma_pen = good_pen - if entry.fk_protected > entry.fk then fk_pen = bad_pen end - if entry.fa_protected > entry.fa then fa_pen = bad_pen end - if entry.mk_protected > entry.mk then mk_pen = bad_pen end - if entry.ma_protected > entry.ma then ma_pen = bad_pen end - - table.insert (choices, { - text = { - { text = racestr, width = racewidth, pad_char = ' ' }, --' ', - { text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ', - { text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ', - { text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ', - { text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ', - { text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ', - { text = bo, width = 8, rjustify = true, pad_char = ' ' } - }, - obj = entry, - }) - end - - local list = self.subviews.list - list:setChoices(choices) -end - -function WatchList:onInput(keys) - if keys.LEAVESCREEN then - self:dismiss() - else - WatchList.super.onInput(self, keys) - end -end - --- check the user input for target population values -function WatchList:checkUserInput(count, text) - if count == nil then - dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) - return false - end - if count < 0 then - dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) - return false - end - return true -end - --- check the user input for sleep timer -function WatchList:checkUserInputSleep(count, text) - if count == nil then - dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) - return false - end - if count < 1000 then - dlg.showMessage('Invalid Number', - 'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!', - COLOR_LIGHTRED) - return false - end - return true -end - -function WatchList:onEditFK() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of female kids:', - COLOR_WHITE, - ' '..fk, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - fk = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditMK() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of male kids:', - COLOR_WHITE, - ' '..mk, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - mk = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditFA() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of female adults:', - COLOR_WHITE, - ' '..fa, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - fa = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditMA() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of male adults:', - COLOR_WHITE, - ' '..ma, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - ma = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditSleepTimer() - local sleep = getSleepTimer() - dlg.showInputPrompt( - 'Edit Sleep Timer', - 'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)', - COLOR_WHITE, - ' '..sleep, - function(text) - local count = tonumber(text) - if self:checkUserInputSleep(count, text) then - sleep = count - setSleepTimer(sleep) - self:updateBottom() - end - end - ) -end - -function WatchList:onToggleWatching() - local selidx,selobj = self.subviews.list:getSelected() - if selidx > 2 then - local entry = selobj.obj - plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched) - end - self:initListChoices() -end - -function WatchList:onDeleteEntry() - local selidx,selobj = self.subviews.list:getSelected() - if(selidx < 3 or selobj == nil) then - return - end - dlg.showYesNoPrompt( - 'Delete from Watchlist', - 'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)', - COLOR_YELLOW, - function() - plugin.autobutcher_removeFromWatchList(selobj.obj.id) - self:initListChoices() - end - ) -end - -function WatchList:onAddRace() - print('onAddRace - not implemented yet') -end - -function WatchList:onUnbutcherRace() - local selidx,selobj = self.subviews.list:getSelected() - if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end - if selidx > 2 then - local entry = selobj.obj - local race = entry.name - plugin.autobutcher_unbutcherRace(entry.id) - self:initListChoices() - self:updateBottom() - end -end - -function WatchList:onButcherRace() - local selidx,selobj = self.subviews.list:getSelected() - if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end - if selidx > 2 then - local entry = selobj.obj - local race = entry.name - plugin.autobutcher_butcherRace(entry.id) - self:initListChoices() - self:updateBottom() - end -end - --- set whole row (fk, mk, fa, ma) to one value -function WatchList:onSetRow() - local selidx,selobj = self.subviews.list:getSelected() - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - local watchindex = selidx - 3 - if selidx > 2 then - local entry = selobj.obj - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Set whole row for '..race, - 'Enter desired maximum for all subtypes:', - COLOR_WHITE, - ' ', - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( count, count, count, count ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( count, count, count, count ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onToggleAutobutcher() - if(plugin.autobutcher_isEnabled()) then - plugin.autobutcher_setEnabled(false) - plugin.autobutcher_sortWatchList() - else - plugin.autobutcher_setEnabled(true) - end - self:initListChoices() - self:updateBottom() -end - -function WatchList:onToggleAutowatch() - if(plugin.autowatch_isEnabled()) then - plugin.autowatch_setEnabled(false) - else - plugin.autowatch_setEnabled(true) - end - self:initListChoices() - self:updateBottom() -end - -if not dfhack.isMapLoaded() then - qerror('Map is not loaded.') -end - -if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then - qerror("This script must not be called while other lua gui stuff is running.") -end - --- maybe this is too strict, there is not really a reason why it can only be called from the status screen --- (other than the hotkey might overlap with other scripts) -if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then - qerror("This script must either be called from the overall status screen or the animal list screen.") -end - - -local screen = WatchList{ } -screen:show() diff --git a/scripts/gui/choose-weapons.lua b/scripts/gui/choose-weapons.lua deleted file mode 100644 index 39e5fbf77..000000000 --- a/scripts/gui/choose-weapons.lua +++ /dev/null @@ -1,169 +0,0 @@ --- Rewrite individual choice weapons to specific types ---[[=begin - -gui/choose-weapons -================== -Bind to a key (the example config uses :kbd:`Ctrl`:kbd:`W`), and activate in the Equip->View/Customize -page of the military screen. - -Depending on the cursor location, it rewrites all 'individual choice weapon' entries -in the selected squad or position to use a specific weapon type matching the assigned -unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites -only that entry, and does it even if it is not 'individual choice'. - -Rationale: individual choice seems to be unreliable when there is a weapon shortage, -and may lead to inappropriate weapons being selected. - -=end]] -local utils = require 'utils' -local dlg = require 'gui.dialogs' - -local defs = df.global.world.raws.itemdefs -local entity = df.global.ui.main.fortress_entity -local tasks = df.global.ui.tasks -local equipment = df.global.ui.equipment - -function find_best_weapon(unit,mode) - local best = nil - local skill = nil - local skill_level = nil - local count = 0 - local function try(id,iskill) - local slevel = dfhack.units.getNominalSkill(unit,iskill) - -- Choose most skill - if (skill ~= nil and slevel > skill_level) - or (skill == nil and slevel > 0) then - best,skill,skill_level,count = id,iskill,slevel,0 - end - -- Then most produced within same skill - if skill == iskill then - local cnt = tasks.created_weapons[id] - if cnt > count then - best,count = id,cnt - end - end - end - for _,id in ipairs(entity.resources.weapon_type) do - local def = defs.weapons[id] - if def.skill_ranged >= 0 then - if mode == nil or mode == 'ranged' then - try(id, def.skill_ranged) - end - else - if mode == nil or mode == 'melee' then - try(id, def.skill_melee) - end - end - end - return best -end - -function unassign_wrong_items(unit,position,spec,subtype) - for i=#spec.assigned-1,0,-1 do - local id = spec.assigned[i] - local item = df.item.find(id) - - if item.subtype.subtype ~= subtype then - spec.assigned:erase(i) - - -- TODO: somewhat unexplored area; maybe missing some steps - utils.erase_sorted(position.assigned_items,id) - if utils.erase_sorted(equipment.items_assigned.WEAPON,item,'id') then - utils.insert_sorted(equipment.items_unassigned.WEAPON,item,'id') - end - equipment.update.weapon = true - unit.military.pickup_flags.update = true - end - end -end - -local count = 0 - -function adjust_uniform_spec(unit,position,spec,force) - if not unit then return end - local best - if spec.indiv_choice.melee then - best = find_best_weapon(unit, 'melee') - elseif spec.indiv_choice.ranged then - best = find_best_weapon(unit, 'ranged') - elseif spec.indiv_choice.any or force then - best = find_best_weapon(unit, nil) - end - if best then - count = count + 1 - spec.item_filter.item_subtype = best - spec.indiv_choice.any = false - spec.indiv_choice.melee = false - spec.indiv_choice.ranged = false - unassign_wrong_items(unit, position, spec, best) - end -end - -function adjust_position(unit,position,force) - if not unit then - local fig = df.historical_figure.find(position.occupant) - if not fig then return end - unit = df.unit.find(fig.unit_id) - end - - for _,v in ipairs(position.uniform.weapon) do - adjust_uniform_spec(unit, position, v, force) - end -end - -function adjust_squad(squad, force) - for _,pos in ipairs(squad.positions) do - adjust_position(nil, pos, force) - end -end - -local args = {...} -local vs = dfhack.gui.getCurViewscreen() -local vstype = df.viewscreen_layer_militaryst -if not vstype:is_instance(vs) then - qerror('Call this from the military screen') -end - -if vs.page == vstype.T_page.Equip -and vs.equip.mode == vstype.T_equip.T_mode.Customize then - local slist = vs.layer_objects[0] - local squad = vs.equip.squads[slist:getListCursor()] - - if slist.active then - print('Adjusting squad.') - adjust_squad(squad) - else - local plist = vs.layer_objects[1] - local pidx = plist:getListCursor() - local pos = squad.positions[pidx] - local unit = vs.equip.units[pidx] - - if plist.active then - print('Adjusting position.') - adjust_position(unit, pos) - elseif unit and vs.equip.edit_mode < 0 then - local wlist = vs.layer_objects[2] - local idx = wlist:getListCursor() - local cat = vs.equip.assigned.category[idx] - - if wlist.active and cat == df.uniform_category.weapon then - print('Adjusting spec.') - adjust_uniform_spec(unit, pos, vs.equip.assigned.spec[idx], true) - end - end - end -else - qerror('Call this from the Equip page of military screen') -end - -if count > 1 then - dlg.showMessage( - 'Choose Weapons', - 'Updated '..count..' uniform entries.', COLOR_GREEN - ) -elseif count == 0 then - dlg.showMessage( - 'Choose Weapons', - 'Did not find any entries to update.', COLOR_YELLOW - ) -end diff --git a/scripts/gui/clone-uniform.lua b/scripts/gui/clone-uniform.lua deleted file mode 100644 index e6ae2fa8a..000000000 --- a/scripts/gui/clone-uniform.lua +++ /dev/null @@ -1,58 +0,0 @@ --- Clone a uniform template in the military screen ---[[=begin - -gui/clone-uniform -================= -Bind to a key (the example config uses :kbd:`Ctrl`:kbd:`C`), and activate in the Uniforms -page of the military screen with the cursor in the leftmost list. - -When invoked, the script duplicates the currently selected uniform template, -and selects the newly created copy. - -=end]] -local utils = require 'utils' -local gui = require 'gui' - -local entity = df.global.ui.main.fortress_entity - -local args = {...} -local vs = dfhack.gui.getCurViewscreen() -local vstype = df.viewscreen_layer_militaryst -if not vstype:is_instance(vs) then - qerror('Call this from the military screen') -end - -local slist = vs.layer_objects[0] - -if vs.page == vstype.T_page.Uniforms -and slist.active and slist.num_entries > 0 -and not vs.equip.in_name_uniform -then - local idx = slist.num_entries - - if #vs.equip.uniforms ~= idx or #entity.uniforms ~= idx then - error('Uniform vector length mismatch') - end - - local uniform = vs.equip.uniforms[slist:getListCursor()] - - local ucopy = uniform:new() - ucopy.id = entity.next_uniform_id - ucopy.name = ucopy.name..'(Copy)' - - for k,v in ipairs(ucopy.uniform_item_info) do - for k2,v2 in ipairs(v) do - v[k2] = v2:new() - end - end - - entity.next_uniform_id = entity.next_uniform_id + 1 - entity.uniforms:insert('#',ucopy) - vs.equip.uniforms:insert('#',ucopy) - - slist.num_entries = idx+1 - slist.cursor = idx-1 - gui.simulateInput(vs, 'STANDARDSCROLL_DOWN') -else - qerror('Call this with a uniform selected on the Uniforms page of military screen') -end diff --git a/scripts/gui/companion-order.lua b/scripts/gui/companion-order.lua deleted file mode 100644 index 60a141d0f..000000000 --- a/scripts/gui/companion-order.lua +++ /dev/null @@ -1,490 +0,0 @@ --- Issue orders to companions in Adventure mode ---[[=begin - -gui/companion-order -=================== -A script to issue orders for companions. Select companions with lower case chars, issue orders with upper -case. Must be in look or talk mode to issue command on tile. - -.. image:: /docs/images/companion-order.png - -* move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you. -* equip - try to equip items on the ground. -* pick-up - try to take items into hand (also wield) -* unequip - remove and drop equipment -* unwield - drop held items -* wait - temporarily remove from party -* follow - rejoin the party after "wait" -* leave - remove from party (can be rejoined by talking) - -=end]] - -local gui = require 'gui' -local dlg = require 'gui.dialogs' -local args={...} -local is_cheat=(#args>0 and args[1]=="-c") -local cursor=xyz2pos(df.global.cursor.x,df.global.cursor.y,df.global.cursor.z) -local permited_equips={} - -permited_equips[df.item_backpackst]="UPPERBODY" -permited_equips[df.item_quiverst]="UPPERBODY" -permited_equips[df.item_flaskst]="UPPERBODY" -permited_equips[df.item_armorst]="UPPERBODY" -permited_equips[df.item_shoesst]="STANCE" -permited_equips[df.item_glovesst]="GRASP" -permited_equips[df.item_helmst]="HEAD" -permited_equips[df.item_pantsst]="LOWERBODY" -function DoesHaveSubtype(item) - if df.item_backpackst:is_instance(item) or df.item_flaskst:is_instance(item) or df.item_quiverst:is_instance(item) then - return false - end - return true -end -function CheckCursor(p) - if p.x==-30000 then - dlg.showMessage( - 'Companion orders', - 'You must have a cursor on some tile!', COLOR_LIGHTRED - ) - return false - end - return true -end -function getxyz() -- this will return pointers x,y and z coordinates. - local x=df.global.cursor.x - local y=df.global.cursor.y - local z=df.global.cursor.z - return x,y,z -- return the coords -end - -function GetCaste(race_id,caste_id) - local race=df.creature_raw.find(race_id) - return race.caste[caste_id] -end - -function EnumBodyEquipable(race_id,caste_id) - local caste=GetCaste(race_id,caste_id) - local bps=caste.body_info.body_parts - local ret={} - for k,v in pairs(bps) do - ret[k]={bp=v,layers={[0]={size=0,permit=0},[1]={size=0,permit=0},[2]={size=0,permit=0},[3]={size=0,permit=0} } } - end - return ret -end -function ReadCurrentEquiped(body_equip,unit) - for k,v in pairs(unit.inventory) do - if v.mode==2 then - local bpid=v.body_part_id - if DoesHaveSubtype(v.item) then - local sb=v.item.subtype.props - local trg=body_equip[bpid] - local trg_layer=trg.layers[sb.layer] - - if trg_layer.permit==0 then - trg_layer.permit=sb.layer_permit - else - if trg_layer.permit>sb.layer_permit then - trg_layer.permit=sb.layer_permit - end - end - trg_layer.size=trg_layer.size+sb.layer_size - end - end - end -end -function LayeringPermits(body_part,item) - if not DoesHaveSubtype(item) then - return true - end - local sb=item.subtype.props - local trg_layer=body_part.layers[sb.layer] - if math.min(trg_layer.permit ,sb.layer_permit)0 and #items>0 do - if(dfhack.items.moveToInventory(items[#items],v,1,grasps[#grasps])) then - table.remove(grasps) - end - table.remove(items) - end - local backpack=GetBackpack(v) - if backpack then - while #items>0 do - dfhack.items.moveToContainer(items[#items],backpack) - table.remove(items) - end - end - end - return true -end}, -{name="unequip",f=function (unit_list) - --remove and drop all the stuff (todo maybe a gui too?) - for k,v in pairs(unit_list) do - while #v.inventory ~=0 do - dfhack.items.moveToGround(v.inventory[0].item,v.pos) - end - end - return true -end}, -{name="unwield",f=function (unit_list) - - for k,v in pairs(unit_list) do - local wep_count=0 - for _,it in pairs(v.inventory) do - if it.mode==1 then - wep_count=wep_count+1 - end - end - for i=1,wep_count do - for _,it in pairs(v.inventory) do - if it.mode==1 then - dfhack.items.moveToGround(it.item,v.pos) - break - end - end - end - end - return true -end}, ---[=[ -{name="roam not working :<",f=function (unit_list,pos,dist) --does not work - if not CheckCursor(pos) then - return false - end - dist=dist or 5 - for k,v in pairs(unit_list) do - v.idle_area:assign(pos) - v.idle_area_threshold=dist - end - return true -end}, ---]=] -{name="wait",f=function (unit_list) - for k,v in pairs(unit_list) do - v.relations.group_leader_id=-1 - end - return true -end}, -{name="follow",f=function (unit_list) - local adv=df.global.world.units.active[0] - for k,v in pairs(unit_list) do - v.relations.group_leader_id=adv.id - end - return true -end}, -{name="leave",f=function (unit_list) - local adv=df.global.world.units.active[0] - local t_nem=dfhack.units.getNemesis(adv) - for k,v in pairs(unit_list) do - - v.relations.group_leader_id=-1 - local u_nem=dfhack.units.getNemesis(v) - if u_nem then - u_nem.group_leader_id=-1 - end - if t_nem and u_nem then - for k,v in pairs(t_nem.companions) do - if v==u_nem.id then - t_nem.companions:erase(k) - break - end - end - end - end - return true -end}, - -} -local cheats={ -{name="Patch up",f=function (unit_list) - local dft=require("plugins.dfusion.tools") - for k,v in pairs(unit_list) do - dft.healunit(v) - end - return true -end}, -{name="Power up",f=function (unit_list) - local dft=require("plugins.dfusion.tools") - for k,d in pairs(unit_list) do - dft.powerup(d) - end - return true -end}, -{name="get in",f=function (unit_list,pos) - if not CheckCursor(pos) then - return false - end - adv=df.global.world.units.active[0] - item=getItemsAtPos(getxyz())[1] - print(item.id) - for k,v in pairs(unit_list) do - v.riding_item_id=item.id - local ref=df.general_ref_unit_riderst:new() - ref.unit_id=v.id - item.general_refs:insert("#",ref) - end - return true -end}, -} ---[[ todo: add cheats...]]-- -function getCompanions(unit) - unit=unit or df.global.world.units.active[0] - local t_nem=dfhack.units.getNemesis(unit) - if t_nem==nil then - qerror("Invalid unit! No nemesis record") - end - local ret={} - for k,v in pairs(t_nem.companions) do - local u=df.nemesis_record.find(v) - if u.unit then - table.insert(ret,u.unit) - end - end - return ret -end - - -CompanionUi=defclass(CompanionUi,gui.FramedScreen) -CompanionUi.ATTRS{ - frame_title = "Companions", -} -function CompanionUi:init(args) - self.unit_list=args.unit_list - self.selected={} - for i=0,26 do - self.selected[i]=true - end -end -function CompanionUi:GetSelectedUnits() - local ret={} - for k,v in pairs(self.unit_list) do - if self.selected[k] then - table.insert(ret,v) - end - end - return ret -end -function CompanionUi:onInput(keys) - - - if keys.LEAVESCREEN then - self:dismiss() - elseif keys._STRING then - local s=keys._STRING - if s==string.byte('*') then - local v=self.selected[1] or false - for i=0,26 do - - self.selected[i]=not v - end - end - if s>=string.byte('a') and s<=string.byte('z') then - local idx=s-string.byte('a')+1 - if self.selected[idx] then - self.selected[idx]=false - else - self.selected[idx]=true - end - end - if s>=string.byte('A') and s<=string.byte('Z') then - local idx=s-string.byte('A')+1 - if orders[idx] and orders[idx].f then - if orders[idx].f(self:GetSelectedUnits(),cursor) then - self:dismiss() - end - end - if is_cheat then - idx=idx-#orders - if cheats[idx] and cheats[idx].f then - if cheats[idx].f(self:GetSelectedUnits(),cursor) then - self:dismiss() - end - end - end - end - end -end -function CompanionUi:onRenderBody( dc) - --list widget goes here... - local char_a=string.byte('a')-1 - dc:newline(1):string("*. All") - for k,v in ipairs(self.unit_list) do - if self.selected[k] then - dc:pen(COLOR_GREEN) - else - dc:pen(COLOR_GREY) - end - dc:newline(1):string(string.char(k+char_a)..". "):string(dfhack.TranslateName(v.name)); - end - dc:pen(COLOR_GREY) - local w,h=self:getWindowSize() - local char_A=string.byte('A')-1 - for k,v in ipairs(orders) do - dc:seek(w/2,k):string(string.char(k+char_A)..". "):string(v.name); - end - if is_cheat then - for k,v in ipairs(cheats) do - dc:seek(w/2,k+#orders):string(string.char(k+#orders+char_A)..". "):string(v.name); - end - end -end -local screen=CompanionUi{unit_list=getCompanions()} -screen:show() \ No newline at end of file diff --git a/scripts/gui/confirm-opts.lua b/scripts/gui/confirm-opts.lua deleted file mode 100644 index d2aba2a94..000000000 --- a/scripts/gui/confirm-opts.lua +++ /dev/null @@ -1,74 +0,0 @@ --- confirm plugin options ---[[=begin - -gui/confirm-opts -================ -A basic configuration interface for the `confirm` plugin. - -=end]] - - -confirm = require 'plugins.confirm' -gui = require 'gui' - -Opts = defclass(Opts, gui.FramedScreen) -Opts.ATTRS = { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'Confirmation dialogs', - frame_width = 32, - frame_height = 20, - frame_inset = 1, - focus_path = 'confirm/opts', -} - -function Opts:init() - self:refresh() - self.cursor = 1 - local active_id = confirm.get_active_id() - for i, c in pairs(self.data) do - if c.id == active_id then - self.cursor = i - break - end - end -end - -function Opts:refresh() - self.data = confirm.get_conf_data() - self.frame_height = #self.data -end - -function Opts:onRenderBody(p) - for i, c in pairs(self.data) do - local highlight = (i == self.cursor and 8 or 0) - p:pen(COLOR_GREY + highlight) - p:string(c.id .. ': ') - p:pen((c.enabled and COLOR_GREEN or COLOR_RED) + highlight) - p:string(c.enabled and 'Enabled' or 'Disabled') - p:newline() - end -end - -function Opts:onInput(keys) - local conf = self.data[self.cursor] - if keys.LEAVESCREEN then - self:dismiss() - elseif keys.SELECT then - confirm.set_conf_state(conf.id, not conf.enabled) - self:refresh() - elseif keys.SEC_SELECT then - for _, c in pairs(self.data) do - confirm.set_conf_state(c.id, not conf.enabled) - end - self:refresh() - elseif keys.STANDARDSCROLL_UP or keys.STANDARDSCROLL_DOWN then - self.cursor = self.cursor + (keys.STANDARDSCROLL_UP and -1 or 1) - if self.cursor < 1 then - self.cursor = #self.data - elseif self.cursor > #self.data then - self.cursor = 1 - end - end -end - -Opts():show() diff --git a/scripts/gui/create-item.lua b/scripts/gui/create-item.lua deleted file mode 100644 index a2c38247a..000000000 --- a/scripts/gui/create-item.lua +++ /dev/null @@ -1,260 +0,0 @@ --- create-item.lua --- A gui-based item creation script. --- author Putnam --- edited by expwnent - ---@module = true ---[[=begin - -gui/create-item -=============== -A graphical interface for creating items. - -=end]] -local function getGenderString(gender) - local genderStr - if gender==0 then - genderStr=string.char(12) - elseif gender==1 then - genderStr=string.char(11) - else - return "" - end - return string.char(40)..genderStr..string.char(41) -end - -local function getCreatureList() - local crList={} - for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do - for kk,ca in ipairs(cr.caste) do - local str=ca.caste_name[0] - str=str..' '..getGenderString(ca.gender) - table.insert(crList,{str,nil,ca}) - end - end - return crList -end - -local function getRestrictiveMatFilter(itemType) - if not args.restrictive then return nil end - local itemTypes={ - WEAPON=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED) - end, - AMMO=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_AMMO) - end, - ARMOR=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_ARMOR) - end, - INSTRUMENT=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_HARD) - end, - AMULET=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD) - end, - ROCK=function(mat,parent,typ,idx) - return (mat.flags.IS_STONE) - end, - BOULDER=ROCK, - BAR=function(mat,parent,typ,idx) - return (mat.flags.IS_METAL or mat.flags.SOAP or mat.id==COAL) - end - - } - for k,v in ipairs({'GOBLET','FLASK','TOY','RING','CROWN','SCEPTER','FIGURINE','TOOL'}) do - itemTypes[v]=itemTypes.INSTRUMENT - end - for k,v in ipairs({'SHOES','SHIELD','HELM','GLOVES'}) do - itemTypes[v]=itemTypes.ARMOR - end - for k,v in ipairs({'EARRING','BRACELET'}) do - itemTypes[v]=itemTypes.AMULET - end - itemTypes.BOULDER=itemTypes.ROCK - return itemTypes[df.item_type[itemType]] -end - -local function getMatFilter(itemtype) - local itemTypes={ - SEEDS=function(mat,parent,typ,idx) - return mat.flags.SEED_MAT - end, - PLANT=function(mat,parent,typ,idx) - return mat.flags.STRUCTURAL_PLANT_MAT - end, - LEAVES=function(mat,parent,typ,idx) - return mat.flags.LEAF_MAT - end, - MEAT=function(mat,parent,typ,idx) - return mat.flags.MEAT - end, - CHEESE=function(mat,parent,typ,idx) - return (mat.flags.CHEESE_PLANT or mat.flags.CHEESE_CREATURE) - end, - LIQUID_MISC=function(mat,parent,typ,idx) - return (mat.flags.LIQUID_MISC_PLANT or mat.flags.LIQUID_MISC_CREATURE or mat.flags.LIQUID_MISC_OTHER) - end, - POWDER_MISC=function(mat,parent,typ,idx) - return (mat.flags.POWDER_MISC_PLANT or mat.flags.POWDER_MISC_CREATURE) - end, - DRINK=function(mat,parent,typ,idx) - return (mat.flags.ALCOHOL_PLANT or mat.flags.ALCOHOL_CREATURE) - end, - GLOB=function(mat,parent,typ,idx) - return (mat.flags.STOCKPILE_GLOB) - end, - WOOD=function(mat,parent,typ,idx) - return (mat.flags.WOOD) - end, - THREAD=function(mat,parent,typ,idx) - return (mat.flags.THREAD_PLANT) - end, - LEATHER=function(mat,parent,typ,idx) - return (mat.flags.LEATHER) - end - } - return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype) -end - -local function createItem(mat,itemType,quality,creator,description,amount) - local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)) - assert(item, 'failed to create item') - quality = math.max(0, math.min(5, quality - 1)) - item:setQuality(quality) - if df.item_type[itemType[1]]=='SLAB' then - item.description=description - end - if tonumber(amount) > 1 then - item:setStackSize(amount) - end -end - -local function qualityTable() - return {{'None'}, - {'-Well-crafted-'}, - {'+Finely-crafted+'}, - {'*Superior*'}, - {string.char(240)..'Exceptional'..string.char(240)}, - {string.char(15)..'Masterwork'..string.char(15)} - } -end - -local script=require('gui.script') - -local function showItemPrompt(text,item_filter,hide_none) - require('gui.materials').ItemTypeDialog{ - prompt=text, - item_filter=item_filter, - hide_none=hide_none, - on_select=script.mkresume(true), - on_cancel=script.mkresume(false), - on_close=script.qresume(nil) - }:show() - - return script.wait() -end - -local function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) --the one included with DFHack doesn't have a filter or the inorganic, creature, plant things available - require('gui.materials').MaterialDialog{ - frame_title = title, - prompt = prompt, - mat_filter = filter, - use_inorganic = inorganic, - use_creature = creature, - use_plant = plant, - on_select = script.mkresume(true), - on_cancel = script.mkresume(false), - on_close = script.qresume(nil) - }:show() - - return script.wait() -end - -local function usesCreature(itemtype) - typesThatUseCreatures={REMAINS=true,FISH=true,FISH_RAW=true,VERMIN=true,PET=true,EGG=true,CORPSE=true,CORPSEPIECE=true} - return typesThatUseCreatures[df.item_type[itemtype]] -end - -local function getCreatureRaceAndCaste(caste) - return df.global.world.raws.creatures.list_creature[caste.index],df.global.world.raws.creatures.list_caste[caste.index] -end - -function hackWish(unit) - script.start(function() - local amountok, amount - local matok,mattype,matindex,matFilter - local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true) - if not itemok then return end - if not args.notRestrictive then - matFilter=getMatFilter(itemtype) - end - if not usesCreature(itemtype) then - matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter) - if not matok then return end - else - local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList()) - if not creatureok then return end - mattype,matindex=getCreatureRaceAndCaste(creatureTable[3]) - end - local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable()) - if not qualityok then return end - local description - if df.item_type[itemtype]=='SLAB' then - local descriptionok - descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE) - if not descriptionok then return end - end - if args.multi then - repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount) or not amountok - if not amountok then return end - if mattype and itemtype then - if df.item_type.attrs[itemtype].is_stackable then - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,amount) - else - for i=1,amount do - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1) - end - end - return true - end - return false - else - if mattype and itemtype then - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1) - return true - end - return false - end - end) -end - -scriptArgs={...} - -utils=require('utils') - -validArgs = validArgs or utils.invert({ - 'startup', - 'all', - 'restrictive', - 'unit', - 'multi' -}) - -args = utils.processArgs({...}, validArgs) - -eventful=require('plugins.eventful') - -if not args.startup then - local unit=args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) - if unit then - hackWish(unit) - else - qerror('A unit needs to be selected to use gui/create-item.') - end -else - eventful.onReactionComplete.hackWishP=function(reaction,unit,input_items,input_reagents,output_items,call_native) - if not reaction.code:find('DFHACK_WISH') then return nil end - hackWish(unit) - end -end diff --git a/scripts/gui/dfstatus.lua b/scripts/gui/dfstatus.lua deleted file mode 100644 index 9789affbc..000000000 --- a/scripts/gui/dfstatus.lua +++ /dev/null @@ -1,229 +0,0 @@ --- a quick access status screen --- originally written by enjia2000@gmail.com (stolencatkarma) - ---[[=begin - -gui/dfstatus -============ -Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. -Sections can be enabled/disabled/configured by editing ``dfhack-config/dfstatus.lua``. - -=end]] -local gui = require 'gui' - -function warn(msg) - dfhack.color(COLOR_LIGHTRED) - print(msg) - dfhack.color(nil) -end - -config = { - flags = { - drink = true, - wood = true, - fuel = true, - prepared_meals = true, - tanned_hides = true, - cloth = true, - metals = true, - }, - metal_ids = {}, -} - -function parse_config() - local metal_map = {} - for id, raw in pairs(df.global.world.raws.inorganics) do - if raw.material.flags.IS_METAL then - metal_map[raw.id:upper()] = id - metal_map[id] = raw.id:upper() - end - end - - local function add_metal(...) - for _, m in pairs({...}) do - id = metal_map[tostring(m):upper()] - if id ~= nil then - table.insert(config.metal_ids, id) - elseif m == '-' then - table.insert(config.metal_ids, '-') - else - warn('Invalid metal: ' .. tostring(m)) - end - end - return add_metal - end - - local env = {} - setmetatable(env, { - __index = function(_, k) - if k == 'metal' or k == 'metals' then - return add_metal - elseif k == 'flags' then - return config.flags - else - error('unknown name: ' .. k, 2) - end - end, - __newindex = function(_, k, v) - if config.flags[k] ~= nil then - if v ~= nil then - config.flags[k] = v - else - config.flags[k] = false - end - else - error('unknown flag: ' .. k, 2) - end - end, - }) - local f, err = loadfile('dfhack-config/dfstatus.lua', 't', env) - if not f then - qerror('error loading config: ' .. err) - end - local ok, err = pcall(f) - if not ok then - qerror('error parsing config: ' .. err) - end -end - -function getInorganicName(id) - return (df.inorganic_raw.find(id).material.state_name.Solid:gsub('^[a-z]', string.upper)) -end - -dfstatus = defclass(dfstatus, gui.FramedScreen) -dfstatus.ATTRS = { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'dfstatus', - frame_width = 16, - frame_height = 17, - frame_inset = 1, - focus_path = 'dfstatus', -} - -function dfstatus:init() - self.text = {} - self.start = 1 - local function write(line) - table.insert(self.text, line) - -- ensure that the window is wide enough for this line plus a scroll arrow - if #line + 1 > self.frame_width then - self.frame_width = #line + 1 - end - end - local function newline() write('') end - local f = config.flags - - local drink = 0 - local wood = 0 - local fuel = 0 - - local prepared_meals = 0 - local tanned_hides = 0 - local cloth = 0 - - local metals = {} - for _, id in pairs(config.metal_ids) do - metals[id] = 0 - end - - for _, item in ipairs(df.global.world.items.all) do - if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then - if item:getType() == df.item_type.WOOD then - wood = wood + item:getStackSize() - elseif item:getType() == df.item_type.DRINK then - drink = drink + item:getStackSize() - elseif item:getType() == df.item_type.SKIN_TANNED then - tanned_hides = tanned_hides + item:getStackSize() - elseif item:getType() == df.item_type.CLOTH then - cloth = cloth + item:getStackSize() - elseif item:getType() == df.item_type.FOOD then - prepared_meals = prepared_meals + item:getStackSize() - elseif item:getType() == df.item_type.BAR then - if item:getMaterial() == df.builtin_mats.COAL then - fuel = fuel + item:getStackSize() - elseif item:getMaterial() == df.builtin_mats.INORGANIC then - local mat_idx = item:getMaterialIndex() - if metals[mat_idx] ~= nil then - metals[mat_idx] = metals[mat_idx] + item:getStackSize() - end - end - end - end - end - if f.drink then - write("Drinks: " .. drink) - end - if f.prepared_meals then - write("Meals: " .. prepared_meals) - end - if f.drink or f.prepared_meals then - newline() - end - if f.wood then - write("Wood: " .. wood) - end - if f.fuel then - write("Fuel: " .. fuel) - end - if f.wood or f.fuel then - newline() - end - if f.tanned_hides then - write("Hides: " .. tanned_hides) - end - if f.cloth then - write("Cloth: " .. cloth) - end - if f.tanned_hides or f.cloth then - newline() - end - if f.metals then - write("Metal bars:") - for _, id in pairs(config.metal_ids) do - if id == '-' then - newline() - else - write(' ' .. ('%-10s'):format(getInorganicName(id) .. ': ') .. metals[id]) - end - end - end - self.start_min = 1 - self.start_max = #self.text - self.frame_height + 1 -end - -function dfstatus:onRenderBody(dc) - dc:pen(COLOR_LIGHTGREEN) - for id, line in pairs(self.text) do - if id >= self.start then - dc:string(line):newline() - end - end - dc:pen(COLOR_LIGHTCYAN) - if self.start > self.start_min then - dc:seek(self.frame_width - 1, 0):char(24) - end - if self.start < self.start_max then - dc:seek(self.frame_width - 1, self.frame_height - 1):char(25) - end -end - -function dfstatus:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - scr = nil - elseif keys.STANDARDSCROLL_UP then - self.start = math.max(self.start - 1, self.start_min) - elseif keys.STANDARDSCROLL_DOWN then - self.start = math.min(self.start + 1, self.start_max) - end -end - -if not scr then - parse_config() - scr = dfstatus() - scr:show() -else - scr:dismiss() - scr = nil -end - diff --git a/scripts/gui/family-affairs.lua b/scripts/gui/family-affairs.lua deleted file mode 100644 index e3806e9f2..000000000 --- a/scripts/gui/family-affairs.lua +++ /dev/null @@ -1,292 +0,0 @@ --- gui/family-affairs --- derived from v1.2 @ http://www.bay12forums.com/smf/index.php?topic=147779 -local help = [[=begin - -gui/family-affairs -================== -A user-friendly interface to view romantic relationships, -with the ability to add, remove, or otherwise change them at -your whim - fantastic for depressed dwarves with a dead spouse -(or matchmaking players...). - -The target/s must be alive, sane, and in fortress mode. - -.. image:: /docs/images/family-affairs.png - :align: center - -``gui/family-affairs [unitID]`` - shows GUI for the selected unit, or the specified unit ID - -``gui/family-affairs divorce [unitID]`` - removes all spouse and lover information from the unit - and it's partner, bypassing almost all checks. - -``gui/family-affairs [unitID] [unitID]`` - divorces the two specificed units and their partners, - then arranges for the two units to marry, bypassing - almost all checks. Use with caution. - -=end]] - -helpstr = help:gsub('=begin', ''):gsub('=end', '') - -local dlg = require ('gui.dialogs') - -function ErrorPopup (msg,color) - if not tostring(msg) then msg = "Error" end - if not color then color = COLOR_LIGHTRED end - dlg.showMessage("Dwarven Family Affairs", msg, color, nil) -end - -function AnnounceAndGamelog(text) - dfhack.gui.showAnnouncement(text, COLOR_LIGHTMAGENTA) -end - -function ListPrompt (msg, choicelist, bool, yes_func) -dlg.showListPrompt( - "Dwarven Family Affairs", - msg, - COLOR_WHITE, - choicelist, - --called if choice is yes - yes_func, - --called on cancel - function() end, - 15, - bool - ) -end - -function GetMarriageSummary (source) - local familystate = "" - - if source.relations.spouse_id ~= -1 then - if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) then - familystate = dfhack.TranslateName(source.name).." has a spouse ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")" - end - if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) == false then - familystate = dfhack.TranslateName(source.name).."'s spouse is dead or not sane, would you like to choose a new one?" - end - end - - if source.relations.spouse_id == -1 and source.relations.lover_id ~= -1 then - if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) then - familystate = dfhack.TranslateName(source.name).." already has a lover ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")" - end - if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) == false then - familystate = dfhack.TranslateName(source.name).."'s lover is dead or not sane, would you like that love forgotten?" - end - end - - if source.relations.spouse_id == -1 and source.relations.lover_id == -1 then - familystate = dfhack.TranslateName(source.name).." is not involved in romantic relationships with anyone" - end - - if source.relations.pregnancy_timer > 0 then - familystate = familystate.."\nShe is pregnant." - local father = df.historical_figure.find(source.relations.pregnancy_spouse) - if father then - familystate = familystate.." The father is "..dfhack.TranslateName(father.name).."." - end - end - - return familystate -end - -function GetSpouseData (source) - local spouse = df.unit.find(source.relations.spouse_id) - local spouse_hf - if spouse then - spouse_hf = df.historical_figure.find (spouse.hist_figure_id) - end - return spouse,spouse_hf -end - -function GetLoverData (source) - local lover = df.unit.find(source.relations.spouse_id) - local lover_hf - if lover then - lover_hf = df.historical_figure.find (lover.hist_figure_id) - end - return lover,lover_hf -end - -function EraseHFLinksLoverSpouse (hf) - for i = #hf.histfig_links-1,0,-1 do - if hf.histfig_links[i]._type == df.histfig_hf_link_spousest or hf.histfig_links[i]._type == df.histfig_hf_link_loverst then - local todelete = hf.histfig_links[i] - hf.histfig_links:erase(i) - todelete:delete() - end - end -end - -function Divorce (source) - local source_hf = df.historical_figure.find(source.hist_figure_id) - local spouse,spouse_hf = GetSpouseData (source) - local lover,lover_hf = GetLoverData (source) - - source.relations.spouse_id = -1 - source.relations.lover_id = -1 - - if source_hf then - EraseHFLinksLoverSpouse (source_hf) - end - if spouse then - spouse.relations.spouse_id = -1 - spouse.relations.lover_id = -1 - end - if lover then - spouse.relations.spouse_id = -1 - spouse.relations.lover_id = -1 - end - if spouse_hf then - EraseHFLinksLoverSpouse (spouse_hf) - end - if lover_hf then - EraseHFLinksLoverSpouse (lover_hf) - end - - local partner = spouse or lover - if not partner then - AnnounceAndGamelog(dfhack.TranslateName(source.name).." is now single") - else - AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(partner.name).." are now single") - end -end - -function Marriage (source,target) - local source_hf = df.historical_figure.find(source.hist_figure_id) - local target_hf = df.historical_figure.find(target.hist_figure_id) - source.relations.spouse_id = target.id - target.relations.spouse_id = source.id - - local new_link = df.histfig_hf_link_spousest:new() -- adding hf link to source - new_link.target_hf = target_hf.id - new_link.link_strength = 100 - source_hf.histfig_links:insert('#',new_link) - - new_link = df.histfig_hf_link_spousest:new() -- adding hf link to target - new_link.target_hf = source_hf.id - new_link.link_strength = 100 - target_hf.histfig_links:insert('#',new_link) -end - -function ChooseNewSpouse (source) - - if not source then - qerror("no unit") return - end - if not dfhack.units.isAdult(source) then - ErrorPopup("target is too young") return - end - if not (source.relations.spouse_id == -1 and source.relations.lover_id == -1) then - ErrorPopup("target already has a spouse or a lover") - qerror("source already has a spouse or a lover") - return - end - - local choicelist = {} - targetlist = {} - - for k,v in pairs (df.global.world.units.active) do - if dfhack.units.isCitizen(v) - and v.race == source.race - and v.sex ~= source.sex - and v.relations.spouse_id == -1 - and v.relations.lover_id == -1 - and dfhack.units.isAdult(v) - then - table.insert(choicelist,dfhack.TranslateName(v.name)..', '..dfhack.units.getProfessionName(v)) - table.insert(targetlist,v) - end - end - - if #choicelist > 0 then - ListPrompt( - "Assign new spouse for "..dfhack.TranslateName(source.name), - choicelist, - true, - function(a,b) - local target = targetlist[a] - Marriage (source,target) - AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(target.name).." have married!") - end) - else - ErrorPopup("No suitable candidates") - end -end - -function MainDialog (source) - - local familystate = GetMarriageSummary(source) - - familystate = familystate.."\nSelect action:" - local choicelist = {} - local on_select = {} - - local adult = dfhack.units.isAdult(source) - local single = source.relations.spouse_id == -1 and source.relations.lover_id == -1 - local ready_for_marriage = single and adult - - if adult then - table.insert(choicelist,"Remove romantic relationships (if any)") - table.insert(on_select, Divorce) - if ready_for_marriage then - table.insert(choicelist,"Assign a new spouse") - table.insert(on_select,ChooseNewSpouse) - end - if not ready_for_marriage then - table.insert(choicelist,"[Assign a new spouse]") - table.insert(on_select,function () ErrorPopup ("Existing relationships must be removed if you wish to assign a new spouse.") end) - end - else - table.insert(choicelist,"Leave this child alone") - table.insert(on_select,nil) - end - - ListPrompt(familystate, choicelist, false, - function(a,b) if on_select[a] then on_select[a](source) end end) -end - - -local args = {...} - -if args[1] == "help" or args[1] == "?" then print(helpstr) return end - -if not dfhack.world.isFortressMode() then - print (helpstr) qerror ("invalid game mode") return -end - -if args[1] == "divorce" and tonumber(args[2]) then - local unit = df.unit.find(args[2]) - if unit then Divorce (unit) return end -end - -if tonumber(args[1]) and tonumber(args[2]) then - local unit1 = df.unit.find(args[1]) - local unit2 = df.unit.find(args[2]) - if unit1 and unit2 then - Divorce (unit1) - Divorce (unit2) - Marriage (unit1,unit2) - return - end -end - -local selected = dfhack.gui.getSelectedUnit(true) -if tonumber(args[1]) then - selected = df.unit.find(tonumber(args[1])) or selected -end - -if selected then - if dfhack.units.isCitizen(selected) and dfhack.units.isSane(selected) then - MainDialog(selected) - else - qerror("You must select a sane fortress citizen.") - return - end -else - print (helpstr) - qerror("Select a sane fortress dwarf") -end diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua deleted file mode 100644 index 38394ec36..000000000 --- a/scripts/gui/gm-editor.lua +++ /dev/null @@ -1,536 +0,0 @@ --- Interface powered item editor. - ---[[=begin - -gui/gm-editor -============= -This editor allows to change and modify almost anything in df. Press :kbd:`?` for -in-game help. There are three ways to open this editor: - -* Callling ``gui/gm-editor`` from a command or keybinding opens the editor - on whatever is selected or viewed (e.g. unit/item description screen) - -* using gui/gm-editor - executes lua command and opens editor on - its results (e.g. ``gui/gm-editor "df.global.world.items.all"`` shows all items) - -* using gui/gm-editor dialog - shows an in game dialog to input lua command. Works - the same as version above. - -.. image:: /docs/images/gm-editor.png - -=end]] -local gui = require 'gui' -local dialog = require 'gui.dialogs' -local widgets =require 'gui.widgets' -local guiScript = require 'gui.script' -local args={...} - -find_funcs = find_funcs or (function() - local t = {} - for k in pairs(df) do - pcall(function() - t[k] = df[k].find - end) - end - return t -end)() - -local keybindings={ - offset={key="CUSTOM_ALT_O",desc="Show current items offset"}, - find={key="CUSTOM_F",desc="Find a value by entering a predicate"}, - find_id={key="CUSTOM_I",desc="Find object with this ID"}, - lua_set={key="CUSTOM_ALT_S",desc="Set by using a lua function"}, - insert={key="CUSTOM_ALT_I",desc="Insert a new value to the vector"}, - delete={key="CUSTOM_ALT_D",desc="Delete selected entry"}, - reinterpret={key="CUSTOM_ALT_R",desc="Open selected entry as something else"}, - start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"}, - help={key="HELP",desc="Show this help"}, - displace={key="STRING_A093",desc="Open reference offseted by index"}, - NOT_USED={key="SEC_SELECT",desc="Edit selected entry as a number (for enums)"}, --not a binding... -} -function getTargetFromScreens() - local my_trg - if dfhack.gui.getCurFocus() == 'item' then - my_trg=dfhack.gui.getCurViewscreen().item - elseif dfhack.gui.getCurFocus() == 'joblist' then - local t_screen=dfhack.gui.getCurViewscreen() - my_trg=t_screen.jobs[t_screen.cursor_pos] - elseif dfhack.gui.getCurFocus() == 'createquota' then - local t_screen=dfhack.gui.getCurViewscreen() - my_trg=t_screen.orders[t_screen.sel_idx] - elseif dfhack.gui.getCurFocus() == 'dwarfmode/LookAround/Flow' then - local t_look=df.global.ui_look_list.items[df.global.ui_look_cursor] - my_trg=t_look.flow - - elseif dfhack.gui.getSelectedUnit(true) then - my_trg=dfhack.gui.getSelectedUnit(true) - elseif dfhack.gui.getSelectedItem(true) then - my_trg=dfhack.gui.getSelectedItem(true) - elseif dfhack.gui.getSelectedJob(true) then - my_trg=dfhack.gui.getSelectedJob(true) - else - qerror("No valid target found") - end - return my_trg -end -function search_relevance(search, candidate) - local function clean(str) - return ' ' .. str:lower():gsub('[^a-z0-9]','') .. ' ' - end - search = clean(search) - candidate = clean(candidate) - local ret = 0 - while #search > 0 do - local pos = candidate:find(search:sub(1, 1), 1, true) - if pos then - ret = ret + (#search - pos) - candidate = candidate:sub(pos + 1) - end - search = search:sub(2) - end - return ret -end - - -GmEditorUi = defclass(GmEditorUi, gui.FramedScreen) -GmEditorUi.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "GameMaster's editor", - } -function GmEditorUi:onHelp() - self.subviews.pages:setSelected(2) -end -function burning_red(input) -- todo does not work! bug angavrilov that so that he would add this, very important!! - local col=COLOR_LIGHTRED - return {text=input,pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}} -end -function Disclaimer(tlb) - local dsc={"Association Of ",{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}, - "Dwarves (AOPD) is not responsible for all the damage",NEWLINE,"that this tool can (and will) cause to you and your loved dwarves",NEWLINE,"and/or saves.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} - if tlb then - for _,v in ipairs(dsc) do - table.insert(tlb,v) - end - end - return dsc -end - -function GmEditorUi:init(args) - self.stack={} - self.item_count=0 - self.keys={} - local helptext={{text="Help"},NEWLINE,NEWLINE} - for k,v in pairs(keybindings) do - table.insert(helptext,{text=v.desc,key=v.key,key_sep=': '}) - table.insert(helptext,NEWLINE) - end - table.insert(helptext,NEWLINE) - Disclaimer(helptext) - - local helpPage=widgets.Panel{ - subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} - local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"), - on_submit2=self:callback("editSelectedRaw"), - text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}} - local mainPage=widgets.Panel{ - subviews={ - mainList, - widgets.Label{text={{text="",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, - widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2}, - on_click=function() self:enable_input(true) end}, - widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"}, - --widgets.Label{text="BLAH2"} - } - ,view_id='page_main'} - - local pages=widgets.Pages{subviews={mainPage,helpPage},view_id="pages"} - self:addviews{ - pages - } - self:pushTarget(args.target) -end -function GmEditorUi:text_input(new_text) - self:updateTarget(true,true) -end -function GmEditorUi:enable_input(enable) - self.subviews.filter_input.active=enable -end -function GmEditorUi:find(test) - local trg=self:currentTarget() - - if test== nil then - dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self)) - return - end - - local e,what=load("return function(k,v) return "..test.." end") - if e==nil then - dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_LIGHTRED) - end - - if trg.target and trg.target._kind and trg.target._kind=="container" then - - for k,v in pairs(trg.target) do - if e()(k,v)==true then - self:pushTarget(v) - return - end - end - else - local i=1 - for k,v in pairs(trg.target) do - if e()(k,v)==true then - self.subviews.list_main:setSelected(i) - return - end - i=i+1 - end - end -end -function GmEditorUi:find_id() - local key = tostring(self:getSelectedKey()) - local id = tonumber(self:getSelectedValue()) - if not id then return end - local opts = {} - for name, func in pairs(find_funcs) do - table.insert(opts, {text=name, callback=func, weight=search_relevance(key, name)}) - end - table.sort(opts, function(a, b) - return a.weight > b.weight - end) - guiScript.start(function() - local ret,idx,choice=guiScript.showListPrompt("Choose type:",nil,3,opts,nil,true) - if ret then - local obj = choice.callback(id) - if obj then - self:pushTarget(obj) - else - dialog.showMessage("Error!", ('%s with ID %d not found'):format(choice.text, id), COLOR_LIGHTRED) - end - end - end) -end -function GmEditorUi:insertNew(typename) - local tp=typename - if typename == nil then - dialog.showInputPrompt("Class type","You can:\n * Enter type name (without 'df.')\n * Leave empty for default type and 'nil' value\n * Enter '*' for default type and 'new' constructed pointer value",COLOR_WHITE,"",self:callback("insertNew")) - return - end - - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="container" then - if tp == "" then - trg.target:resize(#trg.target+1) - elseif tp== "*" then - trg.target:insert("#",{new=true}) - else - local ntype=df[tp] - if ntype== nil then - dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED) - return - end - trg.target:insert("#",{new=ntype}) - end - self:updateTarget(true,true) - end -end -function GmEditorUi:deleteSelected(key) - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="container" then - trg.target:erase(key) - self:updateTarget(true,true) - end -end -function GmEditorUi:getSelectedKey() - return self:currentTarget().keys[self.subviews.list_main:getSelected()] -end -function GmEditorUi:getSelectedValue() - return self:currentTarget().target[self:getSelectedKey()] -end -function GmEditorUi:currentTarget() - return self.stack[#self.stack] -end -function GmEditorUi:getSelectedEnumType() - local trg=self:currentTarget() - local trg_key=trg.keys[self.subviews.list_main:getSelected()] - - local ok,ret=pcall(function () --super safe way to check if the field has enum - return trg.target._field==nil or trg.target:_field(trg_key)==nil - end) - if not ok or ret==true then - return nil - end - - local enum=trg.target:_field(trg_key)._type - if enum._kind=="enum-type" then - return enum - else - return nil - end -end -function GmEditorUi:editSelectedEnum(index,choice) - local enum=self:getSelectedEnumType() - if enum then - local trg=self:currentTarget() - local trg_key=self:getSelectedKey() - local list={} - for i=enum._first_item, enum._last_item do - table.insert(list,{text=('%s (%i)'):format(tostring(enum[i]), i),value=i}) - end - guiScript.start(function() - local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list,nil,true) - if ret then - trg.target[trg_key]=choice.value - self:updateTarget(true) - end - end) - - else - qerror("not an enum") - end -end -function GmEditorUi:openReinterpret(key) - local trg=self:currentTarget() - dialog.showInputPrompt(tostring(trg_key),"Enter new type:",COLOR_WHITE, - "",function(choice) - local ntype=df[tp] - self:pushTarget(df.reinterpret_cast(ntype,trg.target[key])) - end) -end -function GmEditorUi:openOffseted(index,choice) - local trg=self:currentTarget() - local trg_key=trg.keys[index] - - dialog.showInputPrompt(tostring(trg_key),"Enter offset:",COLOR_WHITE,"", - function(choice) - self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice))) - end) -end -function GmEditorUi:editSelectedRaw(index,choice) - self:editSelected(index, choice, {raw=true}) -end -function GmEditorUi:editSelected(index,choice,opts) - opts = opts or {} - local trg=self:currentTarget() - local trg_key=trg.keys[index] - if trg.target and trg.target._kind and trg.target._kind=="bitfield" then - trg.target[trg_key]= not trg.target[trg_key] - self:updateTarget(true) - else - --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") - local trg_type=type(trg.target[trg_key]) - if self:getSelectedEnumType() and not opts.raw then - self:editSelectedEnum() - elseif trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected - dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE, - tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key)) - - elseif trg_type == 'boolean' then - trg.target[trg_key] = not trg.target[trg_key] - self:updateTarget(true) - elseif trg_type == 'userdata' or trg_type == 'table' then - self:pushTarget(trg.target[trg_key]) - elseif trg_type == 'nil' or trg_type == 'function' then - -- ignore - else - print("Unknown type:"..trg_type) - pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end) - end - end -end - -function GmEditorUi:commitEdit(key,value) - local trg=self:currentTarget() - if type(trg.target[key])=='number' then - trg.target[key]=tonumber(value) - elseif type(trg.target[key])=='string' then - trg.target[key]=value - end - self:updateTarget(true) -end - -function GmEditorUi:set(key,input) - local trg=self:currentTarget() - - if input== nil then - dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",self:callback("set",key)) - return - end - local e,what=load("return function(v) return "..input.." end") - if e==nil then - dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_LIGHTRED) - return - end - trg.target[key]=e()(trg) - self:updateTarget(true) -end -function GmEditorUi:onInput(keys) - if keys.LEAVESCREEN then - if self.subviews.filter_input.active then - self:enable_input(false) - return - end - if self.subviews.pages:getSelected()==2 then - self.subviews.pages:setSelected(1) - else - self:popTarget() - end - end - - if self.subviews.filter_input.active then - self.super.onInput(self,keys) - return - end - - if keys[keybindings.offset.key] then - local trg=self:currentTarget() - local _,stoff=df.sizeof(trg.target) - local size,off=df.sizeof(trg.target:_field(self:getSelectedKey())) - dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE) - elseif keys[keybindings.displace.key] then - self:openOffseted(self.subviews.list_main:getSelected()) - elseif keys[keybindings.find.key] then - self:find() - elseif keys[keybindings.find_id.key] then - self:find_id() - elseif keys[keybindings.lua_set.key] then - self:set(self:getSelectedKey()) - elseif keys[keybindings.insert.key] then --insert - self:insertNew() - elseif keys[keybindings.delete.key] then --delete - self:deleteSelected(self:getSelectedKey()) - elseif keys[keybindings.reinterpret.key] then - self:openReinterpret(self:getSelectedKey()) - elseif keys[keybindings.start_filter.key] then - self:enable_input(true) - return - end - - self.super.onInput(self,keys) -end -function getStringValue(trg,field) - local obj=trg.target - - local text=tostring(obj[field]) - pcall(function() - if obj._field ~= nil then - local enum=obj:_field(field)._type - if enum._kind=="enum-type" then - text=text.." ("..tostring(enum[obj[field]])..")" - end - end - end) - return text -end -function GmEditorUi:updateTarget(preserve_pos,reindex) - local trg=self:currentTarget() - local filter=self.subviews.filter_input.text - - if reindex then - trg.keys={} - for k,v in pairs(trg.target) do - if filter~= "" then - local ok,ret=dfhack.pcall(string.match,tostring(k),filter) - if not ok then - table.insert(trg.keys,k) - elseif ret then - table.insert(trg.keys,k) - end - else - table.insert(trg.keys,k) - end - end - end - self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target) - local t={} - for k,v in pairs(trg.keys) do - table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=getStringValue(trg,v)}}}) - end - local last_pos - if preserve_pos then - last_pos=self.subviews.list_main:getSelected() - end - self.subviews.list_main:setChoices(t) - if last_pos then - self.subviews.list_main:setSelected(last_pos) - else - self.subviews.list_main:setSelected(trg.selected) - end -end -function GmEditorUi:pushTarget(target_to_push) - local new_tbl={} - new_tbl.target=target_to_push - new_tbl.keys={} - new_tbl.selected=1 - new_tbl.filter="" - if self:currentTarget()~=nil then - self:currentTarget().selected=self.subviews.list_main:getSelected() - self.stack[#self.stack].filter=self.subviews.filter_input.text - end - for k,v in pairs(target_to_push) do - table.insert(new_tbl.keys,k) - end - new_tbl.item_count=#new_tbl.keys - table.insert(self.stack,new_tbl) - self.subviews.filter_input.text="" - self:updateTarget() -end -function GmEditorUi:popTarget() - table.remove(self.stack) --removes last element - if #self.stack==0 then - self:dismiss() - return - end - self.subviews.filter_input.text=self.stack[#self.stack].filter --restore filter - self:updateTarget() -end -function show_editor(trg) - if not trg then - qerror('Target not found') - end - local screen = GmEditorUi{target=trg} - screen:show() -end -eval_env = {} -setmetatable(eval_env, {__index = function(_, k) - if k == 'scr' or k == 'screen' then - return dfhack.gui.getCurViewscreen() - elseif k == 'bld' or k == 'building' then - return dfhack.gui.getSelectedBuilding() - elseif k == 'item' then - return dfhack.gui.getSelectedItem() - elseif k == 'job' then - return dfhack.gui.getSelectedJob() - elseif k == 'wsjob' or k == 'workshop_job' then - return dfhack.gui.getSelectedWorkshopJob() - elseif k == 'unit' then - return dfhack.gui.getSelectedUnit() - else - for g in pairs(df.global) do - if g == k then - return df.global[k] - end - end - return _G[k] - end -end}) -function eval(s) - local f, err = load("return " .. s, "expression", "t", eval_env) - if err then qerror(err) end - return f() -end -if #args~=0 then - if args[1]=="dialog" then - function thunk(entry) - show_editor(eval(entry)) - end - dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) - elseif args[1]=="free" then - show_editor(df.reinterpret_cast(df[args[2]],args[3])) - else - show_editor(eval(args[1])) - end -else - show_editor(getTargetFromScreens()) -end - diff --git a/scripts/gui/gm-unit.lua b/scripts/gui/gm-unit.lua deleted file mode 100644 index 3432882cb..000000000 --- a/scripts/gui/gm-unit.lua +++ /dev/null @@ -1,604 +0,0 @@ --- Interface powered, user friendly, unit editor - ---[[=begin - -gui/gm-unit -=========== -An editor for various unit attributes. - -=end]] -local gui = require 'gui' -local dialog = require 'gui.dialogs' -local widgets =require 'gui.widgets' -local guiScript = require 'gui.script' -local utils = require 'utils' -local args={...} - - -local target ---TODO: add more ways to guess what unit you want to edit -if args[1]~= nil then - target=df.units.find(args[1]) -else - target=dfhack.gui.getSelectedUnit(true) -end - -if target==nil then - qerror("No unit to edit") --TODO: better error message -end -local editors={} -function add_editor(editor_class) - table.insert(editors,{text=editor_class.ATTRS.frame_title,on_submit=function ( unit ) - editor_class{target_unit=unit}:show() - end}) -end --------------------------------various subeditors--------- ---TODO set local sould or better yet skills vector to reduce long skill list access typing -editor_skills=defclass(editor_skills,gui.FramedScreen) -editor_skills.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Skill editor", - target_unit = DEFAULT_NIL, - learned_only= false, -} -function list_skills(unit,learned_only) - local s_=df.job_skill - local u_skills=unit.status.current_soul.skills - local ret={} - for i,v in ipairs(s_) do - if i>0 then - local u_skill=utils.binsearch(u_skills,i,"id") - if u_skill or not learned_only then - if not u_skill then - u_skill={rating=-1,experience=0} - end - - local rating - if u_skill.rating >=0 then - rating=df.skill_rating.attrs[u_skill.rating] - else - rating={caption="",xp_threshold=0} - end - - local text=string.format("%s: %s %d %d/%d",df.job_skill.attrs[i].caption,rating.caption,u_skill.rating,u_skill.experience,rating.xp_threshold) - table.insert(ret,{text=text,id=i}) - end - end - end - return ret -end -function editor_skills:update_list(no_save_place) - local skill_list=list_skills(self.target_unit,self.learned_only) - if no_save_place then - self.subviews.skills:setChoices(skill_list) - else - self.subviews.skills:setChoices(skill_list,self.subviews.skills:getSelected()) - end -end -function editor_skills:init( args ) - if self.target_unit.status.current_soul==nil then - qerror("Unit does not have soul, can't edit skills") - end - - local skill_list=list_skills(self.target_unit,self.learned_only) - - self:addviews{ - widgets.FilteredList{ - choices=skill_list, - frame = {t=0, b=1,l=1}, - view_id="skills", - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - {text=": remove level ", - key = "SECONDSCROLL_UP", - on_activate=self:callback("level_skill",-1)}, - {text=": add level ", - key = "SECONDSCROLL_DOWN", - on_activate=self:callback("level_skill",1)} - , - {text=": show learned only ", - key = "CHANGETAB", - on_activate=function () - self.learned_only=not self.learned_only - self:update_list(true) - end} - } - }, - } -end -function editor_skills:get_cur_skill() - local list_wid=self.subviews.skills - local _,choice=list_wid:getSelected() - if choice==nil then - qerror("Nothing selected") - end - local u_skill=utils.binsearch(self.target_unit.status.current_soul.skills,choice.id,"id") - return choice,u_skill -end -function editor_skills:level_skill(lvl) - local sk_en,sk=self:get_cur_skill() - if lvl >0 then - local rating - - if sk then - rating=sk.rating+lvl - else - rating=lvl-1 - end - - utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=rating}, 'id') --TODO set exp? - elseif sk and sk.rating==0 and lvl<0 then - utils.erase_sorted_key(self.target_unit.status.current_soul.skills,sk_en.id,"id") - elseif sk and lvl<0 then - utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=sk.rating+lvl}, 'id') --TODO set exp? - end - self:update_list() -end -function editor_skills:remove_rust(skill) - --TODO -end -add_editor(editor_skills) -------- civ editor -RaceBox = defclass(RaceBox, dialog.ListBox) -RaceBox.focus_path = 'RaceBox' - -RaceBox.ATTRS{ - format_name="$NAME ($TOKEN)", - with_filter=true, - allow_none=false, -} -function RaceBox:format_creature(creature_raw) - local t = {NAME=creature_raw.name[0],TOKEN=creature_raw.creature_id} - return string.gsub(self.format_name, "%$(%w+)", t) -end -function RaceBox:preinit(info) - self.format_name=RaceBox.ATTRS.format_name or info.format_name -- preinit does not have ATTRS set yet - local choices={} - if RaceBox.ATTRS.allow_none or info.allow_none then - table.insert(choices,{text="",num=-1}) - end - for i,v in ipairs(df.global.world.raws.creatures.all) do - local text=self:format_creature(v) - table.insert(choices,{text=text,raw=v,num=i,search_key=text:lower()}) - end - info.choices=choices -end -function showRacePrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_none) - RaceBox{ - frame_title = title, - text = text, - text_pen = tcolor, - on_select = on_select, - on_cancel = on_cancel, - frame_width = min_width, - allow_none = allow_none, - }:show() -end -CivBox = defclass(CivBox,dialog.ListBox) -CivBox.focus_path = "CivBox" - -CivBox.ATTRS={ - format_name="$NAME ($ENGLISH):$ID", - format_no_name=":$ID", - name_other="", - with_filter=true, - allow_other=false, -} - -function civ_name(id,format_name,format_no_name,name_other,name_invalid) - if id==-1 then - return name_other or "" - end - local civ - if type(id)=='userdata' then - civ=id - else - civ=df.historical_entity.find(id) - if civ==nil then - return name_invalid or "" - end - end - local t={NAME=dfhack.TranslateName(civ.name),ENGLISH=dfhack.TranslateName(civ.name,true),ID=civ.id} --TODO race?, maybe something from raws? - if t.NAME=="" then - return string.gsub(format_no_name or ":$ID", "%$(%w+)", t) - end - return string.gsub(format_name or "$NAME ($ENGLISH):$ID", "%$(%w+)", t) -end -function CivBox:update_choices() - local choices={} - if self.allow_other then - table.insert(choices,{text=self.name_other,num=-1}) - end - - for i,v in ipairs(df.global.world.entities.all) do - if not self.race_filter or (v.race==self.race_filter) then --TODO filter type - local text=civ_name(v,self.format_name,self.format_no_name,self.name_other,self.name_invalid) - table.insert(choices,{text=text,raw=v,num=i}) - end - end - self.choices=choices - if self.subviews.list then - self.subviews.list:setChoices(self.choices) - end -end -function CivBox:update_race_filter(id) - local raw=df.creature_raw.find(id) - if raw then - self.subviews.race_label:setText(": "..raw.name[0]) - self.race_filter=id - else - self.subviews.race_label:setText(": ") - self.race_filter=nil - end - - self:update_choices() -end -function CivBox:choose_race() - showRacePrompt("Choose race","Select new race:",nil,function (id,choice) - self:update_race_filter(choice.num) - end,nil,nil,true) -end -function CivBox:init(info) - self.subviews.list.frame={t=3,r=0,l=0} - self:addviews{ - widgets.Label{frame={t=1,l=0},text={ - {text="Filter race ",key="CUSTOM_CTRL_A",key_sep="()",on_activate=self:callback("choose_race")}, - }}, - widgets.Label{frame={t=1,l=21},view_id="race_label", - text=": ", - } - } - self:update_choices() -end -function showCivPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other) - CivBox{ - frame_title = title, - text = text, - text_pen = tcolor, - on_select = on_select, - on_cancel = on_cancel, - frame_width = min_width, - allow_other = allow_other, - }:show() -end - -editor_civ=defclass(editor_civ,gui.FramedScreen) -editor_civ.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Civilization editor", - target_unit = DEFAULT_NIL, - } - -function editor_civ:update_curren_civ() - self.subviews.civ_name:setText("Currently: "..civ_name(self.target_unit.civ_id)) -end -function editor_civ:init( args ) - if self.target_unit==nil then - qerror("invalid unit") - end - - self:addviews{ - widgets.Label{view_id="civ_name",frame = { t=1,l=1}, text="Currently: "..civ_name(self.target_unit.civ_id)}, - widgets.Label{frame = { t=2,l=1}, text={{text=": set to other (-1, usually enemy)",key="CUSTOM_N", - on_activate= function() self.target_unit.civ_id=-1;self:update_curren_civ() end}}}, - widgets.Label{frame = { t=3,l=1}, text={{text=": set to current civ("..df.global.ui.civ_id..")",key="CUSTOM_C", - on_activate= function() self.target_unit.civ_id=df.global.ui.civ_id;self:update_curren_civ() end}}}, - widgets.Label{frame = { t=4,l=1}, text={{text=": manually enter",key="CUSTOM_E", - on_activate=function () - dialog.showInputPrompt("Civ id","Enter new civ id:",COLOR_WHITE, - tostring(self.target_unit.civ_id),function(new_value) - self.target_unit.civ_id=new_value - self:update_curren_civ() - end) - end}} - }, - widgets.Label{frame= {t=5,l=1}, text={{text=": select from list",key="CUSTOM_L", - on_activate=function ( ) - showCivPrompt("Choose civilization", "Select units civilization",nil,function ( id,choice ) - self.target_unit.civ_id=choice.num - self:update_curren_civ() - end,nil,nil,true) - end - }}}, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - } - }, - } -end -add_editor(editor_civ) -------- counters editor -editor_counters=defclass(editor_counters,gui.FramedScreen) -editor_counters.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Counters editor", - target_unit = DEFAULT_NIL, - counters1={ - "think_counter", - "job_counter", - "swap_counter", - "winded", - "stunned", - "unconscious", - "suffocation", - "webbed", - "soldier_mood_countdown", - "soldier_mood", --todo enum, - "pain", - "nausea", - "dizziness", - }, - counters2={ - "paralysis", - "numbness", - "fever", - "exhaustion", - "hunger_timer", - "thirst_timer", - "sleepiness_timer", - "stomach_content", - "stomach_food", - "vomit_timeout", - "stored_fat" --TODO what to reset to? - } -} -function editor_counters:fill_counters() - local ret={} - local u=self.target_unit - for i,v in ipairs(self.counters1) do - table.insert(ret,{f=u.counters:_field(v),name=v}) - end - for i,v in ipairs(self.counters2) do - table.insert(ret,{f=u.counters2:_field(v),name=v}) - end - return ret -end -function editor_counters:update_counters() - for i,v in ipairs(self.counter_list) do - v.text=string.format("%s:%d",v.name,v.f.value) - end - self.subviews.counters:setChoices(self.counter_list) -end -function editor_counters:set_cur_counter(value,index,choice) - choice.f.value=value - self:update_counters() -end -function editor_counters:choose_cur_counter(index,choice) - dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE, - tostring(choice.f.value),function(new_value) - self:set_cur_counter(new_value,index,choice) - end) -end -function editor_counters:init( args ) - if self.target_unit==nil then - qerror("invalid unit") - end - - self.counter_list=self:fill_counters() - - - self:addviews{ - widgets.FilteredList{ - choices=self.counter_list, - frame = {t=0, b=1,l=1}, - view_id="counters", - on_submit=self:callback("choose_cur_counter"), - on_submit2=self:callback("set_cur_counter",0),--TODO some things need to be set to different defaults - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - {text=": reset counter ", - key = "SEC_SELECT", - }, - {text=": set counter ", - key = "SELECT", - } - - } - }, - } - self:update_counters() -end -add_editor(editor_counters) - -wound_creator=defclass(wound_creator,gui.FramedScreen) -wound_creator.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Wound creator", - target_wound = DEFAULT_NIL, - --filter -} -function wound_creator:init( args ) - if self.target_wound==nil then - qerror("invalid wound") - end - - - self:addviews{ - widgets.List{ - - frame = {t=0, b=1,l=1}, - view_id="fields", - on_submit=self:callback("edit_cur_wound"), - on_submit2=self:callback("delete_current_wound") - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss")}, - - {text=": edit wound ", - key = "SELECT"}, - - {text=": delete wound ", - key = "SEC_SELECT"}, - {text=": create wound ", - key = "CUSTOM_CTRL_I", - on_activate= self:callback("create_new_wound")}, - - } - }, - } - self:update_wounds() -end -------------------- -editor_wounds=defclass(editor_wounds,gui.FramedScreen) -editor_wounds.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Wound editor", - target_unit = DEFAULT_NIL, - --filter -} -function is_scar( wound_part ) - return wound_part.flags1.scar_cut or wound_part.flags1.scar_smashed or - wound_part.flags1.scar_edged_shake1 or wound_part.flags1.scar_blunt_shake1 -end -function format_flag_name( fname ) - return fname:sub(1,1):upper()..fname:sub(2):gsub("_"," ") -end -function name_from_flags( wp ) - for i,v in ipairs(wp.flags1) do - if v then - return format_flag_name(df.wound_damage_flags1[i]) - end - end - for i,v in ipairs(wp.flags2) do - if v then - return format_flag_name(df.wound_damage_flags2[i]) - end - end - return "" -end -function format_wound( list_id,wound, unit) - - local name="" - if #wound.parts>0 and #wound.parts[0].effect_type>0 then --try to make wound name by effect... - name=tostring(df.wound_effect_type[wound.parts[0].effect_type[0]]) - if #wound.parts>1 then --cheap and probably incorrect... - name=name.."s" - end - elseif #wound.parts>0 and is_scar(wound.parts[0]) then - name="Scar" - elseif #wound.parts>0 then - local wp=wound.parts[0] - name=name_from_flags(wp) - end - - return string.format("%d. %s id=%d",list_id,name,wound.id) -end -function editor_wounds:update_wounds() - local ret={} - for i,v in ipairs(self.trg_wounds) do - table.insert(ret,{text=format_wound(i,v,self.target_unit),wound=v}) - end - self.subviews.wounds:setChoices(ret) - self.wound_list=ret -end -function editor_wounds:dirty_unit() - print("todo: implement unit status recalculation") -end -function editor_wounds:get_cur_wound() - local list_wid=self.subviews.wounds - local _,choice=list_wid:getSelected() - if choice==nil then - qerror("Nothing selected") - end - local ret_wound=utils.binsearch(self.trg_wounds,choice.id,"id") - return choice,ret_wound -end -function editor_wounds:delete_current_wound(index,choice) - - utils.erase_sorted(self.trg_wounds,choice.wound,"id") - choice.wound:delete() - self:dirty_unit() - self:update_wounds() -end -function editor_wounds:create_new_wound() - print("Creating") -end -function editor_wounds:edit_cur_wound(index,choice) - -end -function editor_wounds:init( args ) - if self.target_unit==nil then - qerror("invalid unit") - end - self.trg_wounds=self.target_unit.body.wounds - - self:addviews{ - widgets.List{ - - frame = {t=0, b=1,l=1}, - view_id="wounds", - on_submit=self:callback("edit_cur_wound"), - on_submit2=self:callback("delete_current_wound") - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss")}, - - {text=": edit wound ", - key = "SELECT"}, - - {text=": delete wound ", - key = "SEC_SELECT"}, - {text=": create wound ", - key = "CUSTOM_CTRL_I", - on_activate= self:callback("create_new_wound")}, - - } - }, - } - self:update_wounds() -end -add_editor(editor_wounds) - --------------------------------main window---------------- -unit_editor = defclass(unit_editor, gui.FramedScreen) -unit_editor.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "GameMaster's unit editor", - target_unit = DEFAULT_NIL, - } - - -function unit_editor:init(args) - - self:addviews{ - widgets.FilteredList{ - choices=editors, - on_submit=function (idx,choice) - if choice.on_submit then - choice.on_submit(self.target_unit) - end - end - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - } - }, - } -end - - -unit_editor{target_unit=target}:show() diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua deleted file mode 100644 index be90ffb1c..000000000 --- a/scripts/gui/guide-path.lua +++ /dev/null @@ -1,205 +0,0 @@ --- Show/change the path used by Guide Cart orders ---[[=begin - -gui/guide-path -============== -Bind to a key (the example config uses :kbd:`Alt`:kbd:`P`), and activate in the Hauling menu with -the cursor over a Guide order. - -.. image:: /docs/images/guide-path.png - -The script displays the cached path that will be used by the order; the game -computes it when the order is executed for the first time. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local tile_attrs = df.tiletype.attrs - -GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) - -GuidePathUI.focus_path = 'guide-path' - -GuidePathUI.ATTRS { - route = DEFAULT_NIL, - stop = DEFAULT_NIL, - order = DEFAULT_NIL, -} - -function GuidePathUI:init() - self.saved_mode = df.global.ui.main.mode - - for i=0,#self.route.stops-1 do - if self.route.stops[i] == self.stop then - self.stop_idx = i - break - end - end - - if not self.stop_idx then - error('Could not find stop index') - end - - self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops] -end - -function GuidePathUI:onShow() - -- with cursor, but without those ugly lines from native hauling mode - df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles -end - -function GuidePathUI:onDestroy() - df.global.ui.main.mode = self.saved_mode -end - -local function getTileType(cursor) - local block = dfhack.maps.getTileBlock(cursor) - if block then - return block.tiletype[cursor.x%16][cursor.y%16] - else - return 0 - end -end - -local function isTrackTile(tile) - return tile_attrs[tile].special == df.tiletype_special.TRACK -end - -local function paintMapTile(dc, vp, cursor, pos, ...) - if not same_xyz(cursor, pos) then - local stile = vp:tileToScreen(pos) - if stile.z == 0 then - dc:seek(stile.x,stile.y):char(...) - end - end -end - -local function get_path_point(gpath,i) - return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) -end - -function GuidePathUI:renderPath(cursor) - local gpath = self.order.guide_path - local startp = self.stop.pos - local endp = self.next_stop.pos - local vp = self:getViewport() - local dc = gui.Painter.new(self.df_layout.map) - local visible = gui.blink_visible(500) - - if visible then - paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) - end - - local ok = nil - local pcnt = #gpath.x - if pcnt > 0 then - ok = true - - for i = 0,pcnt-1 do - local pt = get_path_point(gpath, i) - if i == 0 and not same_xyz(startp,pt) then - ok = false - end - if i == pcnt-1 and not same_xyz(endp,pt) then - ok = false - end - local tile = getTileType(pt) - if not isTrackTile(tile) then - ok = false - end - if visible then - local char = '+' - if i < pcnt-1 then - local npt = get_path_point(gpath, i+1) - if npt.x == pt.x+1 then - char = 26 - elseif npt.x == pt.x-1 then - char = 27 - elseif npt.y == pt.y+1 then - char = 25 - elseif npt.y == pt.y-1 then - char = 24 - end - end - local color = COLOR_LIGHTGREEN - if not ok then color = COLOR_LIGHTRED end - paintMapTile(dc, vp, cursor, pt, char, color) - end - end - end - - if gui.blink_visible(120) then - paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) - end - - return ok -end - -function GuidePathUI:onRenderBody(dc) - dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path") - - dc:seek(2,3) - - local cursor = guidm.getCursorPos() - local path_ok = self:renderPath(cursor) - - if path_ok == nil then - dc:string('No saved path', COLOR_DARKGREY) - elseif path_ok then - dc:string('Valid path', COLOR_GREEN) - else - dc:string('Invalid path', COLOR_RED) - end - - dc:newline():newline(1) - dc:key('CUSTOM_Z'):string(": Reset path",COLOR_GREY,nil,path_ok~=nil) - --dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) - dc:newline(1) - dc:key('CUSTOM_C'):string(": Zoom cur, ") - dc:key('CUSTOM_N'):string(": Zoom next") - - dc:newline():newline(1):string('At cursor:') - dc:newline():newline(2) - - local tile = getTileType(cursor) - if isTrackTile(tile) then - dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN) - else - dc:string('No track', COLOR_DARKGREY) - end - - dc:newline():newline(1) - dc:key('LEAVESCREEN'):string(": Back") -end - -function GuidePathUI:onInput(keys) - if keys.CUSTOM_C then - self:moveCursorTo(copyall(self.stop.pos)) - elseif keys.CUSTOM_N then - self:moveCursorTo(copyall(self.next_stop.pos)) - elseif keys.CUSTOM_Z then - local gpath = self.order.guide_path - gpath.x:resize(0) - gpath.y:resize(0) - gpath.z:resize(0) - elseif keys.LEAVESCREEN then - self:dismiss() - elseif self:propagateMoveKeys(keys) then - return - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then - qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order") -end - -local hauling = df.global.ui.hauling -local route = hauling.view_routes[hauling.cursor_top] -local stop = hauling.view_stops[hauling.cursor_top] -local order = hauling.stop_conditions[hauling.cursor_stop] - -local list = GuidePathUI{ route = route, stop = stop, order = order } -list:show() diff --git a/scripts/gui/hack-wish.lua b/scripts/gui/hack-wish.lua deleted file mode 100644 index de423abc2..000000000 --- a/scripts/gui/hack-wish.lua +++ /dev/null @@ -1,8 +0,0 @@ ---@ alias = 'gui/create-item' ---[[=begin - -gui/hack-wish -============= -An alias for `gui/create-item`. Deprecated. - -=end]] \ No newline at end of file diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua deleted file mode 100644 index 324aa11ad..000000000 --- a/scripts/gui/hello-world.lua +++ /dev/null @@ -1,31 +0,0 @@ --- Test lua viewscreens. ---[[=begin - -gui/hello-world -=============== -A basic example for testing, or to start your own script from. - -=end]] -local gui = require 'gui' - -local text = 'Woohoo, lua viewscreen :)' - -local screen = gui.FramedScreen{ - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'Hello World', - frame_width = #text, - frame_height = 1, - frame_inset = 1, -} - -function screen:onRenderBody(dc) - dc:string(text, COLOR_LIGHTGREEN) -end - -function screen:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - end -end - -screen:show() diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua deleted file mode 100644 index 51ebca4bf..000000000 --- a/scripts/gui/liquids.lua +++ /dev/null @@ -1,326 +0,0 @@ --- Interface front-end for liquids plugin. ---[[=begin - -gui/liquids -=========== -To use, bind to a key (the example config uses Alt-L) and activate in the :kbd:`k` mode. - -.. image:: /docs/images/liquids.png - -This script is a gui front-end to `liquids` and works similarly, -allowing you to add or remove water & magma, and create obsidian walls & floors. - -.. warning:: - - There is **no undo support**. Bugs in this plugin have been - known to create pathfinding problems and heat traps. - -The :kbd:`b` key changes how the affected area is selected. The default :guilabel:`Rectangle` -mode works by selecting two corners like any ordinary designation. The :kbd:`p` -key chooses between adding water, magma, obsidian walls & floors, or just -tweaking flags. - -When painting liquids, it is possible to select the desired level with :kbd:`+`:kbd:`-`, -and choose between setting it exactly, only increasing or only decreasing -with :kbd:`s`. - -In addition, :kbd:`f` allows disabling or enabling the flowing water computations -for an area, and :kbd:`r` operates on the "permanent flow" property that makes -rivers power water wheels even when full and technically not flowing. - -After setting up the desired operations using the described keys, use :kbd:`Enter` to apply them. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local liquids = require('plugins.liquids') - -local sel_rect = df.global.selection_rect - -local brushes = { - { tag = 'range', caption = 'Rectangle', range = true }, - { tag = 'block', caption = '16x16 block' }, - { tag = 'column', caption = 'Column' }, - { tag = 'flood', caption = 'Flood' }, -} - -local paints = { - { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'D_LOOK_ARENA_WATER' }, - { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'D_LOOK_ARENA_MAGMA' }, - { tag = 'obsidian', caption = 'Obsidian Wall' }, - { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, - { tag = 'riversource', caption = 'River Source' }, - { tag = 'flowbits', caption = 'Flow Updates', flow = true }, - { tag = 'wclean', caption = 'Clean Salt/Stagnant' }, -} - -local flowbits = { - { tag = '+', caption = 'Enable Updates' }, - { tag = '-', caption = 'Disable Updates' }, - { tag = '.', caption = 'Keep Updates' }, -} - -local setmode = { - { tag = '.', caption = 'Set Exactly' }, - { tag = '+', caption = 'Only Increase' }, - { tag = '-', caption = 'Only Decrease' }, -} - -local permaflows = { - { tag = '.', caption = "Keep Permaflow" }, - { tag = '-', caption = 'Remove Permaflow' }, - { tag = 'N', caption = 'Set Permaflow N' }, - { tag = 'S', caption = 'Set Permaflow S' }, - { tag = 'E', caption = 'Set Permaflow E' }, - { tag = 'W', caption = 'Set Permaflow W' }, - { tag = 'NE', caption = 'Set Permaflow NE' }, - { tag = 'NW', caption = 'Set Permaflow NW' }, - { tag = 'SE', caption = 'Set Permaflow SE' }, - { tag = 'SW', caption = 'Set Permaflow SW' }, -} - -Toggle = defclass(Toggle) - -Toggle.ATTRS{ items = {}, selected = 1 } - -function Toggle:get() - return self.items[self.selected] -end - -function Toggle:render(dc) - local item = self:get() - if item then - dc:string(item.caption) - if item.key then - dc:string(" ("):key(item.key):string(")") - end - else - dc:string('NONE', COLOR_RED) - end -end - -function Toggle:step(delta) - if #self.items > 1 then - delta = delta or 1 - self.selected = 1 + (self.selected + delta - 1) % #self.items - end -end - -LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay) - -LiquidsUI.focus_path = 'liquids' - -function LiquidsUI:init() - self:assign{ - brush = Toggle{ items = brushes }, - paint = Toggle{ items = paints }, - flow = Toggle{ items = flowbits }, - set = Toggle{ items = setmode }, - permaflow = Toggle{ items = permaflows }, - amount = 7, - } -end - -function LiquidsUI:onDestroy() - guidm.clearSelection() -end - -function render_liquid(dc, block, x, y) - local dsgn = block.designation[x%16][y%16] - - if dsgn.flow_size > 0 then - if dsgn.liquid_type == df.tile_liquid.Magma then - dc:pen(COLOR_RED):string("Magma") - else - dc:pen(COLOR_BLUE) - if dsgn.water_stagnant then dc:string("Stagnant ") end - if dsgn.water_salt then dc:string("Salty ") end - dc:string("Water") - end - dc:string(" ["..dsgn.flow_size.."/7]") - else - dc:string('No Liquid') - end -end - -local permaflow_abbr = { - north = 'N', south = 'S', east = 'E', west = 'W', - northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW' -} - -function render_flow_state(dc, block, x, y) - local flow = block.liquid_flow[x%16][y%16] - - if block.flags.update_liquid then - dc:string("Updating", COLOR_GREEN) - else - dc:string("Static") - end - dc:string(", ") - if flow.perm_flow_dir ~= 0 then - local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir] - dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN) - elseif flow.temp_flow_timer > 0 then - dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN) - else - dc:string("No Flow") - end -end - -function LiquidsUI:onRenderBody(dc) - dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) - - local cursor = guidm.getCursorPos() - local block = dfhack.maps.getTileBlock(cursor) - - if block then - local x, y = pos2xyz(cursor) - local tile = block.tiletype[x%16][y%16] - - dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN) - dc:newline(2):pen(COLOR_DARKGREY) - render_liquid(dc, block, x, y) - dc:newline(2):pen(COLOR_DARKGREY) - render_flow_state(dc, block, x, y) - else - dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2) - end - - dc:newline():pen(COLOR_GREY) - - dc:newline(1):key('CUSTOM_B'):string(": ") - self.brush:render(dc) - dc:newline(1):key('CUSTOM_P'):string(": ") - self.paint:render(dc) - - local paint = self.paint:get() - - dc:newline() - if paint.liquid then - dc:newline(1):string("Amount: "..self.amount) - dc:advance(1):string("("):key('SECONDSCROLL_UP'):key('SECONDSCROLL_DOWN'):string(")") - dc:newline(3):key('CUSTOM_S'):string(": ") - self.set:render(dc) - else - dc:advance(0,2) - end - - dc:newline() - if paint.flow then - dc:newline(1):key('CUSTOM_F'):string(": ") - self.flow:render(dc) - dc:newline(1):key('CUSTOM_R'):string(": ") - self.permaflow:render(dc) - else - dc:advance(0,2) - end - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back, ") - dc:key('SELECT'):string(": Paint") -end - -function ensure_blocks(cursor, size, cb) - size = size or xyz2pos(1,1,1) - local cx,cy,cz = pos2xyz(cursor) - local all = true - for x=1,size.x or 1,16 do - for y=1,size.y or 1,16 do - for z=1,size.z do - if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then - all = false - end - end - end - end - if all then - cb() - return - end - dlg.showYesNoPrompt( - 'Instantiate Blocks', - 'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.', - COLOR_YELLOW, - function() - for x=1,size.x or 1,16 do - for y=1,size.y or 1,16 do - for z=1,size.z do - dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1) - end - end - end - cb() - end, - function() - cb() - end - ) -end - -function LiquidsUI:onInput(keys) - local paint = self.paint:get() - local liquid = paint.liquid - if keys.CUSTOM_B then - self.brush:step() - elseif keys.CUSTOM_P then - self.paint:step() - elseif liquid and keys.SECONDSCROLL_UP then - self.amount = math.max(0, self.amount-1) - elseif liquid and keys.SECONDSCROLL_DOWN then - self.amount = math.min(7, self.amount+1) - elseif liquid and keys.CUSTOM_S then - self.set:step() - elseif paint.flow and keys.CUSTOM_F then - self.flow:step() - elseif paint.flow and keys.CUSTOM_R then - self.permaflow:step() - elseif keys.LEAVESCREEN then - if guidm.getSelection() then - guidm.clearSelection() - return - end - self:dismiss() - self:sendInputToParent('CURSOR_DOWN_Z') - self:sendInputToParent('CURSOR_UP_Z') - elseif keys.SELECT then - local cursor = guidm.getCursorPos() - local sp = guidm.getSelection() - local size = nil - if self.brush:get().range then - if not sp then - guidm.setSelectionStart(cursor) - return - else - guidm.clearSelection() - cursor, size = guidm.getSelectionRange(cursor, sp) - end - else - guidm.clearSelection() - end - local cb = curry( - liquids.paint, - cursor, - self.brush:get().tag, self.paint:get().tag, - self.amount, size, - self.set:get().tag, self.flow:get().tag, - self.permaflow:get().tag - ) - ensure_blocks(cursor, size, cb) - elseif self:propagateMoveKeys(keys) then - return - elseif keys.D_LOOK_ARENA_WATER then - self.paint.selected = 1 - elseif keys.D_LOOK_ARENA_MAGMA then - self.paint.selected = 2 - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then - qerror("This script requires the main dwarfmode view in 'k' mode") -end - -local list = LiquidsUI() -list:show() diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua deleted file mode 100644 index d65782469..000000000 --- a/scripts/gui/mechanisms.lua +++ /dev/null @@ -1,146 +0,0 @@ --- Shows mechanisms linked to the current building. ---[[=begin - -gui/mechanisms -============== -To use, bind to a key (the example config uses :kbd:`Ctrl`:kbd:`M`) -and activate in :kbd:`q` mode. - -.. image:: /docs/images/mechanisms.png - -Lists mechanisms connected to the building, and their links. Navigating -the list centers the view on the relevant linked buildings. - -To exit, press :kbd:`Esc` or :kbd:`Enter`; :kbd:`Esc` recenters on -the original building, while :kbd:`Enter` leaves focus on the current -one. :kbd:`Shift`:kbd:`Enter` has an effect equivalent to pressing -:kbd:`Enter`, and then re-entering the mechanisms UI. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' - -function listMechanismLinks(building) - local lst = {} - local function push(item, mode) - if item then - lst[#lst+1] = { - obj = item, mode = mode, - name = utils.getBuildingName(item) - } - end - end - - push(building, 'self') - - if not df.building_actual:is_instance(building) then - return lst - end - - local item, tref, tgt - for _,v in ipairs(building.contained_items) do - item = v.item - if df.item_trappartsst:is_instance(item) then - tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGER) - if tref then - push(tref:getBuilding(), 'trigger') - end - tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGERTARGET) - if tref then - push(tref:getBuilding(), 'target') - end - end - end - - return lst -end - -MechanismList = defclass(MechanismList, guidm.MenuOverlay) - -MechanismList.focus_path = 'mechanisms' - -function MechanismList:init(info) - self:assign{ - links = {}, selected = 1 - } - self:fillList(info.building) -end - -function MechanismList:fillList(building) - local links = listMechanismLinks(building) - - self.old_viewport = self:getViewport() - self.old_cursor = guidm.getCursorPos() - - if #links <= 1 then - links[1].mode = 'none' - end - - self.links = links - self.selected = 1 -end - -local colors = { - self = COLOR_CYAN, none = COLOR_CYAN, - trigger = COLOR_GREEN, target = COLOR_GREEN -} -local icons = { - self = 128, none = 63, trigger = 27, target = 26 -} - -function MechanismList:onRenderBody(dc) - dc:clear() - dc:seek(1,1):string("Mechanism Links", COLOR_WHITE):newline() - - for i,v in ipairs(self.links) do - local pen = { fg=colors[v.mode], bold = (i == self.selected) } - dc:newline(1):pen(pen):char(icons[v.mode]) - dc:advance(1):string(v.name) - end - - local nlinks = #self.links - - if nlinks <= 1 then - dc:newline():newline(1):string("This building has no links", COLOR_LIGHTRED) - end - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back, ") - dc:key('SELECT'):string(": Switch") -end - -function MechanismList:changeSelected(delta) - if #self.links <= 1 then return end - self.selected = 1 + (self.selected + delta - 1) % #self.links - self:selectBuilding(self.links[self.selected].obj) -end - -function MechanismList:onInput(keys) - if keys.SECONDSCROLL_UP then - self:changeSelected(-1) - elseif keys.SECONDSCROLL_DOWN then - self:changeSelected(1) - elseif keys.LEAVESCREEN then - self:dismiss() - if self.selected ~= 1 then - self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view) - end - elseif keys.SELECT_ALL then - if self.selected > 1 then - self:fillList(self.links[self.selected].obj) - end - elseif keys.SELECT then - self:dismiss() - elseif self:simulateViewScroll(keys) then - return - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then - qerror("This script requires the main dwarfmode view in 'q' mode") -end - -local list = MechanismList{ building = df.global.world.selected_building } -list:show() -list:changeSelected(1) diff --git a/scripts/gui/mod-manager.lua b/scripts/gui/mod-manager.lua deleted file mode 100644 index 5c3b39375..000000000 --- a/scripts/gui/mod-manager.lua +++ /dev/null @@ -1,361 +0,0 @@ --- a graphical mod manager for df -local gui=require 'gui' -local widgets=require 'gui.widgets' ---[[=begin - -gui/mod-manager -=============== -A simple way to install and remove small mods. - -It looks for specially formatted mods in :file:`{}/mods/`. Mods are not -included, but some examples are `available here`_. - -.. _`available here`: https://github.com/warmist/df-mini-mods - -.. image:: /docs/images/mod-manager.png - -=end]] -local entity_file=dfhack.getDFPath().."/raw/objects/entity_default.txt" -local init_file=dfhack.getDFPath().."/raw/init.lua" -local mod_dir=dfhack.getDFPath().."/hack/mods" ---[[ mod format: lua script that defines: - name - a name that is displayed in list - author - mod author, also displayed - description - mod description - OPTIONAL: - raws_list - a list (table) of file names that need to be copied over to df raws - patch_entity - a chunk of text to patch entity TODO: add settings to which entities to add - patch_init - a chunk of lua to add to lua init - patch_dofile - a list (table) of files to add to lua init as "dofile" - patch_files - a table of files to patch: - filename - a filename (in raws folder) to patch - patch - what to add - after - a string after which to insert - MORE OPTIONAL: - guard - a token that is used in raw files to find editions and remove them on uninstall - guard_init - a token for lua file - [pre|post]_(un)install - callback functions. Can trigger more complicated behavior -]] - -function fileExists(filename) - local file=io.open(filename,"rb") - if file==nil then - return - else - file:close() - return true - end -end -if not fileExists(init_file) then - local initFile=io.open(init_file,"a") - initFile:close() -end -function copyFile(from,to) --oh so primitive - local filefrom=io.open(from,"rb") - local fileto=io.open(to,"w+b") - local buf=filefrom:read("*a") - printall(buf) - fileto:write(buf) - filefrom:close() - fileto:close() -end -function patchInit(initFileName,patch_guard,code) - local initFile=io.open(initFileName,"a") - initFile:write(string.format("\n%s\n%s\n%s",patch_guard[1], - code,patch_guard[2])) - initFile:close() -end -function patchDofile( luaFileName,patch_guard,dofile_list,mod_path ) - local luaFile=io.open(luaFileName,"a") - luaFile:write(patch_guard[1].."\n") - for _,v in ipairs(dofile_list) do - local fixed_path=mod_path:gsub("\\","/") - luaFile:write(string.format("dofile('%s/%s')\n",fixed_path,v)) - end - luaFile:write(patch_guard[2].."\n") - luaFile:close() -end -function patchFile(file_name,patch_guard,after_string,code) - local input_lines=patch_guard[1].."\n"..code.."\n"..patch_guard[2] - - local badchars="[%:%[%]]" - local find_string=after_string:gsub(badchars,"%%%1") --escape some bad chars - - local entityFile=io.open(file_name,"r") - local buf=entityFile:read("*all") - entityFile:close() - local entityFile=io.open(file_name,"w+") - buf=string.gsub(buf,find_string,after_string.."\n"..input_lines) - entityFile:write(buf) - entityFile:close() -end -function findGuards(str,start,patch_guard) - local pStart=string.find(str,patch_guard[1],start) - if pStart==nil then return nil end - local pEnd=string.find(str,patch_guard[2],pStart) - if pEnd==nil then error("Start guard token found, but end was not found") end - return pStart-1,pEnd+#patch_guard[2]+1 -end -function findGuardsFile(filename,patch_guard) - local file=io.open(filename,"r") - local buf=file:read("*all") - return findGuards(buf,1,patch_guard) -end -function unPatchFile(filename,patch_guard) - local file=io.open(filename,"r") - local buf=file:read("*all") - file:close() - - local newBuf="" - local pos=1 - local lastPos=1 - repeat - local endPos - pos,endPos=findGuards(buf,lastPos,patch_guard) - newBuf=newBuf..string.sub(buf,lastPos,pos) - if endPos~=nil then - lastPos=endPos - end - until pos==nil - - local file=io.open(filename,"w+") - file:write(newBuf) - file:close() -end -function checkInstalled(dfMod) --try to figure out if installed - if dfMod.checkInstalled then - return dfMod.checkInstalled() - else - if dfMod.raws_list then - for k,v in pairs(dfMod.raws_list) do - if fileExists(dfhack.getDFPath().."/raw/objects/"..v) then - return true,v - end - end - end - if dfMod.patch_entity then - if findGuardsFile(entity_file,dfMod.guard)~=nil then - return true,"entity_default.txt" - end - end - if dfMod.patch_files then - for k,v in pairs(dfMod.patch_files) do - if findGuardsFile(dfhack.getDFPath().."/raw/objects/"..v.filename,dfMod.guard)~=nil then - return true,"v.filename" - end - end - end - if dfMod.patch_init then - if findGuardsFile(init_file,dfMod.guard_init)~=nil then - return true,"init.lua" - end - end - end -end -manager=defclass(manager,gui.FramedScreen) - -function manager:init(args) - self.mods={} - local mods=self.mods - local mlist=dfhack.internal.getDir(mod_dir) - - if mlist==nil or #mlist==0 then - qerror("Mod directory not found! Are you sure it is in:"..mod_dir) - end - for k,v in ipairs(mlist) do - if v~="." and v~=".." then - local f,modData=pcall(dofile,mod_dir.."/".. v .. "/init.lua") - if f then - mods[modData.name]=modData - modData.guard=modData.guard or {">>"..modData.name.." patch","<" - end -end -function manager:formAuthor() - return self.selected.author or "" -end -function manager:selectMod(idx,choice) - self.selected=choice.data - if self.subviews.info then - self.subviews.info:setText(self:formDescription()) - self:updateLayout() - end -end -function manager:updateState() - for k,v in pairs(self.mods) do - v.installed=checkInstalled(v) - end -end -function manager:installCurrent() - self:install(self.selected) -end -function manager:uninstallCurrent() - self:uninstall(self.selected) -end -function manager:install(trgMod,force) - - if trgMod==nil then - qerror 'Mod does not exist' - end - if not force then - local isInstalled,file=checkInstalled(trgMod) -- maybe load from .installed? - if isInstalled then - qerror("Mod already installed. File:"..file) - end - end - print("installing:"..trgMod.name) - if trgMod.pre_install then - trgMod.pre_install(args) - end - if trgMod.raws_list then - for k,v in pairs(trgMod.raws_list) do - copyFile(trgMod.path..v,dfhack.getDFPath().."/raw/objects/"..v) - end - end - if trgMod.patch_entity then - local entity_target="[ENTITY:MOUNTAIN]" --TODO configure - patchFile(entity_file,trgMod.guard,entity_target,trgMod.patch_entity) - end - if trgMod.patch_files then - for k,v in pairs(trgMod.patch_files) do - patchFile(dfhack.getDFPath().."/raw/objects/"..v.filename,trgMod.guard,v.after,v.patch) - end - end - if trgMod.patch_init then - patchInit(init_file,trgMod.guard_init,trgMod.patch_init) - end - if trgMod.patch_dofile then - patchDofile(init_file,trgMod.guard_init,trgMod.patch_dofile,trgMod.path) - end - trgMod.installed=true - - if trgMod.post_install then - trgMod.post_install(self) - end - print("done") -end -function manager:uninstall(trgMod) - print("Uninstalling:"..trgMod.name) - if trgMod.pre_uninstall then - trgMod.pre_uninstall(args) - end - - if trgMod.raws_list then - for k,v in pairs(trgMod.raws_list) do - os.remove(dfhack.getDFPath().."/raw/objects/"..v) - end - end - if trgMod.patch_entity then - unPatchFile(entity_file,trgMod.guard) - end - if trgMod.patch_files then - for k,v in pairs(trgMod.patch_files) do - unPatchFile(dfhack.getDFPath().."/raw/objects/"..v.filename,trgMod.guard) - end - end - if trgMod.patch_init or trgMod.patch_dofile then - unPatchFile(init_file,trgMod.guard_init) - end - - trgMod.installed=false - if trgMod.post_uninstall then - trgMod.post_uninstall(args) - end - print("done") -end -function manager:onInput(keys) - - if keys.LEAVESCREEN then - self:dismiss() - else - self:inputToSubviews(keys) - end - -end -if dfhack.gui.getCurFocus()~='title' then - qerror("Can only be used in title screen") -end -local m=manager{} -m:show() diff --git a/scripts/gui/no-dfhack-init.lua b/scripts/gui/no-dfhack-init.lua deleted file mode 100644 index c4352211d..000000000 --- a/scripts/gui/no-dfhack-init.lua +++ /dev/null @@ -1,37 +0,0 @@ --- Shows the warning about missing configuration file. ---[[=begin - -gui/no-dfhack-init -================== -Shows a warning at startup if no valid :file:`dfhack.init` file is found. - -=end]] -local gui = require 'gui' -local dlg = require 'gui.dialogs' - -local dfhack_init = { text = 'dfhack.init', pen = COLOR_LIGHTCYAN } -local dfhack_init_example = { text = 'dfhack.init-example', pen = COLOR_LIGHTCYAN } - -local message = { - 'The ', dfhack_init, ' configuration file is missing. To customize', NEWLINE, - 'your DFHack installation, rename the ', dfhack_init_example, ' file', NEWLINE, - 'to ', dfhack_init, ' and edit it to suit your needs.', NEWLINE, NEWLINE, - 'For now, ', dfhack_init_example, ' will be used instead.' -} - -dfhack.print('\n') - -for k,v in ipairs(message) do - if type(v) == 'table' then - dfhack.color(v.pen) - dfhack.print(v.text) - else - dfhack.color(COLOR_YELLOW) - dfhack.print(v) - end -end - -dfhack.color(COLOR_RESET) -dfhack.print('\n\n') - -dlg.showMessage('DFHack is not configured', message, COLOR_YELLOW) diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua deleted file mode 100644 index ac10e5dc7..000000000 --- a/scripts/gui/power-meter.lua +++ /dev/null @@ -1,135 +0,0 @@ --- Interface front-end for power-meter plugin. ---[[=begin - -gui/power-meter -=============== -An in-game interface for `power-meter`. - -Bind it to a key (default :kbd:`Ctrl`:kbd:`Shift`:kbd:`M`) and activate -after selecting Pressure Plate in the build menu. - -.. image:: /docs/images/power-meter.png - -The script follows the general look and feel of the regular pressure -plate build configuration page, but configures parameters relevant to -the modded power meter building. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local plugin = require('plugins.power-meter') -local bselector = df.global.ui_build_selector - -PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) - -PowerMeter.focus_path = 'power-meter' - -PowerMeter.ATTRS { - frame_background = false -} - -function PowerMeter:init() - self:assign{ - min_power = 0, max_power = -1, invert = false, - } -end - -function PowerMeter:onShow() - PowerMeter.super.onShow(self) - - -- Send an event to update the errors - bselector.plate_info.flags.whole = 0 - self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER') -end - -function PowerMeter:onRenderBody(dc) - dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN) - dc:seek(1,1):pen(COLOR_WHITE) - dc:string("Power Meter"):newline():newline(1) - dc:string("Placement"):newline():newline(1) - - dc:string("Excess power range:") - - dc:newline(3):key('BUILDING_TRIGGER_MIN_WATER_DOWN') - dc:key('BUILDING_TRIGGER_MIN_WATER_UP') - dc:string(": Min ") - if self.min_power <= 0 then - dc:string("(any)") - else - dc:string(''..self.min_power) - end - - dc:newline(3):key('BUILDING_TRIGGER_MAX_WATER_DOWN') - dc:key('BUILDING_TRIGGER_MAX_WATER_UP') - dc:string(": Max ") - if self.max_power < 0 then - dc:string("(any)") - else - dc:string(''..self.max_power) - end - dc:newline():newline(1) - - dc:key('CUSTOM_I'):string(": ") - if self.invert then - dc:string("Inverted") - else - dc:string("Not inverted") - end -end - -function PowerMeter:onInput(keys) - if keys.CUSTOM_I then - self.invert = not self.invert - elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then - self.min_power = self.min_power + 10 - elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then - self.min_power = math.max(0, self.min_power - 10) - elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then - if self.max_power < 0 then - self.max_power = 0 - else - self.max_power = self.max_power + 10 - end - elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then - self.max_power = math.max(-1, self.max_power - 10) - elseif keys.LEAVESCREEN then - self:dismiss() - self:sendInputToParent('LEAVESCREEN') - elseif keys.SELECT then - if #bselector.errors == 0 then - if not plugin.makePowerMeter( - bselector.plate_info, - self.min_power, self.max_power, self.invert - ) - then - dlg.showMessage( - 'Power Meter', - 'Could not initialize.', COLOR_LIGHTRED - ) - - self:dismiss() - self:sendInputToParent('LEAVESCREEN') - return - end - - self:sendInputToParent('SELECT') - if bselector.stage ~= 1 then - self:dismiss() - end - end - elseif self:propagateMoveKeys(keys) then - return - end -end - -if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap' -or bselector.building_subtype ~= df.trap_type.PressurePlate -then - qerror("This script requires the main dwarfmode view in build pressure plate mode") -end - -local list = PowerMeter() -list:show() diff --git a/scripts/gui/prerelease-warning.lua b/scripts/gui/prerelease-warning.lua deleted file mode 100644 index 113375fd1..000000000 --- a/scripts/gui/prerelease-warning.lua +++ /dev/null @@ -1,141 +0,0 @@ --- Shows the warning about missing configuration file. ---[[=begin - -gui/prerelease-warning -====================== -Shows a warning on world load for pre-release builds. - -With no arguments passed, the warning is shown unless the "do not show again" -option has been selected. With the ``force`` argument, the warning is always -shown. - -=end]] - -local gui = require 'gui' -local dlg = require 'gui.dialogs' -local json = require 'json' -local utils = require 'utils' - -local force = ({...})[1] == 'force' -local config = json.open('dfhack-config/prerelease-warning.json') - -if config.data.hide and not force then - return -end -if not dfhack.isPrerelease() and not force 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 and not force then - return -end - -local state = dfhack.getDFHackRelease():lower():match('[a-z]+') -if not utils.invert{'alpha', 'beta', 'rc', 'r'}[state] then - dfhack.printerr('warning: unknown release state: ' .. state) - state = 'unknown' -end - - -message = ({ - alpha = { - 'Warning', - COLOR_YELLOW, - 'This is an alpha 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 often!'} - }, - beta = { - 'Warning', - COLOR_YELLOW, - 'This is a beta release of DFHack. It is more stable than an', NEWLINE, - 'alpha release, but bugs are still possible, possibly including', NEWLINE, - 'crashes and save corruption.', NEWLINE, - 'Make backups of your saves beforehand to be safe.' - }, - rc = { - 'Notice', - COLOR_YELLOW, - 'This is a DFHack release candidate. It is fairly stable but', NEWLINE, - 'likely contains newer features that are not fully-tested.', NEWLINE, - 'Crashes are unlikely, but always make backups of your saves', NEWLINE, - 'to be safe.' - }, - r = { - 'Error', - COLOR_LIGHTRED, - 'This release is flagged as a prerelease but named as a', NEWLINE, - 'stable release.', NEWLINE, - {pen=COLOR_LIGHTMAGENTA, text='Please report this to the DFHack team or a pack maintainer.'} - }, - unknown = { - 'Error', - COLOR_LIGHTMAGENTA, - 'Unknown prerelease DFHack build. This should never happen!', NEWLINE, - 'Please report this to the DFHack team or a pack maintainer.' - } -})[state] - -title = table.remove(message, 1) -color = table.remove(message, 1) - -pack_message = [[ - -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 #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 -end - -dfhack.print('\n') - -for k,v in ipairs(message) do - if type(v) == 'table' then - dfhack.color(v.pen) - dfhack.print(v.text) - else - dfhack.color(color) - dfhack.print(v) - end -end - -dfhack.color(COLOR_RESET) -dfhack.print('\n\n') - -WarningBox = defclass(nil, dlg.MessageBox) - -function WarningBox:getWantedFrameSize() - local w, h = WarningBox.super.getWantedFrameSize(self) - return w, h + 2 -end - -function WarningBox:onRenderFrame(dc,rect) - WarningBox.super.onRenderFrame(self,dc,rect) - dc:pen(COLOR_WHITE):key_pen(COLOR_LIGHTRED) - :seek(rect.x1 + 2, rect.y2 - 2) - :key('CUSTOM_D'):string(': Do not show again') - :advance(10) - :key('LEAVESCREEN'):string('/') - :key('SELECT'):string(': Dismiss') -end - -function WarningBox:onInput(keys) - if keys.CUSTOM_D then - config.data.hide = true - config:write() - keys.LEAVESCREEN = true - end - WarningBox.super.onInput(self, keys) -end - -WarningBox{ - frame_title = title, - text = message, - text_pen = color -}:show() diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua deleted file mode 100644 index 3433f4881..000000000 --- a/scripts/gui/rename.lua +++ /dev/null @@ -1,93 +0,0 @@ --- Rename various objects via gui. ---[[=begin - -gui/rename -========== -Backed by `rename`, this script allows entering the desired name -via a simple dialog in the game ui. - -* ``gui/rename [building]`` in :kbd:`q` mode changes the name of a building. - - .. image:: /docs/images/rename-bld.png - - The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. - It is also possible to rename zones from the :kbd:`i` menu. - -* ``gui/rename [unit]`` with a unit selected changes the nickname. - - Unlike the built-in interface, this works even on enemies and animals. - -* ``gui/rename unit-profession`` changes the selected unit's custom profession name. - - .. image:: /docs/images/rename-prof.png - - Likewise, this can be applied to any unit, and when used on animals it overrides - their species string. - -The ``building`` or ``unit`` options are automatically assumed when in relevant UI state. - -The example config binds building/unit rename to :kbd:`Ctrl`:kbd:`Shift`:kbd:`N`, and -unit profession change to :kbd:`Ctrl`:kbd:`Shift`:kbd:`T`. - -=end]] -local gui = require 'gui' -local dlg = require 'gui.dialogs' -local plugin = require 'plugins.rename' - -local mode = ... -local focus = dfhack.gui.getCurFocus() - -local function verify_mode(expected) - if mode ~= nil and mode ~= expected then - qerror('Invalid UI state for mode '..mode) - end -end - -local unit = dfhack.gui.getSelectedUnit(true) -local building = dfhack.gui.getSelectedBuilding(true) - -if building and (not unit or mode == 'building') then - verify_mode('building') - - if plugin.canRenameBuilding(building) then - dlg.showInputPrompt( - 'Rename Building', - 'Enter a new name for the building:', COLOR_GREEN, - building.name, - curry(plugin.renameBuilding, building) - ) - else - dlg.showMessage( - 'Rename Building', - 'Cannot rename this type of building.', COLOR_LIGHTRED - ) - end -elseif unit then - if mode == 'unit-profession' then - dlg.showInputPrompt( - 'Rename Unit', - 'Enter a new profession for the unit:', COLOR_GREEN, - unit.custom_profession, - function(newval) - unit.custom_profession = newval - end - ) - else - verify_mode('unit') - - local vname = dfhack.units.getVisibleName(unit) - local vnick = '' - if vname and vname.has_name then - vnick = vname.nickname - end - - dlg.showInputPrompt( - 'Rename Unit', - 'Enter a new nickname for the unit:', COLOR_GREEN, - vnick, - curry(dfhack.units.setNickname, unit) - ) - end -elseif mode then - verify_mode(nil) -end diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua deleted file mode 100644 index ac6890317..000000000 --- a/scripts/gui/room-list.lua +++ /dev/null @@ -1,259 +0,0 @@ --- Browses rooms owned by a unit. ---[[=begin - -gui/room-list -============= -To use, bind to a key (the example config uses :kbd:`Alt`:kbd:`R`) and activate in :kbd:`q` mode, -either immediately or after opening the assign owner page. - -.. image:: /docs/images/room-list.png - -The script lists other rooms owned by the same owner, or by the unit selected in the assign -list, and allows unassigning them. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' - -local room_type_table = { - [df.building_bedst] = { token = 'bed', qidx = 2, tile = 233 }, - [df.building_tablest] = { token = 'table', qidx = 3, tile = 209 }, - [df.building_chairst] = { token = 'chair', qidx = 4, tile = 210 }, - [df.building_coffinst] = { token = 'coffin', qidx = 5, tile = 48 }, -} - -local room_quality_table = { - { 1, 'Meager Quarters', 'Meager Dining Room', 'Meager Office', 'Grave' }, - { 100, 'Modest Quarters', 'Modest Dining Room', 'Modest Office', "Servant's Burial Chamber" }, - { 250, 'Quarters', 'Dining Room', 'Office', 'Burial Chamber' }, - { 500, 'Decent Quarters', 'Decent Dining Room', 'Decent Office', 'Tomb' }, - { 1000, 'Fine Quarters', 'Fine Dining Room', 'Splendid Office', 'Fine Tomb' }, - { 1500, 'Great Bedroom', 'Great Dining Room', 'Throne Room', 'Mausoleum' }, - { 2500, 'Grand Bedroom', 'Grand Dining Room', 'Opulent Throne Room', 'Grand Mausoleum' }, - { 10000, 'Royal Bedroom', 'Royal Dining Room', 'Royal Throne Room', 'Royal Mausoleum' } -} - -function getRoomName(building, unit) - local info = room_type_table[building._type] - if not info or not building.is_room then - return utils.getBuildingName(building) - end - - local quality = building:getRoomValue(unit) - local row = room_quality_table[1] - for _,v in ipairs(room_quality_table) do - if v[1] <= quality then - row = v - else - break - end - end - return row[info.qidx] -end - -function makeRoomEntry(bld, unit, is_spouse) - local info = room_type_table[bld._type] or {} - - return { - obj = bld, - token = info.token or '?', - tile = info.tile or '?', - caption = getRoomName(bld, unit), - can_use = (not is_spouse or bld:canUseSpouseRoom()), - owner = unit - } -end - -function listRooms(unit, spouse) - local rv = {} - for _,v in pairs(unit.owned_buildings) do - if v.owner == unit then - rv[#rv+1] = makeRoomEntry(v, unit, spouse) - end - end - return rv -end - -function concat_lists(...) - local rv = {} - for i = 1,select('#',...) do - local v = select(i,...) - if v then - for _,x in ipairs(v) do rv[#rv+1] = x end - end - end - return rv -end - -RoomList = defclass(RoomList, guidm.MenuOverlay) - -RoomList.focus_path = 'room-list' - -RoomList.ATTRS{ unit = DEFAULT_NIL } - -function RoomList:init(info) - local unit = info.unit - local base_bld = df.global.world.selected_building - - self:assign{ - base_building = base_bld, - items = {}, selected = 1, - own_rooms = {}, spouse_rooms = {} - } - - self.old_viewport = self:getViewport() - self.old_cursor = guidm.getCursorPos() - - if unit then - self.own_rooms = listRooms(unit) - self.spouse = df.unit.find(unit.relations.spouse_id) - if self.spouse then - self.spouse_rooms = listRooms(self.spouse, unit) - end - self.items = concat_lists(self.own_rooms, self.spouse_rooms) - end - - if base_bld then - for i,v in ipairs(self.items) do - if v.obj == base_bld then - self.selected = i - v.tile = 26 - goto found - end - end - self.base_item = makeRoomEntry(base_bld, unit) - self.base_item.owner = unit - self.base_item.old_owner = base_bld.owner - self.base_item.tile = 26 - self.items = concat_lists({self.base_item}, self.items) - ::found:: - end -end - -local sex_char = { [0] = 12, [1] = 11 } - -function drawUnitName(dc, unit) - dc:pen(COLOR_GREY) - if unit then - local color = dfhack.units.getProfessionColor(unit) - dc:char(sex_char[unit.sex] or '?'):advance(1):pen(color) - - local vname = dfhack.units.getVisibleName(unit) - if vname and vname.has_name then - dc:string(dfhack.TranslateName(vname)..', ') - end - dc:string(dfhack.units.getProfessionName(unit)) - else - dc:string("No Owner Assigned") - end -end - -function drawRoomEntry(dc, entry, selected) - local color = COLOR_GREEN - if not entry.can_use then - color = COLOR_RED - elseif entry.obj.owner ~= entry.owner or not entry.owner then - color = COLOR_CYAN - end - dc:pen{fg = color, bold = (selected == entry)} - dc:char(entry.tile):advance(1):string(entry.caption) -end - -function can_modify(sel_item) - return sel_item and sel_item.owner - and sel_item.can_use and not sel_item.owner.flags1.dead -end - -function RoomList:onRenderBody(dc) - local sel_item = self.items[self.selected] - - dc:clear():seek(1,1) - drawUnitName(dc, self.unit) - - if self.base_item then - dc:newline():newline(2) - drawRoomEntry(dc, self.base_item, sel_item) - end - if #self.own_rooms > 0 then - dc:newline() - for _,v in ipairs(self.own_rooms) do - dc:newline(2) - drawRoomEntry(dc, v, sel_item) - end - end - if #self.spouse_rooms > 0 then - dc:newline():newline(1) - drawUnitName(dc, self.spouse) - - dc:newline() - for _,v in ipairs(self.spouse_rooms) do - dc:newline(2) - drawRoomEntry(dc, v, sel_item) - end - end - if self.unit and #self.own_rooms == 0 and #self.spouse_rooms == 0 then - dc:newline():newline(2):string("No already assigned rooms.", COLOR_LIGHTRED) - end - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back") - - if can_modify(sel_item) then - dc:string(", "):key('SELECT') - if sel_item.obj.owner == sel_item.owner then - dc:string(": Unassign") - else - dc:string(": Assign") - end - end -end - -function RoomList:changeSelected(delta) - if #self.items <= 1 then return end - self.selected = 1 + (self.selected + delta - 1) % #self.items - self:selectBuilding(self.items[self.selected].obj) -end - -function RoomList:onInput(keys) - local sel_item = self.items[self.selected] - - if keys.SECONDSCROLL_UP then - self:changeSelected(-1) - elseif keys.SECONDSCROLL_DOWN then - self:changeSelected(1) - elseif keys.LEAVESCREEN then - self:dismiss() - - if self.base_building then - if not sel_item or self.base_building ~= sel_item.obj then - self:selectBuilding(self.base_building, self.old_cursor, self.old_view) - end - if self.unit and self.base_building.owner == self.unit then - df.global.ui_building_in_assign = false - end - end - elseif keys.SELECT then - if can_modify(sel_item) then - local owner = sel_item.owner - if sel_item.obj.owner == owner then - owner = sel_item.old_owner - end - dfhack.buildings.setOwner(sel_item.obj, owner) - end - elseif self:simulateViewScroll(keys) then - return - end -end - -local focus = dfhack.gui.getCurFocus() - -if focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then - local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] - RoomList{ unit = unit }:show() -elseif string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then - local base = df.global.world.selected_building - RoomList{ unit = base.owner }:show() -else - qerror("This script requires the main dwarfmode view in 'q' mode") -end diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua deleted file mode 100644 index 8ebfe0dc4..000000000 --- a/scripts/gui/siege-engine.lua +++ /dev/null @@ -1,536 +0,0 @@ --- Front-end for the siege engine plugin. ---[[=begin - -gui/siege-engine -================ -An in-game interface for `siege-engine`. - -Bind it to a key (the example config uses :kbd:`Alt`:kbd:`a`) and -activate after selecting a siege engine in :kbd:`q` mode. - -.. image:: /docs/images/siege-engine.png - -The main mode displays the current target, selected ammo item -type, linked stockpiles and the allowed operator skill range. The -map tile color is changed to signify if it can be hit by the -selected engine: green for fully reachable, blue for out of -range, red for blocked, yellow for partially blocked. - -Pressing :kbd:`r` changes into the target selection mode, which -works by highlighting two points with :kbd:`Enter` like all -designations. When a target area is set, the engine projectiles -are aimed at that area, or units within it (this doesn't actually -change the original aiming code, instead the projectile -trajectory parameters are rewritten as soon as it appears). - -After setting the target in this way for one engine, you can -'paste' the same area into others just by pressing :kbd:`p` in -the main page of this script. The area to paste is kept until you -quit DF, or select another area manually. - -Pressing :kbd:`t` switches to a mode for selecting a stockpile to -take ammo from. - -Exiting from the siege engine script via :kbd:`Esc` reverts the -view to the state prior to starting the script. -:kbd:`Shift`:kbd:`Esc` retains the current viewport, and also -exits from the :kbd:`q` mode to main menu. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local plugin = require 'plugins.siege-engine' -local wmap = df.global.world.map - -local LEGENDARY = df.skill_rating.Legendary - --- Globals kept between script calls -last_target_min = last_target_min or nil -last_target_max = last_target_max or nil - -local item_choices = { - { caption = 'boulders (default)', item_type = df.item_type.BOULDER }, - { caption = 'blocks', item_type = df.item_type.BLOCKS }, - { caption = 'weapons', item_type = df.item_type.WEAPON }, - { caption = 'trap components', item_type = df.item_type.TRAPCOMP }, - { caption = 'bins', item_type = df.item_type.BIN }, - { caption = 'barrels', item_type = df.item_type.BARREL }, - { caption = 'cages', item_type = df.item_type.CAGE }, - { caption = 'anything', item_type = -1 }, -} - -local item_choice_idx = {} -for i,v in ipairs(item_choices) do - item_choice_idx[v.item_type] = i -end - -SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) - -SiegeEngine.focus_path = 'siege-engine' - -SiegeEngine.ATTRS{ building = DEFAULT_NIL } - -function SiegeEngine:init() - self:assign{ - center = utils.getBuildingCenter(self.building), - selected_pile = 1, - mode_main = { - render = self:callback 'onRenderBody_main', - input = self:callback 'onInput_main', - }, - mode_aim = { - render = self:callback 'onRenderBody_aim', - input = self:callback 'onInput_aim', - }, - mode_pile = { - render = self:callback 'onRenderBody_pile', - input = self:callback 'onInput_pile', - } - } -end - -function SiegeEngine:onShow() - SiegeEngine.super.onShow(self) - - self.old_cursor = guidm.getCursorPos() - self.old_viewport = self:getViewport() - - self.mode = self.mode_main - self:showCursor(false) -end - -function SiegeEngine:onDestroy() - if self.save_profile then - plugin.saveWorkshopProfile(self.building) - end - if not self.no_select_building then - self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) - end -end - -function SiegeEngine:onGetSelectedBuilding() - return df.global.world.selected_building -end - -function SiegeEngine:showCursor(enable) - local cursor = guidm.getCursorPos() - if cursor and not enable then - self.cursor = cursor - self.target_select_first = nil - guidm.clearCursorPos() - elseif not cursor and enable then - local view = self:getViewport() - cursor = self.cursor - if not cursor or not view:isVisible(cursor) then - cursor = view:getCenter() - end - self.cursor = nil - guidm.setCursorPos(cursor) - end -end - -function SiegeEngine:centerViewOn(pos) - local cursor = guidm.getCursorPos() - if cursor then - guidm.setCursorPos(pos) - else - self.cursor = pos - end - self:getViewport():centerOn(pos):set() -end - -function SiegeEngine:zoomToTarget() - local target_min, target_max = plugin.getTargetArea(self.building) - if target_min then - local cx = math.floor((target_min.x + target_max.x)/2) - local cy = math.floor((target_min.y + target_max.y)/2) - local cz = math.floor((target_min.z + target_max.z)/2) - local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz)) - self:centerViewOn(pos) - end -end - -function paint_target_grid(dc, view, origin, p1, p2) - local r1, sz, r2 = guidm.getSelectionRange(p1, p2) - - if view.z < r1.z or view.z > r2.z then - return - end - - local p1 = view:tileToScreen(r1) - local p2 = view:tileToScreen(r2) - local org = view:tileToScreen(origin) - dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true } - - -- Frame - dc:fill(p1.x,p1.y,p1.x,p2.y) - dc:fill(p1.x,p1.y,p2.x,p1.y) - dc:fill(p2.x,p1.y,p2.x,p2.y) - dc:fill(p1.x,p2.y,p2.x,p2.y) - - -- Grid - local gxmin = org.x+10*math.ceil((p1.x-org.x)/10) - local gxmax = org.x+10*math.floor((p2.x-org.x)/10) - local gymin = org.y+10*math.ceil((p1.y-org.y)/10) - local gymax = org.y+10*math.floor((p2.y-org.y)/10) - for x = gxmin,gxmax,10 do - for y = gymin,gymax,10 do - dc:fill(p1.x,y,p2.x,y) - dc:fill(x,p1.y,x,p2.y) - end - end -end - -function SiegeEngine:renderTargetView(target_min, target_max) - local view = self:getViewport() - local map = self.df_layout.map - local map_dc = gui.Painter.new(map) - - plugin.paintAimScreen( - self.building, view:getPos(), - xy2pos(map.x1, map.y1), view:getSize() - ) - - if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then - paint_target_grid(map_dc, view, self.center, target_min, target_max) - end - - local cursor = guidm.getCursorPos() - if cursor then - local cx, cy, cz = pos2xyz(view:tileToScreen(cursor)) - if cz == 0 then - map_dc:seek(cx,cy):char('X', COLOR_YELLOW) - end - end -end - -function SiegeEngine:scrollPiles(delta) - local links = plugin.getStockpileLinks(self.building) - if links then - self.selected_pile = 1+(self.selected_pile+delta-1) % #links - return links[self.selected_pile] - end -end - -function SiegeEngine:renderStockpiles(dc, links, nlines) - local idx = (self.selected_pile-1) % #links - local page = math.floor(idx/nlines) - for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do - local color = COLOR_BROWN - if i == idx then - color = COLOR_YELLOW - end - dc:newline(2):string(utils.getBuildingName(links[i+1]), color) - end -end - -function SiegeEngine:onRenderBody_main(dc) - dc:newline(1):pen(COLOR_WHITE):string("Target: ") - - local target_min, target_max = plugin.getTargetArea(self.building) - if target_min then - dc:string( - (target_max.x-target_min.x+1).."x".. - (target_max.y-target_min.y+1).."x".. - (target_max.z-target_min.z+1).." Rect" - ) - else - dc:string("None (default)") - end - - dc:newline(3):key('CUSTOM_R'):string(": Rectangle") - if last_target_min then - dc:string(", "):key('CUSTOM_P'):string(": Paste") - end - dc:newline(3) - if target_min then - dc:key('CUSTOM_X'):string(": Clear, ") - dc:key('CUSTOM_Z'):string(": Zoom") - end - - dc:newline():newline(1) - if self.building.type == df.siegeengine_type.Ballista then - dc:string("Uses ballista arrows") - else - local item = plugin.getAmmoItem(self.building) - dc:key('CUSTOM_U'):string(": Use ") - if item_choice_idx[item] then - dc:string(item_choices[item_choice_idx[item]].caption) - else - dc:string(df.item_type[item]) - end - end - - dc:newline():newline(1) - dc:key('CUSTOM_T'):string(": Take from stockpile"):newline(3) - local links = plugin.getStockpileLinks(self.building) - local bottom = dc.height - 5 - if links then - dc:key('CUSTOM_D'):string(": Delete, ") - dc:key('CUSTOM_O'):string(": Zoom"):newline() - self:renderStockpiles(dc, links, bottom-2-dc:cursorY()) - dc:newline():newline() - end - - local prof = self.building:getWorkshopProfile() or {} - dc:seek(1,math.max(dc:cursorY(),19)) - dc:key('CUSTOM_G'):key('CUSTOM_H'):key('CUSTOM_J'):key('CUSTOM_K') - dc:string(': ') - dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-') - dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption) - dc:newline():newline() - - if self.target_select_first then - self:renderTargetView(self.target_select_first, guidm.getCursorPos()) - else - self:renderTargetView(target_min, target_max) - end -end - -function SiegeEngine:setTargetArea(p1, p2) - self.target_select_first = nil - - if not plugin.setTargetArea(self.building, p1, p2) then - dlg.showMessage( - 'Set Target Area', - 'Could not set the target area', COLOR_LIGHTRED - ) - else - last_target_min = p1 - last_target_max = p2 - end -end - -function SiegeEngine:setAmmoItem(choice) - if self.building.type == df.siegeengine_type.Ballista then - return - end - - if not plugin.setAmmoItem(self.building, choice.item_type) then - dlg.showMessage( - 'Set Ammo Item', - 'Could not set the ammo item', COLOR_LIGHTRED - ) - end -end - -function SiegeEngine:onInput_main(keys) - if keys.CUSTOM_R then - self:showCursor(true) - self.target_select_first = nil - self.mode = self.mode_aim - elseif keys.CUSTOM_P and last_target_min then - self:setTargetArea(last_target_min, last_target_max) - elseif keys.CUSTOM_U then - local item = plugin.getAmmoItem(self.building) - local idx = 1 + (item_choice_idx[item] or 0) % #item_choices - self:setAmmoItem(item_choices[idx]) - elseif keys.CUSTOM_Z then - self:zoomToTarget() - elseif keys.CUSTOM_X then - plugin.clearTargetArea(self.building) - elseif keys.SECONDSCROLL_UP then - self:scrollPiles(-1) - elseif keys.SECONDSCROLL_DOWN then - self:scrollPiles(1) - elseif keys.CUSTOM_D then - local pile = self:scrollPiles(0) - if pile then - plugin.removeStockpileLink(self.building, pile) - end - elseif keys.CUSTOM_O then - local pile = self:scrollPiles(0) - if pile then - self:centerViewOn(utils.getBuildingCenter(pile)) - end - elseif keys.CUSTOM_T then - self:showCursor(true) - self.mode = self.mode_pile - self:sendInputToParent('CURSOR_DOWN_Z') - self:sendInputToParent('CURSOR_UP_Z') - elseif keys.CUSTOM_G then - local prof = plugin.saveWorkshopProfile(self.building) - prof.min_level = math.max(0, prof.min_level-1) - plugin.saveWorkshopProfile(self.building) - elseif keys.CUSTOM_H then - local prof = plugin.saveWorkshopProfile(self.building) - prof.min_level = math.min(LEGENDARY, prof.min_level+1) - plugin.saveWorkshopProfile(self.building) - elseif keys.CUSTOM_J then - local prof = plugin.saveWorkshopProfile(self.building) - prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1) - plugin.saveWorkshopProfile(self.building) - elseif keys.CUSTOM_K then - local prof = plugin.saveWorkshopProfile(self.building) - prof.max_level = math.min(LEGENDARY, prof.max_level+1) - if prof.max_level >= LEGENDARY then prof.max_level = 3000 end - plugin.saveWorkshopProfile(self.building) - elseif self:simulateViewScroll(keys) then - self.cursor = nil - else - return false - end - return true -end - -local status_table = { - ok = { pen = COLOR_GREEN, msg = "Target accessible" }, - out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, - blocked = { pen = COLOR_RED, msg = "Target obstructed" }, - semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" }, -} - -function SiegeEngine:onRenderBody_aim(dc) - local cursor = guidm.getCursorPos() - local first = self.target_select_first - - dc:newline(1):string('Select target rectangle'):newline() - - local info = status_table[plugin.getTileStatus(self.building, cursor)] - if info then - dc:newline(2):string(info.msg, info.pen) - else - dc:newline(2):string('ERROR', COLOR_RED) - end - - dc:newline():newline(1):key('SELECT') - if first then - dc:string(": Finish rectangle") - else - dc:string(": Start rectangle") - end - dc:newline() - - local target_min, target_max = plugin.getTargetArea(self.building) - if target_min then - dc:newline(1):key('CUSTOM_Z'):string(": Zoom to current target") - end - - if first then - self:renderTargetView(first, cursor) - else - local target_min, target_max = plugin.getTargetArea(self.building) - self:renderTargetView(target_min, target_max) - end -end - -function SiegeEngine:onInput_aim(keys) - if keys.SELECT then - local cursor = guidm.getCursorPos() - if self.target_select_first then - self:setTargetArea(self.target_select_first, cursor) - - self.mode = self.mode_main - self:showCursor(false) - else - self.target_select_first = cursor - end - elseif keys.CUSTOM_Z then - self:zoomToTarget() - elseif keys.LEAVESCREEN then - self.mode = self.mode_main - self:showCursor(false) - elseif self:simulateCursorMovement(keys) then - self.cursor = nil - else - return false - end - return true -end - -function SiegeEngine:onRenderBody_pile(dc) - dc:newline(1):string('Select pile to take from'):newline():newline(2) - - local sel = df.global.world.selected_building - - if df.building_stockpilest:is_instance(sel) then - dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1) - - if plugin.isLinkedToPile(self.building, sel) then - dc:string("Already taking from here"):newline():newline(2) - dc:key('CUSTOM_D'):string(": Delete link") - else - dc:key('SELECT'):string(": Take from this pile") - end - elseif sel then - dc:string(utils.getBuildingName(sel), COLOR_DARKGREY) - dc:newline():newline(1) - dc:string("Not a stockpile",COLOR_LIGHTRED) - else - dc:string("No building selected", COLOR_DARKGREY) - end -end - -function SiegeEngine:onInput_pile(keys) - if keys.SELECT then - local sel = df.global.world.selected_building - if df.building_stockpilest:is_instance(sel) - and not plugin.isLinkedToPile(self.building, sel) then - plugin.addStockpileLink(self.building, sel) - - df.global.world.selected_building = self.building - self.mode = self.mode_main - self:showCursor(false) - end - elseif keys.CUSTOM_D then - local sel = df.global.world.selected_building - if df.building_stockpilest:is_instance(sel) then - plugin.removeStockpileLink(self.building, sel) - end - elseif keys.LEAVESCREEN then - df.global.world.selected_building = self.building - self.mode = self.mode_main - self:showCursor(false) - elseif self:propagateMoveKeys(keys) then - -- - else - return false - end - return true -end - -function SiegeEngine:onRenderBody(dc) - dc:clear() - dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline() - - self.mode.render(dc) - - dc:seek(1, math.max(dc:cursorY(), 21)):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back, ") - dc:key('CUSTOM_C'):string(": Recenter") -end - -function SiegeEngine:onInput(keys) - if self.mode.input(keys) then - -- - elseif keys.CUSTOM_C then - self:centerViewOn(self.center) - elseif keys.LEAVESCREEN then - self:dismiss() - elseif keys.LEAVESCREEN_ALL then - self:dismiss() - self.no_select_building = true - guidm.clearCursorPos() - df.global.ui.main.mode = df.ui_sidebar_mode.Default - df.global.world.selected_building = nil - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then - qerror("This script requires a siege engine selected in 'q' mode") -end - -local building = df.global.world.selected_building - -if not df.building_siegeenginest:is_instance(building) then - qerror("A siege engine must be selected") -end -if building:getBuildStage() < building:getMaxBuildStage() then - qerror("This engine is not completely built yet") -end - -local list = SiegeEngine{ building = building } -list:show() diff --git a/scripts/gui/stockpiles.lua b/scripts/gui/stockpiles.lua deleted file mode 100644 index 4e2f28281..000000000 --- a/scripts/gui/stockpiles.lua +++ /dev/null @@ -1,78 +0,0 @@ --- lave/load stockpile settings with a GUI ---[[=begin - -gui/stockpiles -============== -An in-game interface for `stocksettings`, to -load and save stockpile settings from the :kbd:`q` menu. - -Usage: - -:gui/stockpiles -save: to save the current stockpile -:gui/stockpiles -load: to load settings into the current stockpile -:gui/stockpiles -dir : set the default directory to save settings into -:gui/stockpiles -help: to see this message - -Don't forget to ``enable stockpiles`` and create the ``stocksettings`` directory in -the DF folder before trying to use the GUI. - -=end]] -local stock = require 'plugins.stockpiles' - -function check_enabled() - return stock.isEnabled() -end - -function world_guard() - if not dfhack.isMapLoaded() then - qerror("World is not loaded") - return false - end - return true -end - -function guard() - if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then - qerror("This script requires a stockpile selected in the 'q' mode") - return false - end - return true -end - -utils = require('utils') -validArgs = validArgs or utils.invert({ - 'help', - 'load', - 'save', - 'dir', -}) - -args = utils.processArgs({...}, validArgs) - -function usage() - print("") - print("Stockpile Settings. Arguments: ") - print("-save to save the current stockpile") - print("-load to load settings into the current stockpile") - print("-dir set the default directory to save settings into") - if dfhack.isMapLoaded() then - print(" Current directory is: " .. stock.get_path()) - end - print("") -end - -if not check_enabled() then - qerror("Stockpiles plugin not enabled. Enable it with: enable stockpiles") -elseif args.load then - if not guard() then return end - stock.load_settings() -elseif args.save then - if not guard() then return end - local sp = dfhack.gui.getSelectedBuilding(true) - stock.save_settings(sp) -elseif args.dir then - if not world_guard() then return end - stock.set_path(args.dir) -else - usage() -end diff --git a/scripts/gui/unit-info-viewer.lua b/scripts/gui/unit-info-viewer.lua deleted file mode 100644 index 1d7cb763d..000000000 --- a/scripts/gui/unit-info-viewer.lua +++ /dev/null @@ -1,800 +0,0 @@ --- unit-info-viewer.lua --- Displays age, birth, maxage, shearing, milking, grazing, egg laying, body size, and death info about a unit. Recommended keybinding Alt-I --- version 1.04 --- original author: Kurik Amudnil --- edited by expwnent ---[[=begin - -gui/unit-info-viewer -==================== -Displays age, birth, maxage, shearing, milking, grazing, egg laying, body size, -and death info about a unit. Recommended keybinding :kbd:`Alt`:kbd:`I`. - -=end]] -local gui = require 'gui' -local widgets =require 'gui.widgets' -local utils = require 'utils' - -local DEBUG = false -if DEBUG then print('-----') end - -local pens = { - BLACK = dfhack.pen.parse{fg=COLOR_BLACK,bg=0}, - BLUE = dfhack.pen.parse{fg=COLOR_BLUE,bg=0}, - GREEN = dfhack.pen.parse{fg=COLOR_GREEN,bg=0}, - CYAN = dfhack.pen.parse{fg=COLOR_CYAN,bg=0}, - RED = dfhack.pen.parse{fg=COLOR_RED,bg=0}, - MAGENTA = dfhack.pen.parse{fg=COLOR_MAGENTA,bg=0}, - BROWN = dfhack.pen.parse{fg=COLOR_BROWN,bg=0}, - GREY = dfhack.pen.parse{fg=COLOR_GREY,bg=0}, - DARKGREY = dfhack.pen.parse{fg=COLOR_DARKGREY,bg=0}, - LIGHTBLUE = dfhack.pen.parse{fg=COLOR_LIGHTBLUE,bg=0}, - LIGHTGREEN = dfhack.pen.parse{fg=COLOR_LIGHTGREEN,bg=0}, - LIGHTCYAN = dfhack.pen.parse{fg=COLOR_LIGHTCYAN,bg=0}, - LIGHTRED = dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}, - LIGHTMAGENTA = dfhack.pen.parse{fg=COLOR_LIGHTMAGENTA,bg=0}, - YELLOW = dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}, - WHITE = dfhack.pen.parse{fg=COLOR_WHITE,bg=0}, -} - -function getUnit_byID(id) -- get unit by id from units.all via binsearch - if type(id) == 'number' then - -- (vector,key,field,cmpfun,min,max) { item/nil , found true/false , idx/insert at } - return utils.binsearch(df.global.world.units.all,id,'id') - end -end - -function getUnit_byVS(silent) -- by view screen mode - silent = silent or false - -- if not world loaded, return nil ? - local u,tmp -- u: the unit to return ; tmp: temporary for intermediate tests/return values - local v = dfhack.gui.getCurViewscreen() - u = dfhack.gui.getSelectedUnit(true) -- supports gui scripts/plugin that provide a hook for getSelectedUnit() - if u then - return u - -- else: contexts not currently supported by dfhack.gui.getSelectedUnit() - elseif df.viewscreen_dwarfmodest:is_instance(v) then - tmp = df.global.ui.main.mode - if tmp == 17 or tmp == 42 or tmp == 43 then - -- context: @dwarfmode/QueryBuiding/Some/Cage -- (q)uery cage - -- context: @dwarfmode/ZonesPenInfo/AssignUnit -- i (zone) -> pe(N) - -- context: @dwarfmode/ZonesPitInfo -- i (zone) -> (P)it - u = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] - elseif tmp == 49 and df.global.ui.burrows.in_add_units_mode then - -- @dwarfmode/Burrows/AddUnits - u = df.global.ui.burrows.list_units[ df.global.ui.burrows.unit_cursor_pos ] - - elseif df.global.ui.follow_unit ~= -1 then - -- context: follow unit mode - u = getUnit_byID(df.global.ui.follow_unit) - end -- end viewscreen_dwarfmodest - elseif df.viewscreen_petst:is_instance(v) then - -- context: @pet/List/Unit -- z (status) -> animals - if v.mode == 0 then -- List - if not v.is_vermin[v.cursor] then - u = v.animal[v.cursor].unit - end - --elseif v.mode = 1 then -- training knowledge (no unit reference) - elseif v.mode == 2 then -- select trainer - u = v.trainer_unit[v.trainer_cursor] - end - elseif df.viewscreen_layer_workshop_profilest:is_instance(v) then - -- context: @layer_workshop_profile/Unit -- (q)uery workshop -> (P)rofile -- df.global.ui.main.mode == 17 - u = v.workers[v.layer_objects[0].cursor] - elseif df.viewscreen_layer_overall_healthst:is_instance(v) then - -- context @layer_overall_health/Units -- z -> health - u = v.unit[v.layer_objects[0].cursor] - elseif df.viewscreen_layer_militaryst:is_instance(v) then - local PG_ASSIGNMENTS = 0 - local PG_EQUIPMENT = 2 - local TB_POSITIONS = 1 - local TB_CANDIDATES = 2 - -- layer_objects[0: squads list; 1: positions list; 2: candidates list] - -- page 0:positions/assignments 1:alerts 2:equipment 3:uniforms 4:supplies 5:ammunition - if v.page == PG_ASSIGNMENTS and v.layer_objects[TB_CANDIDATES].enabled and v.layer_objects[TB_CANDIDATES].active then - -- context: @layer_military/Positions/Position/Candidates -- m -> Candidates - u = v.positions.candidates[v.layer_objects[TB_CANDIDATES].cursor] - elseif v.page == PG_ASSIGNMENTS and v.layer_objects[TB_POSITIONS].enabled and v.layer_objects[TB_POSITIONS].active then - -- context: @layer_military/Positions/Position -- m -> Positions - u = v.positions.assigned[v.layer_objects[TB_POSITIONS].cursor] - elseif v.page == PG_EQUIPMENT and v.layer_objects[TB_POSITIONS].enabled and v.layer_objects[TB_POSITIONS].active then - -- context: @layer_military/Equip/Customize/View/Position -- m -> (e)quip -> Positions - -- context: @layer_military/Equip/Uniform/Positions -- m -> (e)quip -> assign (U)niforms -> Positions - u = v.equip.units[v.layer_objects[TB_POSITIONS].cursor] - end - elseif df.viewscreen_layer_noblelistst:is_instance(v) then - if v.mode == 0 then - -- context: @layer_noblelist/List -- (n)obles - u = v.info[v.layer_objects[v.mode].cursor].unit - elseif v.mode == 1 then - -- context: @layer_noblelist/Appoint -- (n)obles -> (r)eplace - u = v.candidates[v.layer_objects[v.mode].cursor].unit - end - elseif df.viewscreen_unitst:is_instance(v) then - -- @unit -- (v)unit -> z ; loo(k) -> enter ; (n)obles -> enter ; others - u = v.unit - elseif df.viewscreen_customize_unitst:is_instance(v) then - -- @customize_unit -- @unit -> y - u = v.unit - elseif df.viewscreen_layer_unit_healthst:is_instance(v) then - -- @layer_unit_health -- @unit -> h ; @layer_overall_health/Units -> enter - if df.viewscreen_layer_overall_healthst:is_instance(v.parent) then - -- context @layer_overall_health/Units -- z (status)-> health - u = v.parent.unit[v.parent.layer_objects[0].cursor] - elseif df.viewscreen_unitst:is_instance(v.parent) then - -- @unit -- (v)unit -> z ; loo(k) -> enter ; (n)obles -> enter ; others - u = v.parent.unit - end - elseif df.viewscreen_textviewerst:is_instance(v) then - -- @textviewer -- @unit -> enter (thoughts and preferences) - if df.viewscreen_unitst:is_instance(v.parent) then - -- @unit -- @unit -> enter (thoughts and preferences) - u = v.parent.unit - elseif df.viewscreen_itemst:is_instance(v.parent) then - tmp = v.parent.entry_ref[v.parent.cursor_pos] - if df.general_ref_unit:is_instance(tmp) then -- general_ref_unit and derived ; general_ref_contains_unitst ; others? - u = getUnit_byID(tmp.unit_id) - end - elseif df.viewscreen_dwarfmodest:is_instance(v.parent) then - tmp = df.global.ui.main.mode - if tmp == 24 then -- (v)iew units {g,i,p,w} -> z (thoughts and preferences) - -- context: @dwarfmode/ViewUnits/... - --if df.global.ui_selected_unit > -1 then -- -1 = 'no units nearby' - u = df.global.world.units.active[df.global.ui_selected_unit] - --end - elseif tmp == 25 then -- loo(k) unit -> enter (thoughs and preferences) - -- context: @dwarfmode/LookAround/Unit - tmp = df.global.ui_look_list.items[df.global.ui_look_cursor] - if tmp.type == 2 then -- 0:item 1:terrain >>2: unit<< 3:building 4:colony/vermin 7:spatter - u = tmp.unit - end - end - elseif df.viewscreen_unitlistst:is_instance(v.parent) then -- (u)nit list -> (v)iew unit (not citizen) - -- context: @unitlist/Citizens ; @unitlist/Livestock ; @unitlist/Others ; @unitlist/Dead - u = v.parent.units[v.parent.page][ v.parent.cursor_pos[v.parent.page] ] - end - end -- switch viewscreen - if not u and not silent then - dfhack.printerr('No unit is selected in the UI or context not supported.') - end - return u -end -- getUnit_byVS() - ---http://lua-users.org/wiki/StringRecipes ---------- -function str2FirstUpper(str) - return str:gsub("^%l", string.upper) -end - --------------------------------------------------- ---http://lua-users.org/wiki/StringRecipes ---------- -local function tchelper(first, rest) - return first:upper()..rest:lower() -end - --- Add extra characters to the pattern if you need to. _ and ' are --- found in the middle of identifiers and English words. --- We must also put %w_' into [%w_'] to make it handle normal stuff --- and extra stuff the same. --- This also turns hex numbers into, eg. 0Xa7d4 -function str2TitleCase(str) - return str:gsub("(%a)([%w_']*)", tchelper) -end - --------------------------------------------------- ---isBlank suggestion by http://stackoverflow.com/a/10330861 -function isBlank(x) - x = tostring(x) or "" - -- returns (not not match_begin), _ = match_end => not not true , _ => true - -- returns not not nil => false (no match) - return not not x:find("^%s*$") -end - ---http://lua-users.org/wiki/StringRecipes (removed indents since I am not using them) -function wrap(str, limit)--, indent, indent1) - --indent = indent or "" - --indent1 = indent1 or indent - local limit = limit or 72 - local here = 1 ---#indent1 - return str:gsub("(%s+)()(%S+)()", --indent1..str:gsub( - function(sp, st, word, fi) - if fi-here > limit then - here = st -- - #indent - return "\n"..word --..indent..word - end - end) -end - - --------------------------------------------------- ----------------------- Time ---------------------- --------------------------------------------------- -local TU_PER_DAY = 1200 ---[[ -if advmode then TU_PER_DAY = 86400 ? or only for cur_year_tick? -advmod_TU / 72 = ticks ---]] -local TU_PER_MONTH = TU_PER_DAY * 28 -local TU_PER_YEAR = TU_PER_MONTH * 12 - -local MONTHS = { - 'Granite', - 'Slate', - 'Felsite', - 'Hematite', - 'Malachite', - 'Galena', - 'Limestone', - 'Sandstone', - 'Timber', - 'Moonstone', - 'Opal', - 'Obsidian', -} -Time = defclass(Time) -function Time:init(args) - self.year = args.year or 0 - self.ticks = args.ticks or 0 -end -function Time:getDays() -- >>float<< Days as age (including years) - return self.year * 336 + (self.ticks / TU_PER_DAY) -end -function Time:getMonths() -- >>int<< Months as age (not including years) - return math.floor (self.ticks / TU_PER_MONTH) -end -function Time:getMonthStr() -- Month as date - return MONTHS[self:getMonths()+1] or 'error' -end -function Time:getDayStr() -- Day as date - local d = math.floor ( (self.ticks % TU_PER_MONTH) / TU_PER_DAY ) + 1 - if d == 11 or d == 12 or d == 13 then - d = tostring(d)..'th' - elseif d % 10 == 1 then - d = tostring(d)..'st' - elseif d % 10 == 2 then - d = tostring(d)..'nd' - elseif d % 10 == 3 then - d = tostring(d)..'rd' - else - d = tostring(d)..'th' - end - return d -end ---function Time:__add() ---end -function Time:__sub(other) - if DEBUG then print(self.year,self.ticks) end - if DEBUG then print(other.year,other.ticks) end - if self.ticks < other.ticks then - return Time{ year = (self.year - other.year - 1) , ticks = (TU_PER_YEAR + self.ticks - other.ticks) } - else - return Time{ year = (self.year - other.year) , ticks = (self.ticks - other.ticks) } - end -end --------------------------------------------------- --------------------------------------------------- - --------------------------------------------------- --------------------- Identity -------------------- --------------------------------------------------- -local SINGULAR = 0 -local PLURAL = 1 ---local POSSESSIVE = 2 - -local PRONOUNS = { - [0]='She', - [1]='He', - [2]='It', -} -local BABY = 0 -local CHILD = 1 -local ADULT = 2 - -local TRAINING_LEVELS = { - [0] = ' (Semi-Wild)', -- Semi-wild - ' (Trained)', -- Trained - ' (-Trained-)', -- Well-trained - ' (+Trained+)', -- Skillfully trained - ' (*Trained*)', -- Expertly trained - ' ('..string.char(240)..'Trained'..string.char(240)..')', -- Exceptionally trained - ' ('..string.char(15)..'Trained'..string.char(15)..')', -- Masterully Trained - ' (Tame)', -- Domesticated - '', -- undefined - '', -- wild/untameable -} - -local DEATH_TYPES = { - [0] = ' died of old age', -- OLD_AGE - ' starved to death', -- HUNGER - ' died of dehydration', -- THIRST - ' was shot and killed', -- SHOT - ' bled to death', -- BLEED - ' drowned', -- DROWN - ' suffocated', -- SUFFOCATE - ' was struck down', -- STRUCK_DOWN - ' was scuttled', -- SCUTTLE - " didn't survive a collision", -- COLLISION - ' took a magma bath', -- MAGMA - ' took a magma shower', -- MAGMA_MIST - ' was incinerated by dragon fire', -- DRAGONFIRE - ' was killed by fire', -- FIRE - ' experienced death by SCALD', -- SCALD - ' was crushed by cavein', -- CAVEIN - ' was smashed by a drawbridge', -- DRAWBRIDGE - ' was killed by falling rocks', -- FALLING_ROCKS - ' experienced death by CHASM', -- CHASM - ' experienced death by CAGE', -- CAGE - ' was murdered', -- MURDER - ' was killed by a trap', -- TRAP - ' vanished', -- VANISH - ' experienced death by QUIT', -- QUIT - ' experienced death by ABANDON', -- ABANDON - ' suffered heat stroke', -- HEAT - ' died of hypothermia', -- COLD - ' experienced death by SPIKE', -- SPIKE - ' experienced death by ENCASE_LAVA', -- ENCASE_LAVA - ' experienced death by ENCASE_MAGMA', -- ENCASE_MAGMA - ' was preserved in ice', -- ENCASE_ICE - ' became headless', -- BEHEAD - ' was crucified', -- CRUCIFY - ' experienced death by BURY_ALIVE', -- BURY_ALIVE - ' experienced death by DROWN_ALT', -- DROWN_ALT - ' experienced death by BURN_ALIVE', -- BURN_ALIVE - ' experienced death by FEED_TO_BEASTS', -- FEED_TO_BEASTS - ' experienced death by HACK_TO_PIECES', -- HACK_TO_PIECES - ' choked on air', -- LEAVE_OUT_IN_AIR - ' experienced death by BOIL', -- BOIL - ' melted', -- MELT - ' experienced death by CONDENSE', -- CONDENSE - ' experienced death by SOLIDIFY', -- SOLIDIFY - ' succumbed to infection', -- INFECTION - "'s ghost was put to rest with a memorial", -- MEMORIALIZE - ' scared to death', -- SCARE - ' experienced death by DARKNESS', -- DARKNESS - ' experienced death by COLLAPSE', -- COLLAPSE - ' was drained of blood', -- DRAIN_BLOOD - ' was slaughtered', -- SLAUGHTER - ' became roadkill', -- VEHICLE - ' killed by a falling object', -- FALLING_OBJECT -} - ---GHOST_TYPES[unit.relations.ghost_info.type].." This spirit has not been properly memorialized or buried." -local GHOST_TYPES = { - [0]="A murderous ghost.", - "A sadistic ghost.", - "A secretive ghost.", - "An energetic poltergeist.", - "An angry ghost.", - "A violent ghost.", - "A moaning spirit returned from the dead. It will generally trouble one unfortunate at a time.", - "A howling spirit. The ceaseless noise is making sleep difficult.", - "A troublesome poltergeist.", - "A restless haunt, generally troubling past acquaintances and relatives.", - "A forlorn haunt, seeking out known locations or drifting around the place of death.", -} - - -Identity = defclass(Identity) -function Identity:init(args) - local u = args.unit - self.ident = dfhack.units.getIdentity(u) - - self.unit = u - self.name = dfhack.TranslateName( dfhack.units.getVisibleName(u) ) - self.name_en = dfhack.TranslateName( dfhack.units.getVisibleName(u) , true) - self.raw_prof = dfhack.units.getProfessionName(u) - self.pronoun = PRONOUNS[u.sex] or 'It' - - if self.ident then - self.birth_date = Time{year = self.ident.birth_year, ticks = self.ident.birth_second} - self.race_id = self.ident.race - self.caste_id = self.ident.caste - if self.ident.histfig_id > -1 then - self.hf_id = self.ident.histfig_id - end - else - self.birth_date = Time{year = self.unit.relations.birth_year, ticks = self.unit.relations.birth_time} - self.race_id = u.race - self.caste_id = u.caste - if u.hist_figure_id > -1 then - self.hf_id = u.hist_figure_id - end - end - self.race = df.global.world.raws.creatures.all[self.race_id] - self.caste = self.race.caste[self.caste_id] - - self.isCivCitizen = (df.global.ui.civ_id == u.civ_id) - self.isStray = u.flags1.tame --self.isCivCitizen and not u.flags1.merchant - self.cur_date = Time{year = df.global.cur_year, ticks = df.global.cur_year_tick} - - - ------------ death ------------ - self.dead = u.flags1.dead - self.ghostly = u.flags3.ghostly - self.undead = u.enemy.undead - - if self.dead and self.hf_id then -- dead-dead not undead-dead - local events = df.global.world.history.events2 - local e - for idx = #events - 1,0,-1 do - e = events[idx] - if df.history_event_hist_figure_diedst:is_instance(e) and e.victim_hf == self.hf_id then - self.death_event = e - break - end - end - end - if u.counters.death_id > -1 then -- if undead/ghostly dead or dead-dead - self.incident = df.global.world.incidents.all[u.counters.death_id] - if not self.incident.flags.discovered then - self.missing = true - end - end - -- slaughtered? - if self.death_event then - self.death_date = Time{year = self.death_event.year, ticks = self.death_event.seconds} - elseif self.incident then - self.death_date = Time{year = self.incident.event_year, ticks = self.incident.event_time} - end - -- age now or age death? - if self.dead and self.death_date then -- if cursed with no age? -- if hacked a ressurection, such that they aren't dead anymore, don't use the death date - self.age_time = self.death_date - self.birth_date - else - self.age_time = self.cur_date - self.birth_date - end - if DEBUG then print( self.age_time.year,self.age_time.ticks,self.age_time:getMonths() ) end - ---------- ---------- ---------- - - - ---------- caste_name ---------- - self.caste_name = {} - if isBlank(self.caste.caste_name[SINGULAR]) then - self.caste_name[SINGULAR] = self.race.name[SINGULAR] - else - self.caste_name[SINGULAR] = self.caste.caste_name[SINGULAR] - end - if isBlank(self.caste.caste_name[PLURAL]) then - self.caste_name[PLURAL] = self.race.name[PLURAL] - else - self.caste_name[PLURAL] = self.caste.caste_name[PLURAL] - end - ---------- ---------- ---------- - - --------- growth_status --------- - -- 'baby_age' is the age the baby becomes a child - -- 'child_age' is the age the child becomes an adult - if self.age_time.year >= self.caste.misc.child_age then -- has child come of age becoming adult? - self.growth_status = ADULT - elseif self.age_time.year >= self.caste.misc.baby_age then -- has baby come of age becoming child? - self.growth_status = CHILD - else - self.growth_status = BABY - end - ---------- ---------- ---------- - - -------- aged_caste_name -------- - local caste_name, race_name - if self.growth_status == ADULT then - caste_name = self.caste.caste_name[SINGULAR] - race_name = self.race.name[SINGULAR] - elseif self.growth_status == CHILD then - caste_name = self.caste.child_name[SINGULAR] - race_name = self.race.general_child_name[SINGULAR] - else --if self.growth_status == BABY then - caste_name = self.caste.baby_name[SINGULAR] - race_name = self.race.general_baby_name[SINGULAR] - end - self.aged_caste_name = {} - if isBlank(caste_name[SINGULAR]) then - self.aged_caste_name[SINGULAR] = race_name[SINGULAR] - else - self.aged_caste_name[SINGULAR] = caste_name[SINGULAR] - end - if isBlank(caste_name[PLURAL]) then - self.aged_caste_name[PLURAL] = race_name[PLURAL] - else - self.aged_caste_name[PLURAL] = caste_name[PLURAL] - end - ---------- ---------- ---------- - - ----- Profession adjustment ----- - local prof = self.raw_prof - if self.undead then - prof = str2TitleCase( self.caste_name[SINGULAR] ) - if isBlank(u.enemy.undead.anon_7) then - prof = prof..' Corpse' - else - prof = u.enemy.undead.anon_7 -- a reanimated body part will use this string instead - end - end - --[[ - if self.ghostly then - prof = 'Ghostly '..prof - end - --]] - if u.curse.name_visible and not isBlank(u.curse.name) then - prof = prof..' '..u.curse.name - end - if isBlank(self.name) then - if self.isStray then - prof = 'Stray '..prof --..TRAINING_LEVELS[u.training_level] - end - end - self.prof = prof - ---------- ---------- ---------- -end --------------------------------------------------- --------------------------------------------------- ---[[ - prof_id ? - group_id ? - fort_race_id - fort_civ_id - --fort_group_id? ---]] - - -UnitInfoViewer = defclass(UnitInfoViewer, gui.FramedScreen) -UnitInfoViewer.focus_path = 'unitinfoviewer' -- -> dfhack/lua/unitinfoviewer -UnitInfoViewer.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_inset = 2, -- used by init - frame_outset = 1,--3, -- new, used by init; 0 = full screen, suggest 0, 1, or 3 or maybe 5 - --frame_title , -- not used - --frame_width,frame_height calculated by frame inset and outset in init -} -function UnitInfoViewer:init(args) -- requires args.unit - --if DEBUG then print('-----') end - local x,y = dfhack.screen.getWindowSize() - -- what if inset or outset are defined as {l,r,t,b}? - x = x - 2*(self.frame_inset + 1 + self.frame_outset) -- 1=frame border thickness - y = y - 2*(self.frame_inset + 1 + self.frame_outset) -- 1=frame border thickness - self.frame_width = args.frame_width or x - self.frame_height = args.frame_height or y - self.text = {} - if df.unit:is_instance(args.unit) then - self.ident = Identity{ unit = args.unit } - if not isBlank(self.ident.name_en) then - self.frame_title = 'Unit: '..self.ident.name_en - elseif not isBlank(self.ident.prof) then - self.frame_title = 'Unit: '..self.ident.prof - if self.ident.isStray then - self.frame_title = self.frame_title..TRAINING_LEVELS[self.ident.unit.training_level] - end - end - self:chunk_Name() - self:chunk_Description() - if not (self.ident.dead or self.ident.undead or self.ident.ghostly) then --not self.dead - if self.ident.isCivCitizen then - self:chunk_Age() - self:chunk_MaxAge() - end - if self.ident.isStray then - if self.ident.growth_status == ADULT then - self:chunk_Milkable() - end - self:chunk_Grazer() - if self.ident.growth_status == ADULT then - self:chunk_Shearable() - end - if self.ident.growth_status == ADULT then - self:chunk_EggLayer() - end - end - self:chunk_BodySize() - elseif self.ident.ghostly then - self:chunk_Dead() - self:chunk_Ghostly() - elseif self.ident.undead then - self:chunk_BodySize() - self:chunk_Dead() - else - self:chunk_Dead() - end - else - self:insert_chunk("No unit is selected in the UI or context not supported.",pens.LIGHTRED) - end - self:addviews{ widgets.Label{ frame={yalign=0}, text=self.text } } -end -function UnitInfoViewer:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - end -end -function UnitInfoViewer:onGetSelectedUnit() - return self.ident.unit -end -function UnitInfoViewer:insert_chunk(str,pen) - local lines = utils.split_string( wrap(str,self.frame_width) , NEWLINE ) - for i = 1,#lines do - table.insert(self.text,{text=lines[i],pen=pen}) - table.insert(self.text,NEWLINE) - end - table.insert(self.text,NEWLINE) -end -function UnitInfoViewer:chunk_Name() - local i = self.ident - local u = i.unit - local prof = i.prof - local color = dfhack.units.getProfessionColor(u) - local blurb - if i.ghostly then - prof = 'Ghostly '..prof - end - if i.isStray then - prof = prof..TRAINING_LEVELS[u.training_level] - end - if isBlank(i.name) then - if isBlank(prof) then - blurb = 'I am a mystery' - else - blurb = prof - end - else - if isBlank(prof) then - blurb=i.name - else - blurb=i.name..', '..prof - end - end - self:insert_chunk(blurb,dfhack.pen.parse{fg=color,bg=0}) -end -function UnitInfoViewer:chunk_Description() - local dsc = self.ident.caste.description - if not isBlank(dsc) then - self:insert_chunk(dsc,pens.WHITE) - end -end - -function UnitInfoViewer:chunk_Age() - local i = self.ident - local age_str -- = '' - if i.age_time.year > 1 then - age_str = tostring(i.age_time.year)..' years old' - elseif i.age_time.year > 0 then -- == 1 - age_str = '1 year old' - else --if age_time.year == 0 then - local age_m = i.age_time:getMonths() -- math.floor - if age_m > 1 then - age_str = tostring(age_m)..' months old' - elseif age_m > 0 then -- age_m == 1 - age_str = '1 month old' - else -- if age_m == 0 then -- and age_m < 0 which would be an error - age_str = 'a newborn' - end - end - local blurb = i.pronoun..' is '..age_str - if i.race_id == df.global.ui.race_id then - blurb = blurb..', born on the '..i.birth_date:getDayStr()..' of '..i.birth_date:getMonthStr()..' in the year '..tostring(i.birth_date.year)..PERIOD - else - blurb = blurb..PERIOD - end - self:insert_chunk(blurb,pens.YELLOW) -end - -function UnitInfoViewer:chunk_MaxAge() - local i = self.ident - local maxage = math.floor( (i.caste.misc.maxage_max + i.caste.misc.maxage_min)/2 ) - --or i.unit.curse.add_tags1.NO_AGING hidden ident? - if i.caste.misc.maxage_min == -1 then - maxage = ' die of unnatural causes.' - elseif maxage == 0 then - maxage = ' die at a very young age.' - elseif maxage == 1 then - maxage = ' live about '..tostring(maxage)..' year.' - else - maxage = ' live about '..tostring(maxage)..' years.' - end - --' is expected to '.. - local blurb = str2FirstUpper(i.caste_name[PLURAL])..maxage - self:insert_chunk(blurb,pens.DARKGREY) -end -function UnitInfoViewer:chunk_Grazer() - if self.ident.caste.flags.GRAZER then - local blurb = 'Grazing satisfies '..tostring(self.ident.caste.misc.grazer)..' units of hunger.' - self:insert_chunk(blurb,pens.LIGHTGREEN) - end -end -function UnitInfoViewer:chunk_EggLayer() - local caste = self.ident.caste - if caste.flags.LAYS_EGGS then - local clutch = math.floor( (caste.misc.clutch_size_max + caste.misc.clutch_size_min)/2 ) - local blurb = 'Lays clutches of about '..tostring(clutch) - if clutch > 1 then - blurb = blurb..' eggs.' - else - blurb = blurb..' egg.' - end - self:insert_chunk(blurb,pens.GREEN) - end -end -function UnitInfoViewer:chunk_Milkable() - local i = self.ident - if i.caste.flags.MILKABLE then - local milk = dfhack.matinfo.decode( i.caste.extracts.milkable_mat , i.caste.extracts.milkable_matidx ) - if milk then - local days,seconds = math.modf ( i.caste.misc.milkable / TU_PER_DAY ) - days = (seconds > 0) and (tostring(days)..' to '..tostring(days + 1)) or tostring(days) - --local blurb = pronoun..' produces '..milk:toString()..' every '..days..' days.' - local blurb = (i.growth_status == ADULT) and (i.pronoun..' secretes ') or str2FirstUpper(i.caste_name[PLURAL])..' secrete ' - blurb = blurb..milk:toString()..' every '..days..' days.' - self:insert_chunk(blurb,pens.LIGHTCYAN) - end - end -end -function UnitInfoViewer:chunk_Shearable() - local i = self.ident - local mat_types = i.caste.body_info.materials.mat_type - local mat_idxs = i.caste.body_info.materials.mat_index - local mat_info, blurb - for idx,mat_type in ipairs(mat_types) do - mat_info = dfhack.matinfo.decode(mat_type,mat_idxs[idx]) - if mat_info and mat_info.material.flags.YARN then - blurb = (i.growth_status == ADULT) and (i.pronoun..' produces ') or str2FirstUpper(i.caste_name[PLURAL])..' produce ' - blurb = blurb..mat_info:toString()..PERIOD - self:insert_chunk(blurb,pens.BROWN) - end - end -end -function UnitInfoViewer:chunk_BodySize() - local i = self.ident - local pat = i.unit.body.physical_attrs - local blurb = i.pronoun..' appears to be about '..pat.STRENGTH.value..':'..pat.AGILITY.value..' cubic decimeters in size.' - self:insert_chunk(blurb,pens.LIGHTBLUE) -end -function UnitInfoViewer:chunk_Ghostly() - local blurb = GHOST_TYPES[self.ident.unit.relations.ghost_info.type].." This spirit has not been properly memorialized or buried." - self:insert_chunk(blurb,pens.LIGHTMAGENTA) - -- Arose in relations.curse_year curse_time -end -function UnitInfoViewer:chunk_Dead() - local i = self.ident - local blurb, str, pen - if i.missing then --dfhack.units.isDead(unit) - str = ' is missing.' - pen = pens.WHITE - elseif i.death_event then - --str = "The Caste_name Unit_Name died in year #{e.year}" - --str << " (cause: #{e.death_cause.to_s.downcase})," - --str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 - --str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON - --str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON - str = DEATH_TYPES[i.death_event.death_cause]..PERIOD - pen = pens.MAGENTA - elseif i.incident then - --str = "The #{u.race_tg.name[0]}" - --str << " #{u.name}" if u.name.has_name - --str << " died" - --str << " in year #{incident.event_year}" if incident - --str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1 - --str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer - str = DEATH_TYPES[i.incident.death_cause]..PERIOD - pen = pens.MAGENTA - elseif i.unit.flags2.slaughter and i.unit.flags2.killed then - str = ' was slaughtered.' - pen = pens.MAGENTA - else - str = ' is dead.' - pen = pens.MAGENTA - end - if i.undead or i.ghostly then - str = ' is undead.' - pen = pens.GREY - end - blurb = 'The '..i.prof -- assume prof is not blank - if not isBlank(i.name) then - blurb = blurb..', '..i.name - end - blurb = blurb..str - self:insert_chunk(blurb,pen) -end - --- only show if UnitInfoViewer isn't the current focus -if dfhack.gui.getCurFocus() ~= 'dfhack/lua/'..UnitInfoViewer.focus_path then - local gui_no_unit = false -- show if not found? - local unit = getUnit_byVS(gui_no_unit) -- silent? or let the gui display - if unit or gui_no_unit then - local kan_viewscreen = UnitInfoViewer{unit = unit} - kan_viewscreen:show() - end -end - diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua deleted file mode 100644 index 6dda9e764..000000000 --- a/scripts/gui/workflow.lua +++ /dev/null @@ -1,1099 +0,0 @@ --- A GUI front-end for the workflow plugin. ---[[=begin - -gui/workflow -============ -Bind to a key (the example config uses Alt-W), and activate with a job selected -in a workshop in :kbd:`q` mode. - -.. image:: /docs/images/workflow.png - -This script provides a simple interface to constraints managed by `workflow`. -When active, it displays a list of all constraints applicable to the -current job, and their current status. - -A constraint specifies a certain range to be compared against either individual -*item* or whole *stack* count, an item type and optionally a material. When the -current count is below the lower bound of the range, the job is resumed; if it -is above or equal to the top bound, it will be suspended. Within the range, the -specific constraint has no effect on the job; others may still affect it. - -Pressing :kbd:`i` switches the current constraint between counting stacks or items. -Pressing :kbd:`r` lets you input the range directly; -:kbd:`e`, :kbd:`r`, :kbd:`d`, :kbd:`f` adjust the -bounds by 5, 10, or 20 depending on the direction and the :kbd:`i` setting (counting -items and expanding the range each gives a 2x bonus). - -Pressing :kbd:`a` produces a list of possible outputs of this job as guessed by -workflow, and lets you create a new constraint by choosing one as template. If you -don't see the choice you want in the list, it likely means you have to adjust -the job material first using `job` ``item-material`` or `gui/workshop-job`, -as described in the `workflow` documentation. In this manner, this feature -can be used for troubleshooting jobs that don't match the right constraints. - -.. image:: /docs/images/workflow-new1.png - -If you select one of the outputs with :kbd:`Enter`, the matching constraint is simply -added to the list. If you use :kbd:`Shift`:kbd:`Enter`, the interface proceeds to the -next dialog, which allows you to edit the suggested constraint parameters to -suit your need, and set the item count range. - -.. image:: /docs/images/workflow-new2.png - -Pressing :kbd:`s` (or, with the example config, Alt-W in the :kbd:`z` stocks screen) -opens the overall status screen: - -.. image:: /docs/images/workflow-status.png - -This screen shows all currently existing workflow constraints, and allows -monitoring and/or changing them from one screen. The constraint list can -be filtered by typing text in the field below. - -The color of the stock level number indicates how "healthy" the stock level -is, based on current count and trend. Bright green is very good, green is good, -red is bad, bright red is very bad. - -The limit number is also color-coded. Red means that there are currently no -workshops producing that item (i.e. no jobs). If it's yellow, that means the -production has been delayed, possibly due to lack of input materials. - -The chart on the right is a plot of the last 14 days (28 half day plots) worth -of stock history for the selected item, with the rightmost point representing -the current stock value. The bright green dashed line is the target -limit (maximum) and the dark green line is that minus the gap (minimum). - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local guimat = require 'gui.materials' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -local workflow = require 'plugins.workflow' - -function check_enabled(cb) - if workflow.isEnabled() then - return cb() - else - dlg.showYesNoPrompt( - 'Enable Plugin', - { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE, - 'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' }, - COLOR_YELLOW, - function() - workflow.setEnabled(true) - return cb() - end - ) - end -end - -function check_repeat(job, cb) - if job.flags['repeat'] then - return cb() - else - dlg.showYesNoPrompt( - 'Not Repeat Job', - { 'Workflow only tracks repeating jobs.', NEWLINE, NEWLINE, - 'Press ', { key = 'MENU_CONFIRM' }, ' to make this one repeat.' }, - COLOR_YELLOW, - function() - job.flags['repeat'] = true - return cb() - end - ) - end -end - -function describe_item_type(iobj) - local itemline = 'any item' - if iobj.is_craft then - itemline = 'any craft' - elseif iobj.item_type >= 0 then - itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local subtype = iobj.item_subtype or -1 - local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype) - if def then - itemline = def.name - elseif count >= 0 then - itemline = 'any '..itemline - end - end - return itemline -end - -function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type or -1) -end - -function describe_material(iobj) - local matflags = utils.list_bitfield_flags(iobj.mat_mask) - if #matflags > 0 then - matflags = 'any '..table.concat(matflags, '/') - else - matflags = nil - end - - if is_caste_mat(iobj) then - return 'no material' - elseif (iobj.mat_type or -1) >= 0 then - local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) - local matline - if info then - matline = info:toString() - else - matline = iobj.mat_type..':'..iobj.mat_index - end - return matline, matflags - else - return matflags or 'any material' - end -end - -function current_stock(iobj) - if iobj.goal_by_count then - return iobj.cur_count - else - return iobj.cur_amount - end -end - -function if_by_count(iobj,bc,ba) - if iobj.goal_by_count then - return bc - else - return ba - end -end - -function compute_trend(history,field) - local count = #history - if count == 0 then - return 0 - end - local sumX,sumY,sumXY,sumXX = 0,0,0,0 - for i,v in ipairs(history) do - sumX = sumX + i - sumY = sumY + v[field] - sumXY = sumXY + i*v[field] - sumXX = sumXX + i*i - end - return (count * sumXY - sumX * sumY) / (count * sumXX - sumX * sumX) -end - ------------------------- --- RANGE EDITOR GROUP -- ------------------------- - -local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } - -RangeEditor = defclass(RangeEditor, widgets.Label) - -RangeEditor.ATTRS { - get_cb = DEFAULT_NIL, - save_cb = DEFAULT_NIL, - keys = { - count = 'CUSTOM_SHIFT_I', - modify = 'CUSTOM_SHIFT_R', - min_dec = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', - min_inc = 'BUILDING_TRIGGER_MIN_SIZE_UP', - max_dec = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', - max_inc = 'BUILDING_TRIGGER_MAX_SIZE_UP', - } -} - -function RangeEditor:init(args) - self:setText{ - { key = self.keys.count, - text = function() - local cons = self.get_cb() or null_cons - if cons.goal_by_count then - return ': Count stacks ' - else - return ': Count items ' - end - end, - on_activate = self:callback('onChangeUnit') }, - { key = self.keys.modify, text = ': Range', - on_activate = self:callback('onEditRange') }, - NEWLINE, ' ', - { key = self.keys.min_dec, - on_activate = self:callback('onIncRange', 'goal_gap', 2) }, - { key = self.keys.min_inc, - on_activate = self:callback('onIncRange', 'goal_gap', -1) }, - { text = function() - local cons = self.get_cb() or null_cons - return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) - end }, - { key = self.keys.max_dec, - on_activate = self:callback('onIncRange', 'goal_value', -1) }, - { key = self.keys.max_inc, - on_activate = self:callback('onIncRange', 'goal_value', 2) }, - { text = function() - local cons = self.get_cb() or null_cons - return string.format(': Max %-4d', cons.goal_value) - end }, - } -end - -function RangeEditor:onChangeUnit() - local cons = self.get_cb() - cons.goal_by_count = not cons.goal_by_count - self.save_cb(cons) -end - -function RangeEditor:onEditRange() - local cons = self.get_cb() - dlg.showInputPrompt( - 'Input Range', - 'Enter the new constraint range:', - COLOR_WHITE, - (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value, - function(text) - local maxv = string.match(text, '^%s*(%d+)%s*$') - if maxv then - cons.goal_value = maxv - return self.save_cb(cons) - end - local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$') - if minv and maxv and minv ~= maxv then - cons.goal_value = math.max(minv,maxv) - cons.goal_gap = math.abs(maxv-minv) - return self.save_cb(cons) - end - dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED) - end - ) -end - -function RangeEditor:onIncRange(field, delta) - local cons = self.get_cb() - if not cons.goal_by_count then - delta = delta * 2 - end - cons[field] = math.max(1, cons[field] + delta*5) - self.save_cb(cons) -end - ---------------------------- --- NEW CONSTRAINT DIALOG -- ---------------------------- - -NewConstraint = defclass(NewConstraint, gui.FramedScreen) - -NewConstraint.focus_path = 'workflow/new' - -NewConstraint.ATTRS { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'New workflow constraint', - frame_width = 39, - frame_height = 20, - frame_inset = 1, - constraint = DEFAULT_NIL, - on_submit = DEFAULT_NIL, -} - -function NewConstraint:init(args) - self.constraint = args.constraint or { item_type = -1 } - rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false }) - - local matlist = {} - local matsel = 1 - local matmask = self.constraint.mat_mask - - for i,v in ipairs(df.dfhack_material_category) do - if v and v ~= 'wood2' then - table.insert(matlist, { icon = self:callback('isMatSelected', v), text = v }) - if matmask and matmask[v] and matsel == 1 then - matsel = #matlist - end - end - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = 'Items matching:' - }, - widgets.Label{ - frame = { l = 1, t = 2, w = 26 }, - text = { - 'Type: ', - { pen = function() - if self:isValid() then return COLOR_LIGHTCYAN else return COLOR_LIGHTRED end - end, - text = function() - if self:isValid() then - return describe_item_type(self.constraint) - else - return 'item not set' - end - end }, - NEWLINE, ' ', - { key = 'CUSTOM_T', text = ': Select, ', - on_activate = self:callback('chooseType') }, - { key = 'CUSTOM_SHIFT_C', text = ': Crafts', - on_activate = self:callback('chooseCrafts') }, - NEWLINE, NEWLINE, - 'Material: ', - { pen = COLOR_LIGHTCYAN, - text = function() return describe_material(self.constraint) end }, - NEWLINE, ' ', - { key = 'CUSTOM_P', text = ': Specific', - on_activate = self:callback('chooseMaterial') }, - NEWLINE, NEWLINE, - 'Other:', - NEWLINE, ' ', - { key = 'D_MILITARY_SUPPLIES_WATER_DOWN', - on_activate = self:callback('incQuality', -1) }, - { key = 'D_MILITARY_SUPPLIES_WATER_UP', key_sep = ': ', - text = function() - return df.item_quality[self.constraint.min_quality or 0]..' quality' - end, - on_activate = self:callback('incQuality', 1) }, - NEWLINE, ' ', - { key = 'CUSTOM_L', key_sep = ': ', - text = function() - if self.constraint.is_local then - return 'Locally made only' - else - return 'Include foreign' - end - end, - on_activate = self:callback('toggleLocal') }, - } - }, - widgets.Label{ - frame = { l = 0, t = 14 }, - text = { - 'Desired range: ', - { pen = COLOR_LIGHTCYAN, - text = function() - local cons = self.constraint - local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value - if cons.goal_by_count then - return goal .. ' stacks' - else - return goal .. ' items' - end - end }, - } - }, - RangeEditor{ - frame = { l = 1, t = 16 }, - get_cb = self:cb_getfield('constraint'), - save_cb = self:callback('onRangeChange'), - }, - widgets.Label{ - frame = { l = 30, t = 0 }, - text = 'Mat class' - }, - widgets.List{ - view_id = 'matlist', - frame = { l = 30, t = 2, w = 9, h = 18 }, - scroll_keys = widgets.STANDARDSCROLL, - choices = matlist, - selected = matsel, - on_submit = self:callback('onToggleMatclass') - }, - widgets.Label{ - frame = { l = 0, b = 0, w = 29 }, - text = { - { key = 'LEAVESCREEN', text = ': Cancel, ', - on_activate = self:callback('dismiss') }, - { key = 'MENU_CONFIRM', key_sep = ': ', - enabled = self:callback('isValid'), - text = function() - if self.is_existing then return 'Update' else return 'Create new' end - end, - on_activate = function() - self:dismiss() - if self.on_submit then - self.on_submit(self.constraint) - end - end }, - } - }, - } -end - -function NewConstraint:postinit() - self:onChange() -end - -function NewConstraint:isValid() - return self.constraint.item_type >= 0 or self.constraint.is_craft -end - -function NewConstraint:onChange() - local token = workflow.constraintToToken(self.constraint) - local out - - if self:isValid() then - out = workflow.findConstraint(token) - end - - if out then - self.constraint = out - self.is_existing = true - else - self.constraint.token = token - self.is_existing = false - end -end - -function NewConstraint:chooseType() - guimat.ItemTypeDialog{ - prompt = 'Please select a new item type', - hide_none = true, - on_select = function(itype,isub) - local cons = self.constraint - cons.item_type = itype - cons.item_subtype = isub - cons.is_craft = nil - self:onChange() - end - }:show() -end - -function NewConstraint:chooseCrafts() - local cons = self.constraint - cons.item_type = -1 - cons.item_subtype = -1 - cons.is_craft = true - self:onChange() -end - -function NewConstraint:chooseMaterial() - local cons = self.constraint - guimat.MaterialDialog{ - prompt = 'Please select a new material', - none_caption = 'any material', - frame_width = 37, - on_select = function(mat_type, mat_index) - local cons = self.constraint - cons.mat_type = mat_type - cons.mat_index = mat_index - cons.mat_mask = nil - self:onChange() - end - }:show() -end - -function NewConstraint:incQuality(diff) - local cons = self.constraint - local nq = (cons.min_quality or 0) + diff - if nq < 0 then - nq = df.item_quality.Masterful - elseif nq > df.item_quality.Masterful then - nq = 0 - end - cons.min_quality = nq - self:onChange() -end - -function NewConstraint:toggleLocal() - local cons = self.constraint - cons.is_local = not cons.is_local - self:onChange() -end - -function NewConstraint:isMatSelected(token) - if self.constraint.mat_mask and self.constraint.mat_mask[token] then - return { ch = '\xfb', fg = COLOR_LIGHTGREEN } - else - return nil - end -end - -function NewConstraint:onToggleMatclass(idx,obj) - local cons = self.constraint - if cons.mat_mask and cons.mat_mask[obj.text] then - cons.mat_mask[obj.text] = false - else - cons.mat_mask = cons.mat_mask or {} - cons.mat_mask[obj.text] = true - cons.mat_type = -1 - cons.mat_index = -1 - end - self:onChange() -end - -function NewConstraint:onRangeChange() - local cons = self.constraint - cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) -end - ------------------------------- --- CONSTRAINT HISTORY GRAPH -- ------------------------------- - -HistoryGraph = defclass(HistoryGraph, widgets.Widget) - -HistoryGraph.ATTRS { - frame_inset = 1, - history_pen = COLOR_CYAN, -} - -function HistoryGraph:init(info) -end - -function HistoryGraph:setData(history, bars) - self.history = history or {} - self.bars = bars or {} - - local maxval = 1 - for i,v in ipairs(self.history) do - maxval = math.max(maxval, v) - end - for i,v in ipairs(self.bars) do - maxval = math.max(maxval, v.value) - end - self.max_value = maxval -end - -function HistoryGraph:onRenderFrame(dc,rect) - dc:fill(rect.x1,rect.y1,rect.x1,rect.y2,{ch='\xb3', fg=COLOR_BROWN}) - dc:fill(rect.x1,rect.y2,rect.x2,rect.y2,{ch='\xc4', fg=COLOR_BROWN}) - dc:seek(rect.x1,rect.y1):char('\x1e', COLOR_BROWN) - dc:seek(rect.x1,rect.y2):char('\xc5', COLOR_BROWN) - dc:seek(rect.x2,rect.y2):char('\x10', COLOR_BROWN) - dc:seek(rect.x1,rect.y2-1):char('0', COLOR_BROWN) -end - -function HistoryGraph:onRenderBody(dc) - local coeff = (dc.height-1)/self.max_value - - for i,v in ipairs(self.bars) do - local y = dc.height-1-math.floor(0.5 + coeff*v.value) - dc:fill(0,y,dc.width-1,y,v.pen or {ch='-', fg=COLOR_GREEN}) - end - - local xbase = dc.width-1-#self.history - for i,v in ipairs(self.history) do - local x = xbase + i - local y = dc.height-1-math.floor(0.5 + coeff*v) - dc:seek(x,y):char('*', self.history_pen) - end -end - ------------------------------- --- GLOBAL CONSTRAINT SCREEN -- ------------------------------- - -ConstraintList = defclass(ConstraintList, gui.FramedScreen) - -ConstraintList.focus_path = 'workflow/list' - -ConstraintList.ATTRS { - frame_title = 'Workflow Status', - frame_inset = 0, - frame_background = COLOR_BLACK, - frame_style = gui.BOUNDARY_FRAME, -} - -function ConstraintList:init(args) - local fwidth_cb = self:cb_getfield('fwidth') - - self.fwidth = 20 - self.sort_by_severity = false - - self:addviews{ - widgets.Panel{ - frame = { l = 0, r = 31 }, - frame_inset = 1, - on_layout = function(body) - self.fwidth = body.width - (12+1+1+7+1+1+1+7) - end, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - text_pen = COLOR_CYAN, - text = { - { text = 'Item', width = 12 }, ' ', - { text = 'Material etc', width = fwidth_cb }, ' ', - { text = 'Stock / Limit' }, - } - }, - widgets.FilteredList{ - view_id = 'list', - frame = { t = 2, b = 2 }, - edit_below = true, - not_found_label = 'No matching constraints', - edit_pen = COLOR_LIGHTCYAN, - text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, - cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, - on_select = self:callback('onSelectConstraint'), - }, - widgets.Label{ - frame = { b = 0, h = 1 }, - text = { - { key = 'CUSTOM_SHIFT_A', text = ': Add', - on_activate = self:callback('onNewConstraint') }, ', ', - { key = 'CUSTOM_SHIFT_X', text = ': Delete', - on_activate = self:callback('onDeleteConstraint') }, ', ', - { key = 'CUSTOM_SHIFT_O', text = ': Severity Order', - on_activate = self:callback('onSwitchSort'), - pen = function() - if self.sort_by_severity then - return COLOR_LIGHTCYAN - else - return COLOR_WHITE - end - end }, - } - } - } - }, - widgets.Panel{ - frame = { w = 30, r = 0, h = 6, t = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - enabled = self:callback('isAnySelected'), - text = { - { text = function() - local cur = self:getCurConstraint() - if cur then - return string.format( - 'Currently %d (%d in use)', - current_stock(cur), - if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) - ) - else - return 'No constraint selected' - end - end } - } - }, - RangeEditor{ - frame = { l = 0, t = 2 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - keys = { - count = 'CUSTOM_SHIFT_I', - modify = 'CUSTOM_SHIFT_R', - min_dec = 'SECONDSCROLL_PAGEUP', - min_inc = 'SECONDSCROLL_PAGEDOWN', - max_dec = 'SECONDSCROLL_UP', - max_inc = 'SECONDSCROLL_DOWN', - } - }, - } - }, - widgets.Widget{ - active = false, - frame = { w = 1, r = 30 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Widget{ - active = false, - frame = { w = 30, r = 0, h = 1, t = 6 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - HistoryGraph{ - view_id = 'graph', - frame = { w = 30, r = 0, t = 7, b = 0 }, - } - } - - self:initListChoices(nil, args.select_token) -end - -function stock_trend_color(cons) - local stock = current_stock(cons) - if stock >= cons.goal_value - cons.goal_gap then - return COLOR_LIGHTGREEN, 0 - elseif stock <= cons.goal_gap then - return COLOR_LIGHTRED, 4 - elseif stock >= cons.goal_value - 2*cons.goal_gap then - return COLOR_GREEN, 1 - elseif stock <= 2*cons.goal_gap then - return COLOR_RED, 3 - else - local trend = if_by_count(cons, cons.trend_count, cons.trend_amount) - if trend > 0.3 then - return COLOR_GREEN, 1 - elseif trend < -0.3 then - return COLOR_RED, 3 - else - return COLOR_GREY, 2 - end - end -end - -function ConstraintList:initListChoices(clist, sel_token) - clist = clist or workflow.listConstraints(nil, true) - - local fwidth_cb = self:cb_getfield('fwidth') - local choices = {} - - for i,cons in ipairs(clist) do - cons.trend_count = compute_trend(cons.history, 'cur_count') - cons.trend_amount = compute_trend(cons.history, 'cur_amount') - - local itemstr = describe_item_type(cons) - local matstr,matflagstr = describe_material(cons) - if matflagstr then - matstr = matflagstr .. ' ' .. matstr - end - - if cons.min_quality > 0 or cons.is_local then - local lst = {} - if cons.is_local then - table.insert(lst, 'local') - end - if cons.min_quality > 0 then - table.insert(lst, string.lower(df.item_quality[cons.min_quality])) - end - matstr = matstr .. ' ('..table.concat(lst,',')..')' - end - - local goal_color = COLOR_GREY - if #cons.jobs == 0 then - goal_color = COLOR_RED - elseif cons.is_delayed then - goal_color = COLOR_YELLOW - end - - table.insert(choices, { - text = { - { text = itemstr, width = 12, pad_char = ' ' }, ' ', - { text = matstr, width = fwidth_cb, pad_char = ' ' }, ' ', - { text = curry(current_stock,cons), width = 7, rjustify = true, - pen = function() return { fg = stock_trend_color(cons) } end }, - { text = curry(if_by_count,cons,'S','I'), gap = 1, - pen = { fg = COLOR_GREY } }, - { text = function() return cons.goal_value end, gap = 1, - pen = { fg = goal_color } } - }, - severity = select(2, stock_trend_color(cons)), - search_key = itemstr .. ' | ' .. matstr, - token = cons.token, - obj = cons - }) - end - - self:setChoices(choices, sel_token) -end - -function ConstraintList:isAnySelected() - return self.subviews.list:getSelected() ~= nil -end - -function ConstraintList:getCurConstraint() - local selidx,selobj = self.subviews.list:getSelected() - if selobj then return selobj.obj end -end - -function ConstraintList:onSwitchSort() - self.sort_by_severity = not self.sort_by_severity - self:setChoices(self.subviews.list:getChoices()) -end - -function ConstraintList:setChoices(choices, sel_token) - if self.sort_by_severity then - table.sort(choices, function(a,b) - return a.severity > b.severity - or (a.severity == b.severity and - current_stock(a.obj)/a.obj.goal_value < current_stock(b.obj)/b.obj.goal_value) - end) - else - table.sort(choices, function(a,b) return a.search_key < b.search_key end) - end - - local selidx = nil - if sel_token then - selidx = utils.linear_index(choices, sel_token, 'token') - end - - local list = self.subviews.list - local filter = list:getFilter() - - list:setChoices(choices, selidx) - - if filter ~= '' then - list:setFilter(filter, selidx) - - if selidx and list:getSelected() ~= selidx then - list:setFilter('', selidx) - end - end -end - -function ConstraintList:onInput(keys) - if keys.LEAVESCREEN then - self:dismiss() - else - ConstraintList.super.onInput(self, keys) - end -end - -function ConstraintList:onNewConstraint() - NewConstraint{ - on_submit = self:callback('saveConstraint') - }:show() -end - -function ConstraintList:saveConstraint(cons) - local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) - self:initListChoices(nil, out.token) -end - -function ConstraintList:onDeleteConstraint() - local cons = self:getCurConstraint() - dlg.showYesNoPrompt( - 'Delete Constraint', - 'Really delete the current constraint?', - COLOR_YELLOW, - function() - workflow.deleteConstraint(cons.token) - self:initListChoices() - end - ) -end - -function ConstraintList:onSelectConstraint(idx,item) - local history, bars - - if item then - local cons = item.obj - local vfield = if_by_count(cons, 'cur_count', 'cur_amount') - - bars = { - { value = cons.goal_value - cons.goal_gap, pen = {ch='-', fg=COLOR_GREEN} }, - { value = cons.goal_value, pen = {ch='-', fg=COLOR_LIGHTGREEN} }, - } - - history = {} - for i,v in ipairs(cons.history or {}) do - table.insert(history, v[vfield]) - end - - table.insert(history, cons[vfield]) - end - - self.subviews.graph:setData(history, bars) -end - -------------------------------- --- WORKSHOP JOB INFO OVERLAY -- -------------------------------- - -JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) - -JobConstraints.focus_path = 'workflow/job' - -JobConstraints.ATTRS { - job = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function JobConstraints:init(args) - self.building = dfhack.job.getHolder(self.job) - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - 'Workflow Constraints' - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 2, b = 6 }, - row_height = 4, - scroll_keys = widgets.SECONDSCROLL, - }, - RangeEditor{ - frame = { l = 0, b = 3 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'CUSTOM_SHIFT_A', text = ': Add limit, ', - on_activate = self:callback('onNewConstraint') }, - { key = 'CUSTOM_SHIFT_X', text = ': Delete', - enabled = self:callback('isAnySelected'), - on_activate = self:callback('onDeleteConstraint') }, - NEWLINE, NEWLINE, - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') }, - ' ', - { key = 'CUSTOM_SHIFT_S', text = ': Status', - on_activate = function() - local sel = self:getCurConstraint() - ConstraintList{ select_token = (sel or {}).token }:show() - end } - } - }, - } - - self:initListChoices(args.clist) -end - -function JobConstraints:onGetSelectedBuilding() - return self.building -end - -function JobConstraints:onGetSelectedJob() - return self.job -end - -function JobConstraints:initListChoices(clist, sel_token) - clist = clist or workflow.listConstraints(self.job) - - local choices = {} - - for i,cons in ipairs(clist) do - local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value - local curval - if cons.goal_by_count then - goal = goal .. ' stacks' - curval = cons.cur_count - else - goal = goal .. ' items' - curval = cons.cur_amount - end - local order_pen = COLOR_GREY - if cons.request == 'resume' then - order_pen = COLOR_GREEN - elseif cons.request == 'suspend' then - order_pen = COLOR_BLUE - end - local itemstr = describe_item_type(cons) - if cons.min_quality > 0 or cons.is_local then - local lst = {} - if cons.is_local then - table.insert(lst, 'local') - end - if cons.min_quality > 0 then - table.insert(lst, string.lower(df.item_quality[cons.min_quality])) - end - itemstr = itemstr .. ' ('..table.concat(lst,',')..')' - end - local matstr,matflagstr = describe_material(cons) - - table.insert(choices, { - text = { - goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, - ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', (matflagstr or '') - }, - token = cons.token, - obj = cons - }) - end - - local selidx = nil - if sel_token then - selidx = utils.linear_index(choices, sel_token, 'token') - end - - self.subviews.list:setChoices(choices, selidx) -end - -function JobConstraints:isAnySelected() - return self.subviews.list:getSelected() ~= nil -end - -function JobConstraints:getCurConstraint() - local i,v = self.subviews.list:getSelected() - if v then return v.obj end -end - -function JobConstraints:saveConstraint(cons) - local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) - self:initListChoices(nil, out.token) -end - -function JobConstraints:onNewConstraint() - local outputs = workflow.listJobOutputs(self.job) - if #outputs == 0 then - dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED) - return - end - - local variants = workflow.listWeakenedConstraints(outputs) - - local choices = {} - for i,cons in ipairs(variants) do - local itemstr = describe_item_type(cons) - local matstr,matflags = describe_material(cons) - if matflags then - matstr = matflags..' '..matstr - end - - table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) - end - - dlg.ListBox{ - frame_title = 'Add limit', - text = 'Select one of the possible outputs:', - text_pen = COLOR_WHITE, - choices = choices, - on_select = function(idx,item) - self:saveConstraint(item.obj) - end, - select2_hint = 'Advanced', - on_select2 = function(idx,item) - NewConstraint{ - constraint = item.obj, - on_submit = self:callback('saveConstraint') - }:show() - end, - }:show() -end - -function JobConstraints:onDeleteConstraint() - local cons = self:getCurConstraint() - dlg.showYesNoPrompt( - 'Delete Constraint', - 'Really delete the current constraint?', - COLOR_YELLOW, - function() - workflow.deleteConstraint(cons.token) - self:initListChoices() - end - ) -end - -function JobConstraints:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - JobConstraints.super.onInput(self, keys) - end -end - -local args = {...} - -if args[1] == 'status' then - check_enabled(function() ConstraintList{}:show() end) -else - if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") - end - - local job = dfhack.gui.getSelectedJob() - - check_enabled(function() - check_repeat(job, function() - local clist = workflow.listConstraints(job) - if not clist then - dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) - return - end - JobConstraints{ job = job, clist = clist }:show() - end) - end) -end diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua deleted file mode 100644 index f3af15d10..000000000 --- a/scripts/gui/workshop-job.lua +++ /dev/null @@ -1,332 +0,0 @@ --- Show and modify properties of jobs in a workshop. ---[[=begin - -gui/workshop-job -================ -Bind to a key (the example config uses :kbd:`Alt`:kbd:`a`), and activate with a job selected in -a workshop in the :kbd:`q` mode. - -.. image:: /docs/images/workshop-job.png - -The script shows a list of the input reagents of the selected job, and allows changing -them like the `job` ``item-type`` and `job` ``item-material`` commands. - -Specifically, pressing the :kbd:`i` key pops up a dialog that lets you select an item -type from a list. - -.. image:: /docs/images/workshop-job-item.png - -Pressing :kbd:`m`, unless the item type does not allow a material, -lets you choose a material. - -.. image:: /docs/images/workshop-job-material.png - -Since there are a lot more materials than item types, this dialog is more complex -and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked -with an arrow on the left. - -.. warning:: - - Due to the way input reagent matching works in DF, you must select an item type - if you select a material, or the material will be matched incorrectly in some cases. - If you press :kbd:`m` without choosing an item type, the script will auto-choose - if there is only one valid choice, or pop up an error message box instead of the - material selection dialog. - -Note that both materials and item types presented in the dialogs are filtered -by the job input flags, and even the selected item type for material selection, -or material for item type selection. Many jobs would let you select only one -input item type. - -For example, if you choose a *plant* input item type for your prepare meal job, -it will only let you select cookable materials. - -If you choose a *barrel* item instead (meaning things stored in barrels, like -drink or milk), it will let you select any material, since in this case the -material is matched against the barrel itself. Then, if you select, say, iron, -and then try to change the input item type, now it won't let you select *plant*; -you have to unset the material first. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local guimat = require 'gui.materials' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -JobDetails = defclass(JobDetails, guidm.MenuOverlay) - -JobDetails.focus_path = 'workshop-job' - -JobDetails.ATTRS { - job = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function JobDetails:init(args) - self.building = dfhack.job.getHolder(self.job) - - local status = { text = 'No worker', pen = COLOR_DARKGREY } - local worker = dfhack.job.getWorker(self.job) - if self.job.flags.suspend then - status = { text = 'Suspended', pen = COLOR_RED } - elseif worker then - status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, - ' ', status - } - }, - widgets.Label{ - frame = { l = 0, t = 4 }, - text = { - { key = 'CUSTOM_I', text = ': Input item, ', - enabled = self:callback('canChangeIType'), - on_activate = self:callback('onChangeIType') }, - { key = 'CUSTOM_M', text = ': Material', - enabled = self:callback('canChangeMat'), - on_activate = self:callback('onChangeMat') } - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 6, b = 2 }, - row_height = 4, - scroll_keys = widgets.SECONDSCROLL, - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } - } - }, - } - - self:initListChoices() -end - -function JobDetails:onGetSelectedBuilding() - return self.building -end - -function JobDetails:onGetSelectedJob() - return self.job -end - -function describe_item_type(iobj) - local itemline = 'any item' - if iobj.item_type >= 0 then - itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) - if def then - itemline = def.name - elseif count >= 0 then - itemline = 'any '..itemline - end - end - return itemline -end - -function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type) -end - -function describe_material(iobj) - local matline = 'any material' - if is_caste_mat(iobj) then - matline = 'material not applicable' - elseif iobj.mat_type >= 0 then - local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) - if info then - matline = info:toString() - else - matline = iobj.mat_type..':'..iobj.mat_index - end - end - return matline -end - -function list_flags(list, bitfield) - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) - end - end -end - -function JobDetails:initListChoices() - local items = {} - for i,ref in ipairs(self.job.items) do - local idx = ref.job_item_idx - if idx >= 0 then - items[idx] = (items[idx] or 0) + 1 - end - end - - local choices = {} - for i,iobj in ipairs(self.job.job_items) do - local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity - if iobj.min_dimension > 0 then - head = head .. '(size '..iobj.min_dimension..')' - end - - local line1 = {} - local reaction = df.reaction.find(iobj.reaction_id) - if reaction and #iobj.contains > 0 then - for _,ri in ipairs(iobj.contains) do - table.insert(line1, 'has '..utils.call_with_string( - reaction.reagents[ri],'getDescription',iobj.reaction_id - )) - end - end - if iobj.metal_ore >= 0 then - local ore = dfhack.matinfo.decode(0, iobj.metal_ore) - if ore then - table.insert(line1, 'ore of '..ore:toString()) - end - end - if iobj.has_material_reaction_product ~= '' then - table.insert(line1, 'product '..iobj.has_material_reaction_product) - end - if iobj.reaction_class ~= '' then - table.insert(line1, 'class '..iobj.reaction_class) - end - if iobj.has_tool_use >= 0 then - table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) - end - list_flags(line1, iobj.flags1) - list_flags(line1, iobj.flags2) - list_flags(line1, iobj.flags3) - if #line1 == 0 then - table.insert(line1, 'no flags') - end - - table.insert(choices, { - index = i, - iobj = iobj, - text = { - head, NEWLINE, - ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, - ' ', { text = curry(describe_material, iobj) }, NEWLINE, - ' ', table.concat(line1, ', '), NEWLINE - } - }) - end - - self.subviews.list:setChoices(choices) -end - -function JobDetails:canChangeIType() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil -end - -function JobDetails:setItemType(obj, item_type, item_subtype) - obj.iobj.item_type = item_type - obj.iobj.item_subtype = item_subtype - - if is_caste_mat(obj.iobj) then - self:setMaterial(obj, -1, -1) - end -end - -function JobDetails:onChangeIType() - local idx, obj = self.subviews.list:getSelected() - guimat.ItemTypeDialog{ - prompt = 'Please select a new item type for input '..idx, - none_caption = 'any item', - item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), - on_select = self:callback('setItemType', obj) - }:show() -end - -function JobDetails:canChangeMat() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil and not is_caste_mat(obj.iobj) -end - -function JobDetails:setMaterial(obj, mat_type, mat_index) - if obj.index == 0 - and self.job.mat_type == obj.iobj.mat_type - and self.job.mat_index == obj.iobj.mat_index - and self.job.job_type ~= df.job_type.PrepareMeal - then - self.job.mat_type = mat_type - self.job.mat_index = mat_index - end - - obj.iobj.mat_type = mat_type - obj.iobj.mat_index = mat_index -end - -function JobDetails:findUnambiguousItem(iobj) - local count = 0 - local itype - - for i = 0,df.item_type._last_item do - if dfhack.job.isSuitableItem(iobj, i, -1) then - count = count + 1 - if count > 1 then return nil end - itype = i - end - end - - return itype -end - -function JobDetails:onChangeMat() - local idx, obj = self.subviews.list:getSelected() - - if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then - -- If the job allows only one specific item type, use it - local vitype = self:findUnambiguousItem(obj.iobj) - - if vitype then - obj.iobj.item_type = vitype - else - dlg.showMessage( - 'Bug Alert', - { 'Please set a specific item type first.\n\n', - 'Otherwise the material will be matched\n', - 'incorrectly due to a limitation in DF code.' }, - COLOR_YELLOW - ) - return - end - end - - guimat.MaterialDialog{ - prompt = 'Please select a new material for input '..idx, - none_caption = 'any material', - mat_filter = function(mat,parent,mat_type,mat_index) - return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index) - end, - on_select = self:callback('setMaterial', obj) - }:show() -end - -function JobDetails:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - JobDetails.super.onInput(self, keys) - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") -end - -local dlg = JobDetails{ job = dfhack.gui.getSelectedJob() } -dlg:show() diff --git a/scripts/hfs-pit.lua b/scripts/hfs-pit.lua deleted file mode 100644 index 7fc571090..000000000 --- a/scripts/hfs-pit.lua +++ /dev/null @@ -1,105 +0,0 @@ --- Creates a pit to the Underworld under the target --- Based on script by IndigoFenix, @ https://gist.github.com/IndigoFenix/8776696 ---[[=begin - -hfs-pit -======= -Creates a pit to the underworld at the cursor. - -Takes three arguments: diameter of the pit in tiles, whether to wall off -the pit, and whether to insert stairs. If no arguments are given, the default -is ``hfs-pit 1 0 0``, ie single-tile wide with no walls or stairs.:: - - hfs-pit 4 0 1 - hfs-pit 2 1 0 - -First example is a four-across pit with stairs but no walls; second is a -two-across pit with stairs but no walls. - -=end]] - -args={...} - -if args[1] == '?' then - print("Example usage: 'hfs-pit 2 1 1'") - print("First parameter is size of the pit in all directions.") - print("Second parameter is 1 to wall off the sides of the pit on all layers except the underworld, or anything else to leave them open.") - print("Third parameter is 1 to add stairs. Stairs are buggy; they will not reveal the bottom until you dig somewhere, but underworld creatures will path in.") - print("If no arguments are given, the default is 'hfs-pit 1 0 0', ie single-tile wide with no walls or stairs.") - return -end - -pos = copyall(df.global.cursor) -size = tonumber(args[1]) -if size == nil or size < 1 then size = 1 end - -wallOff = tonumber(args[2]) -stairs = tonumber(args[3]) - ---Get the layer of the underworld -for index,value in ipairs(df.global.world.features.map_features) do - local featureType=value:getType() - if featureType==9 then --Underworld - underworldLayer = value.layer - end -end - -if pos.x==-30000 then - qerror("Select a location by placing the cursor") -end -local x = 0 -local y = 0 -for x=pos.x-size,pos.x+size,1 do - for y=pos.y-size,pos.y+size,1 do - z=1 - local hitAir = false - local hitCeiling = false - while z <= pos.z do - local block = dfhack.maps.ensureTileBlock(x,y,z) - if block then - if block.tiletype[x%16][y%16] ~= 335 then - hitAir = true - end - if hitAir == true then - if not hitCeiling then - if block.global_feature ~= underworldLayer or z > 10 then hitCeiling = true end - if stairs == 1 and x == pos.x and y == pos.y then - if block.tiletype[x%16][y%16] == 32 then - if z == pos.z then - block.tiletype[x%16][y%16] = 56 - else - block.tiletype[x%16][y%16] = 55 - end - else - block.tiletype[x%16][y%16] = 57 - end - end - end - if hitCeiling == true then - if block.designation[x%16][y%16].flow_size > 0 or wallOff == 1 then needsWall = true else needsWall = false end - if (x == pos.x-size or x == pos.x+size or y == pos.y-size or y == pos.y+size) and z==pos.z then - --Do nothing, this is the lip of the hole - elseif x == pos.x-size and y == pos.y-size then if needsWall == true then block.tiletype[x%16][y%16]=320 end - elseif x == pos.x-size and y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=321 end - elseif x == pos.x+size and y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=322 end - elseif x == pos.x+size and y == pos.y-size then if needsWall == true then block.tiletype[x%16][y%16]=323 end - elseif x == pos.x-size or x == pos.x+size then if needsWall == true then block.tiletype[x%16][y%16]=324 end - elseif y == pos.y-size or y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=325 end - elseif stairs == 1 and x == pos.x and y == pos.y then - if z == pos.z then block.tiletype[x%16][y%16]=56 - else block.tiletype[x%16][y%16]=55 end - else block.tiletype[x%16][y%16]=32 - end - block.designation[x%16][y%16].hidden = false - --block.designation[x%16][y%16].liquid_type = true -- if true, magma. if false, water. - block.designation[x%16][y%16].flow_size = 0 - dfhack.maps.enableBlockUpdates(block) - block.designation[x%16][y%16].flow_forbid = false - end - end - block.designation[x%16][y%16].hidden = false - end - z = z+1 - end - end -end \ No newline at end of file diff --git a/scripts/hotkey-notes.lua b/scripts/hotkey-notes.lua deleted file mode 100644 index 428a02d64..000000000 --- a/scripts/hotkey-notes.lua +++ /dev/null @@ -1,16 +0,0 @@ --- prints info on assigned hotkeys to the console ---[[=begin - -hotkey-notes -============ -Lists the key, name, and jump position of your hotkeys in the DFHack console. - -=end]] - -for i=1, #df.global.ui.main.hotkeys do - local hk = df.global.ui.main.hotkeys[i-1] - local key = dfhack.screen.getKeyDisplay(df.interface_key.D_HOTKEY1 + i - 1) - if hk.cmd ~= -1 then - print(key..': '..hk.name..': x='..hk.x..' y='..hk.y..' z='..hk.z) - end -end diff --git a/scripts/item-descriptions.lua b/scripts/item-descriptions.lua deleted file mode 100644 index b2c57d2e4..000000000 --- a/scripts/item-descriptions.lua +++ /dev/null @@ -1,663 +0,0 @@ --- Holds custom descriptions for view-item-info --- By PeridexisErrant ---[[=begin - -item-descriptions -================= -Exports a table with custom description text for every item in the game. -Used by `view-item-info`; see instructions there for how to override -for mods. - -=end]] - --- Each line near the bottom has 53 characters of room until --- it starts clipping over the UI in an ugly fashion. --- For proper spacing, 50 characters is the maximum. --- Descriptions which aren't pushed down the page by --- barrel contents or such line up with the UI on the --- 11th line down. There is room for a 10th long line --- without clipping, but stopping at 9 leaves enough space --- for ideal legibility. - --- The following people contributed descriptions: --- Raideau, PeridexisErrant, /u/Puffin4Tom, /u/KroyMortlach --- /u/genieus, /u/TeamsOnlyMedic, /u/johny5w, /u/DerTanni --- /u/schmee101, /u/coaldiamond, /u/stolencatkarma, /u/sylth01 --- /u/MperorM, /u/SockHoarder, /u/_enclave_, WesQ3 --- /u/Xen0nex, /u/Jurph - -if not moduleMode then - print("scripts/item-descriptions.lua is a content library; calling it does nothing.") -end - -local help --[[ -This script has a single function: to return a custom description for every -vanilla item in the game. - -If "raw/scripts/item-descriptions.lua" exists, it will entirely replace this one. -Instead, mods should use "raw/scripts/more-item-descriptions.lua" to add content or replace -descriptions on a case-by-case basis. If an item description cannot be found in -the latter script, view-item-info will fall back to the former. -]] - --- see http://dwarffortresswiki.org/index.php/cv:Item_token -descriptions = { - AMULET = { "An item of jewellery worn around the neck for it's aesthetic value.", - "An amulet does not interfere with wearing other equipment."}, - ANIMALTRAP = { - "This tiny trap is used by your trappers to catch vermin. Some dwarves", - "like vermin as pets - if your cats don't get them first. May be built", - "from wood or metal. Catching vermin requires trap to be set with bait."}, - ANVIL = { "An essential component of the forge."}, - ARMORSTAND = { - "A rack for the storage of military equipment, specifically armor.", - "Barracks may be designated and assigned from them. Military squads", - "may use their assigned barracks for training, storage, or sleeping,", - "depending on the settings. Some nobles demand an armor stand of", - "their own."}, - BACKPACK = {"A backpack can be used by militia to carry rations in the field.", - "In Adventure mode, backpacks can be used to carry more equipment."}, - BALLISTAARROWHEAD = { - "The arrowtip used to create metal ballista arrows in a siege workshop."}, - BALLISTAPARTS = { - "Three of these can be used to construct a Ballista."}, - BAR = { "A small ingot made of metal, fuel, ash, or soap, made to facilitate", - "stacking and storage. Aside from the uses unique to each material, bars", - "of any type can also be used as building materials in place of blocks.", - "", - "Metal bars are used with fuel at a Metalsmith's Forge to make metal", - "goods and decorations. Fuel is used at furnaces and workshops requiring", - "intense heat, with the exception of magma furnaces or a Wood Furnace.", - "Soap is used by hospitals to greatly reduce the chance of infected", - "wounds, and rarely used by individual dwarves to clean themselves or", - "surrounding tiles. Ash is processed at an Ashery to make potash or lye,", - "potash is used as farm plot fertilizer or made into pearlash, and", - "pearlash is used to make clear or crystal glass products."}, - BARREL = { "A hollow cylinder with a removable lid. It is used to hold liquids,", - "food, and seeds. It can be made from metal or wood, and is replaceable", - "with a rock pot. A barrel (or rock pot) is needed to brew drinks."}, - BED = { "A pallet for dwarves to sleep on, which must be made from wood.", - "It prevents the stress of sleeping on the ground, and can be used", - "to designate a bedroom (used by one dwarf or couple), a dormitory", - "(used by multiple dwarves), or a barracks (used by a military", - "squad for training or sleep)."}, - BIN = { "A container for the storage of ammunition, armor and weapons, bars,", - "blocks, cloth and leather, coins, finished goods and gems. It can", - "be used to carry multiple items to the Trade Depot at once.", - "A bin can be made from wood or forged from metal."}, - BLOCKS = { "Blocks can be used for constructions in place of raw materials such", - "as logs or bars. Cutting boulders into blocks gives four times as", - "many items, all of which are lighter for faster hauling and yield", - "smooth constructions."}, - BOX = { "A container for storing dwarves' items. They are required by nobles,", - "and will increase the value of rooms they are placed in. Also", - "required to store hospital supplies. They can be made from stone or", - "metal (coffers), wood (chests),or textiles or leather (bags)."}, - BUCKET = { "A small cylindrical or conical container for holding and carrying", - "small amounts of liquid such as water or lye. They are used by", - "dwarves to give water to other dwarves, to store lye, and are", - "required to build wells and certain workshops. They can be made", - "from wood or metal."}, - BOOK = { "It's a book. Some books contain the secrets of life and death."}, - BOULDER = { "Mining may yield loose stones for industry. There are four categories:", - "non-economic stones for building materials, ores for metal industry,", - "gems, and special-purpose economic stones like flux, coal and lignite."}, - BRACELET = {"A bracelet is an item of jewellery worn on the hands."}, - CABINET = { "A place for dwarves to store old clothing, used by any dwarf whose room", - "overlaps the cabinet. May be built from wood, stone, metal, or glass."}, - CAGE = { "A cage can be made of glass, metal or wood. All materials are equally", - "strong as cages can not be broken, however the weight of the material", - "affects the time taken to move cages. Cages can be combined with a", - "mechanism to create a cage trap. Cages can also be built as furniture,", - "after which they can store an infinite number of animals or prisoners."}, - CATAPULTPARTS = { - "Three of these can be used to construct a Catapult."}, - CHAIN = { "A chain made of metal. A chain or rope is required to build a well.", - "Due to the marvels of dwarven engineering, a single chain can be used", - "for a well of any depth. Chains are also used to create restraints", - "for prisoners or animals."}, - CHAIR = { "Furniture used for sitting. Named a chair if made from wood,", - "or a throne if made from stone, glass, or metal. Offices may be", - "designated and assigned from them. Dwarves will complain if there", - "aren't enough chairs in the dining room."}, - CLOTH = { "A piece of fabric made from threads of plant fiber, yarn, silk or", - "adamantine. Cloth may be dyed. It is used at a Clothier's Workshop to", - "make clothing, bags, rope, and decorative sewn images. At a", - "Craftsdwarf's Workshop, it can be made into trade goods. Hospitals", - "use cloth for wound dressing - though expensive cloth confers no", - "benefit. Specific types of cloth can be required by a strange mood."}, - COFFIN = { "A final resting place for dwarves. Must be built and assigned before", - "burial can occur. Named a coffin when made from stone or glass,", - "casket when made from wood, and sarcophagus when made from metal.", - "Tombs may be designated and assigned from them. Corpses contained in", - "coffins may still reanimate."}, - COIN = { "A metal coin, which represents value. Surprisingly useless in trade."}, - CROWN = { "A crown may be worn as headgear, or on top of a helmet. Although", - "usually just decorative or symbolic, crowns sometimes deflect attacks."}, - CRUTCH = { "Item used in health-care. May be made from wood or metal. Given to", - "dwarves who receive injuries that impair or prevent normal movement.", - "Requires one hand."}, - DOOR = { "A barrier that covers a hole in a wall and controls horizontal passage.", - "Intelligent creatures can open and close doors as needed. Doors can", - "also block the flow of liquids as long as they remain closed. May be", - "set as 'locked' to prevent all passage or 'tightly closed' to prevent", - "animal passage. Creatures cannot manually open or close doors that are", - "mechanically controlled. May be linked via mechanisms to devices such", - "as pressure plates and levers. Will become stuck open if an item", - "occupies its tile."}, - EARRING = { "Earrings are decorative jewellery. Eleven can be worn on each ear."}, - FIGURINE = {"A small piece of art carved in the likeness of a creature."}, - FLASK = { "A drink container that is worn on the body, keeping the hands free.", - "Soldiers and adventurers can carry any drink of their choice in this", - "container. Called a flask when made from metal, or a vial when", - "made from glass."}, - FLOODGATE = { - "A mechanical gate used to control the flow of water. Floodgates are", - "initially closed when installed, and must be linked to a lever or", - "pressure plate in order to be either opened or closed. Furniture for", - "blocking horizontal passage. It is built as a solid tile and may be", - "linked via mechanism to devices such as pressure plates and levers.", - "When activated, the floodgate will disappear. Will become stuck open", - "if an item occupies its tile."}, - GOBLET = { "A small drink container that is held in one hand."}, - GRATE = { "A barrier with small openings, grates block solid objects and", - "creatures - but not line of sight, liquids, or projectiles. Grates can", - "be installed vertically on a floor, or horizontally over open space.", - "Grates can be retracted if they are mechanically linked to a lever or", - "pressure plate. Grates are not stable enough to support constructions."}, - HATCH_COVER = { - "A barrier that covers a hole in the floor. A hatch cover acts like a", - "door, but placed over a vertical opening. Hatches can also cover carved", - "stairways and ramps. Hatches can be linked to mechanical controls.", - "Creatures cannot manually open or close hatches that are mechanically", - "controlled. They may also be set as 'locked' to prevent all passage or", - "'tightly closed' to prevent animal passage."}, - ITEM_AMMO_ARROWS = { - "Ammunition for bows, which are primarily used by elves."}, - ITEM_AMMO_BOLTS = { - "Ammunition for crossbows, which are a dwarf's preferred ranged weapon.", - "It is not recommended to store bolts in bins, due to pickup bugs."}, - ITEM_ARMOR_BREASTPLATE = { - "A breastplate is a piece of plate armor that covers the upper body and", - "the lower body. It is usually worn in the armor layer."}, - ITEM_ARMOR_CAPE = { - "A (cool-looking) cape. Protects the chest."}, - ITEM_ARMOR_CLOAK = { - "A cloth cloak. Protects the face, neck, chest, arms and upper legs."}, - ITEM_ARMOR_COAT = { - "A heavy cloth coat. Protects the face, neck, chest, arms and upper legs."}, - ITEM_ARMOR_DRESS = { - "A cloth dress. Protects the face, neck, chest, arms and legs."}, - ITEM_ARMOR_LEATHER = { - "Leather armor is light and covers both arms and legs", - "in addition to body"}, - ITEM_ARMOR_MAIL_SHIRT = { - "A chainmail shirt. Protects the face, neck, chest,", - "upper arms and upper legs."}, - ITEM_ARMOR_ROBE = { - "A cloth robe. Protects the face, neck, chest, arms and legs."}, - ITEM_ARMOR_SHIRT = { - "A cloth shirt. Protects the neck, chest and arms."}, - ITEM_ARMOR_TOGA = { - "A cloth toga. Protects the face, neck, chest, upper arms and upper legs."}, - ITEM_ARMOR_TUNIC = { - "A cloth tunic. Protects the neck, chest and upper arms."}, - ITEM_ARMOR_VEST = { - "A cloth vest. Protects the chest."}, - ITEM_FOOD_BISCUITS = { - "Biscuits are the lowest tier of meals that can be prepared by your", - "dwarves. They are made in a kitchen with the 'Prepare Easy Meal' order", - "and use two ingredients. Preparing easy meals is the easiest way to,", - "get experience for you cooks, but the larger volume produced means more", - "hauling to take them to storage."}, - ITEM_FOOD_ROAST = { - "Roasts are the highest tier of meals that can be prepared by your ", - "dwarves. They are made in a kitchen with the 'Prepare Lavish Meal'", - "order, and use four ingredients. As there are more ingredients, there", - "is a better chance that a dwarf will like at least one ingredient."}, - ITEM_FOOD_STEW = { - "Stews are the middle tier of meals that can be prepared by your ", - "dwarves. They are made in a kitchen with the 'Prepare Fine Meal' order,", - "and use three ingredients. They provide more food than Biscuits,", - "but are less valuable than Roasts."}, - ITEM_GLOVES_GAUNTLETS = { - "Gauntlets are armor worn on any body part that can grasp, which for", - "dwarves are the hands. They are similar to mittens and gloves, but", - "act as an armor layer and provide much more protection. Like other", - "armor, gauntlets can be made of metal, shell, or bone."}, - ITEM_GLOVES_GLOVES = { - "Gloves cover the hands, wrapping each finger and thumb individually", - "to preserve the wearer's dexterity at the cost of some warmth"}, - ITEM_GLOVES_MITTENS = { - "Mittens cover the fingers together and thumb separately, preserving", - "the ability to grasp but keeping fingers together for more warmth in", - "cold climates"}, - ITEM_HELM_CAP = { - "A cap covers only the crown of the head. It prevents heat loss through", - "a bald pate and protects the skull from falling objects and", - "downward strikes."}, - ITEM_HELM_HELM = { - "A helm covers the entire face and head. It protects the wearer from", - "falling objects and a variety of weapon strikes from all directions.", - "Every other type of head covering, save a hood, is worn under a helm", - "for padding."}, - ITEM_HELM_HOOD = { - "A hood is a soft loose covering for the head and sides of the face.", - "It shields the wearer from cold breezes and can cushion blows from", - "any direction. It is pulled over the wearer's other headgear, providing", - "a final outer layer of protection from the elements."}, - ITEM_HELM_MASK = { - "A mask hides the wearer's face from view and protects it from all but", - "the most accurate piercing attacks. Some can be carved to present the", - "enemy with a more fearsome visage, or to show no face at all.", - "Masks are worn underneath other layers of headgear."}, - ITEM_HELM_SCARF_HEAD = { - "A head scarf is a loose wrap of cloth or leather that is typically", - "worn in hot climates to protect the head from the rays of the sun.", - "It provides light cushioning against some blows."}, - ITEM_HELM_TURBAN = { - "A turban is a length of cloth or leather that is wrapped many times", - "around the head to shield the wearer from the sun's rays and provide", - "several layers of insulation. A turban can be pinned or clasped in", - "place, or simply folded and tucked into a stable configuration."}, - ITEM_HELM_VEIL_FACE = { - "A face veil is a soft covering that protects the lower half of the", - "wearer's face, leaving only the eyes to gaze out. It can prevent", - "noxious fluids from splashing into the wearer's mouth.", - "It is worn under every other layer of headgear."}, - ITEM_HELM_VEIL_HEAD = { - "A veil for the whole head is a wall of sheer cloth or finely-punched", - "leather extending from above the brow to below the chin, and often hung", - "from a more solid cloth headpiece that covers the crown and cheeks.", - "It admits some light but almost entirely obscures the wearer's face."}, - ITEM_INSTRUMENT_DRUM = { - "Short, wide, and round, this cylindrical percussion instrument can", - "play music when banged by one's hands. It is only useful to trade."}, - ITEM_INSTRUMENT_FLUTE = { - "This long cylindrical woodwind instrument can make a wide array of", - "tones and music when blown into. It is only useful to trade."}, - ITEM_INSTRUMENT_HARP = { - "Vaguely triangular in shape, this stringed instrument can play a", - "variety of notes by plucking the strings with one's fingers.", - "It is only useful to trade."}, - ITEM_INSTRUMENT_PICCOLO = { - "Similar to a flute, but smaller and with a higher tone, a piccolo is", - "a cylindrical woodwind instrument. It is only useful to trade."}, - ITEM_INSTRUMENT_TRUMPET = { - "A dwarven brass instrument - which need not be made of brass.", - "It is only useful to trade."}, - ITEM_PANTS_BRAIES = { - "Braies are undergarments that cover from the waist to the knees.", - "Dwarves cannot craft braies, so they must be obtained through", - "other means."}, - ITEM_PANTS_GREAVES = { - "Greaves are plated armor meant to protect the lower legs, though", - "they are equipped as pants."}, - ITEM_PANTS_LEGGINGS = { - "Leggings are garments that cover everything from the waist to the", - "ankles, though with a tighter fit than other trousers."}, - ITEM_PANTS_LOINCLOTH = { - "Loincloths are draped undergarments meant to cover little more than", - "the 'geldables'. Dwarves cannot craft loincloths, so they must be", - "obtained through other means."}, - ITEM_PANTS_PANTS = { - "Trousers are a garment that covers everything from the waist to the", - "ankles. They keep the legs and lower body warm."}, - ITEM_PANTS_SKIRT = { - "A skirt is a cone-shaped garment that hangs from the waist, covering", - "part of the legs. Its use is more for modesty than protection.", - "Dwarves cannot craft skirts, so they must be obtained through other means."}, - ITEM_PANTS_SKIRT_LONG = { - "A skirt is a cone-shaped garment that hangs from the waist, covering", - "part of the legs. Its use is more for modesty than protection. Long", - "skirts fulfil this purpose well. Dwarves cannot craft long skirts,", - "so they must be obtained through other means."}, - ITEM_PANTS_SKIRT_SHORT = { - "A skirt is a cone-shaped garment that hangs from the waist, covering", - "part of the legs. Its use is more for modesty than protection, though", - "short skirts offer less in the way of modesty. Dwarves cannot craft", - "short skirts, so they must be obtained through other means."}, - ITEM_PANTS_THONG = { - "Thongs are strapped undergarments meant to cover little more than", - "the 'geldables'. Dwarves cannot craft thongs, so they must be obtained", - "through other means."}, - ITEM_SHIELD_BUCKLER = { - "A smaller and less protective type of shield. A buckler can be used", - "to block attacks, and with skill anything from a goblin axe", - "to Dragonfire can be deflected."}, - ITEM_SHIELD_SHIELD = { - "Larger and more defensive than a buckler, a full-sized shield can be", - "used to block attacks. With skill anything from a goblin axe to", - "Dragonfire can be deflected."}, - ITEM_SHOES_BOOTS = { - "Boots are more protective kind of shoe, covering from the foot up to", - "the knee."}, - ITEM_SHOES_BOOTS_LOW = { - "Low boots are more protective kind of shoe, covering from the foot up", - "to just past the ankle."}, - ITEM_SHOES_CHAUSSE = { - "Chausses are chainmail armor meant to protect the legs, though these", - "are equipped as footwear. Dwarves cannot craft chausses, so they", - "must be obtained through other means."}, - ITEM_SHOES_SANDAL = { - "Sandals are open footwear consisting of soles and some number of", - "straps. Dwarves cannot craft sandals, so they must be obtained", - "through other means."}, - ITEM_SHOES_SHOES = { - "Shoes are closed footwear meant to protect the feet from rough terrain", - "and the elements."}, - ITEM_SHOES_SOCKS = { - "Socks are tubular articles of clothing, worn on each foot along with", - "shoes or other footwear."}, - ITEM_SIEGEAMMO_BALLISTA = { - "Ballista ammunition, for an enormous siege weapon."}, - ITEM_TOOL_BOWL = { - "Bowls are used to contain individual servings of meals.", - "At the moment, dwarves have no use for these."}, - ITEM_TOOL_CAULDRON = { - "Cauldrons are large metal pots used to cook meals like soups or stews", - "over an open fire. At the moment, dwarves have no use for these."}, - ITEM_TOOL_FORK_CARVING = { - "A carving fork typically has only two prongs and is exceptionally long.", - "It is used to hold down a piece of cooked meat while using a knife."}, - ITEM_TOOL_HELVE = { - "A helve is the handle of a tool such as an axe.", - "It is not useful in this state - but adding a rock makes a stone axe,", - "which can be used for woodcutting in Adventure mode."}, - ITEM_TOOL_HIVE = { - "Hives are structures that house colonies of honey bees. To be", - "productive, they need to be constructed on an above-ground tile with", - "an accessible honey bee colony somewhere on the map. Some time after", - "bees are 'installed' by a beekeeper, the hive will be ready to harvest", - "or split into new colonies."}, - ITEM_TOOL_HONEYCOMB = { - "Honeycomb is an intermediate product of beekeeping, produced along", - "with royal jelly when a beekeeper harvests a suitable hive. It must", - "be processed by a Presser at a Screw Press to produce honey, which may", - "be used in cooking or made into mead and a wax cake, which can be used", - "to make low-value crafts."}, - ITEM_TOOL_JUG = { - "Jugs are small food storage containers that hold royal jelly, honey,", - "or oil. They are used by beekeepers when harvesting suitable hives and", - "by pressers when processing honeycomb or seed pastes at a screw press."}, - ITEM_TOOL_KNIFE_BONING = { - "A boning knife has a sharp point and narrow blade. It is an excellent", - "all-around kitchen knife and decent weapon in a pinch."}, - ITEM_TOOL_KNIFE_CARVING = { - "A carving knife is for cutting thin slices of cooked meat for serving.", - "It may be useful as an improvised weapon."}, - ITEM_TOOL_KNIFE_MEAT_CLEAVER = { - "A meat cleaver is a heavy square-bladed knife for cutting bone and", - "meat alike."}, - ITEM_TOOL_KNIFE_SLICING = { - "A slicing knife is for cutting thin slices of cooked meat."}, - ITEM_TOOL_LADLE = { - "A ladle is a large spoon with a long handle and a deep bowl,", - "intended for serving out portions of soups and stews."}, - ITEM_TOOL_LARGE_POT = { - "Large pots are storage containers made of any hard material. They", - "function identically to barrels when brewing or storing food, while", - "being much lighter than barrels made of the same material.", - "Unfortunately, they cannot be used when making dwarven syrup or when", - "building asheries and dyer's workshops."}, - ITEM_TOOL_MINECART = { - "A minecart is a tool for hauling, and can be made from wood or metal.", - "", - "Minecart systems are the most efficient and most complicated way to", - "move items, and can do anything from improving industrial efficiency,", - "to transporting magma or launching hundreds of weapons at enemies.", - "Misuse may result in horrific injury to drivers and pedestrians."}, - ITEM_TOOL_MORTAR = { - "Half of a mortar and pestle, the mortar is a bowl in which to grind", - "up plants or other reagents."}, - ITEM_TOOL_NEST_BOX = { - "A place for birds to lay eggs. Must be built before use.", - "Forbid eggs to hatch into chicks before a dwarf picks them up."}, - ITEM_TOOL_PESTLE = { - "Half of a mortar and pestle, the pestle is a stick used to grind up", - "plants or other reagents."}, - ITEM_TOOL_POUCH = { - "A small bag used to carry a variety of tools."}, - ITEM_TOOL_STEPLADDER = { - "A small stepladder. If you have one of these, you can use zones to", - "assign your dwarves to pick fruit from certain trees."}, - ITEM_TOOL_STONE_AXE = { - "This tool can be made in Adventure mode, and is used to cut", - "trees for building or carpentry."}, - ITEM_TOOL_WHEELBARROW = { - "A small hand-cart with long handles and a single wheel, this", - "wheelbarrow makes heavy hauling jobs much more manageable."}, - ITEM_TOY_AXE = { - "A small toy axe without an edge. Useless except as a trade good."}, - ITEM_TOY_BOAT = { - "A tiny model of a boat. Only good for trade."}, - ITEM_TOY_HAMMER = { - "A toy hammer. Its only use is to sell."}, - ITEM_TOY_MINIFORGE = { - "A model of a blacksmith's forge that dwarf children love.", - "Only useful as a trade good."}, - ITEM_TOY_PUZZLEBOX = { - "A perplexing toy that dwarves of all ages enjoy.", - "Its only use is as a trade good."}, - ITEM_TRAPCOMP_ENORMOUSCORKSCREW = { - "A massive screw-like object. Can be used to make a pump,", - "or as a component in a trap."}, - ITEM_TRAPCOMP_GIANTAXEBLADE = { - "This massive blade is typically made of metal and can be used in weapon", - "traps, swinging once to slice anyone unfortunate enough to activate it."}, - ITEM_TRAPCOMP_LARGESERRATEDDISC = { - "Serrated discs are typically made of metal and can be used in weapon", - "traps, in which they eviscerate its victims with three powerful slicing", - "attacks. Such traps have a tendency to sever multiple body parts and", - "make a gigantic mess."}, - ITEM_TRAPCOMP_MENACINGSPIKE = { - "Menacing spikes are made of wood or metal and can be used in weapon", - "traps or upright spike traps, in which they impale the victim. They", - "are especially effective against unarmored foes or, in an upright", - "spike trap, anyone falling from great heights."}, - ITEM_TRAPCOMP_SPIKEDBALL = { - "This trap component menaces with spikes of wood or metal. It hits three", - "times with its spikes, but does not penetrate as deeply as a menacing", - "spike. Compared to other trap components, spiked balls are slightly", - "more effective against heavily armored foes. They also make for a", - "surprisingly valuable trade good, on par with serrated discs."}, - ITEM_WEAPON_AXE_BATTLE = { - "A battle axe is an edged weapon: essentially a sharp blade", - "mounted along the end of a short and heavy handle.", - "", - "Dwarves can forge battle axes out of any weapon-grade metal,", - "though those with superior edge properties are more effective.", - "", - "A battle axe may also be used as a tool for chopping down trees."}, - ITEM_WEAPON_AXE_GREAT = { - "This is an axe nearly twice as large as a battle axe. Its size", - "makes it unsuitable for a dwarf, but those who can wield it find", - "its increased size and weight contribute to its effectiveness."}, - ITEM_WEAPON_AXE_TRAINING = { - "As a battleaxe made from wood, this practise weapon is useful for", - "training recruits. Thanks to good craftsdwarfship, it can also", - "be used to cut down trees."}, - ITEM_WEAPON_BLOWGUN = { - "A very simple ranged weapon: blow into one end of the long narrow", - "tube, and project a pellet or dart into the body of one's prey.", - "If the prey approaches, this blowgun makes a useless melee weapon."}, - ITEM_WEAPON_BOW = { - "Bows are the preferred ranged weapon for elves and goblins, and", - "shoot arrows as projectiles. As they are a foreign weapon, they", - "cannot be made in your fort. In melee, bowmen will use their bow as", - "a weapon, training the swordsman skill."}, - ITEM_WEAPON_CROSSBOW = { - "The favoured ranged weapon of choice for any dwarf, crossbows can be", - "made of wood, bones or metal, and shoot bolts as projectiles. Hunters", - "or marks-dwarves that run out of ammunition will use their crossbow", - "as a melee weapon, training the hammerdwarf skill."}, - ITEM_WEAPON_DAGGER_LARGE = { - "A large dagger is a edge weapon that is essentially just a bit smaller", - "than a short sword. It's used for stabbing rather than slashing. Large", - "daggers use and train the knife user skill, and are common weapons for", - "kobold and goblin thieves. As foreign weapons dwarves cannot forge", - "large daggers."}, - ITEM_WEAPON_FLAIL = { - "A flail is a blunt weapon that consists of a rounded weight attached to", - "a handle by a length of chain. Flails are the same size as a morningstar,", - "but have a contact area twice as large as a much larger maul. As foreign", - "weapons dwarves cannot forge flails. Flails use and train the", - "macedwarf skill."}, - ITEM_WEAPON_HALBERD = { - "A halberd is a foreign weapon, and cannot be made by your dwarves.", - "Even the largest and strongest dwarves cannot use a halberd, making", - "them useless in military terms. They can however be placed in a weapon", - "trap or melted down to provide metal bars, redeeming them."}, - ITEM_WEAPON_HAMMER_WAR = { - "A war hammer is a blunt weapon that is essentially a hammer with a long", - "handle. War hammers use and train the hammerdwarf skill. Dwarves can", - "forge war hammers out of any weapons-grade metal, though those with", - "higher densities tend to cause more damage."}, - ITEM_WEAPON_MACE = { - "A mace is a blunt weapon that consists of a rounded or flanged weight", - "mounted on the end of a handle. Despite similarities to a morningstar", - "in appearance, a mace is 60% larger and has twice the contact area.", - "Maces use and train the macedwarf skill. Dwarves can forge maces out of", - "any weapons-grade metal, though those with higher densities", - "(like silver) cause more damage."}, - ITEM_WEAPON_MAUL = { - "A maul is a blunt weapon that is essentially a very large war hammer,", - "similar to a sledgehammer. Mauls are more than three times larger than", - "standard war hammers, with a similar 'bash' attack. Mauls also have", - "ten times the contact area and greatly reduced penetration, which", - "reduces their effectiveness. Mauls use and train the hammerdwarf", - "skill. Being foreign weapons, dwarves cannot forge mauls."}, - ITEM_WEAPON_MORNINGSTAR = { - "A morningstar is an edged weapon that consists of a spiked ball mounted", - "on the end of a handle. Despite similarities to a mace in appearance,", - "a morningstar's size and contact area are closer to those of a war", - "hammer. Specifically, a morningstar is 25% larger than a war hammer", - "with the same contact area, and uses piercing damage to inflict internal", - "injuries. Morningstars use and train the macedwarf skill."}, - ITEM_WEAPON_PICK = { - "The most important item for a beginning fortress, a pick can", - "get a party underground. Also crucial mining for stone or", - "metals, expansion of living space, and so on.", - "", - "A pick is also useful as a weapon, though putting miners in the", - "military causes equipment clashes."}, - ITEM_WEAPON_PIKE = { - "A pike is a weapon that is essentially a very long spear.", - "Pikes use and train the Pikedwarf skill. As foreign weapons,", - "dwarves cannot forge pikes."}, - ITEM_WEAPON_SCIMITAR = { - "A scimitar is an edged weapon with a curved blade that is very similar", - "to a short sword. Scimitars use and train the swordsdwarf skill.", - "As foreign weapons dwarves cannot forge scimitars."}, - ITEM_WEAPON_SCOURGE = { - "A scourge is an edge weapon that consists of a spike or bladed weight", - "on the end of a flexible length of material that can be swung at", - "high speed. Scourges are similar to whips, though the whip is a blunt", - "weapon with an even smaller contact area.Scourges use and train the", - "lasher skill. As foreign weapons dwarves cannot forge scourges."}, - ITEM_WEAPON_SPEAR = { - "A pole weapon consisting of a shaft, usually of wood, with a pointed", - "head made of metal or just the sharpened end of the shaft itself.", - "With the ability to pin opponents, spears are most effective with axe", - "or macedwarves for combo attacks. "}, - ITEM_WEAPON_SPEAR_TRAINING = { - "A wooden training spear, this has no sharp edges and thus presents", - "little risk of injury. Military dwarves can become", - "attached to them, and refuse to swap them for weapons that cause", - "actual injury to your enemies."}, - ITEM_WEAPON_SWORD_2H = { - "An enormous sword taller than many humans. Victims may be split in", - "two by a single blow, though no dwarf is large enough to wield a", - "greatsword and do so. As foreign weapons, dwarves cannot forge them."}, - ITEM_WEAPON_SWORD_LONG = { - "A longsword is a classic weapon, consisting of a short handle and a", - "long sharp blade. Most dwarves are large enough to use a longsword,", - "but as foreign weapons cannot forge them."}, - ITEM_WEAPON_SWORD_SHORT = { - "A sword just the right size for dwarves, though small dwarves may", - "need both hands. Shortswords can be made from metal at a forge."}, - ITEM_WEAPON_SWORD_SHORT_TRAINING = { - "A wooden training sword, this has no sharp edges and thus presents", - "little risk of injury. Military dwarves can become", - "attached to them, and refuse to swap them for weapons that cause", - "actual injury to your enemies."}, - ITEM_WEAPON_WHIP = { - "A highly effective weapon known to cause large amounts of pain.", - "It cannot be forged by dwarves."}, - MEAT = { "Butchering an animal gives meat, the amount depending on the size", - "of the butchered animal. Along with plants, meat is the", - "backbone of every food industry."}, - MILLSTONE = { - "A large grinding stone, used in a mill to produce flour, sugar, and", - "dyes much faster than a quern. It is too large to be operated by hand,", - "and must be powered for operation. Millstones are made of stone."}, - ORTHOPEDIC_CAST = { - "Casts are made from plaster, and are used to keep broken bones in", - "place until they are healed. Applying a cast requires a bucket,", - "cloth and a water source."}, - PIPE_SECTION = { - "An enormous piece of pipe, it is a part of a screw pump."}, - QUERN = { "A hand-operated mill for plants, grains, and seeds. It mills plants", - "much slower than a millstone. Must be built from stone."}, - QUIVER = { "Item used to hold ammunition, made out of leather. Hunting dwarves", - "and crossbow dwarves will automatically grab one to store their ammo."}, - RING = { "A ring is an item of jewellery, which does not interfere with", - "wearing other equipment. Eleven rings can be worn on each finger", - "or toe, for a maximum of 220 rings."}, - ROCK = { "A small rock, sharpened as a weapon in Adventure mode."}, - ROUGH = { "Rough gemstones and raw glass are cut by a Gem Cutter at a Jeweler's", - "Workshop into small decorative gems. Sometimes, the gem-cutting job", - "results in a craft or large gem that is useless except as a very", - "valuable trade good."}, - SCEPTER = { "A scepter is a short, ornamental rod or wand typically associated", - "with royalty. It's only use is as a trade good."}, - SKIN_TANNED = { - "The tanned hide of animals is flexible enough to be made into an", - "assortment of goods for military and civilian use. Leather can also", - "be used to decorate items with sewn images at a Leather Works. Armor", - "and shields made from leather are not terribly effective, but are", - "still better than nothing at all."}, - SLAB = { "A memorial stone, used to quiet restless ghost when engraved with", - "the name of the deceased and built."}, - SMALLGEM = {"Cut gemstones and the odd gizzard stone (a product of butchering", - "certain species of animals) are used by a Gem Setter to decorate items", - "at a Jeweler's Workshop."}, - SPLINT = { "Splints are used to immobilise fractured limbs. They are made out of", - "wood or metal, and allow dwarves to leave the hospital and continue", - "their normal jobs. Splints are applied with the bonedoctor skill."}, - STATUE = { "A large piece of art carved in the likeness of a creature. Statues", - "can be installed on any open floor space, but cannot share the space", - "with creatures. Statues can be used as the focal point of a", - "recreational statue garden. Dwarves will admire or revile as they", - "pass, depending on the statue and the individual's preferences."}, - TABLE = { "A flat-topped piece of furniture useful as a work-surface for a", - "scribe or a dining-surface for a hungry dwarf. Typically found in", - "shops, dinning rooms, and offices. Dining rooms may be designated and", - "assigned from them, though dwarves will complain if there are too few."}, - THREAD = { "A small bundle of processed material, ready to be woven into cloth.", - "Thread made from animal hair will not be used to make cloth. Thread", - "can also be used by doctors to sew wounds shut. It is sourced from", - "shearing, plant processing, trade, or web gathering. It can be dyed", - "for additional value before being woven."}, - TOTEM = { "A carved and polished skull."}, - TRACTION_BENCH = { - "A special hospital bed made to secure dwarves with complex or", - "overlapping fractures until healed. Patients may need several months", - "or more in a traction bench to heal. Constructed from a table,", - "a mechanism, and a rope or chain."}, - TRAPPARTS = { - "Used to build traps, levers and other machines."}, - WEAPONRACK = { - "Furniture used for training. Barracks may be designated and assigned", - "from them. Military squads may use their assigned barracks for", - "training, storage, or sleeping, depending on the settings."}, - WINDOW = { "Furniture used for ambiance. Either made in a glass furnace from glass", - "or built on site using three cut gems. While it is treated as a wall,", - "it does not support constructions. Passing dwarves will admire them."}, - WOOD = { "A porous and fibrous structural tissue found in the stems and roots", - "of trees and underground fungus. Wood is renewable and essential for", - "numerous industries. It can be made into charcoal, ash for further", - "processing, furniture, crafts, tools, some trap components, training", - "gear, and (ineffective) weapons and armor. Elves take serious offence", - "when wood or wooden items are offered in trade."} -} diff --git a/scripts/lever.rb b/scripts/lever.rb deleted file mode 100644 index 05a159861..000000000 --- a/scripts/lever.rb +++ /dev/null @@ -1,152 +0,0 @@ -# control your levers from the dfhack console -=begin - -lever -===== -Allow manipulation of in-game levers from the dfhack console. - -Can list levers, including state and links, with:: - - lever list - -To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``. -This is the same as :kbd:`q` querying the building and queue a :kbd:`P` pull request. - -To magically toggle the lever immediately, use:: - - lever pull 42 --now - -=end - -def lever_pull_job(bld) - ref = DFHack::GeneralRefBuildingHolderst.cpp_new - ref.building_id = bld.id - - job = DFHack::Job.cpp_new - job.job_type = :PullLever - job.pos = [bld.centerx, bld.centery, bld.z] - job.general_refs << ref - bld.jobs << job - df.job_link job - - puts lever_descr(bld) -end - -def lever_pull_cheat(bld) - bld.linked_mechanisms.each { |i| - i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| - r.building_tg.setTriggerState(bld.state) - } - } - - bld.state = (bld.state == 0 ? 1 : 0) - - puts lever_descr(bld) -end - -def lever_descr(bld, idx=nil) - ret = [] - - # lever description - descr = '' - descr << "#{idx}: " if idx - descr << "lever ##{bld.id} " - descr << "(#{bld.name}) " if bld.name.length != 0 - descr << "@[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" - bld.jobs.each { |j| - if j.job_type == :PullLever - flags = '' - flags << ', repeat' if j.flags.repeat - flags << ', suspended' if j.flags.suspend - descr << " (pull order#{flags})" - end - } - - bld.linked_mechanisms.map { |i| - i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) - }.flatten.each { |r| - # linked building description - tg = r.building_tg - state = '' - if tg.respond_to?(:gate_flags) - state << (tg.gate_flags.closed ? 'closed' : 'opened') - state << ", closing (#{tg.timer})" if tg.gate_flags.closing - state << ", opening (#{tg.timer})" if tg.gate_flags.opening - end - - ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") - - # indent other links - descr = descr.gsub(/./, ' ') - } - - ret << descr if ret.empty? - - ret -end - -def lever_list - @lever_list = [] - df.world.buildings.other[:TRAP].find_all { |bld| - bld.trap_type == :Lever - }.sort_by { |bld| bld.id }.each { |bld| - puts lever_descr(bld, @lever_list.length) - @lever_list << bld.id - } -end - - -@lever_list ||= [] - -case $script_args[0] -when 'pull' - cheat = $script_args.delete('--cheat') || $script_args.delete('--now') - - if $script_args[1].nil? - bld = df.building_find(:selected) if not bld - raise 'no lever under cursor and no lever id given' if not bld - else - id = $script_args[1].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld - end - - if cheat - lever_pull_cheat(bld) - else - lever_pull_job(bld) - end - -when 'list' - lever_list - -when /^\d+$/ - id = $script_args[0].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld - - puts lever_descr(bld) - -else - - puts < 0 and bx+dx < df.world.map.x_count-1 and by+dy > 0 and by+dy < df.world.map.y_count-1 - pos = [bx+dx, by+dy, bz] - end - } - } - } - } - df.center_viewscreen(*pos) - df.map_tile_at(*pos).dig - puts "Here is some #{df.world.raws.inorganics[found_mat].id}" - else - puts "Cannot find unmined #{mats.map { |mat| df.world.raws.inorganics[mat].id }.join(', ')}" - end - -else - puts "Available ores:", $ore_veins.sort_by { |mat, pos| pos.length }.map { |mat, pos| - ore = df.world.raws.inorganics[mat] - metals = ore.metal_ore.mat_index.map { |m| df.world.raws.inorganics[m] } - ' ' + ore.id.downcase + ' (' + metals.map { |m| m.id.downcase }.join(', ') + ')' - } - -end diff --git a/scripts/lua.lua b/scripts/lua.lua deleted file mode 100644 index 17c47d41a..000000000 --- a/scripts/lua.lua +++ /dev/null @@ -1,91 +0,0 @@ --- Execute lua commands interactively or from files. ---[[=begin - -lua -=== -There are the following ways to invoke this command: - -1. ``lua`` (without any parameters) - - This starts an interactive lua interpreter. - -2. ``lua -f "filename"`` or ``lua --file "filename"`` - - This loads and runs the file indicated by filename. - -3. ``lua -s ["filename"]`` or ``lua --save ["filename"]`` - - This loads and runs the file indicated by filename from the save - directory. If the filename is not supplied, it loads "dfhack.lua". - -4. ``:lua`` *lua statement...* - - Parses and executes the lua statement like the interactive interpreter would. - -=end]] - -local args={...} -local cmd = args[1] - -env = env or {} -setmetatable(env, {__index = function(self, k) - if k == 'scr' or k == 'screen' then - return dfhack.gui.getCurViewscreen() - elseif k == 'bld' or k == 'building' then - return dfhack.gui.getSelectedBuilding() - elseif k == 'item' then - return dfhack.gui.getSelectedItem() - elseif k == 'job' then - return dfhack.gui.getSelectedJob() - elseif k == 'wsjob' or k == 'workshop_job' then - return dfhack.gui.getSelectedWorkshopJob() - elseif k == 'unit' then - return dfhack.gui.getSelectedUnit() - else - return _G[k] - end -end}) - -if cmd=="--file" or cmd=="-f" then - local f,err=loadfile (args[2]) - if f==nil then - qerror(err) - end - dfhack.safecall(f,table.unpack(args,3)) -elseif cmd=="--save" or cmd=="-s" then - if df.global.world.cur_savegame.save_dir=="" then - qerror("Savefile not loaded") - end - local fname=args[2] or "dfhack.lua" - fname=string.format("data/save/%s/%s",df.global.world.cur_savegame.save_dir,fname) - local f,err=loadfile (fname) - if f==nil then - qerror(err) - end - dfhack.safecall(f,table.unpack(args,3)) -elseif cmd~=nil then - -- Support some of the prefixes allowed by dfhack.interpreter - local prefix - if string.match(cmd, "^[~@!]") then - prefix = string.sub(cmd, 1, 1) - cmd = 'return '..string.sub(cmd, 2) - end - - local f,err=load(cmd,'=(lua command)', 't') - if f==nil then - qerror(err) - end - - local rv = table.pack(dfhack.safecall(f,table.unpack(args,2))) - - if rv[1] and prefix then - print(table.unpack(rv,2,rv.n)) - if prefix == '~' then - printall(rv[2]) - elseif prefix == '@' then - printall_ipairs(rv[2]) - end - end -else - dfhack.interpreter("lua","lua.history",env) -end diff --git a/scripts/make-legendary.lua b/scripts/make-legendary.lua deleted file mode 100644 index da29cd4c2..000000000 --- a/scripts/make-legendary.lua +++ /dev/null @@ -1,141 +0,0 @@ --- Make a skill or skills of a unit Legendary +5 --- by vjek ---[[=begin - -make-legendary -============== -Makes the selected dwarf legendary in one skill, a group of skills, or all -skills. View groups with ``make-legendary classes``, or all skills with -``make-legendary list``. Use ``make-legendary MINING`` when you need something -dug up, or ``make-legendary all`` when only perfection will do. - -=end]] - --- this function will return the number of elements, starting at zero. --- useful for counting things where #foo doesn't work -function count_this(to_be_counted) - local count = -1 - local var1 = "" - while var1 ~= nil do - count = count + 1 - var1 = (to_be_counted[count]) - end - count=count-1 - return count -end - -function getName(unit) - return dfhack.df2console(dfhack.TranslateName(dfhack.units.getVisibleName(unit))) -end - -function make_legendary(skillname) - local skillnamenoun,skillnum - unit=dfhack.gui.getSelectedUnit() - - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - - if (df.job_skill[skillname]) then - skillnamenoun = df.job_skill.attrs[df.job_skill[skillname]].caption_noun - else - print ("The skill name provided is not in the list.") - return - end - - if skillnamenoun ~= nil then - utils = require 'utils' - skillnum = df.job_skill[skillname] - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = skillnum, rating = 20 }, 'id') - print (getName(unit) .. " is now a Legendary "..skillnamenoun) - else - print ("Empty skill name noun, bailing out!") - return - end -end - -function PrintSkillList() - local count_max = count_this(df.job_skill) - local i - for i=0, count_max do - print("'"..df.job_skill.attrs[i].caption.."' "..df.job_skill[i].." Type: "..df.job_skill_class[df.job_skill.attrs[i].type]) - end - print ("Provide the UPPER CASE argument, for example: PROCESSPLANTS rather than Threshing") -end - -function BreathOfArmok() - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - local i - local count_max = count_this(df.job_skill) - utils = require 'utils' - for i=0, count_max do - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - print ("The breath of Armok has engulfed "..getName(unit)) -end - -function LegendaryByClass(skilltype) - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - - utils = require 'utils' - local i - local skillclass - local count_max = count_this(df.job_skill) - for i=0, count_max do - skillclass = df.job_skill_class[df.job_skill.attrs[i].type] - if skilltype == skillclass then - print ("Skill "..df.job_skill.attrs[i].caption.." is type: "..skillclass.." and is now Legendary for "..getName(unit)) - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - end -end - -function PrintSkillClassList() - local i - local count_max = count_this(df.job_skill_class) - for i=0, count_max do - print(df.job_skill_class[i]) - end - print ("Provide one of these arguments, and all skills of that type will be made Legendary") - print ("For example: Medical will make all medical skills legendary") -end - ---main script operation starts here ----- -local opt = ... -local skillname - -if opt then - if opt=="list" then - PrintSkillList() - return - end - if opt=="classes" then - PrintSkillClassList() - return - end - if opt=="all" then - BreathOfArmok() - return - end - if opt=="Normal" or opt=="Medical" or opt=="Personal" or opt=="Social" or opt=="Cultural" or opt=="MilitaryWeapon" or opt=="MilitaryAttack" or opt=="MilitaryDefense" or opt=="MilitaryMisc" then - LegendaryByClass(opt) - return - end - skillname = opt -else - print ("No skillname supplied.\nUse argument 'list' to see a list, 'classes' to show skill classes, or use 'all' if you want it all!") - print ("Example: To make a legendary miner, use make_legendary MINING") - return -end - -make_legendary(skillname) diff --git a/scripts/make-monarch.lua b/scripts/make-monarch.lua deleted file mode 100644 index 1c8764367..000000000 --- a/scripts/make-monarch.lua +++ /dev/null @@ -1,39 +0,0 @@ ---set target unit as king/queen ---[[=begin - -make-monarch -============ -Make the selected unit King or Queen of your civilisation. - -=end]] - -local unit=dfhack.gui.getSelectedUnit() -if not unit then qerror("No unit selected") end -local newfig=dfhack.units.getNemesis(unit).figure -local my_entity=df.historical_entity.find(df.global.ui.civ_id) -local monarch_id -for k,v in pairs(my_entity.positions.own) do - if v.code=="MONARCH" then - monarch_id=v.id - break - end -end -if not monarch_id then qerror("No monarch found!") end -local old_id -for pos_id,v in pairs(my_entity.positions.assignments) do - if v.position_id==monarch_id then - old_id=v.histfig - v.histfig=newfig.id - local oldfig=df.historical_figure.find(old_id) - - for k,v in pairs(oldfig.entity_links) do - if df.histfig_entity_link_positionst:is_instance(v) and v.assignment_id==pos_id and v.entity_id==df.global.ui.civ_id then - oldfig.entity_links:erase(k) - break - end - end - newfig.entity_links:insert("#",{new=df.histfig_entity_link_positionst,entity_id=df.global.ui.civ_id, - link_strength=100,assignment_id=pos_id,start_year=df.global.cur_year}) - break - end -end diff --git a/scripts/markdown.lua b/scripts/markdown.lua deleted file mode 100644 index 14ecdaa2c..000000000 --- a/scripts/markdown.lua +++ /dev/null @@ -1,239 +0,0 @@ --- Save a text screen in markdown (eg for reddit) --- This is a derivatiwe work based upon scripts/forum-dwarves.lua by Caldfir and expwnent --- Adapted for markdown by Mchl https://github.com/Mchl ---[[=begin - -markdown -======== -Save a copy of a text screen in markdown (for reddit among others). -Use ``markdown help`` for more details. - -=end]] - -local args = {...} - -if args[1] == 'help' then - print([[ -description: - This script will attempt to read the current df-screen, and if it is a - text-viewscreen (such as the dwarf 'thoughts' screen or an item / creature - 'description') or an announcement list screen (such as announcements and - combat reports) then append a marked-down version of this text to the - target file (for easy pasting on reddit for example). - Previous entries in the file are not overwritten, so you - may use the 'markdown' command multiple times to create a single - document containing the text from multiple screens (eg: text screens - from several dwarves, or text screens from multiple artifacts/items, - or some combination). - -usage: - markdown [/n] [filename] - - /n - overwrites contents of output file - filename - if provided, the data will be saved in md_filename.md instead - of default md_export.md - -known screens: - The screens which have been tested and known to function properly with - this script are: - 1: dwarf/unit 'thoughts' screen - 2: item/art 'description' screen - 3: individual 'historical item/figure' screens - 4: manual - 4: announements screen - 5: combat reports screen - 6: latest news (when meeting with liaison) - There may be other screens to which the script applies. It should be - safe to attempt running the script with any screen active, with an - error message to inform you when the selected screen is not appropriate - for this script. - -target file: - The default target file's name is 'md_export.md'. A remider to this effect - will be displayed if the script is successful. - -character encoding: - The text will likely be using system-default encoding, and as such - will likely NOT display special characters (eg:é,õ,ç) correctly. To - fix this, you need to modify the character set that you are reading - the document with. 'Notepad++' is a freely available program which - can do this using the following steps: - 1: open the document in Notepad++ - 2: in the menu-bar, select - Encoding->Character Sets->Western European->OEM-US - 3: copy the text normally to wherever you want to use it -]]) - return -end - -local writemode = 'a' - --- check if we want to append to an existing file (default) or overwrite previous contents -if args[1] == '/n' then - writemode = 'w' - table.remove(args, 1) -end - -local filename - -if args[1] ~= nil then - filename = 'md_' .. table.remove(args, 1) .. '.md' -else - filename = 'md_export.md' -end - -local utils = require 'utils' -local gui = require 'gui' -local dialog = require 'gui.dialogs' - - - - -local scrn = dfhack.gui.getCurViewscreen() -local flerb = dfhack.gui.getFocusString(scrn) - -local months = { - [1] = 'Granite', - [2] = 'Slate', - [3] = 'Felsite', - [4] = 'Hematite', - [5] = 'Malachite', - [6] = 'Galena', - [7] = 'Limestone', - [8] = 'Sandstone', - [9] = 'Timber', - [10] = 'Moonstone', - [11] = 'Opal', - [12] = 'Obsidian', -} - -local function getFileHandle() - return io.open(filename, writemode) -end - -local function closeFileHandle(handle) - handle:write('\n***\n\n') - handle:close() - print ('Data exported to "' .. filename .. '"') -end - -local function reformat(strin) - local strout = strin - - -- [P] tags seem to indicate a new paragraph - local newline_idx = string.find(strout, '[P]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout, 1, newline_idx - 1) .. '\n***\n\n' .. string.sub(strout, newline_idx + 3) - newline_idx = string.find(strout, '[P]', 1, true) - end - - -- [R] tags seem to indicate a new 'section'. Let's mark it with a horizontal line. - newline_idx = string.find(strout, '[R]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout, 1, newline_idx - 1) .. '\n***\n\n' .. string.sub(strout,newline_idx + 3) - newline_idx = string.find(strout, '[R]', 1, true) - end - - -- No idea what [B] tags might indicate. Just removing them seems to work fine - newline_idx = string.find(strout, '[B]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout, 1, newline_idx - 1) .. string.sub(strout,newline_idx + 3) - newline_idx = string.find(strout, '[B]', 1, true) - end - - -- Reddit doesn't support custom colors in markdown. We need to remove all color information :( - local color_idx = string.find(strout, '[C:', 1, true) - while color_idx ~= nil do - strout = string.sub(strout, 1, color_idx - 1) .. string.sub(strout, color_idx + 9) - color_idx = string.find(strout, '[C:', 1, true) - end - - return strout -end - -local function formattime(year, ticks) - -- Dwarf Mode month is 33600 ticks long - local month = math.floor(ticks / 33600) - local dayRemainder = ticks - month * 33600 - - -- Dwarf Mode day is 1200 ticks long - local day = math.floor(dayRemainder / 1200) - local timeRemainder = dayRemainder - day * 1200 - - -- Assuming a 24h day each Dwarf Mode tick corresponds to 72 seconds - local seconds = timeRemainder * 72 - - local H = string.format("%02.f", math.floor(seconds / 3600)); - local m = string.format("%02.f", math.floor(seconds / 60 - (H * 60))); - local i = string.format("%02.f", math.floor(seconds - H * 3600 - m * 60)); - - day = day + 1 - if (day == 1 or day == 21) then - day = day .. 'st' - elseif (day == 2 or day == 22) then - day = day .. 'nd' - else - day = day .. 'th' - end - - return (day .. " " .. months[month + 1] .. " " .. year .. " " .. H .. ":" .. m..":" .. i) -end - -if flerb == 'textviewer' then - - local lines = scrn.src_text - - if lines ~= nil then - - local log = getFileHandle() - log:write('### ' .. scrn.title .. '\n') - - print('Exporting ' .. scrn.title .. '\n') - - for n,x in ipairs(lines) do - log:write(reformat(x.value).." ") --- debug output --- print(x.value) - end - closeFileHandle(log) - end - -elseif flerb == 'announcelist' then - - local lines = scrn.reports - - if lines ~= nil then - local log = getFileHandle() - local lastTime = "" - - for n,x in ipairs(lines) do - local currentTime = formattime(x.year, x.time) - if (currentTime ~= lastTime) then - lastTime = currentTime - log:write('\n***\n\n') - log:write('## ' .. currentTime .. '\n') - end --- debug output --- print(x.text) - log:write(x.text .. '\n') - end - closeFileHandle(log) - end - - -elseif flerb == 'topicmeeting' then - local lines = scrn.text - - if lines ~= nil then - local log = getFileHandle() - - for n,x in ipairs(lines) do --- debug output --- print(x.value) - log:write(x.value .. '\n') - end - closeFileHandle(log) - end -else - print 'This is not a textview, announcelist or topicmeeting screen. Can\'t export data, sorry.' -end diff --git a/scripts/masspit.rb b/scripts/masspit.rb deleted file mode 100644 index 9c276faf5..000000000 --- a/scripts/masspit.rb +++ /dev/null @@ -1,51 +0,0 @@ -# pit all caged creatures in a zone -=begin - -masspit -======= -Designate all creatures in cages on top of a pit/pond activity zone for pitting. -Works best with an animal stockpile on top of the zone. - -Works with a zone number as argument (eg ``Activity Zone #6`` -> ``masspit 6``) -or with the game cursor on top of the area. - -=end - -case $script_args[0] -when '?', 'help' - puts <= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y - } - -end - -if not bld - puts "Please select a pit/pond zone" - throw :script_finished -end - -found = 0 -df.world.items.other[:CAGE].each { |cg| - next if not cg.flags.on_ground - next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2 - next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first - found += 1 - u = uref.unit_tg - puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}" - u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id) - bld.assigned_units << u.id -} -puts "No creature available for pitting" if found == 0 diff --git a/scripts/migrants-now.lua b/scripts/migrants-now.lua deleted file mode 100644 index aa9ad7b3f..000000000 --- a/scripts/migrants-now.lua +++ /dev/null @@ -1,16 +0,0 @@ --- Force a migrant wave (only after hardcoded waves) ---[[=begin - -migrants-now -============ -Forces an immediate migrant wave. Only works after migrants have -arrived naturally. Equivalent to `modtools/force` ``-eventType migrants`` - -=end]] -df.global.timed_events:insert('#',{ - new = true, - type = df.timed_event_type.Migrants, - season = df.global.cur_season, - season_ticks = df.global.cur_season_tick+1, - entity = df.historical_entity.find(df.global.ui.civ_id) -}) diff --git a/scripts/modtools/about.txt b/scripts/modtools/about.txt deleted file mode 100644 index d2924b301..000000000 --- a/scripts/modtools/about.txt +++ /dev/null @@ -1,17 +0,0 @@ -``modtools/*`` scripts provide tools for modders, often with changes -to the raw files, and are not intended to be called manually by end-users. - -These scripts are mostly useful for raw modders and scripters. They all have -standard arguments: arguments are of the form ``tool -argName1 argVal1 --argName2 argVal2``. This is equivalent to ``tool -argName2 argVal2 -argName1 -argVal1``. It is not necessary to provide a value to an argument name: ``tool --argName3`` is fine. Supplying the same argument name multiple times will -result in an error. Argument names are preceded with a dash. The ``-help`` -argument will print a descriptive usage string describing the nature of the -arguments. For multiple word argument values, brackets must be used: ``tool --argName4 [ sadf1 sadf2 sadf3 ]``. In order to allow passing literal braces as -part of the argument, backslashes are used: ``tool -argName4 [ \] asdf \foo ]`` -sets ``argName4`` to ``\] asdf foo``. The ``*-trigger`` scripts have a similar -policy with backslashes. - -Any of these scripts can be called with ``-help`` for more information. diff --git a/scripts/modtools/add-syndrome.lua b/scripts/modtools/add-syndrome.lua deleted file mode 100644 index 2de2462b1..000000000 --- a/scripts/modtools/add-syndrome.lua +++ /dev/null @@ -1,114 +0,0 @@ --- Add or remove syndromes from units ---author expwnent ---[[=begin - -modtools/add-syndrome -===================== -This allows adding and removing syndromes from units. - -=end]] -local syndromeUtil = require 'syndrome-util' -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'syndrome', - 'resetPolicy', - 'erase', - 'eraseOldest', - 'eraseAll', - 'target', - 'skipImmunities', - 'eraseClass' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/add-syndrome usage: -arguments: - -help - print this help message - -syndrome name - the name of the syndrome to operate on - examples: - "gila monster bite" - -resetPolicy policy - specify a policy of what to do if the unit already has an instance of the syndrome - examples: - NewInstance - default behavior: create a new instance of the syndrome - DoNothing - ResetDuration - AddDuration - -erase - instead of adding an instance of the syndrome, erase one - -eraseAll - erase every instance of the syndrome - -eraseClass SYN_CLASS - erase every instance of every syndrome with the given SYN_CLASS - -target id - the unit id of the target unit - examples: - 0 - 28 - -skipImmunities - add the syndrome to the target regardless of whether it is immune to the syndrome -]]) - return -end - -if not args.target then - error 'Specify a target.' -end -local targ = df.unit.find(tonumber(args.target)) -if not targ then - error ('Could not find target: ' .. args.target) -end -args.target = targ - -if args.eraseClass then - local count = syndromeUtil.eraseSyndromeClass(args.target, args.eraseClass) - --print('deleted ' .. tostring(count) .. ' syndromes') - return -end - -if args.resetPolicy then - args.resetPolicy = syndromeUtil.ResetPolicy[args.resetPolicy] - if not args.resetPolicy then - error ('Invalid reset policy.') - end -end - -if not args.syndrome then - error 'Specify a syndrome name.' -end - -local syndrome -for _,syn in ipairs(df.global.world.raws.syndromes.all) do - if syn.syn_name == args.syndrome then - syndrome = syn - break - end -end -if not syndrome then - error ('Invalid syndrome: ' .. args.syndrome) -end -args.syndrome = syndrome - -if args.erase then - syndromeUtil.eraseSyndrome(args.target,args.syndrome.id,args.eraseOldest) - return -end - -if args.eraseAll then - syndromeUtil.eraseSyndromes(args.target,args.syndrome.id) - return -end - -if skipImmunities then - syndromeUtil.infectWithSyndrome(args.target,args.syndrome,args.resetPolicy) -else - syndromeUtil.infectWithSyndromeIfValidTarget(args.target,args.syndrome,args.resetPolicy) -end - diff --git a/scripts/modtools/anonymous-script.lua b/scripts/modtools/anonymous-script.lua deleted file mode 100644 index 73eaefb92..000000000 --- a/scripts/modtools/anonymous-script.lua +++ /dev/null @@ -1,27 +0,0 @@ --- invoke simple lua scripts from strings ---author expwnent ---[[=begin - -modtools/anonymous-script -========================= -This allows running a short simple Lua script passed as an argument instead of -running a script from a file. This is useful when you want to do something too -complicated to make with the existing modtools, but too simple to be worth its -own script file. Example:: - - anonymous-script "print(args[1])" arg1 arg2 - # prints "arg1" - -=end]] -local args = {...} - ---automatically collect arguments to make the anonymous script more succinct ---load(ld,source,mode,env) -local f,err = load('local args = {...}; ' .. args[1], '=(anonymous lua script)') --,'=(lua command)', 't') -if err then - error(err) -end - ---we don't care about the result even if they return something for some reason: we just want to ensure its side-effects happen and print appropriate error messages -dfhack.safecall(f,table.unpack(args,2)) - diff --git a/scripts/modtools/create-item.lua b/scripts/modtools/create-item.lua deleted file mode 100644 index 0e4149222..000000000 --- a/scripts/modtools/create-item.lua +++ /dev/null @@ -1,140 +0,0 @@ --- creates an item of a given type and material ---author expwnent ---[[=begin - -modtools/create-item -==================== -Replaces the `createitem` plugin, with standard -arguments. The other versions will be phased out in a later version. - -=end]] -local utils = require 'utils' - -validArgs = --[[validArgs or--]] utils.invert({ - 'help', - 'creator', - 'material', - 'item', --- 'creature', --- 'caste', - 'leftHand', - 'rightHand', - 'quality' -}) - -organicTypes = organicTypes or utils.invert({ - df.item_type.REMAINS, - df.item_type.FISH, - df.item_type.FISH_RAW, - df.item_type.VERMIN, - df.item_type.PET, - df.item_type.EGG, -}) - -badTypes = badTypes or utils.invert({ - df.item_type.CORPSE, - df.item_type.CORPSEPIECE, - df.item_type.FOOD, -}) - -function createItem(creatorID, item, material, leftHand, rightHand, quality) - local itemQuality = quality and df.item_quality[quality] - print(itemQuality) - - local creator = df.unit.find(creatorID) - if not creator then - error 'Invalid creator.' - end - - if not item then - error 'Invalid item.' - end - local itemType = dfhack.items.findType(item) - if itemType == -1 then - error 'Invalid item.' - end - local itemSubtype = dfhack.items.findSubtype(item) - - if organicTypes[itemType] then - --TODO: look up creature and caste - error 'Not yet supported.' - end - - if badTypes[itemType] then - error 'Not supported.' - end - - if not material then - error 'Invalid material.' - end - local materialInfo = dfhack.matinfo.find(material) - if not materialInfo then - error 'Invalid material.' - end - - local item1 = dfhack.items.createItem(itemType, itemSubtype, materialInfo['type'], materialInfo.index, creator) - local item = df.item.find(item1) - if leftHand then - item:setGloveHandedness(2) - elseif rightHand then - item:setGloveHandedness(1) - end - - if itemQuality then - item:setQuality(itemQuality) - end - --[[if matchingGloves or matchingShoes then - if matchingGloves then - item1 = df.item.find(item1) - item1:setGloveHandedness(1); - end - local item2 = dfhack.items.createItem(itemType, itemSubtype, materialInfo['type'], materialInfo.index, creator) - if matchingGloves then - item2 = df.item.find(item2) - item2:setGloveHandedness(2); - end - end --]] - return item1 -end - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print( -[[scripts/modtools/create-item.lua -arguments: - -help - print this help message - -creator id - specify the id of the unit who will create the item, or \\LAST to indicate the unit with id df.global.unit_next_id-1 - examples: - 0 - 2 - \\LAST - -material matstring - specify the material of the item to be created - examples: - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -item itemstr - specify the itemdef of the item to be created - examples: - WEAPON:ITEM_WEAPON_PICK - -matchingShoes - create two of this item - -matchingGloves - create two of this item, and set handedness appropriately - ]]) - return -end - -if args.creator == '\\LAST' then - args.creator = tostring(df.global.unit_next_id-1) -end - -createItem(tonumber(args.creator), args.item, args.material, args.leftHand, args.rightHand, args.quality) diff --git a/scripts/modtools/create-unit.lua b/scripts/modtools/create-unit.lua deleted file mode 100644 index 9505ec739..000000000 --- a/scripts/modtools/create-unit.lua +++ /dev/null @@ -1,575 +0,0 @@ --- Creates a unit. Beta; use at own risk. --- Originally created by warmist, edited by Putnam for the dragon ball mod to be used in reactions, modified by Dirst for use in The Earth Strikes Back mod, incorporating fixes discovered by Boltgun then Mifiki wrote the bit where it switches to arena mode briefly to do some of the messy work, then Expwnent combined that with the old script to make it function for histfigs --- version 0.51 --- This is a beta version. Use at your own risk. - --- Modifications from 0.5: civ -1 creates are NOT historical figures, mitigated screen-movement bug in createUnit() - ---[[ - TODO - children and babies: set child/baby job - confirm body size is computed appropriately for different ages / life stages - incarnate pre-existing historical figures - some sort of invasion helper script - set invasion_id, etc - announcement for fake natural birth if appropriate -]] ---[[=begin - -modtools/create-unit -==================== -Creates a unit. Use ``modtools/create-unit -help`` for more info. - -=end]] - ---[[ -if dfhack.gui.getCurViewscreen()._type ~= df.viewscreen_dwarfmodest or df.global.ui.main.mode ~= df.ui_sidebar_mode.LookAround then - print 'activate loo[k] mode' - return -end ---]] - -local utils=require 'utils' - -function createUnit(race_id, caste_id) - local view_x = df.global.window_x - local view_y = df.global.window_y - local view_z = df.global.window_z - - local curViewscreen = dfhack.gui.getCurViewscreen() - local dwarfmodeScreen = df.viewscreen_dwarfmodest:new() - curViewscreen.child = dwarfmodeScreen - dwarfmodeScreen.parent = curViewscreen - local oldMode = df.global.ui.main.mode - df.global.ui.main.mode = df.ui_sidebar_mode.LookAround - - local gui = require 'gui' - - df.global.world.arena_spawn.race:resize(0) - df.global.world.arena_spawn.race:insert(0,race_id) --df.global.ui.race_id) - - df.global.world.arena_spawn.caste:resize(0) - df.global.world.arena_spawn.caste:insert(0,caste_id) - - df.global.world.arena_spawn.creature_cnt:resize(0) - df.global.world.arena_spawn.creature_cnt:insert(0,0) - - --df.global.world.arena_spawn.equipment.skills:insert(0,99) - --df.global.world.arena_spawn.equipment.skill_levels:insert(0,0) - - local old_gametype = df.global.gametype - df.global.gametype = df.game_type.DWARF_ARENA - - gui.simulateInput(dfhack.gui.getCurViewscreen(), 'D_LOOK_ARENA_CREATURE') - gui.simulateInput(dfhack.gui.getCurViewscreen(), 'SELECT') - - df.global.gametype = old_gametype - - curViewscreen.child = nil - dwarfmodeScreen:delete() - df.global.ui.main.mode = oldMode - - local id = df.global.unit_next_id-1 - - df.global.window_x = view_x - df.global.window_y = view_y - df.global.window_z = view_z - - return id -end - ---local u = df.unit.find(df.global.unit_next_id-1) ---u.civ_id = df.global.ui.civ_id ---u.population_id = df.historical_entity.find(df.global.ui.civ_id).populations[0] ---local group = df.global.ui.group_id - --- Picking a caste or gender at random -function getRandomCasteId(race_id) - local cr = df.creature_raw.find(race_id) - local caste_id, casteMax - - casteMax = #cr.caste - 1 - - if casteMax > 0 then - return math.random(0, casteMax) - end - - return 0 -end - -local function allocateNewChunk(hist_entity) - hist_entity.save_file_id=df.global.unit_chunk_next_id - df.global.unit_chunk_next_id=df.global.unit_chunk_next_id+1 - hist_entity.next_member_idx=0 - print("allocating chunk:",hist_entity.save_file_id) -end - -local function allocateIds(nemesis_record,hist_entity) - if hist_entity.next_member_idx==100 then - allocateNewChunk(hist_entity) - end - nemesis_record.save_file_id=hist_entity.save_file_id - nemesis_record.member_idx=hist_entity.next_member_idx - hist_entity.next_member_idx=hist_entity.next_member_idx+1 -end - -function createFigure(trgunit,he,he_group) - local hf=df.historical_figure:new() - hf.id=df.global.hist_figure_next_id - hf.race=trgunit.race - hf.caste=trgunit.caste - hf.profession = trgunit.profession - hf.sex = trgunit.sex - df.global.hist_figure_next_id=df.global.hist_figure_next_id+1 - hf.appeared_year = df.global.cur_year - - hf.born_year = trgunit.relations.birth_year - hf.born_seconds = trgunit.relations.birth_time - hf.curse_year = trgunit.relations.curse_year - hf.curse_seconds = trgunit.relations.curse_time - hf.birth_year_bias = trgunit.relations.birth_year_bias - hf.birth_time_bias = trgunit.relations.birth_time_bias - hf.old_year = trgunit.relations.old_year - hf.old_seconds = trgunit.relations.old_time - hf.died_year = -1 - hf.died_seconds = -1 - hf.name:assign(trgunit.name) - hf.civ_id = trgunit.civ_id - hf.population_id = trgunit.population_id - hf.breed_id = -1 - hf.unit_id = trgunit.id - - df.global.world.history.figures:insert("#",hf) - - hf.info = df.historical_figure_info:new() - hf.info.unk_14 = df.historical_figure_info.T_unk_14:new() -- hf state? - --unk_14.region_id = -1; unk_14.beast_id = -1; unk_14.unk_14 = 0 - hf.info.unk_14.unk_18 = -1; hf.info.unk_14.unk_1c = -1 - -- set values that seem related to state and do event - --change_state(hf, dfg.ui.site_id, region_pos) - - - --lets skip skills for now - --local skills = df.historical_figure_info.T_skills:new() -- skills snap shot - -- ... - -- note that innate skills are automaticaly set by DF - hf.info.skills = {new=true} - - - he.histfig_ids:insert('#', hf.id) - he.hist_figures:insert('#', hf) - if he_group then - he_group.histfig_ids:insert('#', hf.id) - he_group.hist_figures:insert('#', hf) - hf.entity_links:insert("#",{new=df.histfig_entity_link_memberst,entity_id=he_group.id,link_strength=100}) - end - trgunit.flags1.important_historical_figure = true - trgunit.flags2.important_historical_figure = true - trgunit.hist_figure_id = hf.id - trgunit.hist_figure_id2 = hf.id - - hf.entity_links:insert("#",{new=df.histfig_entity_link_memberst,entity_id=trgunit.civ_id,link_strength=100}) - - --add entity event - local hf_event_id=df.global.hist_event_next_id - df.global.hist_event_next_id=df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#",{new=df.history_event_add_hf_entity_linkst,year=trgunit.relations.birth_year, - seconds=trgunit.relations.birth_time,id=hf_event_id,civ=hf.civ_id,histfig=hf.id,link_type=0}) - return hf -end - -function createNemesis(trgunit,civ_id,group_id) - local id=df.global.nemesis_next_id - local nem=df.nemesis_record:new() - - nem.id=id - nem.unit_id=trgunit.id - nem.unit=trgunit - nem.flags:resize(4) - --not sure about these flags... - -- [[ - nem.flags[4]=true - nem.flags[5]=true - nem.flags[6]=true - nem.flags[7]=true - nem.flags[8]=true - nem.flags[9]=true - --]] - --[[for k=4,8 do - nem.flags[k]=true - end]] - nem.unk10=-1 - nem.unk11=-1 - nem.unk12=-1 - df.global.world.nemesis.all:insert("#",nem) - df.global.nemesis_next_id=id+1 - trgunit.general_refs:insert("#",{new=df.general_ref_is_nemesisst,nemesis_id=id}) - trgunit.flags1.important_historical_figure=true - - nem.save_file_id=-1 - - local he=df.historical_entity.find(civ_id) - he.nemesis_ids:insert("#",id) - he.nemesis:insert("#",nem) - local he_group - if group_id and group_id~=-1 then - he_group=df.historical_entity.find(group_id) - end - if he_group then - he_group.nemesis_ids:insert("#",id) - he_group.nemesis:insert("#",nem) - end - allocateIds(nem,he) - nem.figure=createFigure(trgunit,he,he_group) -end - ---createNemesis(u, u.civ_id,group) -function createUnitInCiv(race_id, caste_id, civ_id, group_id) - local uid = createUnit(race_id, caste_id) - local unit = df.unit.find(uid) - if ( civ_id ) then - createNemesis(unit, civ_id, group_id) - end - return uid -end - -function createUnitInFortCiv(race_id, caste_id) - return createUnitInCiv(race_id, caste_id, df.global.ui.civ_id) -end - -function createUnitInFortCivAndGroup(race_id, caste_id) - return createUnitInCiv(race_id, caste_id, df.global.ui.civ_id, df.global.ui.group_id) -end - -function domesticate(uid, group_id) - local u = df.unit.find(uid) - group_id = group_id or df.global.ui.group_id - -- If a friendly animal, make it domesticated. From Boltgun & Dirst - local caste=df.creature_raw.find(u.race).caste[u.caste] - if not(caste.flags.CAN_SPEAK and caste.flags.CAN_LEARN) then - -- Fix friendly animals (from Boltgun) - u.flags2.resident = false; - u.flags3.body_temp_in_range = true; - u.population_id = -1 - u.status.current_soul.unit_id = u.id - - u.animal.population.region_x = -1 - u.animal.population.region_y = -1 - u.animal.population.unk_28 = -1 - u.animal.population.population_idx = -1 - u.animal.population.depth = -1 - - u.counters.soldier_mood_countdown = -1 - u.counters.death_cause = -1 - - u.enemy.anon_4 = -1 - u.enemy.anon_5 = -1 - u.enemy.anon_6 = -1 - - -- And make them tame (from Dirst) - u.flags1.tame = true - u.training_level = 7 - end -end - -function wild(uid) - local u = df.unit.find(uid) - local caste=df.creature_raw.find(u.race).caste[u.caste] - -- x = df.global.world.world_data.active_site[0].pos.x - -- y = df.global.world.world_data.active_site[0].pos.y - -- region = df.global.map.map_blocks[df.global.map.x_count_block*x+y] - if not(caste.flags.CAN_SPEAK and caste.flags.CAN_LEARN) then - u.animal.population.region_x = 1 - u.animal.population.region_y = 1 - u.animal.population.unk_28 = -1 - u.animal.population.population_idx = 1 - u.animal.population.depth = 0 - end -end - - -function nameUnit(id, entityRawName, civ_id) - --pick a random appropriate name - --choose three random words in the appropriate things - local unit = df.unit.find(id) - local entity_raw - if entityRawName then - for k,v in ipairs(df.global.world.raws.entities) do - if v.code == entityRawName then - entity_raw = v - break - end - end - else - local entity = df.historical_entity.find(civ_id) - entity_raw = entity.entity_raw - end - - if not entity_raw then - error('entity raw = nil: ', id, entityRawName, civ_id) - end - - local translation = entity_raw.translation - local translationIndex - for k,v in ipairs(df.global.world.raws.language.translations) do - if v.name == translation then - translationIndex = k - break - end - end - --translation = df.language_translation.find(translation) - local language_word_table = entity_raw.symbols.symbols1[0] --educated guess - function randomWord() - local index = math.random(0, #language_word_table.words[0] - 1) - return index - end - local firstName = randomWord() - local lastName1 = randomWord() - local lastName2 = randomWord() - local name = unit.status.current_soul.name - name.words[0] = language_word_table.words[0][lastName1] - name.parts_of_speech[0] = language_word_table.parts[0][lastName1] - name.words[1] = language_word_table.words[0][lastName2] - name.parts_of_speech[1] = language_word_table.parts[0][lastName2] - name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]] - name.has_name = true - name.language = translationIndex - name = unit.name - name.words[0] = language_word_table.words[0][lastName1] - name.parts_of_speech[0] = language_word_table.parts[0][lastName1] - name.words[1] = language_word_table.words[0][lastName2] - name.parts_of_speech[1] = language_word_table.parts[0][lastName2] - name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]] - name.has_name = true - name.language = translationIndex - if unit.hist_figure_id ~= -1 then - local histfig = df.historical_figure.find(unit.hist_figure_id) - name = histfig.name - name.words[0] = language_word_table.words[0][lastName1] - name.parts_of_speech[0] = language_word_table.parts[0][lastName1] - name.words[1] = language_word_table.words[0][lastName2] - name.parts_of_speech[1] = language_word_table.parts[0][lastName2] - name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]] - name.has_name = true - name.language = translationIndex - end -end - -validArgs = --[[validArgs or]]utils.invert({ - 'help', - 'race', - 'caste', - 'domesticate', - 'civId', - 'groupId', - 'flagSet', - 'flagClear', - 'name', - 'nick', - 'location', - 'age' -}) - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) -if args.help then - print( -[[scripts/modtools/create-unit.lua -arguments: - -help - print this help message - -race raceName - specify the race of the unit to be created - examples: - DWARF - HUMAN - -caste casteName - specify the caste of the unit to be created - examples: - MALE - FEMALE - -domesticate - if the unit can't learn or can't speak, then make it a friendly animal - -civId id - make the created unit a member of the specified civ (or none if id = -1) - if id is \\LOCAL, then make it a member of the civ associated with the current fort - otherwise id must be an integer - -groupId id - make the created unit a member of the specified group (or none if id = -1) - if id is \\LOCAL, then make it a member of the group associated with the current fort - otherwise id must be an integer - -name entityRawName - set the unit's name to be a random name appropriate for the given entity - examples: - MOUNTAIN - -nick nickname - set the unit's nickname directly - -location [ x y z ] - create the unit at the specified coordinates - -age howOld - set the birth date of the unit to the specified number of years ago - -flagSet [ flag1 flag2 ... ] - set the specified unit flags in the new unit to true - flags may be selected from df.unit_flags1, df.unit_flags2, or df.unit_flags3 - -flagClear [ flag1 flag2 ... ] - set the specified unit flags in the new unit to false - flags may be selected from df.unit_flags1, df.unit_flags2, or df.unit_flags3 -]]) - return -end - -local race -local raceIndex -local casteIndex - -if not args.race or not args.caste then - error 'Specfiy a race and caste for the new unit.' -end - ---find race -for i,v in ipairs(df.global.world.raws.creatures.all) do - if v.creature_id == args.race then - raceIndex = i - race = v - break - end -end - -if not race then - error 'Invalid race.' -end - -for i,v in ipairs(race.caste) do - if v.caste_id == args.caste then - casteIndex = i - break - end -end - -if not casteIndex then - error 'Invalid caste.' -end - -local age -if args.age then - age = tonumber(args.age) - if not age and not age == 0 then - error('Invalid age: ' .. args.age) - end -end - -local civ_id -if args.civId == '\\LOCAL' then - civ_id = df.global.ui.civ_id -elseif args.civId and tonumber(args.civId) then - civ_id = tonumber(args.civId) -end - -local group_id -if args.groupId == '\\LOCAL' then - group_id = df.global.ui.group_id -elseif args.groupId and tonumber(args.groupId) then - group_id = tonumber(args.groupId) -end - -local unitId -if civ_id == -1 then - unitId = createUnit(raceIndex, casteIndex) - else - unitId = createUnitInCiv(raceIndex, casteIndex, civ_id, group_id) -end - -if args.domesticate then - domesticate(unitId, group_id) - else - wild(unitId) -end - -if age or age == 0 then - local u = df.unit.find(unitId) - local oldYearDelta = u.relations.old_year - u.relations.birth_year - u.relations.birth_year = df.global.cur_year - age - u.relations.old_year = u.relations.birth_year + oldYearDelta - --these flags are an educated guess of how to get the game to compute sizes correctly: use -flagSet and -flagClear arguments to override or supplement - u.flags2.calculated_nerves = false - u.flags2.calculated_bodyparts = false - u.flags3.body_part_relsize_computed = false - u.flags3.size_modifier_computed = false - u.flags3.compute_health = true - u.flags3.weight_computed = false - --TODO: if the unit is a child or baby it will still behave like an adult -end - -if args.flagSet or args.flagClear then - local u = df.unit.find(unitId) - local flagsToSet = {} - local flagsToClear = {} - for _,v in ipairs(args.flagSet or {}) do - flagsToSet[v] = true - end - for _,v in ipairs(args.flagClear or {}) do - flagsToClear[v] = true - end - for _,k in ipairs(df.unit_flags1) do - if flagsToSet[k] then - u.flags1[k] = true; - elseif flagsToClear[k] then - u.flags1[k] = false; - end - end - for _,k in ipairs(df.unit_flags2) do - if flagsToSet[k] then - u.flags2[k] = true; - elseif flagsToClear[k] then - u.flags2[k] = false; - end - end - for _,k in ipairs(df.unit_flags3) do - if flagsToSet[k] then - u.flags3[k] = true; - elseif flagsToClear[k] then - u.flags3[k] = false; - end - end -end - -if args.name then - nameUnit(unitId, args.name, civ_id) -else - local unit = df.unit.find(unitId) - unit.name.has_name = false - if unit.status.current_soul then - unit.status.current_soul.name.has_name = false - end - --[[if unit.hist_figure_id ~= -1 then - local histfig = df.historical_figure.find(unit.hist_figure_id) - histfig.name.has_name = false - end--]] -end - -if args.nick and type(args.nick) == 'string' then - dfhack.units.setNickname(df.unit.find(unitId), args.nick) -end - -if civ_id then - local u = df.unit.find(unitId) - u.civ_id = civ_id -end - -if args.location then - local u = df.unit.find(unitId) - local pos = df.coord:new() - pos.x = tonumber(args.location[1]) - pos.y = tonumber(args.location[2]) - pos.z = tonumber(args.location[3]) - local teleport = dfhack.script_environment('teleport') - teleport.teleport(u, pos) -end - ---[[if group_id then - local u = df.unit.find(unitId) - u.group_id = group_id -end--]] diff --git a/scripts/modtools/equip-item.lua b/scripts/modtools/equip-item.lua deleted file mode 100644 index bf17fc127..000000000 --- a/scripts/modtools/equip-item.lua +++ /dev/null @@ -1,105 +0,0 @@ --- equip an item on a unit with a particular body part ---[[=begin - -modtools/equip-item -=================== -Force a unit to equip an item with a particular body part; useful in -conjunction with the ``create`` scripts above. See also `forceequip`. - -=end]] -local utils = require 'utils' - -function equipItem(unit, item, bodyPart, mode) - --it is assumed that the item is on the ground - item.flags.on_ground = false - item.flags.in_inventory = true - local block = dfhack.maps.getTileBlock(item.pos) - local occupancy = block.occupancy[item.pos.x%16][item.pos.y%16] - for k,v in ipairs(block.items) do - --local blockItem = df.item.find(v) - if v == item.id then - block.items:erase(k) - break - end - end - local foundItem = false - for k,v in ipairs(block.items) do - local blockItem = df.item.find(v) - if blockItem.pos.x == item.pos.x and blockItem.pos.y == item.pos.y then - foundItem = true - break - end - end - if not foundItem then - occupancy.item = false - end - - local inventoryItem = df.unit_inventory_item:new() - inventoryItem.item = item - inventoryItem.mode = mode - inventoryItem.body_part_id = bodyPart - unit.inventory:insert(#unit.inventory,inventoryItem) - -end - -validArgs = --[[validArgs or--]] utils.invert({ - 'help', - 'unit', - 'item', - 'bodyPart', - 'mode' -}) - -if moduleMode then - return -end - - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print( -[[scripts/modtools/equip-item.lua -arguments: - -help - print this help message - ]]) - return -end - -local unitId = tonumber(args.unit) or ((args.unit == '\\LAST') and (df.global.unit_next_id-1)) -local unit = df.unit.find(unitId) -if not unit then - error('invalid unit!', args.unit) -end - -local itemId = tonumber(args.item) or ((args.item == '\\LAST') and (df.global.item_next_id-1)) -local item = df.item.find(itemId) -if not item then - error('invalid item!', args.item) -end - -local bodyPartName = args.bodyPart -local creature_raw = df.global.world.raws.creatures.all[unit.race] -local caste_raw = creature_raw.caste[unit.caste] -local body_info = caste_raw.body_info - -local partId -local part -for k,v in ipairs(body_info.body_parts) do - if v.token == bodyPartName then - partId = k - part = v - break - end -end - -if not part then - error('invalid body part name: ', bodyPartName) -end - -local mode = args.mode -mode = df.unit_inventory_item.T_mode[mode] - -equipItem(unit, item, partId, mode) - diff --git a/scripts/modtools/extra-gamelog.lua b/scripts/modtools/extra-gamelog.lua deleted file mode 100644 index 0046ceb26..000000000 --- a/scripts/modtools/extra-gamelog.lua +++ /dev/null @@ -1,192 +0,0 @@ --- Regularly writes extra info to gamelog.txt -local help = [[=begin - -modtools/extra-gamelog -====================== -This script writes extra information to the gamelog. -This is useful for tools like :forums:`Soundsense <106497>`. - -=end]] - -msg = dfhack.gui.writeToGamelog - -function log_on_load(op) - if op ~= SC_WORLD_LOADED then return end - - -- Seasons fix for Soundsense - local seasons = { - [-1] = 'Nothing', -- worldgen - [0] = 'Spring', - [1] = 'Summer', - [2] = 'Autumn', - [3] = 'Winter'} - msg(seasons[df.global.cur_season]..' has arrived on the calendar.') - - -- Weather fix for Soundsense - local raining = false - local snowing = false - for _, row in ipairs(df.global.current_weather) do - for _, weather in ipairs(row) do - raining = raining or weather == 1 - snowing = snowing or weather == 2 - end - end - if (not snowing and not raining) then msg("The weather has cleared.") - elseif raining then msg("It has started raining.") - elseif snowing then msg("A snow storm has come.") - end - - -- Log site information for forts - if df.world_site.find(df.global.ui.site_id) == nil then return end - local site = df.world_site.find(df.global.ui.site_id) - local fort_ent = df.global.ui.main.fortress_entity - local civ_ent = df.historical_entity.find(df.global.ui.civ_id) - local function fullname(item) - return dfhack.TranslateName(item.name)..' ('..dfhack.TranslateName(item.name ,true)..')' - end - msg('Loaded '..df.global.world.cur_savegame.save_dir..', '..fullname(df.global.world.world_data).. - ' at coordinates ('..site.pos.x..','..site.pos.y..')') - msg('Loaded the fortress '..fullname(site).. - (fort_ent and ', colonized by the group '..fullname(fort_ent) or '').. - (civ_ent and ' of the civilization '..fullname(civ_ent)..'.' or '.')) -end - - -old_expedition_leader = nil -old_mayor = nil -function log_nobles() - local expedition_leader = nil - local mayor = nil - local function check(unit) - if not dfhack.units.isCitizen(unit) then return end - for _, pos in ipairs(dfhack.units.getNoblePositions(unit) or {}) do - if pos.position.name[0] == "expedition leader" then - expedition_leader = unit - elseif pos.position.name[0] == "mayor" then - mayor = unit - end - end - end - for _, unit in ipairs(df.global.world.units.active) do - check(unit) - end - - if old_mayor == nil and expedition_leader == nil and mayor ~= nil and old_expedition_leader ~= nil then - msg("Expedition leader was replaced by mayor.") - end - - if expedition_leader ~= old_expedition_leader then - if expedition_leader == nil then - msg("Expedition leader position is now vacant.") - else - msg(dfhack.TranslateName(dfhack.units.getVisibleName(expedition_leader)).." became expedition leader.") - end - end - - if mayor ~= old_mayor then - if mayor == nil then - msg("Mayor position is now vacant.") - else - msg(dfhack.TranslateName(dfhack.units.getVisibleName(mayor)).." became mayor.") - end - end - old_mayor = mayor - old_expedition_leader = expedition_leader -end - -siege = false -function log_siege() - local function cur_siege() - for _, unit in ipairs(df.global.world.units.active) do - if unit.flags1.active_invader then return true end - end - return false - end - local old_siege = siege - siege = cur_siege() - if siege ~= old_siege and siege then - msg("A vile force of darkness has arrived!") - elseif siege ~= old_siege and not siege then - msg("Siege was broken.") - end -end - - -local workshopTypes = { - [0]="Carpenters Workshop", - "Farmers Workshop", - "Masons Workshop", - "Craftsdwarfs Workshop", - "Jewelers Workshop", - "Metalsmiths Forge", - "Magma Forge", - "Bowyers Workshop", - "Mechanics Workshop", - "Siege Workshop", - "Butchers Workshop", - "Leatherworks Workshop", - "Tanners Workshop", - "Clothiers Workshop", - "Fishery", - "Still", - "Loom", - "Quern", - "Kennels", - "Kitchen", - "Ashery", - "Dyers Workshop", - "Millstone", - "Custom", - "Tool", - } - -local furnaceTypes = { - [0]="Wood Furnace", - "Smelter", - "Glass Furnace", - "Kiln", - "Magma Smelter", - "Magma Glass Furnace", - "Magma Kiln", - "Custom Furnace", - } - -buildStates = {} - -function log_buildings() - for _, building in ipairs(df.global.world.buildings.all) do - if getmetatable(building) == "building_workshopst" or getmetatable(building) == "building_furnacest" then - buildStates[building.id] = buildStates[building.id] or building.flags.exists - if buildStates[building.id] ~= building.flags.exists then - buildStates[building.id] = building.flags.exists - if building.flags.exists then - if getmetatable(building) == "building_workshopst" then - msg(workshopTypes[building.type].." was built.") - elseif getmetatable(building) == "building_furnacest" then - msg(furnaceTypes[building.type].." was built.") - end - end - end - end - end -end - -local function event_loop() - log_nobles() - log_siege() - log_buildings() - if extra_gamelog_enabled then dfhack.timeout(50, 'ticks', event_loop) end -end - -extra_gamelog_enabled = false -local args = {...} -if args[1] == 'disable' then - dfhack.onStateChange[_ENV] = nil - extra_gamelog_enabled = false -elseif args[1] == 'enable' then - dfhack.onStateChange[_ENV] = log_on_load - extra_gamelog_enabled = true - event_loop() -else - print(help) -end diff --git a/scripts/modtools/force.lua b/scripts/modtools/force.lua deleted file mode 100644 index 3b25b0895..000000000 --- a/scripts/modtools/force.lua +++ /dev/null @@ -1,90 +0,0 @@ --- Forces an event (caravan, migrants, etc) --- author Putnam --- edited by expwnent ---[[=begin - -modtools/force -============== -This tool triggers events like megabeasts, caravans, and migrants. - -=end]] -local utils = require 'utils' - -local function findCiv(arg) - local entities = df.global.world.entities.all - if tonumber(arg) then return arg end - if arg then - for eid,entity in ipairs(entities) do - if entity.entity_raw.code == arg then return entity end - end - end - return nil -end - -validArgs = validArgs or utils.invert({ - 'eventType', - 'help', - 'civ' -}) - -local args = utils.processArgs({...}, validArgs) -if next(args) == nil or args.help then - print([[force usage -arguments: - -help - print this help message - -eventType event - specify the type of the event to trigger - examples: - MegaBeast - Migrants - Caravan - Diplomat - WildlifeCurious - WildlifeMischievous - WildlifeFlier - NightCreature - -civ entity - specify the civ of the event, if applicable - examples: - player - MOUNTAIN - EVIL - 28 -]]) - print('force: -eventType [Megabeast, Migrants, Caravan, Diplomat, WildlifeCurious, WildlifeMischievous, WildlifeFlier, NightCreature] -civ [player,ENTITY_ID]') - return -end - -if not args.eventType then - error 'Specify an eventType.' -elseif not df.timed_event_type[args.eventType] then - error('Invalid eventType: ' .. args.eventType) -end - -if args.civ == 'player' then - args.civ = df.historical_entity.find(df.global.ui.civ_id) -elseif args.civ then - local civ = args.civ - args.civ = findCiv(args.civ) - if not args.civ then - error('Invalid civ: ' .. civ) - end -elseif args.eventType == 'Caravan' or args.eventType == 'Diplomat' then - error('Specify civ for this eventType') -end - -if args.eventType == 'Migrants' then - args.civ = df.historical_entity.find(df.global.ui.civ_id) -end - -local timedEvent = df.timed_event:new() -timedEvent['type'] = df.timed_event_type[args.eventType] -timedEvent.season = df.global.cur_season -timedEvent.season_ticks = df.global.cur_season_tick -if args.civ then - timedEvent.entity = args.civ -end - -df.global.timed_events:insert('#', timedEvent) - diff --git a/scripts/modtools/interaction-trigger.lua b/scripts/modtools/interaction-trigger.lua deleted file mode 100644 index e611a27fe..000000000 --- a/scripts/modtools/interaction-trigger.lua +++ /dev/null @@ -1,176 +0,0 @@ --- triggers scripts based on unit interactions ---author expwnent ---[[=begin - -modtools/interaction-trigger -============================ -This triggers events when a unit uses an interaction on another. It works by -scanning the announcements for the correct attack verb, so the attack verb -must be specified in the interaction. It includes an option to suppress this -announcement after it finds it. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -attackTriggers = attackTriggers or {} -defendTriggers = defendTriggers or {} -commands = commands or {} -commandCount = commandCount or 0 - -eventful.enableEvent(eventful.eventType.INTERACTION,1) --cheap, so every tick is fine -eventful.enableEvent(eventful.eventType.UNLOAD,1) - -eventful.onUnload.interactionTrigger = function() - attackTriggers = {} - defendTriggers = {} - commands = {} - commandCount = 0 -end - -local function processTrigger(args) - local command = {} - for _,arg in ipairs(args.command) do - if arg == '\\ATTACK_VERB' then - table.insert(command,args.attackVerb) - elseif arg == '\\DEFEND_VERB' then - table.insert(command,args.defendVerb) - elseif arg == '\\ATTACKER_ID' then - table.insert(command,args.attackerId) - elseif arg == '\\DEFENDER_ID' then - table.insert(command,args.defenderId) - elseif arg == '\\ATTACK_REPORT' then - table.insert(command,args.attackReport) - elseif arg == '\\DEFEND_REPORT' then - table.insert(command,args.defendReport) - elseif string.sub(arg,1,1) == '\\' then - table.insert(command,string.sub(arg,2)) - else - table.insert(command,arg) - end - end - dfhack.run_command(table.unpack(command)) -end - -eventful.onInteraction.interactionTrigger = function(attackVerb, defendVerb, attacker, defender, attackReport, defendReport) - local extras = {} - extras.attackVerb = attackVerb - extras.defendVerb = defendVerb - extras.attackReport = attackReport - extras.defendReport = defendReport - extras.attackerId = attacker - extras.defenderId = defender - local suppressAttack = false - local suppressDefend = false - local todo = {} - for _,trigger in ipairs(attackTriggers[attackVerb] or {}) do - todo[trigger] = true - end - for _,trigger in ipairs(defendTriggers[defendVerb] or {}) do - todo[trigger] = true - end - for k,v in pairs(todo) do - command = commands[k] - suppressAttack = suppressAttack or command.suppressAttack - suppressDefend = suppressDefend or command.suppressDefend - utils.fillTable(command,extras) - processTrigger(command) - utils.unfillTable(command,extras) - end - - local eraseReport = function(unit,report) - for i,v in ipairs(unit.reports.log.Combat) do - if v == report then - unit.reports.log.Combat:erase(i) - break - end - end - end - if suppressAttack or suppressDefend then - attacker = df.unit.find(tonumber(attacker)) - defender = df.unit.find(tonumber(defender)) - end - if suppressAttack then - eraseReport(attacker,attackReport) - eraseReport(defender,attackReport) - end - if suppressDefend then - eraseReport(attacker,defendReport) - eraseReport(defender,defendReport) - end - --TODO: get rid of combat report on LHS of screen -end - ----------------------------------------------------- ---argument processing - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'onAttackStr', - 'onDefendStr', - 'command', - 'suppressAttack', - 'suppressDefend', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/interaction-trigger.lua -arguments: - -help - print this help message - -clear - unregisters all triggers - -onAttackStr str - trigger the command when the attack verb is "str". both onAttackStr and onDefendStr MUST be specified - -onDefendStr str - trigger the command when the defend verb is "str". both onAttackStr and onDefendStr MUST be specified - -suppressAttack - delete the attack announcement from the combat logs - -suppressDefend - delete the defend announcement from the combat logs - -command [ commandStrs ] - specify the command to be executed - commandStrs - \\ATTACK_VERB - \\DEFEND_VERB - \\ATTACKER_ID - \\DEFENDER_ID - \\ATTACK_REPORT - \\DEFEND_REPORT - \\anything -> \anything - anything -> anything -You must specify both an attack string and a defend string to guarantee correct performance. Either will trigger the script when it happens, but it will not be triggered twice in a row if both happen. -]]) - return -end - -if args.clear then - triggers = {} - commands = {} - commandCount = 0 -end - -if not args.command then - return -end - -commands[commandCount] = args - -if args.onAttackStr then - if not attackTriggers[args.onAttackStr] then - attackTriggers[args.onAttackStr] = {} - end - table.insert(attackTriggers[args.onAttackStr],commandCount) -end - -if args.onDefendStr then - if not defendTriggers[args.onDefendStr] then - defendTriggers[args.onDefendStr] = {} - end - table.insert(defendTriggers[args.onDefendStr],commandCount) -end - -commandCount = commandCount+1 diff --git a/scripts/modtools/invader-item-destroyer.lua b/scripts/modtools/invader-item-destroyer.lua deleted file mode 100644 index 9b06c944e..000000000 --- a/scripts/modtools/invader-item-destroyer.lua +++ /dev/null @@ -1,184 +0,0 @@ --- delete invader items when they die ---author expwnent ---[[=begin - -modtools/invader-item-destroyer -=============================== -This tool configurably destroys invader items to prevent clutter or to prevent -the player from getting tools exclusive to certain races. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - ---invaders = invaders or {} -entities = entities or {} -items = items or {} - -allEntities = allEntities or false -allItems = allitems or true - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.invaderItemDestroyer = function() - entities = {} - items = {} - allEntities = false - allItems = true -end - -eventful.enableEvent(eventful.eventType.UNIT_DEATH, 1) --requires iterating through all units -eventful.onUnitDeath.invaderItemDestroyer = function(unitId) - local unit = df.unit.find(unitId) - if not unit then - return - end - - local entity = df.historical_entity.find(unit.civ_id) - if not allEntities and not entity then - return - end - - if not allEntities and not entities[entity.entity_raw.code] then - return - end - - if dfhack.units.isCitizen(unit) then - return - end - - local function forEach(item) - if not allItems and not items[dfhack.items.getSubtypeDef(item:getType(),item:getSubtype()).id] then - return - end - if not (item.flags.foreign and item.flags.forbid) then - return - end - if item.pos.x ~= unit.pos.x then - return - end - if item.pos.y ~= unit.pos.y then - return - end - if item.pos.z ~= unit.pos.z then - return - end - item.flags.garbage_collect = true - item.flags.forbid = true - item.flags.hidden = true - end - - for _,item in ipairs(unit.inventory) do - local item2 = df.item.find(item.item) - forEach(item2) - end - --for each item on the ground - local block = dfhack.maps.getTileBlock(unit.pos.x, unit.pos.y, unit.pos.z) - for _,item in ipairs(block.items) do - local item2 = df.item.find(item) - forEach(item2) - end -end ---[[eventful.onUnitDeath.invaderItemDestroyer = function(unit) - if invaders[unit] then - print ('Invader ' .. unit .. ' dies.') - end - for _,item in ipairs(invaders[unit] or {}) do - local item2 = df.item.find(item) - if item2 then - print ('deleting item ' .. item) - item2.flags.garbage_collect = true - item2.flags.forbid = true - item2.flags.hidden = true - item2.flags.encased = true - end - end - invaders[unit] = nil - --TODO: delete corpses? -end]] - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'allRaces', - 'allEntities', - 'allItems', - 'item', - 'entity', - 'race', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.clear then - entities = {} - items = {} - allEntities = false - allItems = true -end - -if args.help then - print([[scripts/modtools/invader-item-destroyer.lua usage -arguments: - -help - print this help message - -clear - reset all registered data - -allEntities [true/false] - set whether it should delete items from invaders from any civ - -allItems [true/false] - set whether it should delete all invader items regardless of type when an appropriate invader dies - -item itemdef - set a particular itemdef to be destroyed when an invader from an appropriate civ dies - examples: - ITEM_WEAPON_PICK - -entity entityName - set a particular entity up so that its invaders destroy their items shortly after death - examples: - MOUNTAIN - EVIL -]]) - return -end - -if args.allEntities then - if args.allEntities == 'true' then - allEntities = true - else - allEntities = false - end -end -if args.allItems then - if args.allItems == 'true' then - allItems = true - else - allItems = false - end -end - -if args.item then - local itemType - for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do - if itemdef.id == args.item then - itemType = itemdef.id - break - end - end - if not itemType then - error ('Invalid item type: ' .. args.item) - end - items[itemType] = true -end -if args.entity then - local success - for _,entity in ipairs(df.global.world.entities.all) do - if entity.entity_raw.code == args.entity then - success = true - break - end - end - if not success then - error 'Invalid entity' - end - entities[args.entity] = true -end - diff --git a/scripts/modtools/item-trigger.lua b/scripts/modtools/item-trigger.lua deleted file mode 100644 index ee44fd447..000000000 --- a/scripts/modtools/item-trigger.lua +++ /dev/null @@ -1,294 +0,0 @@ --- trigger commands based on attacks with certain items ---author expwnent ---based on itemsyndrome by Putnam ---triggers scripts when a unit attacks another with a weapon type, a weapon of a particular material, or a weapon contaminated with a particular material, or when a unit equips/unequips a particular item type, an item of a particular material, or an item contaminated with a particular material ---[[=begin - -modtools/item-trigger -===================== -This powerful tool triggers DFHack commands when a unit equips, unequips, or -attacks another unit with specified item types, specified item materials, or -specified item contaminants. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -itemTriggers = itemTriggers or {} -materialTriggers = materialTriggers or {} -contaminantTriggers = contaminantTriggers or {} - -eventful.enableEvent(eventful.eventType.UNIT_ATTACK,1) -- this event type is cheap, so checking every tick is fine -eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,5) --this is expensive, but you might still want to set it lower -eventful.enableEvent(eventful.eventType.UNLOAD,1) - -eventful.onUnload.itemTrigger = function() - itemTriggers = {} - materialTriggers = {} - contaminantTriggers = {} -end - -function processTrigger(command) - local command2 = {} - for i,arg in ipairs(command.command) do - if arg == '\\ATTACKER_ID' then - command2[i] = '' .. command.attacker.id - elseif arg == '\\DEFENDER_ID' then - command2[i] = '' .. command.defender.id - elseif arg == '\\ITEM_MATERIAL' then - command2[i] = command.itemMat:getToken() - elseif arg == '\\ITEM_MATERIAL_TYPE' then - command2[i] = command.itemMat['type'] - elseif arg == '\\ITEM_MATERIAL_INDEX' then - command2[i] = command.itemMat.index - elseif arg == '\\ITEM_ID' then - command2[i] = '' .. command.item.id - elseif arg == '\\ITEM_TYPE' then - command2[i] = command.itemType - elseif arg == '\\CONTAMINANT_MATERIAL' then - command2[i] = command.contaminantMat:getToken() - elseif arg == '\\CONTAMINANT_MATERIAL_TYPE' then - command2[i] = command.contaminantMat['type'] - elseif arg == '\\CONTAMINANT_MATERIAL_INDEX' then - command2[i] = command.contaminantMat.index - elseif arg == '\\MODE' then - command2[i] = command.mode - elseif arg == '\\UNIT_ID' then - command2[i] = command.unit.id - elseif string.sub(arg,1,1) == '\\' then - command2[i] = string.sub(arg,2) - else - command2[i] = arg - end - end - dfhack.run_command(table.unpack(command2)) -end - -function getitemType(item) - if item:getSubtype() ~= -1 then - itemType = dfhack.items.getSubtypeDef(item:getType(),item:getSubtype()).id - else - itemType = df.item_type[item:getType()] - end - return itemType -end - -function handler(table) - local itemMat = dfhack.matinfo.decode(table.item) - local itemMatStr = itemMat:getToken() - local itemType = getitemType(table.item) - table.itemMat = itemMat - table.itemType = itemType - - for _,command in ipairs(itemTriggers[itemType] or {}) do - if command[table.mode] then - utils.fillTable(command,table) - processTrigger(command) - utils.unfillTable(command,table) - end - end - - for _,command in ipairs(materialTriggers[itemMatStr] or {}) do - if command[table.mode] then - utils.fillTable(command,table) - processTrigger(command) - utils.unfillTable(command,table) - end - end - - for _,contaminant in ipairs(table.item.contaminants or {}) do - local contaminantMat = dfhack.matinfo.decode(contaminant.mat_type, contaminant.mat_index) - local contaminantStr = contaminantMat:getToken() - table.contaminantMat = contaminantMat - for _,command in ipairs(contaminantTriggers[contaminantStr] or {}) do - utils.fillTable(command,table) - processTrigger(command) - utils.unfillTable(command,table) - end - table.contaminantMat = nil - end -end - -function equipHandler(unit, item, isEquip) - local mode = (isEquip and 'onEquip') or (not isEquip and 'onUnequip') - - local table = {} - table.mode = mode - table.item = df.item.find(item) - table.unit = df.unit.find(unit) - handler(table) -end - -eventful.onInventoryChange.equipmentTrigger = function(unit, item, item_old, item_new) - if item_old and item_new then - return - end - - local isEquip = item_new and not item_old - equipHandler(unit,item,isEquip) -end - -eventful.onUnitAttack.attackTrigger = function(attacker,defender,wound) - attacker = df.unit.find(attacker) - defender = df.unit.find(defender) - - if not attacker then - return - end - - local attackerWeapon - for _,item in ipairs(attacker.inventory) do - if item.mode == df.unit_inventory_item.T_mode.Weapon then - attackerWeapon = item.item - break - end - end - - if not attackerWeapon then - return - end - - local table = {} - table.attacker = attacker - table.defender = defender - table.item = attackerWeapon - table.mode = 'onStrike' - handler(table) -end - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'checkAttackEvery', - 'checkInventoryEvery', - 'command', - 'itemType', - 'onStrike', - 'onEquip', - 'onUnequip', - 'material', - 'contaminant', -}) -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/item-trigger.lua usage -arguments: - -help - print this help message - -clear - clear all registered triggers - -checkAttackEvery n - check the attack event at least every n ticks - -checkInventoryEvery n - check inventory event at least every n ticks - -itemType type - trigger the command for items of this type - examples: - ITEM_WEAPON_PICK - RING - -onStrike - trigger the command when someone strikes someone with an appropriate weapon - -onEquip - trigger the command when someone equips an appropriate item - -onUnequip - trigger the command when someone unequips an appropriate item - -material mat - trigger the commmand on items with the given material - examples - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -contaminant mat - trigger the command on items with a given material contaminant - examples - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -command [ commandStrs ] - specify the command to be executed - commandStrs - \\ATTACKER_ID - \\DEFENDER_ID - \\ITEM_MATERIAL - \\ITEM_MATERIAL_TYPE - \\ITEM_ID - \\ITEM_TYPE - \\CONTAMINANT_MATERIAL - \\CONTAMINANT_MATERIAL_TYPE - \\CONTAMINANT_MATERIAL_INDEX - \\MODE - \\UNIT_ID - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - itemTriggers = {} - materialTriggers = {} - contaminantTriggers = {} -end - -if args.checkAttackEvery then - if not tonumber(args.checkAttackEvery) then - error('checkAttackEvery must be a number') - end - eventful.enableEvent(eventful.eventType.UNIT_ATTACK,tonumber(args.checkAttackEvery)) -end - -if args.checkInventoryEvery then - if not tonumber(args.checkInventoryEvery) then - error('checkInventoryEvery must be a number') - end - eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,tonumber(args.checkInventoryEvery)) -end - -if not args.command then - if not args.clear then - error 'specify a command' - end - return -end - -if args.itemType then - if dfhack.items.findType(args.itemType) == -1 then - local temp - for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do - if itemdef.id == args.itemType then - temp = args.itemType --itemdef.subtype - break - end - end - if not temp then - error 'Could not find item type.' - end - args.itemType = temp - end -end - -local numConditions = (args.material and 1 or 0) + (args.itemType and 1 or 0) + (args.contaminant and 1 or 0) -if numConditions > 1 then - error 'too many conditions defined: not (yet) supported (pester expwnent if you want it)' -elseif numConditions == 0 then - error 'specify a material, weaponType, or contaminant' -end - -if args.material then - if not materialTriggers[args.material] then - materialTriggers[args.material] = {} - end - table.insert(materialTriggers[args.material],args) -elseif args.itemType then - if not itemTriggers[args.itemType] then - itemTriggers[args.itemType] = {} - end - table.insert(itemTriggers[args.itemType],args) -elseif args.contaminant then - if not contaminantTriggers[args.contaminant] then - contaminantTriggers[args.contaminant] = {} - end - table.insert(contaminantTriggers[args.contaminant],args) -end - diff --git a/scripts/modtools/moddable-gods.lua b/scripts/modtools/moddable-gods.lua deleted file mode 100644 index 9445af7a9..000000000 --- a/scripts/modtools/moddable-gods.lua +++ /dev/null @@ -1,102 +0,0 @@ --- Create gods from the command-line ---based on moddableGods by Putnam ---edited by expwnent ---[[=begin - -modtools/moddable-gods -====================== -This is a standardized version of Putnam's moddableGods script. It allows you -to create gods on the command-line. - -=end]] -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'name', - 'spheres', - 'gender', - 'depictedAs', - 'domain', - 'description', --- 'entities', -}) -local args = utils.processArgs({...}) - -if args.help then - print([[scripts/modtools/moddable-gods.lua -arguments: - -help - print this help message - -name godName - sets the name of the god to godName - if there's already a god of that name, the script halts - -spheres [ sphereList ] - define a space-separated list of spheres of influence of the god - -depictedAs str - often depicted as a str - -domain str - set the domain of the god - -description str - set the description of the god -]]) - return -end - -if not args.name or not args.depictedAs or not args.domain or not args.description or not args.spheres or not args.gender then - error('All arguments must be specified.') -end - -local templateGod -for _,fig in ipairs(df.global.world.history.figures) do - if fig.flags.deity then - templateGod = fig - break - end -end -if not templateGod then - error 'Could not find template god.' -end - -if args.gender == 'male' then - args.gender = 1 -elseif args.gender == 'female' then - args.gender = 0 -else - error 'invalid gender' -end - -for _,fig in ipairs(df.global.world.history.figures) do - if fig.name.first_name == args.name then - print('god ' .. args.name .. ' already exists. Skipping') - return - end -end - -local godFig = df.historical_figure:new() -godFig.appeared_year = -1 -godFig.born_year = -1 -godFig.born_seconds = -1 -godFig.curse_year = -1 -godFig.curse_seconds = -1 -godFig.old_year = -1 -godFig.old_seconds = -1 -godFig.died_year = -1 -godFig.died_seconds = -1 -godFig.name.has_name = true -godFig.breed_id = -1 -godFig.flags:assign(templateGod.flags) -godFig.id = df.global.hist_figure_next_id -df.global.hist_figure_next_id = 1+df.global.hist_figure_next_id -godFig.info = df.historical_figure_info:new() -godFig.info.spheres = {new=true} -godFig.info.secret = df.historical_figure_info.T_secret:new() - -godFig.sex = args.gender -godFig.name.first_name = args.name -for _,sphere in ipairs(args.spheres) do - godFig.info.spheres:insert('#',df.sphere_type[sphere]) -end -df.global.world.history.figures:insert('#',godFig) - - diff --git a/scripts/modtools/outside-only.lua b/scripts/modtools/outside-only.lua deleted file mode 100644 index f4ed1414f..000000000 --- a/scripts/modtools/outside-only.lua +++ /dev/null @@ -1,141 +0,0 @@ --- enables outside only and inside only buildings ---author expwnent ---[[=begin - -modtools/outside-only -===================== -This allows you to specify certain custom buildings as outside only, or inside -only. If the player attempts to build a building in an inappropriate location, -the building will be destroyed. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -buildingType = buildingType or utils.invert({'EITHER','OUTSIDE_ONLY','INSIDE_ONLY'}) -registeredBuildings = registeredBuildings or {} -checkEvery = checkEvery or 100 -timeoutId = timeoutId or nil - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.outsideOnly = function() - registeredBuildings = {} - checkEvery = 100 - timeoutId = nil -end - -local function destroy(building) - if #building.jobs > 0 and building.jobs[0] and building.jobs[0].job_type == df.job_type.DestroyBuilding then - return - end - local b = dfhack.buildings.deconstruct(building) - if b then - --TODO: print an error message to the user so they know - return - end --- building.flags.almost_deleted = 1 -end - -local function checkBuildings() - local toDestroy = {} - local function forEach(building) - if building:getCustomType() < 0 then - --TODO: support builtin building types if someone wants - return - end - local pos = df.coord:new() - pos.x = building.centerx - pos.y = building.centery - pos.z = building.z - local outside = dfhack.maps.getTileBlock(pos).designation[pos.x%16][pos.y%16].outside - local def = df.global.world.raws.buildings.all[building:getCustomType()] - local btype = registeredBuildings[def.code] - if btype then --- print('outside: ' .. outside==true .. ', type: ' .. btype) - end - - if not btype or btype == buildingType.EITHER then - registeredBuildings[def.code] = nil - return - elseif btype == buildingType.OUTSIDE_ONLY then - if outside then - return - end - else - if not outside then - return - end - end - table.insert(toDestroy,building) - end - for _,building in ipairs(df.global.world.buildings.all) do - forEach(building) - end - for _,building in ipairs(toDestroy) do - destroy(building) - end - if timeoutId then - dfhack.timeout_active(timeoutId,nil) - timeoutId = nil - end - timeoutId = dfhack.timeout(checkEvery, 'ticks', checkBuildings) -end - -eventful.enableEvent(eventful.eventType.BUILDING, 100) -eventful.onBuildingCreatedDestroyed.outsideOnly = function(buildingId) - checkBuildings() -end - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'checkEvery', - 'building', - 'type' -}) -local args = utils.processArgs({...}, validArgs) -if args.help then - print([[scripts/modtools/outside-only -arguments - -help - print this help message - -clear - clears the list of registered buildings - -checkEvery n - set how often existing buildings are checked for whether they are in the appropriate location to n ticks - -type [EITHER, OUTSIDE_ONLY, INSIDE_ONLY] - specify what sort of restriction to put on the building - -building name - specify the id of the building -]]) - return -end - -if args.clear then - registeredBuildings = {} -end - -if args.checkEvery then - if not tonumber(args.checkEvery) then - error('Invalid checkEvery.') - end - checkEvery = tonumber(args.checkEvery) -end - -if not args.building then - return -end - -if not args['type'] then - print 'outside-only: please specify type' - return -end - -if not buildingType[args['type']] then - error('Invalid building type: ' .. args['type']) -end - -registeredBuildings[args.building] = buildingType[args['type']] - -checkBuildings() - diff --git a/scripts/modtools/projectile-trigger.lua b/scripts/modtools/projectile-trigger.lua deleted file mode 100644 index 8e6227e0a..000000000 --- a/scripts/modtools/projectile-trigger.lua +++ /dev/null @@ -1,108 +0,0 @@ --- trigger commands when projectiles hit targets ---author expwnent ---based on Putnam's projectileExpansion ---TODO: trigger based on contaminants ---[[=begin - -modtools/projectile-trigger -=========================== -This triggers dfhack commands when projectiles hit their targets. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -materialTriggers = materialTriggers or {} - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.projectileTrigger = function() - materialTriggers = {} -end - -function processTrigger(args) - local command2 = {} - for _,arg in ipairs(args.command) do - if arg == '\\LOCATION' then - table.insert(command2,args.pos.x) - table.insert(command2,args.pos.y) - table.insert(command2,args.pos.z) - elseif arg == '\\PROJECTILE_ID' then - table.insert(command2,args.projectile.id) - elseif arg == '\\FIRER_ID' then - table.insert(command2,args.projectile.firer.id) - elseif string.sub(arg,1,1) == '\\' then - table.insert(command2,string.sub(arg,2)) - else - table.insert(command2,arg) - end - end - dfhack.run_command(table.unpack(command2)) -end - -eventful.onProjItemCheckImpact.expansion = function(projectile) - local matStr = dfhack.matinfo.decode(projectile.item):getToken() - local table = {} - table.pos = projectile.cur_pos - table.projectile = projectile - table.item = projectile.item - for _,args in ipairs(materialTriggers[matStr] or {}) do - utils.fillTable(args,table) - processTrigger(args) - utils.unfillTable(args,table) - end -end - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'command', - 'material', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/projectile-trigger.lua -arguments - -help - print this help message - -clear - unregister all triggers - -material - specify a material for projectiles that will trigger the command - examples: - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -command [ commandList ] - \\LOCATION - \\PROJECTILE_ID - \\FIRER_ID - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - materialTriggers = {} -end - -if not args.command then - return -end - -if not args.material then - error 'specify a material' -end - -if not dfhack.matinfo.find(args.material) then - error ('invalid material: ' .. args.material) -end - -if not materialTriggers[args.material] then - materialTriggers[args.material] = {} -end -table.insert(materialTriggers[args.material], args) - - diff --git a/scripts/modtools/random-trigger.lua b/scripts/modtools/random-trigger.lua deleted file mode 100644 index b234b85ef..000000000 --- a/scripts/modtools/random-trigger.lua +++ /dev/null @@ -1,179 +0,0 @@ --- triggers random scripts ---[[=begin - -modtools/random-trigger -======================= -This triggers random dfhack commands with specified probabilities. -Register a few scripts, then tell it to "go" and it will pick one -based on the probability weights you specified. Outcomes are mutually -exclusive. To make independent random events, call the script multiple -times. - -=end]] -local utils = require 'utils' -local eventful = require 'plugins.eventful' - -outcomeLists = outcomeLists or {} -randomGen = randomGen or dfhack.random.new() - -eventful.enableEvent(eventful.eventType.UNLOAD, 1) -eventful.onUnload.randomTrigger = function() - outcomeLists = {} -end - -validArgs = validArgs or utils.invert({ - 'help', - 'command', - 'outcomeListName', - 'weight', - 'seed', - 'trigger', - 'preserveList', - 'withProbability', - 'listOutcomes', - 'clear', -}) - -local function triggerEvent(outcomeListName) - local outcomeList = outcomeLists[outcomeListName] - local r = randomGen:random(outcomeList.total) - local sum = 0 - --print ('r = ' .. r) - for i,outcome in ipairs(outcomeList.outcomes) do - sum = sum + outcome.weight - if sum > r then - local temp = outcome.command - --print('triggering outcome ' .. i .. ': "' .. table.concat(temp, ' ') .. '"') - --dfhack.run_command(table.unpack(temp)) - dfhack.run_script(table.unpack(temp)) - break - else - --print ('sum = ' .. sum .. ' <= r = ' .. r) - end - end - --print('Done.') - --dfhack.print('\n') -end - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/random-trigger.lua -Allows mutually-exclusive random events. Register a list of scripts along with positive integer relative weights, then tell the script to select one of them with the specified probabilities and run it. -The weights must be positive integers, but they do NOT have to sum to 100 or any other particular number. -The outcomes are mutually exclusive: only one will be triggered. -If you want multiple independent random events, call the script multiple times. -99% of the time, you won't need to worry about this, but just in case, you can specify a name of a list of outcomes to prevent interference from other scripts that call this one. -That also permits situations where you don't know until runtime what outcomes you want. -For example, you could make a reaction-trigger that registers the worker as a mayor candidate, then run this script to choose a random mayor from the list of units that did the mayor reaction. - -arguments: - -help - print this help message - -outcomeListName name - specify the name of this list of outcomes to prevent interference if two scripts are registering outcomes at the same time - if none is specified, the default outcome list is selected automatically - -command [ commandStrs ] - specify the command to be run if this outcome is selected - must be specified unless the -trigger argument is given - -weight n - the relative probability weight of this outcome - n must be a non-negative integer - if not specified, n=1 is used by default - -trigger - selects a random script based on the specified outcomeList (or the default one if none is specified) - -preserveList - when combined with trigger, preserves the list of outcomes so you don't have to register them again - it is extremely highly recommended that you always specify the outcome list name when you give this command to prevent almost certain interference - if you want to trigger one of 5 outcomes three times, you might want this option even without -outcomeListName - most of the time, you won't want this - will NOT be preserved after the user saves/loads (ask expwnent if you want this: it's not that hard but if nobody wants it I won't bother) - performance will be slightly faster if you preserve the outcome lists when possible and trigger them multiple times instead of reregistering each time, but the effect should be small - -withProbability p - p is a real number between 0 and 1 inclusive - triggers the command immediately with this probability - -seed s - sets the random seed (guarantees the same sequence of random numbers will be produced internally) - use for debugging purposes - -listOutcomes - lists the currently registered list of outcomes of the outcomeList along with their probability weights - use for debugging purposes - -clear - unregister everything -]]) - return -end - -if args.clear then - outcomeLists = {} -end - -if args.weight and not tonumber(args.weight) then - error ('Invalid weight: ' .. args.weight) -end -args.weight = (args.weight and tonumber(args.weight)) or 1 -if args.weight ~= math.floor(args.weight) then - error 'Noninteger weight.' -end -if args.weight < 0 then - error 'invalid weight: must be non-negative' -end - -if args.seed then - randomGen:init(tonumber(args.seed), 37) --37 is probably excessive and definitely arbitrary -end - -args.outcomeListName = args.outcomeListName or '' -args.outcomeListName = 'outcomeList ' .. args.outcomeListName - -if args.withProbability then - args.withProbability = tonumber(args.withProbability) - if not args.withProbability or args.withProbability < 0 or args.withProbability > 1 then - error('Invalid withProbability: ' .. (args.withProbability or 'nil')) - end - if randomGen:drandom() < args.withProbability then - dfhack.run_command(table.unpack(args.command)) - end -end - -if args.trigger then - triggerEvent(args.outcomeListName) - if not args.preserveList then - outcomeLists[args.outcomeListName] = nil - end - return -end - -if args.listOutcomes then - local outcomeList = outcomeLists[args.outcomeListName] - if not outcomeList then - print ('No outcomes registered.') - return - end - print ('Total weight: ' .. outcomeList.total) - for _,outcome in ipairs(outcomeList.outcomes) do - print(' outcome weight ' .. outcome.weight .. ': ' .. table.concat(outcome.command, ' ')) - end - print('\n') - return -end - -if not args.command then - return -end - ---actually register -local outcomeList = outcomeLists[args.outcomeListName] -if not outcomeList then - outcomeLists[args.outcomeListName] = {} - outcomeList = outcomeLists[args.outcomeListName] -end - -outcomeList.total = args.weight + (outcomeList.total or 0) -local outcome = {} -outcome.weight = args.weight -outcome.command = args.command -outcomeList.outcomes = outcomeList.outcomes or {} -table.insert(outcomeList.outcomes, outcome) - - diff --git a/scripts/modtools/reaction-product-trigger.lua b/scripts/modtools/reaction-product-trigger.lua deleted file mode 100644 index 69688ce2c..000000000 --- a/scripts/modtools/reaction-product-trigger.lua +++ /dev/null @@ -1,132 +0,0 @@ --- trigger commands before/after reactions produce items --- author expwnent ---@ module = true ---[[=begin - -modtools/reaction-product-trigger -================================= -This triggers dfhack commands when reaction products are produced, once per -product. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - ---TODO: onUnload -productHooks = productHooks or {} - -reactionInputItems = reactionInputItems - -function preserveReagents() - reactionInputItems:resize(0) -end - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.reactionProductTrigger = function() - productHooks = {} -end - ---productHooks.before = productHooks.before or {} ---productHooks.after = productHooks.after or {} - -local function processArgs(args, reaction, reaction_product, unit, input_items, input_reagents, output_items, buildingId) - local result = {} - for _,arg in ipairs(args) do - if arg == '\\WORKER_ID' then - table.insert(result,tostring(unit.id)) - elseif arg == '\\REACTION' then - table.insert(result,reaction.code) --- elseif arg == '\\REACTION_PRODUCT' then --- table.insert(result,reaction_product) - elseif arg == '\\INPUT_ITEMS' then - --table.insert(result,'[') - for _,item in ipairs(input_items) do - table.insert(result,tostring(item.id)) - end - --table.insert(result,']') - elseif arg == '\\OUTPUT_ITEMS' then - --table.insert(result,'[') - for _,item in ipairs(output_items) do - table.insert(result,tostring(item.id)) - end - --table.insert(result,']') - elseif arg == '\\BUILDING_ID' then - table.insert(result,tostring(buildingId)) - elseif string.sub(arg,1,1) == '\\' then - table.insert(result,string.sub(arg,2)) - else - table.insert(result,arg) - end - end - return result -end - -local function afterProduce(reaction,reaction_product,unit,input_items,input_reagents,output_items) - --printall(unit.job.current_job) - local _,buildingId = dfhack.script_environment('modtools/reaction-trigger').getWorkerAndBuilding(unit.job.current_job) - for _,hook in ipairs(productHooks[reaction.code] or {}) do - local command = hook.command - local processed = processArgs(command, reaction, reaction_product, unit, input_items, input_reagents, output_items, buildingId) - dfhack.run_command(table.unpack(processed)) - end -end - -eventful.onReactionComplete.reactionProductTrigger = function(reaction,reaction_product,unit,input_items,input_reagents,output_items) - reactionInputItems = input_items - afterProduce(reaction,reaction_product,unit,input_items,input_reagents,output_items) - reactionInputItems = nil -end - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'reactionName', - 'command', -}) - -if moduleMode then - return -end - -local args = {...} or {} -args = utils.processArgs(args, validArgs) - -if args.help then - print([[scripts/modtools/reaction-product-trigger.lua -arguments: - -help - print this help message - -clear - unregister all reaction hooks - -reactionName name - specify the name of the reaction - -command [ commandStrs ] - specify the command to be run on the target(s) - special args - \\WORKER_ID - \\REACTION - \\BUILDING_ID - \\LOCATION - \\INPUT_ITEMS - \\OUTPUT_ITEMS - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - productHooks = {} -end - -if not args.reactionName then - error('No reactionName.') -end - -if not args.command then - error('No command.') -end - -productHooks[args.reactionName] = productHooks[args.reactionName] or {} -table.insert(productHooks[args.reactionName], args) - diff --git a/scripts/modtools/reaction-trigger-transition.lua b/scripts/modtools/reaction-trigger-transition.lua deleted file mode 100644 index 3d0b7ee65..000000000 --- a/scripts/modtools/reaction-trigger-transition.lua +++ /dev/null @@ -1,147 +0,0 @@ --- help transition from autoSyndrome --- author expwnent ---[[=begin - -modtools/reaction-trigger-transition -==================================== -Scans raw files and creates a file to help modders transition from -autoSyndrome to reaction-trigger. - -Prints useful things to the console and a file to help modders -transition from autoSyndrome to reaction-trigger. This script -is basically an apology for breaking backward compatibiility, -and will be removed eventually. - -=end]] -local function maybeQuote(str) - if str == '' or string.find(str,' ') then - return ('"' .. str .. '"') - else - return str - end -end - -warnings = '' -output = '' -for _,reaction in ipairs(df.global.world.raws.reactions) do - local function foreachProduct(product) - local prodType = product:getType() - if prodType ~= df.reaction_product_type.item then - return - end - if product.item_type ~= df.item_type.BOULDER then - return - end - if product.mat_index < 0 then - return - end - local inorganic = df.global.world.raws.inorganics[product.mat_index] - local didInorganicName - for _,syndrome in ipairs(inorganic.material.syndrome) do - local workerOnly = true - local allowMultipleTargets = false; - local command - local commandStr - local destroyRock = true; - local foundAutoSyndrome = false; - local resetPolicy; - for i,synclass in ipairs(syndrome.syn_class) do - synclass = synclass.value - if false then - elseif synclass == '\\AUTO_SYNDROME' then - foundAutoSyndrome = true - elseif synclass == '\\ALLOW_NONWORKER_TARGETS' then - workerOnly = false - elseif synclass == '\\ALLOW_MULTIPLE_TARGETS' then - allowMultipleTargets = true - elseif synclass == '\\PRESERVE_ROCK' then - destroyRock = false - elseif synclass == '\\RESET_POLICY DoNothing' then - resetPolicy = 'DoNothing' - elseif synclass == '\\RESET_POLICY ResetDuration' then - resetPolicy = 'ResetDuration' - elseif synclass == '\\RESET_POLICY AddDuration' then - resetPolicy = 'AddDuration' - elseif synclass == '\\RESET_POLICY NewInstance' then - resetPolicy = 'NewInstance' - elseif synclass == '\\COMMAND' then - command = '' - elseif command then - if synclass == '\\LOCATION' then - command = command .. '\\LOCATION ' - elseif synclass == '\\WORKER_ID' then - command = command .. '\\WORKER_ID ' - elseif synclass == '\\REACTION_INDEX' then - warnings = warnings .. ('Warning: \\REACTION_INDEX is deprecated. Use \\REACTION_NAME instead.\n') - command = command .. '\\REACTION_NAME ' - else - commandStr = true - command = command .. maybeQuote(synclass) .. ' ' - end - end - end - if foundAutoSyndrome then - if destroyRock then - warnings = warnings .. ('Warning: instead of destroying the rock, do not produce it in the first place.\n') - end - if workerOnly then - workerOnly = 'true' - else - workerOnly = 'false' - end - if allowMultipleTargets then - allowMultipleTargets = 'true' - else - allowMultipleTargets = 'false' - end - local reactionTriggerStr = 'modtools/reaction-trigger -reactionName ' .. maybeQuote(reaction.code) --.. '"' - if workerOnly ~= 'true' then - reactionTriggerStr = reactionTriggerStr .. ' -workerOnly ' .. workerOnly - end - if allowMultipleTargets ~= 'false' then - reactionTriggerStr = reactionTriggerStr .. ' -allowMultipleTargets ' .. allowMultipleTargets - end - if resetPolicy and resetPolicy ~= 'NewInstance' then - reactionTriggerStr = reactionTriggerStr .. ' -resetPolicy ' .. resetPolicy - end - if #syndrome.ce > 0 then - if syndrome.syn_name == '' then - warnings = warnings .. ('Warning: give this syndrome a name!\n') - end - reactionTriggerStr = reactionTriggerStr .. ' -syndrome ' .. maybeQuote(syndrome.syn_name) .. '' - end - if command and commandStr then - reactionTriggerStr = reactionTriggerStr .. ' -command [ ' .. command .. ']' - end - if (not command or command == '') and (not syndrome.syn_name or syndrome.syn_name == '') then - --output = output .. '#' - else - if not didInorganicName then --- output = output .. '# ' .. (inorganic.id) .. '\n' - didInorganicName = true - end - output = output .. (reactionTriggerStr) .. '\n' - end - end - end - end - for _,product in ipairs(reaction.products) do - foreachProduct(product) - end -end - -print(warnings) -print('\n\n\n\n') -print(output) -local file = io.open('reaction-trigger-transition.txt', 'w+') ---io.output(file) ---file:write(warnings) ---file:write('\n\n\n\n') -file:write(output) -file:flush() ---io.flush(file) -io.close(file) ---io.output() -print('transition information written to reaction-trigger-transition.txt') - - diff --git a/scripts/modtools/reaction-trigger.lua b/scripts/modtools/reaction-trigger.lua deleted file mode 100644 index 9589a9c4c..000000000 --- a/scripts/modtools/reaction-trigger.lua +++ /dev/null @@ -1,241 +0,0 @@ --- trigger commands when custom reactions complete --- author expwnent --- replaces autoSyndrome ---@ module = true ---[[=begin - -modtools/reaction-trigger -========================= -Triggers dfhack commands when custom reactions complete, regardless of whether -it produced anything, once per completion. Use the ``-help`` command -for more information. - -=end]] -local eventful = require 'plugins.eventful' -local syndromeUtil = require 'syndrome-util' -local utils = require 'utils' - -reactionHooks = reactionHooks or {} - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.reactionTrigger = function() - reactionHooks = {} -end - -function getWorkerAndBuilding(job) - local workerId = -1 - local buildingId = -1 - for _,generalRef in ipairs(job.general_refs) do - if generalRef:getType() == df.general_ref_type.UNIT_WORKER then - if workerId ~= -1 then - print(job) - printall(job) - error('reaction-trigger: two workers on same job: ' .. workerId .. ', ' .. generalRef.unit_id) - else - workerId = generalRef.unit_id - if workerId == -1 then - print(job) - printall(job) - error('reaction-trigger: invalid worker') - end - end - elseif generalRef:getType() == df.general_ref_type.BUILDING_HOLDER then - if buildingId ~= -1 then - print(job) - printall(job) - error('reaction-trigger: two buildings same job: ' .. buildingId .. ', ' .. generalRef.building_id) - else - buildingId = generalRef.building_id - if buildingId == -1 then - print(job) - printall(job) - error('reaction-trigger: invalid building') - end - end - end - end - return workerId,buildingId -end - -local function processCommand(job, worker, target, building, command) - local result = {} - for _,arg in ipairs(command) do - if arg == '\\WORKER_ID' then - table.insert(result,''..worker.id) - elseif arg == '\\TARGET_ID' then - table.insert(result,''..target.id) - elseif arg == '\\BUILDING_ID' then - table.insert(result,''..building.id) - elseif arg == '\\LOCATION' then - table.insert(result,''..job.pos.x) - table.insert(result,''..job.pos.y) - table.insert(result,''..job.pos.z) - elseif arg == '\\REACTION_NAME' then - table.insert(result,''..job.reaction_name) - elseif string.sub(arg,1,1) == '\\' then - table.insert(result,string.sub(arg,2)) - else - table.insert(result,arg) - end - end - return result -end - -eventful.onJobCompleted.reactionTrigger = function(job) - if job.completion_timer > 0 then - return - end - --- if job.job_type ~= df.job_type.CustomReaction then --- --TODO: support builtin reaction triggers if someone asks --- return --- end - - if not job.reaction_name or job.reaction_name == '' then - return - end --- print('reaction name: ' .. job.reaction_name) - if not job.reaction_name or not reactionHooks[job.reaction_name] then - return - end - - local worker,building = getWorkerAndBuilding(job) - worker = df.unit.find(worker) - building = df.building.find(building) - if not worker or not building then - --this probably means that it finished before EventManager could get a copy of the job while the job was running - --TODO: consider printing a warning once - return - end - - local function doAction(action) - local didSomething - if action.command then - local processed = processCommand(job, worker, worker, building, action.command) - dfhack.run_command(table.unpack(processed)) - end - if action.syndrome then - didSomething = syndromeUtil.infectWithSyndromeIfValidTarget(worker, action.syndrome, action.resetPolicy) or didSomething - end - if action.workerOnly then - return - end - if didSomething and not action.allowMultipleTargets then - return - end - local function foreach(unit) - if unit == worker then - return false - elseif unit.pos.z ~= building.z then - return false - elseif unit.pos.x < building.x1 or unit.pos.x > building.x2 then - return false - elseif unit.pos.y < building.y1 or unit.pos.y > building.y2 then - return false - else - if action.command then - processCommand(job, worker, unit, building, action.command) - end - if action.syndrome then - didSomething = syndrome.infectWithSyndromeIfValidTarget(unit,action.syndrome,action.resetPolicy) or didSomething - end - if didSomething and not action.allowMultipleTargets then - return true - end - return false - end - end - for _,unit in ipairs(df.global.world.units.all) do - if foreach(unit) then - break - end - end - end - for _,action in ipairs(reactionHooks[job.reaction_name]) do - doAction(action) - end -end -eventful.enableEvent(eventful.eventType.JOB_COMPLETED,0) --0 is necessary to catch cancelled jobs and not trigger them - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'reactionName', - 'syndrome', - 'command', - 'allowNonworkerTargets', - 'allowMultipleTargets' -}) - -if moduleMode then - return -end -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/reaction-trigger.lua -arguments: - -help - print this help message - -clear - unregister all reaction hooks - -reactionName name - specify the name of the reaction - -syndrome name - specify the name of the syndrome to be applied to the targets - -allowNonworkerTargets - allow other units in the same building to be targetted by either the script or the syndrome - -allowMultipleTargets - allow multiple targets to the script or syndrome - if absent: - if running a script, only one target will be used - if applying a syndrome, then only one target will be infected - -resetPolicy policy - set the reset policy in the case that the syndrome is already present - policy - NewInstance (default) - DoNothing - ResetDuration - AddDuration - -command [ commandStrs ] - specify the command to be run on the target(s) - special args - \\WORKER_ID - \\TARGET_ID - \\BUILDING_ID - \\LOCATION - \\REACTION_NAME - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - reactionHooks = {} -end - -if not args.reactionName then - return -end - -if not reactionHooks[args.reactionName] then - reactionHooks[args.reactionName] = {} -end - -if args.syndrome then - local foundIt - for _,syndrome in ipairs(df.global.world.raws.syndromes.all) do - if syndrome.syn_name == args.syndrome then - args.syndrome = syndrome - foundIt = true - break - end - end - if not foundIt then - error('Could not find syndrome ' .. args.syndrome) - end -end - -table.insert(reactionHooks[args.reactionName], args) - diff --git a/scripts/modtools/skill-change.lua b/scripts/modtools/skill-change.lua deleted file mode 100644 index c5cd52889..000000000 --- a/scripts/modtools/skill-change.lua +++ /dev/null @@ -1,115 +0,0 @@ --- Sets or modifies a skill of a unit ---author expwnent ---based on skillChange.lua by Putnam ---TODO: update skill level once experience increases/decreases ---TODO: skill rust? ---[[=begin - -modtools/skill-change -===================== -Sets or modifies a skill of a unit. Args: - -:-help: print the help message -:-skill skillName: set the skill that we're talking about -:-mode (add/set): are we adding experience/levels or setting them? -:-granularity (experience/level): - direct experience, or experience levels? -:-unit id: id of the target unit -:-value amount: how much to set/add - -=end]] -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'skill', - 'mode', - 'value', - 'granularity', - 'unit' -}) - -mode = mode or utils.invert({ - 'add', - 'set', -}) - -granularity = granularity or utils.invert({ - 'experience', - 'level', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/skill-change.lua -arguments - -help - print this help message - -skill skillName - set the skill that we're talking about - -mode (add/set) - are we adding experience/levels or setting them? - -granularity (experience/level) - direct experience, or experience levels? - -unit id - id of the target unit - -value amount - how much to set/add -]]) - return -end - -if not args.unit or not tonumber(args.unit) or not df.unit.find(tonumber(args.unit)) then - error 'Invalid unit.' -end -args.unit = df.unit.find(tonumber(args.unit)) - -args.skill = df.job_skill[args.skill] -args.mode = mode[args.mode or 'set'] -args.granularity = granularity[args.granularity or 'level'] -args.value = tonumber(args.value) - -if not args.skill then - error('invalid skill') -end -if not args.value then - error('invalid value') -end - -local skill -for _,skill_c in ipairs(args.unit.status.current_soul.skills) do - if skill_c.id == args.skill then - skill = skill_c - end -end - -if not skill then - skill = df.unit_skill:new() - skill.id = args.skill - utils.insert_sorted(args.unit.status.current_soul.skills,skill,'id') -end - -print('old: ' .. skill.rating .. ': ' .. skill.experience) -if args.granularity == granularity.experience then - if args.mode == mode.set then - skill.experience = args.value - elseif args.mode == mode.add then - skill.experience = skill.experience + args.value - else - error 'bad mode' - end -elseif args.granularity == granularity.level then - if args.mode == mode.set then - skill.rating = args.value - elseif args.mode == mode.add then - skill.rating = args.value + skill.rating - else - error 'bad mode' - end -else - error 'bad granularity' -end - -print('new: ' .. skill.rating .. ': ' .. skill.experience) - diff --git a/scripts/modtools/spawn-flow.lua b/scripts/modtools/spawn-flow.lua deleted file mode 100644 index 6bca9ace4..000000000 --- a/scripts/modtools/spawn-flow.lua +++ /dev/null @@ -1,91 +0,0 @@ --- spawns flows at locations ---author expwnent ---[[=begin - -modtools/spawn-flow -=================== -Creates flows at the specified location. - -=end]] -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'material', - 'flowType', - 'location', - 'flowSize', -}) -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/spawn-flow.lua -arguments: - -help - print this help message - -material mat - specify the material of the flow, if applicable - examples: - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -location [ x y z] - the location to spawn the flow - -flowType type - specify the flow type - examples: - Miasma - Steam - Mist - MaterialDust - MagmaMist - Smoke - Dragonfire - Fire - Web - MaterialGas - MaterialVapor - OceanWave - SeaFoam - -flowSize size - specify how big the flow is -]]) - return -end - -local mat_index = -1; -local mat_type = -1; -if args.material then - local mat = dfhack.matinfo.find(args.material) - if not mat then - error ('Invalid material: ' .. mat) - end - mat_index = mat.index - mat_type = mat['type'] -end - -if args.flowSize and not tonumber(args.flowSize) then - error ('Invalid flow size: ' .. args.flowSize) -end - -args.flowSize = tonumber(args.flowSize or 'z') or 100 - -if not args.flowType or not df.flow_type[args.flowType] then - error ('Invalid flow type: ' .. (args.flowType or 'none specified')) -end -args.flowType = df.flow_type[args.flowType] - -if not args.location then - error 'Specify a location.' -end - -local pos = df.coord:new(); -pos.x = tonumber(args.location[1] or 'a') -pos.y = tonumber(args.location[2] or 'a') -pos.z = tonumber(args.location[3] or 'a') -if not pos.x or not pos.y or not pos.z then - error ('Invalid pos.') -end - -dfhack.maps.spawnFlow(pos, args.flowType, mat_type, mat_index, args.flowSize) - diff --git a/scripts/modtools/syndrome-trigger.lua b/scripts/modtools/syndrome-trigger.lua deleted file mode 100644 index f46bbc5ad..000000000 --- a/scripts/modtools/syndrome-trigger.lua +++ /dev/null @@ -1,122 +0,0 @@ --- triggers scripts when a syndrome is applied ---author expwnent ---[[=begin - -modtools/syndrome-trigger -========================= -Triggers dfhack commands when syndromes are applied to units. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -onInfection = onInfection or {} - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.syndromeTrigger = function() - onInfection = {} -end - -eventful.enableEvent(eventful.eventType.SYNDROME,5) --requires iterating through every unit, so not cheap, but not slow either - -local function processTrigger(args) - local command = {} - for i,arg in ipairs(args.command) do - if arg == '\\SYNDROME_ID' then - table.insert(command, '' .. args.syndrome.id) - elseif arg == '\\UNIT_ID' then - table.insert(command, '' .. args.unit.id) - elseif arg == '\\LOCATION' then - table.insert(command, '' .. args.unit.pos.x) - table.insert(command, '' .. args.unit.pos.y) - table.insert(command, '' .. args.unit.pos.z) - elseif string.sub(arg,1,1) == '\\' then - table.insert(command, string.sub(arg,2)) - else - table.insert(command, arg) - end - end - dfhack.run_command(table.unpack(command)) -end - -eventful.onSyndrome.syndromeTrigger = function(unitId, syndromeIndex) - local unit = df.unit.find(unitId) - local unit_syndrome = unit.syndromes.active[syndromeIndex] - local syn_id = unit_syndrome['type'] - if not onInfection[syn_id] then - return - end - local syndrome = df.syndrome.find(syn_id) - local table = {} - table.unit = unit - table.unit_syndrome = unit_syndrome - table.syndrome = syndrome - for _,args in ipairs(onInfection[syn_id] or {}) do - utils.fillTable(args,table) - processTrigger(args) - utils.unfillTable(args,table) - end -end - ------------------------------- ---argument processing - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'command', - 'syndrome' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/syndrome-trigger.lua -arguments - -help - print this help message - -clear - clear all triggers - -syndrome name - specify the name of a syndrome - -command [ commandStrs ] - specify the command to be executed after infection - args - \\SYNDROME_ID - \\UNIT_ID - \\LOCATION - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - onInfection = {} -end - -if not args.command then - return -end - -if not args.syndrome then - error 'Select a syndrome.' -end - -local syndrome -for _,syn in ipairs(df.global.world.raws.syndromes.all) do - if syn.syn_name == args.syndrome then - if syndrome then - error ('Multiple syndromes with same name: ' .. syn.syn_name) - end - syndrome = syn.id - end -end - -if not syndrome then - error ('Could not find syndrome named ' .. args.syndrome) -end - -onInfection[syndrome] = onInfection[syndrome] or {} -table.insert(onInfection[syndrome], args) - diff --git a/scripts/modtools/transform-unit.lua b/scripts/modtools/transform-unit.lua deleted file mode 100644 index 2ebdc3c8a..000000000 --- a/scripts/modtools/transform-unit.lua +++ /dev/null @@ -1,164 +0,0 @@ --- Transforms a unit into another unit type ---author expwnent ---based on shapechange by Putnam ---[[=begin - -modtools/transform-unit -======================= -Transforms a unit into another unit type, possibly permanently. -Warning: this will crash arena mode if you view the unit on the -same tick that it transforms. If you wait until later, it will be fine. - -=end]] -local utils = require 'utils' - -normalRace = normalRace or {} - -local function transform(unit,race,caste) - unit.enemy.normal_race = race - unit.enemy.normal_caste = caste - unit.enemy.were_race = race - unit.enemy.were_caste = caste -end - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'unit', - 'duration', - 'setPrevRace', - 'keepInventory', - 'race', - 'caste', - 'suppressAnnouncement', - 'untransform', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/transform-unit.lua -arguments - -help - print this help message - -clear - clear records of normal races - -unit id - set the target unit - -duration ticks - how long it should last, or "forever" - -setPrevRace - make a record of the previous race so that you can change it back with -untransform - -keepInventory - move items back into inventory after transformation - -race raceName - -caste casteName - -suppressAnnouncement - don't show the Unit has transformed into a Blah! event - -untransform - turn the unit back into what it was before -]]) - return -end - -if args.clear then - normalRace = {} -end - -if not args.unit then - error 'Specify a unit.' -end - -if not args.duration then - args.duration = 'forever' -end - -local raceIndex -local race -local caste -if args.untransform then - local unit = df.unit.find(tonumber(args.unit)) - raceIndex = normalRace[args.unit].race - race = df.creature_raw.find(raceIndex) - caste = normalRace[args.unit].caste - normalRace[args.unit] = nil -else - if not args.race or not args.caste then - error 'Specficy a target form.' - end - - --find race - for i,v in ipairs(df.global.world.raws.creatures.all) do - if v.creature_id == args.race then - raceIndex = i - race = v - break - end - end - - if not race then - error 'Invalid race.' - end - - for i,v in ipairs(race.caste) do - if v.caste_id == args.caste then - caste = i - break - end - end - - if not caste then - error 'Invalid caste.' - end -end - -local unit = df.unit.find(tonumber(args.unit)) -local oldRace = unit.enemy.normal_race -local oldCaste = unit.enemy.normal_caste -if args.setPrevRace then - normalRace[args.unit] = {} - normalRace[args.unit].race = oldRace - normalRace[args.unit].caste = oldCaste -end -transform(unit,raceIndex,caste,args.setPrevRace) - -local inventoryItems = {} - -local function getInventory() - local result = {} - for _,item in ipairs(unit.inventory) do - table.insert(result, item:new()); - end - return result -end - -local function restoreInventory() - dfhack.timeout(1, 'ticks', function() - for _,item in ipairs(inventoryItems) do - dfhack.items.moveToInventory(item.item, unit, item.mode, item.body_part_id) - item:delete() - end - inventoryItems = {} - end) -end - -if args.keepInventory then - inventoryItems = getInventory() -end - -if args.keepInventory then - restoreInventory() -end -if args.duration and args.duration ~= 'forever' then - --when the timeout ticks down, transform them back - dfhack.timeout(tonumber(args.duration), 'ticks', function() - if args.keepInventory then - inventoryItems = getInventory() - end - transform(unit,oldRace,oldCaste) - if args.keepInventory then - restoreInventory() - end - end) -end - diff --git a/scripts/multicmd.rb b/scripts/multicmd.rb deleted file mode 100644 index 1481cc52c..000000000 --- a/scripts/multicmd.rb +++ /dev/null @@ -1,16 +0,0 @@ -# run many dfhack commands separated by ; -=begin - -multicmd -======== -Run multiple dfhack commands. The argument is split around the -character ; and all parts are run sequentially as independent -dfhack commands. Useful for hotkeys. - -Example:: - - multicmd locate-ore IRON ; digv ; digcircle 16 - -=end - -$script_args.join(' ').split(/\s*;\s*/).each { |cmd| df.dfhack_run cmd } diff --git a/scripts/points.lua b/scripts/points.lua deleted file mode 100644 index b81d4637b..000000000 --- a/scripts/points.lua +++ /dev/null @@ -1,13 +0,0 @@ --- Set available points at the embark screen --- http://www.bay12forums.com/smf/index.php?topic=135506.msg4925005#msg4925005 ---[[=begin - -points -====== -Sets available points at the embark screen to the specified number. Eg. -``points 1000000`` would allow you to buy everything, or ``points 0`` would -make life quite difficult. - -=end]] - -df.global.world.worldgen.worldgen_parms.embark_points=tonumber(...) diff --git a/scripts/position.lua b/scripts/position.lua deleted file mode 100644 index 2ff44c14a..000000000 --- a/scripts/position.lua +++ /dev/null @@ -1,51 +0,0 @@ ---prints current time and position ---[[=begin - -position -======== -Reports the current time: date, clock time, month, and season. Also reports -location: z-level, cursor position, window size, and mouse location. - -=end]] - -local months = { - 'Granite, in early Spring.', - 'Slate, in mid Spring.', - 'Felsite, in late Spring.', - 'Hematite, in early Summer.', - 'Malachite, in mid Summer.', - 'Galena, in late Summer.', - 'Limestone, in early Autumn.', - 'Sandstone, in mid Autumn.', - 'Timber, in late Autumn.', - 'Moonstone, in early Winter.', - 'Opal, in mid Winter.', - 'Obsidian, in late Winter.', -} - ---Fortress mode counts 1200 ticks per day and 403200 per year ---Adventurer mode counts 86400 ticks to a day and 29030400 ticks per year ---Twelve months per year, 28 days to every month, 336 days per year - -local julian_day = math.floor(df.global.cur_year_tick / 1200) + 1 -local month = math.floor(julian_day / 28) + 1 --days and months are 1-indexed -local day = julian_day % 28 - -local time_of_day = math.floor(df.global.cur_year_tick_advmode / 336) -local second = time_of_day % 60 -local minute = math.floor(time_of_day / 60) % 60 -local hour = math.floor(time_of_day / 3600) % 24 - -print('Time:') -print(' The time is '..string.format('%02d:%02d:%02d', hour, minute, second)) -print(' The date is '..string.format('%05d-%02d-%02d', df.global.cur_year, month, day)) -print(' It is the month of '..months[month]) ---TODO: print(' It is the Age of '..age_name) - -print('Place:') -print(' The z-level is z='..df.global.window_z) -print(' The cursor is at x='..df.global.cursor.x..', y='..df.global.cursor.y) -print(' The window is '..df.global.gps.dimx..' tiles wide and '..df.global.gps.dimy..' tiles high') -if df.global.gps.mouse_x == -1 then print(' The mouse is not in the DF window') else -print(' The mouse is at x='..df.global.gps.mouse_x..', y='..df.global.gps.mouse_y..' within the window') end ---TODO: print(' The fortress is at '..x, y..' on the world map ('..worldsize..' square)') diff --git a/scripts/pref-adjust.lua b/scripts/pref-adjust.lua deleted file mode 100644 index a43ee8712..000000000 --- a/scripts/pref-adjust.lua +++ /dev/null @@ -1,106 +0,0 @@ --- Adjust all preferences of all dwarves in play --- by vjek ---[[=begin - -pref-adjust -=========== -A two-stage script: ``pref-adjust clear`` removes preferences from all dwarves, -and ``pref-adjust`` inserts an 'ideal' set which is easy to satisfy:: - - Feb Idashzefon likes wild strawberries for their vivid red color, - fisher berries for their round shape, prickle berries for their - precise thorns, plump helmets for their rounded tops, prepared meals, - plants, drinks, doors, thrones, tables and beds. When possible, she - prefers to consume wild strawberries, fisher berries, prickle - berries, plump helmets, strawberry wine, fisher berry wine, prickle - berry wine, and dwarven wine. - -=end]] - --- --------------------------------------------------------------------------- -function brainwash_unit(unit) - - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local pss_counter=31415926 - - local prefcount = #(unit.status.current_soul.preferences) - print ("Before, unit "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." has "..prefcount.." preferences") - - utils = require 'utils' - -- below populates an array with all creature names and id's, used for 'detests...' - rtbl={} - vec=df.global.world.raws.creatures.all - for k=0,#vec-1 do - local name=vec[k].creature_id - rtbl[name]=k - end - - -- Now iterate through for the type 3 detests... - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.TROLL , creature_id = rtbl.TROLL , color_id = rtbl.TROLL , shape_id = rtbl.TROLL , plant_id = rtbl.TROLL , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.BIRD_BUZZARD , creature_id = rtbl.BIRD_BUZZARD , color_id = rtbl.BIRD_BUZZARD , shape_id = rtbl.BIRD_BUZZARD , plant_id = rtbl.BIRD_BUZZARD , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.BIRD_VULTURE , creature_id = rtbl.BIRD_VULTURE , color_id = rtbl.BIRD_VULTURE , shape_id = rtbl.BIRD_VULTURE , plant_id = rtbl.BIRD_VULTURE , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.CRUNDLE , creature_id = rtbl.CRUNDLE , color_id = rtbl.CRUNDLE , shape_id = rtbl.CRUNDLE , plant_id = rtbl.CRUNDLE , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- and the type 4 likes - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 4 , item_type = df.item_type.WEAPON , creature_id = df.item_type.WEAPON , color_id = df.item_type.WEAPON , shape_id = df.item_type.WEAPON , plant_id = df.item_type.WEAPON , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 4 , item_type = df.item_type.ARMOR , creature_id = df.item_type.ARMOR , color_id = df.item_type.ARMOR , shape_id = df.item_type.ARMOR , plant_id = df.item_type.ARMOR , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 4 , item_type = df.item_type.SHIELD , creature_id = df.item_type.SHIELD , color_id = df.item_type.SHIELD , shape_id = df.item_type.SHIELD , plant_id = df.item_type.SHIELD , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- prefers plump helmets for their ... - local ph_mat_type=dfhack.matinfo.find("MUSHROOM_HELMET_PLUMP:STRUCTURAL").index - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 5 , item_type = ph_mat_type , creature_id = ph_mat_type , color_id = ph_mat_type , shape_id = ph_mat_type , plant_id = ph_mat_type , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- prefers to consume dwarven wine: - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 2 , item_type = 68 , creature_id = 68 , color_id = 68 , shape_id = 68 , plant_id = 68 , item_subtype = -1 , mattype = dfhack.matinfo.find("MUSHROOM_HELMET_PLUMP:DRINK").type , matindex = dfhack.matinfo.find("MUSHROOM_HELMET_PLUMP:DRINK").index , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- likes iron, steel (0,8) adam is 25 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 0 , item_type = -1 , creature_id = -1 , color_id = -1 , shape_id = -1 , plant_id = -1 , item_subtype = -1 , mattype = 0 , matindex = dfhack.matinfo.find("IRON").index , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 0 , item_type = -1 , creature_id = -1 , color_id = -1 , shape_id = -1 , plant_id = -1 , item_subtype = -1 , mattype = 0 , matindex = dfhack.matinfo.find("STEEL").index , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - - prefcount = #(unit.status.current_soul.preferences) - print ("After, unit "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." has "..prefcount.." preferences") - -end --- --------------------------------------------------------------------------- -function clear_preferences(v) - unit=v - - local prefs=unit.status.current_soul.preferences - for index,pref in ipairs(prefs) do - pref:delete() - end - prefs:resize(0) -end --- --------------------------------------------------------------------------- -function clearpref_all_dwarves() - for _,v in ipairs(df.global.world.units.active) do - if v.race == df.global.ui.race_id then - print("Clearing Preferences for "..dfhack.TranslateName(dfhack.units.getVisibleName(v))) - clear_preferences(v) - end - end -end --- --------------------------------------------------------------------------- -function adjust_all_dwarves() - for _,v in ipairs(df.global.world.units.active) do - if v.race == df.global.ui.race_id then - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(v))) - brainwash_unit(v) - end - end -end --- --------------------------------------------------------------------------- --- main script operation starts here --- --------------------------------------------------------------------------- -clearpref_all_dwarves() -adjust_all_dwarves() diff --git a/scripts/putontable.lua b/scripts/putontable.lua deleted file mode 100644 index daba8a46f..000000000 --- a/scripts/putontable.lua +++ /dev/null @@ -1,36 +0,0 @@ --- Makes item appear on the table (just like in shops) ---[[=begin - -putontable -========== -Makes item appear on the table, like in adventure mode shops. -Arguments: ``-a`` or ``--all`` for all items. - -=end]] - -local pos=df.global.cursor -local args={...} -local doall -if args[1]=="-a" or args[1]=="--all" then - doall=true -end -local build,items -items={} -build=dfhack.buildings.findAtTile(pos.x,pos.y,pos.z) -if not df.building_tablest:is_instance(build) then - error("No table found at cursor") -end -for k,v in pairs(df.global.world.items.all) do - if pos.x==v.pos.x and pos.y==v.pos.y and pos.z==v.pos.z and v.flags.on_ground then - table.insert(items,v) - if not doall then - break - end - end -end -if #items==0 then - error("No items found!") -end -for k,v in pairs(items) do - dfhack.items.moveToBuilding(v,build,0) -end diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua deleted file mode 100644 index 3f4cc3954..000000000 --- a/scripts/quicksave.lua +++ /dev/null @@ -1,39 +0,0 @@ --- Makes the game immediately save the state. ---[[=begin - -quicksave -========= -If called in dwarf mode, makes DF immediately saves the game by setting a flag -normally used in seasonal auto-save. - -=end]] - -if not dfhack.isMapLoaded() then - qerror("World and map aren't loaded.") -end - -if not dfhack.world.isFortressMode() then - qerror('This script can only be used in fortress mode') -end - -local ui_main = df.global.ui.main -local flags4 = df.global.d_init.flags4 - -local function restore_autobackup() - if ui_main.autosave_request and dfhack.isMapLoaded() then - dfhack.timeout(10, 'frames', restore_autobackup) - else - flags4.AUTOBACKUP = true - end -end - --- Request auto-save -ui_main.autosave_request = true - --- And since it will overwrite the backup, disable it temporarily -if flags4.AUTOBACKUP then - flags4.AUTOBACKUP = false - restore_autobackup() -end - -print 'The game should save the state now.' diff --git a/scripts/region-pops.lua b/scripts/region-pops.lua deleted file mode 100644 index 935cd0f02..000000000 --- a/scripts/region-pops.lua +++ /dev/null @@ -1,199 +0,0 @@ --- Show or edit regional plant and animal populations ---[[=begin - -region-pops -=========== -Show or modify the populations of animals in the region. - -Usage: - -:region-pops list [pattern]: - Lists encountered populations of the region, possibly restricted by pattern. -:region-pops list-all [pattern]: - Lists all populations of the region. -:region-pops boost : - Multiply all populations of TOKEN by factor. - If the factor is greater than one, increases the - population, otherwise decreases it. -:region-pops boost-all : - Same as above, but match using a pattern acceptable to list. -:region-pops incr : - Augment (or diminish) all populations of TOKEN by factor (additive). -:region-pops incr-all : - Same as above, but match using a pattern acceptable to list. - -=end]] - -local utils = require 'utils' - -local function sort_keys(tab) - local kt = {} - for k,v in pairs(tab) do table.insert(kt,k) end - table.sort(kt) - return ipairs(kt) -end - -local is_plant_map = { - Animal = false, Vermin = false, VerminInnumerable = false, - ColonyInsect = false, Tree = true, Grass = true, Bush = true -} - -function enum_populations() - local stat_table = { - plants = {}, - creatures = {}, - any = {} - } - - for i,v in ipairs(df.global.world.populations) do - local typeid = df.world_population_type[v.type] - local is_plant = is_plant_map[typeid] - local id, obj, otable, idtoken - - if is_plant then - id = v.plant - obj = df.plant_raw.find(id) - otable = stat_table.plants - idtoken = obj.id - else - id = v.race - obj = df.creature_raw.find(id) - otable = stat_table.creatures - idtoken = obj.creature_id - end - - local entry = otable[idtoken] - if not entry then - entry = { - obj = obj, token = idtoken, id = id, records = {}, - count = 0, known_count = 0, - known = false, infinite = false - } - otable[idtoken] = entry - stat_table.any[idtoken] = entry - end - - table.insert(entry.records, v) - entry.known = entry.known or v.flags.discovered - - if v.quantity < 10000001 then - entry.count = entry.count + v.quantity - if v.flags.discovered then - entry.known_count = entry.known_count + v.quantity - end - else - entry.infinite = true - end - end - - return stat_table -end - -function list_poptable(entries, all, pattern) - for _,k in sort_keys(entries) do - local entry = entries[k] - if (all or entry.known) and (not pattern or string.match(k,pattern)) then - local count = entry.known_count - if all then - count = entry.count - end - if entry.infinite then - count = 'innumerable' - end - print(string.format('%-40s %s', entry.token, count)) - end - end -end - -function list_populations(stat_table, all, pattern) - print('Plants:') - list_poptable(stat_table.plants, true, pattern) - print('\nCreatures and vermin:') - list_poptable(stat_table.creatures, all, pattern) -end - - -function boost_population(entry, factor, boost_count) - for _,v in ipairs(entry.records) do - if v.quantity < 10000001 then - boost_count = boost_count + 1 - v.quantity = math.floor(v.quantity * factor) - end - end - return boost_count -end - -function incr_population(entry, factor, boost_count) - for _,v in ipairs(entry.records) do - if v.quantity < 10000001 then - boost_count = boost_count + 1 - v.quantity = math.max(0, v.quantity + factor) - end - end - return boost_count -end - -local args = {...} -local pops = enum_populations() - -if args[1] == 'list' or args[1] == 'list-all' then - list_populations(pops, args[1] == 'list-all', args[2]) -elseif args[1] == 'boost' or args[1] == 'boost-all' then - local factor = tonumber(args[3]) - if not factor or factor < 0 then - qerror('Invalid boost factor.') - end - - local count = 0 - - if args[1] == 'boost' then - local entry = pops.any[args[2]] or qerror('Unknown population token.') - count = boost_population(entry, factor, count) - else - for k,entry in pairs(pops.any) do - if string.match(k, args[2]) then - count = boost_population(entry, factor, count) - end - end - end - - print('Updated '..count..' populations.') -elseif args[1] == 'incr' or args[1] == 'incr-all' then - local factor = tonumber(args[3]) - if not factor then - qerror('Invalid increment factor.') - end - - local count = 0 - - if args[1] == 'incr' then - local entry = pops.any[args[2]] or qerror('Unknown population token.') - count = incr_population(entry, factor, count) - else - for k,entry in pairs(pops.any) do - if string.match(k, args[2]) then - count = incr_population(entry, factor, count) - end - end - end - - print('Updated '..count..' populations.') -else - print([[ -Usage: - region-pops list [pattern] - Lists encountered populations of the region, possibly restricted by pattern. - region-pops list-all [pattern] - Lists all populations of the region. - region-pops boost - Multiply all populations of TOKEN by factor. - If the factor is greater than one, increases the - population, otherwise decreases it. - region-pops boost-all - Same as above, but match using a pattern acceptable to list. - region-pops incr - Augment (or diminish) all populations of TOKEN by factor (additive). - region-pops incr-all - Same as above, but match using a pattern acceptable to list. -]]) -end diff --git a/scripts/rejuvenate.lua b/scripts/rejuvenate.lua deleted file mode 100644 index fe19ad545..000000000 --- a/scripts/rejuvenate.lua +++ /dev/null @@ -1,30 +0,0 @@ --- make the selected dwarf 20 years old --- by vjek ---[[=begin - -rejuvenate -========== -Set the age of the selected dwarf to 20 years. Useful if valuable citizens are -getting old, or there are too many babies around... - -=end]] - -function rejuvenate() - local current_year,newbirthyear - unit=dfhack.gui.getSelectedUnit() - - if unit==nil then print ("No unit under cursor! Aborting.") return end - - current_year=df.global.cur_year - newbirthyear=current_year - 20 - if unit.relations.birth_year < newbirthyear then - unit.relations.birth_year=newbirthyear - end - if unit.relations.old_year < current_year+100 then - unit.relations.old_year=current_year+100 - end - print (dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." is now 20 years old and will live at least 100 years") - -end - -rejuvenate() diff --git a/scripts/remove-stress.lua b/scripts/remove-stress.lua deleted file mode 100644 index d384129fd..000000000 --- a/scripts/remove-stress.lua +++ /dev/null @@ -1,41 +0,0 @@ --- Sets stress to negative one million ---By Putnam; http://www.bay12forums.com/smf/index.php?topic=139553.msg5820486#msg5820486 ---[[=begin - -remove-stress -============= -Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or -very stressed dwarves taking on negative or greater values respectively. -Applies to the selected unit, or use ``remove-stress -all`` to apply to all units. - -=end]] - -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'all' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[ -remove-stress [-all] - sets the stress level of every unit to -1000000, or just the selected unit if the '-all' argument is not given -]]) - return -end - -if args.all then - for k,v in ipairs(df.global.world.units.active) do - v.status.current_soul.personality.stress_level=-1000000 - end -else - local unit = dfhack.gui.getSelectedUnit() - if unit then - unit.status.current_soul.personality.stress_level=-1000000 - else - error 'Invalid usage: No unit selected and -all argument not given.' - end -end diff --git a/scripts/remove-wear.lua b/scripts/remove-wear.lua deleted file mode 100644 index e1ed3989a..000000000 --- a/scripts/remove-wear.lua +++ /dev/null @@ -1,62 +0,0 @@ --- Resets all items in your fort to 0 wear --- original author: Laggy, edited by expwnent ---[[=begin - -remove-wear -=========== -Sets the wear on all items in your fort to zero. - -=end]] - -local args = {...} - -if args[1] == 'help' then - print([[remove-wear - this script removes wear from all items, or from individual ones - -remove-wear all - remove wear from all items -remove-wear n1 n2 n3 ... - remove wear from items with the given ids. order does not matter -repeat -time 2 months -command remove-wear all - remove wear from all items every 2 months. see repeat.lua for details -]]) - do return end -elseif args[1] == 'all' then - local count = 0; - for _,item in ipairs(df.global.world.items.all) do - if (item.wear > 0) then - item:setWear(0) - count = count+1 - end - end - print('remove-wear removed wear from '..count..' objects') -else - local argIndex = 1 - local isCompleted = {} - for i,x in ipairs(args) do - args[i] = tonumber(x) - end - table.sort(args) - for _,item in ipairs(df.global.world.items.all) do - local function loop() - if argIndex > #args then - return - elseif item.id > args[argIndex] then - argIndex = argIndex+1 - loop() - return - elseif item.id == args[argIndex] then - --print('removing wear from item with id ' .. args[argIndex]) - item:setWear(0) - isCompleted[args[argIndex]] = true - argIndex = argIndex+1 - end - end - loop() - end - for _,arg in ipairs(args) do - if isCompleted[arg] ~= true then - print('failed to remove wear from item ' .. arg .. ': could not find item with that id') - end - end -end diff --git a/scripts/repeat.lua b/scripts/repeat.lua deleted file mode 100644 index b048584d8..000000000 --- a/scripts/repeat.lua +++ /dev/null @@ -1,85 +0,0 @@ --- repeatedly call a lua script --- eg "repeat -time 1 months -command cleanowned"; to disable "repeat -cancel cleanowned" --- repeat -help for details --- author expwnent --- vaguely based on a script by Putnam ---[[=begin - -repeat -====== -Repeatedly calls a lua script at the specified interval. - -This allows neat background changes to the function of the game, especially when -invoked from an init file. For detailed usage instructions, use ``repeat -help``. - -Usage examples:: - - repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ] - repeat -time 1 -timeUnits months -command [ multicmd cleanowned scattered x; clean all ] -name clean - -The first example is abstract; the second will regularly remove all contaminants -and worn items from the game. - -``-name`` sets the name for the purposes of cancelling and making sure you don't schedule the -same repeating event twice. If not specified, it's set to the first argument after ``-command``. -``-time delay -timeUnits units``; delay is some positive integer, and units is some valid time -unit for ``dfhack.timeout(delay,timeUnits,function)``. ``-command [ ... ]`` specifies the -command to be run. - -=end]] - -local repeatUtil = require 'repeat-util' -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'cancel', - 'name', - 'time', - 'timeUnits', - 'command' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[repeat.lua - repeat -help - print this help message - repeat -cancel bob - cancels the repetition with the name bob - repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ] - -name sets the name for the purposes of cancelling and making sure you don't schedule the same repeating event twice - if not specified, it's set to the first argument after -command - -time delay -timeUnits units - delay is some positive integer - units is some valid time unit for dfhack.timeout(delay,timeUnits,function) - -command [ ... ] - specify the command to be run - ]]) - return -end - -if args.cancel then - repeatUtil.cancel(args.cancel) - if args.name then - repeatUtil.cancel(args.name) - end - return -end - -args.time = tonumber(args.time) -if not args.name then - args.name = args.command[1] -end - -if not args.timeUnits then - args.timeUnits = 'ticks' -end - -local callCommand = function() - dfhack.run_command(table.unpack(args.command)) -end - -repeatUtil.scheduleEvery(args.name,args.time,args.timeUnits,callCommand) - diff --git a/scripts/setfps.lua b/scripts/setfps.lua deleted file mode 100644 index 6deb68bb2..000000000 --- a/scripts/setfps.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Set the FPS cap at runtime. ---[[=begin - -setfps -====== -Run ``setfps `` to set the FPS cap at runtime, in case you want to watch -combat in slow motion or something. - -=end]] - -local cap = ... -local capnum = tonumber(cap) - -if not capnum or capnum < 1 then - qerror('Invalid FPS cap value: '..cap) -end - -df.global.enabler.fps = capnum diff --git a/scripts/show-unit-syndromes.rb b/scripts/show-unit-syndromes.rb deleted file mode 100644 index 112b33474..000000000 --- a/scripts/show-unit-syndromes.rb +++ /dev/null @@ -1,1035 +0,0 @@ -# Show syndromes affecting units, including duration -# original author: drayath, edited by expwnent -=begin - -show-unit-syndromes -=================== -Show syndromes affecting units and the remaining and maximum duration, along -with (optionally) substantial detail on the effects. - -Use one or more of the following options: - -:help: Show the help message -:showall: Show units even if not affected by any syndrome -:showeffects: Show detailed effects of each syndrome -:showdisplayeffects: Show effects that only change the look of the unit -:selected: Show selected unit -:dwarves: Show dwarves -:livestock: Show livestock -:wildanimals: Show wild animals -:hostile: Show hostiles (e.g. invaders, thieves, forgotten beasts etc) -:world: Show all defined syndromes in the world -:export: ``export:`` sends output to the given file, showing all - syndromes affecting each unit with the maximum and present duration. - -=end - -#TODO: When showing effects on a unit, show the actual change to the unit -# E.g. if +150%, +500 strength show actual total bonus based on the unit stats. -# For this also need to know -# how does size_delays affect the start/peak/end time -# how does size_dilute affect the Severity, does it also affect the phy/mental stat adjustments? -# how does peak affect the Severity, does it also affect the phy/mental stat adjustments? -#TODO: Add interaction info needs to display a bit more data, but the required structures are not yet decoded - -#TODO: Several of the unk_xxx fields have been identified here, and can get some more by comparing the raws with the printed interaction and effect information. Pass these onto the dfhack guys. - -def print_help() - puts "Use one or more of the following options:" - puts " showall: Show units even if not affected by any syndrome" - puts " showeffects: shows detailed effects of each syndrome" - puts " showdisplayeffects: show effects that only change the look of the unit" - puts " ignorehiddencurse: Hides syndromes the user should not be able to know about (TODO)" - puts " selected: Show selected unit" - puts " dwarves: Show dwarves" - puts " livestock: Show livestock" - puts " wildanimals: Show wild animals" - puts " hostile: Show hostiles (e.g. invaders, thieves, forgotten beasts etc)" - puts " world: Show all defined syndromes in the world" - puts " export: Write the output to a file instead of the console." - puts "" - puts "Will show all syndromes affecting each units with the maximum and present duration." -end - -class Output - attr_accessor :fileLogger, :indent_level - - def initialize(filename) - indent_level = "" - if filename==nil - @fileLogger = nil - else - @fileLogger = File.new(filename + ".html", "w") - @fileLogger.puts("") - end - end - - RED = "red" - GREEN = "green" - BLUE = "blue" - DEFAULT = "black" - HIGHLIGHT = "black\" size=\"+1" - - def colorize(text, color_code) - if @fileLogger == nil - return text - else - new_text = "#{text}" - if color_code == HIGHLIGHT - new_text = "" + new_text + "" - end - - return new_text - end - end - - def inactive(text) - if @fileLogger == nil - return "###" + text - else - return "#{text}" - end - end - - def indent() - if @fileLogger == nil - @indent_level = "#{@indent_level} - " - else - @fileLogger.puts("
    ") - end - end - - def unindent() - if @fileLogger == nil - @indent_level = @indent_level.chomp(" - ") - else - @fileLogger.puts("
") - end - end - - def break() - if @fileLogger == nil - puts("\n") - else - @fileLogger.puts("

") - end - end - - def close() - if @fileLogger != nil - @fileLogger.puts("") - @fileLogger.flush - @fileLogger.close - @fileLogger = nil - end - end - - def log(text, color=nil) - if @fileLogger == nil - puts("#{@indent_level}#{text}") - elsif color==nil - @fileLogger.puts(text+"
") - elsif @indent_level == "" - @fileLogger.puts(colorize(text, color)) - else - @fileLogger.puts("
  • " + colorize(text, color)+"
  • ") - end - end -end - -def get_mental_att(att_index) - - case att_index - when 0 - return "Analytical Ability" - when 1 - return "Focus" - when 2 - return "Willpower" - when 3 - return "Creativity" - when 4 - return "Intuition" - when 5 - return "Patience" - when 6 - return "Memory" - when 7 - return "Linguistics" - when 8 - return "Spacial Sense" - when 9 - return "Musicality" - when 10 - return "Kinesthetic Sense" - when 11 - return "Empathy" - when 12 - return "Social Awareness" - else - return "Unknown" - end -end - -def get_physical_att(att_index) - - case att_index - when 0 - return "strength" - when 1 - return "agility" - when 2 - return "toughness" - when 3 - return "endurance" - when 4 - return "recuperation" - when 5 - return "disease resistance" - else - return "unknown" - end -end - -def get_effect_target(target) - - values = [] - - limit = target.key.length - 1 - for i in (0..limit) - - if(target.mode[i].to_s() != "") - - items = "" - - #case target.mode[i].to_s() - #when "BY_TYPE" - # item = "type(" - #when "BY_TOKEN" - # item = "token(" - #when "BY_CATEGORY" - # item = "category(" - #end - - if(target.key[i].to_s()!="") - item = "#{item}#{target.key[i].to_s().capitalize()}" - end - - if target.tissue[i].to_s() != "ALL" - if(target.key[i].to_s()!="" and target.tissue[i].to_s()!="") - item = "#{item}:" - end - - if(target.tissue[i].to_s()!="") - item = "#{item}#{target.tissue[i].to_s().capitalize()}" - end - end - - #item = item + ")" - - values.push(item) - end - - end - - if values.length == 0 or (values.length == 1 and values[0] == "All") - return "" - else - return ", target=" + values.join(", ") - end -end - -def get_att_pairs(values, percents, physical) - - items = [] - - color = Output::DEFAULT - - limit = values.length - 1 - for i in (0..limit) - if (values[i] != 0 or percents[i] != 100) - - if physical - item = "#{get_physical_att(i)}(" - else - item = "#{get_mental_att(i)}(" - end - - if(values[i]!=0) - item = item + "%+d" % values[i] - end - - if (values[i]!=0 and percents[i]!=100) - item = item + ", " - end - - if (percents[i]!=100 or values[i]==0) - item = item + "%d" % percents[i] + "%" - end - - item = item + ")" - - if color != Output::RED and values[i] >= 0 and percents[i] > 100 - color = Output::GREEN - elsif values[i] <0 || percents[i] < 100 - color = Output::RED - end - - items.push(item) - end - end - - return items.join(", "), color -end - -def get_display_name(name, verb) - if name != nil and name != "" - return name.capitalize() - end - - if verb == nil or verb == "" - return "Mystery" - end - - if verb.length > 100 - verb = verb.slice(0, 100).capitalize() - end - - pos = verb.index(".") - if pos == nil - return verb.slice(0, verb.rindex(" ")).capitalize() - else - return verb.slice(0, pos).capitalize() - end -end - -def get_interaction(interaction) - - # name, USAGE_HINT, range, wait period are probably all we really want to show. - - #result = "a=#{interaction.unk_6c} b=#{interaction.unk_7c} c=#{interaction.unk_8c} d=#{interaction.unk_a8} e=#{interaction.unk_c4} f=#{interaction.unk_e4} " - #result = result + "g=#{interaction.unk_e0} h=#{interaction.unk_e4} i=#{interaction.unk_100} j=#{interaction.unk_11c} k=#{interaction.unk_138} l=#{interaction.unk_154} " - #result = result + "m=#{interaction.unk_170} n=#{interaction.unk_18c} o=#{interaction.unk_1a8} p=#{interaction.unk_1c4} q=#{interaction.unk_1e8} r=#{interaction.unk_25c} " - #result = result + "s=#{interaction.unk_278}" - - if interaction.name == "" - name = "mystery" - else - name = interaction.name - end - - return "ability=#{get_display_name(interaction.name, interaction.verb[0])}, delay=#{interaction.usage_delay}, actionType=TODO, range=TODO, maxTargets=TODO" -end - -def get_effect_flags(flags) - - values = [] - - if(flags.SIZE_DELAYS) then values.push("size delays") end - if(flags.SIZE_DILUTES) then values.push("size dilutes") end - if(flags.VASCULAR_ONLY) then values.push("vascular only") end - if(flags.MUSCULAR_ONLY) then values.push("muscles only") end - if(flags.RESISTABLE) then values.push("resistable") end - if(flags.LOCALIZED) then values.push("localized") end - - return values.join(",") -end - -def get_tag1_flags(logger, flags, add) - - values = [] - - good = false - bad = false - - if add - good_color = Output::GREEN - bad_color = Output::RED - else - good_color = Output::RED - bad_color = Output::GREEN - end - - if(flags.EXTRAVISION) - values.push(logger.colorize("extravision", good_color)) - good = true - end - - if(flags.OPPOSED_TO_LIFE) - values.push(logger.colorize("attack the living", bad_color)) - bad = true - end - - if(flags.NOT_LIVING) - values.push(logger.colorize("undead", Output::DEFAULT)) - end - - if(flags.NOEXERT) - values.push(logger.colorize("does not tire", good_color)) - good = true - end - - if(flags.NOPAIN) - values.push(logger.colorize("does not feel pain", good_color)) - good = true - end - - if(flags.NOBREATHE) - values.push(logger.colorize("does not breathe", good_color)) - good = true - end - - if(flags.HAS_BLOOD) - values.push(logger.colorize("has blood", Output::DEFAULT)) - end - - if(flags.NOSTUN) - values.push(logger.colorize("can't be stunned", good_color)) - good = true - end - - if(flags.NONAUSEA) - values.push(logger.colorize("does not get nausea", good_color)) - good = true - end - - if(flags.NO_DIZZINESS) - values.push(logger.colorize("does not get dizzy", good_color)) - good = true - end - - if(flags.NO_FEVERS) - values.push(logger.colorize("does not get fever", good_color)) - good = true - end - - if(flags.TRANCES) - values.push(logger.colorize("can enter trance", good_color)) - good = true - end - - if(flags.NOEMOTION) - values.push(logger.colorize("feels no emotion", good_color)) - good = true - end - - if(flags.LIKES_FIGHTING) - values.push(logger.colorize("like fighting", Output::DEFAULT)) - end - - if(flags.PARALYZEIMMUNE) - values.push(logger.colorize("can't be paralyzed", good_color)) - good = true - end - if(flags.NOFEAR) - values.push(logger.colorize("does not feel fear", good_color)) - good = true - end - - if(flags.NO_EAT) - values.push(logger.colorize("does not eat", good_color)) - good = true - end - - if(flags.NO_DRINK) - values.push(logger.colorize("does not drink", good_color)) - good = true - end - - if(flags.NO_SLEEP) - values.push(logger.colorize("does not sleep", good_color)) - good = true - end - if(flags.MISCHIEVOUS) - values.push(logger.colorize("mischievous", Output::DEFAULT)) - end - - if(flags.NO_PHYS_ATT_GAIN) - values.push(logger.colorize("physical stats cant improve", good_color)) - good = true - end - - if(flags.NO_PHYS_ATT_RUST) - values.push(logger.colorize("physical stats do not rust", good_color)) - good = true - end - - if(flags.NOTHOUGHT) - values.push(logger.colorize("stupid", bad_color)) - bad = true - end - - if(flags.NO_THOUGHT_CENTER_FOR_MOVEMENT) - values.push(logger.colorize("no brain needed to move", good_color)) - good = true - end - - if(flags.CAN_SPEAK) - values.push(logger.colorize("can speak", good_color)) - good = true - end - - if(flags.CAN_LEARN) - values.push(logger.colorize("can learn", good_color)) - good = true - end - - if(flags.UTTERANCES) - values.push(logger.colorize("utterances", Output::DEFAULT)) - end - - if(flags.CRAZED) - values.push(logger.colorize("crazed", bad_color)) - bad = true - end - - if(flags.BLOODSUCKER) - values.push(logger.colorize("drinks blood", bad_color)) - bad = true - end - - if(flags.NO_CONNECTIONS_FOR_MOVEMENT) - values.push(logger.colorize("can move without nerves", good_color)) - good = true - end - - if(flags.SUPERNATURAL) - values.push(logger.colorize("supernatural", good_color)) - good = true - end - - if add - if bad - color = Output::RED - elsif good - color = Output::GREEN - else - color = Output::DEFAULT - end - else - if good - color = Output::RED - elsif bad - color = Output::GREEN - else - color = Output::DEFAULT - end - end - - return values.join(", "), color -end - -def get_tag2_flags(logger, flags, add) - values = [] - - good = false - bad = false - - if add - good_color = Output::GREEN - bad_color = Output::RED - else - good_color = Output::RED - bad_color = Output::GREEN - end - - if(flags.NO_AGING) - good = true - values.push(logger.colorize("does not age", good_color)) - end - - if(flags.MORTAL) - bad = true - values.push(logger.colorize("mortal", bad_color)) - end - - if(flags.STERILE) - values.push(logger.colorize("can't have children", Output::DEFAULT)) - end - - if(flags.FIT_FOR_ANIMATION) - values.push(logger.colorize("can be animated", Output::DEFAULT)) - end - - if(flags.FIT_FOR_RESURRECTION) - good = true - values.push(logger.colorize("can be resurrected", Output::DEFAULT)) - end - - if add - if bad - color = Output::RED - elsif good - color = Output::GREEN - else - color = Output::DEFAULT - end - else - if good - color = Output::RED - elsif bad - color = Output::GREEN - else - color = Output::DEFAULT - end - end - - return values.join(", "), color -end - -def find_creature_name(id, casteid) - creature = df.world.raws.creatures.all.find{ |c| c.creature_id == id } - - if creature == nil - return id, casteid - end - - creature_name = creature.name[0].capitalize() - - if casteid == "DEFAULT" - return creature_name, "" - end - - caste = creature.caste.find{ |c| c.caste_id == casteid } - - if caste == nil - return creature_name, casteid - elsif creature.name[0].downcase() == caste.caste_name[0].downcase() - return creature_name, "" - else - castename = caste.caste_name[0].downcase().chomp(creature.name[0].downcase()).strip() - - if castename.start_with?(creature.name[0]) - castename = castename.slice(creature.name[0].length, castename.length - creature.name[0].length).strip() - end - - if castename.start_with?("of the") - castename = castename.slice("of the".length, castename.length - "of the".length).strip() - end - - return creature_name, castename.downcase() - end -end - -def get_effect(logger, ce, ticks, showdisplayeffects) - - flags = get_effect_flags(ce.flags) - if flags != "" - flags = " (#{flags})" - end - - if ce.end == -1 - duration = " [permanent]" - elsif ce.start >= ce.peak or ce.peak <= 1 - duration = " [#{ce.start}-#{ce.end}]" - else - duration = " [#{ce.start}-#{ce.peak}-#{ce.end}]" - end - - case ce.getType().to_s() - when "PAIN" - name = "Pain" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "SWELLING" - name = "Swelling" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "OOZING" - name = "Oozing" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "BRUISING" - name = "Bruising" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "BLISTERS" - name = "Blisters" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "NUMBNESS" - name = "Numbness" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::GREEN - when "PARALYSIS" - name = "Paralysis" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "FEVER" - name = "Fever" - desc = "power=#{ce.sev}" - color = Output::RED - when "BLEEDING" - name = "Bleeding" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "COUGH_BLOOD" - name = "Cough Blood" - desc = "power=#{ce.sev}" - color = Output::RED - when "VOMIT_BLOOD" - name = "Vomit Blood" - desc = "power=#{ce.sev}" - color = Output::RED - when "NAUSEA" - name = "Nausea" - desc = "power=#{ce.sev}" - color = Output::RED - when "UNCONSCIOUSNESS" - name = "Unconsciousness" - desc = "power=#{ce.sev}" - color = Output::RED - when "NECROSIS" - name = "Necrosis" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "IMPAIR_FUNCTION" - name = "Impairs" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "DROWSINESS" - name = "Drowsiness" - desc = "power=#{ce.sev}" - color = Output::RED - when "DIZZINESS" - name = "Dizziness" - desc = "power=#{ce.sev}" - color = Output::RED - when "ADD_TAG" - name = "Add" - tags1 = get_tag1_flags(logger, ce.tags1, true) - tags2 = get_tag2_flags(logger, ce.tags2, true) - desc = "#{tags1[0]},#{tags2[0]}" - - if tags1[1] == Output::RED || tags2[1] == Output::RED - color = Output::RED - elsif tags1[1] == Output::GREEN || tags2[1] == Output::GREEN - color = Output::GREEN - else - color = Output::DEFAULT - end - when "REMOVE_TAG" - name = "Remove" - tags1 = get_tag1_flags(logger, ce.tags1, true) - tags2 = get_tag2_flags(logger, ce.tags2, true) - desc = "#{tags1[0]},#{tags2[0]}" - - if tags1[1] == Output::RED || tags2[1] == Output::RED - color = Output::RED - elsif tags1[1] == Output::GREEN || tags2[1] == Output::GREEN - color = Output::GREEN - else - color = Output::DEFAULT - end - when "DISPLAY_TILE" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Tile" - desc = "Tile=#{ce.tile}, Colour=#{ce.color}" - color = Output::DEFAULT - when "FLASH_TILE" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Flash" - color = ce.sym_color >> 8 - tile = ce.sym_color - (color * 256) - desc = "tile = #{tile}, colour=#{color}, time=#{ce.period}, period=#{ce.time}" - color = Output::DEFAULT - when "SPEED_CHANGE" - name = "Physical" - desc = "speed(" - - value = ce.bonus_add - percent = ce.bonus_perc - if(value!=0) - desc = desc + "%+d" % value - end - - if (value!=0 and percent!=100) - desc = desc + ", " - end - - if (percent!=100 or value==0) - desc = desc + "%d" % percent + "%" - end - - desc = desc + ")" - - if value < 0 or percent < 100 - color = Output::RED - elsif value >0 or percent >100 - color = Output::GREEN - else - color = Output::DEFAULT - end - - when "CAN_DO_INTERACTION" - name = "Add interaction" - desc = "#{get_interaction(ce)}" - color = Output::GREEN - when "SKILL_ROLL_ADJUST" - name = "Skill check" - desc = "modifier=#{ce.multiplier}%, chance=#{ce.chance}%" - - if ce.multiplier > 100 - color = Output::GREEN - elsif ce.multiplier < 100 - color = Output::RED - else - color = Output::DEFAULT - end - - when "BODY_TRANSFORMATION" - name = "Transformation" - - if ce.chance > 0 - chance = ", chance=#{ce.chance} " - else - chance = "" - end - - creature_name = find_creature_name(ce.race_str, ce.caste_str) - - if creature_name[1] == "" - desc = "#{creature_name[0]}#{chance}" - else - desc = "#{creature_name[0]}(#{creature_name[1]})#{chance}" - end - - color = Output::BLUE - when "PHYS_ATT_CHANGE" - name = "Physical" - data = get_att_pairs(ce.phys_att_add, ce.phys_att_perc, true) - desc = data[0] - color = data[1] - when "MENT_ATT_CHANGE" - name = "Mental" - data = get_att_pairs(ce.ment_att_add, ce.ment_att_perc, false) - desc = data[0] - color = data[1] - when "MATERIAL_FORCE_MULTIPLIER" - name = "Material force multiplier" - desc = "received damage scaled by #{(ce.fraction_mul * 100 / ce.fraction_div * 100)/100}%" - if ce.fraction_div > ce.fraction_mul - color = Output::GREEN - elsif ce.fraction_div < ce.fraction_mul - color = Output::RED - else - color = Output::DEFAULT - end - - if ce.mat_index >=0 - mat = df.decode_mat(ce.mat_type, ce.mat_index ) - elsif ce.mat_type >= 0 - mat = df.decode_mat(ce.mat_type, 0 ) - else - mat = nil - end - - if mat!= nil - token = mat.token - if token.start_with?("INORGANIC:") - token = token.slice("INORGANIC:".length, token.length - "INORGANIC:".length) - end - - desc = "#{desc} vs #{token.capitalize()}" - end - - when "BODY_MAT_INTERACTION" - # interactionId, SundromeTriggerType - name = "Body material interaction" - desc = "a???=#{ce.unk_6c}, b???=#{ce.unk_88}, c???=#{ce.unk_8c}, d???=#{ce.unk_90}, e???=#{ce.unk_94}" - color = Output::DEFAULT - when "BODY_APPEARANCE_MODIFIER" - if !showdisplayeffects then return "", Output::DEFAULT end - # !!! seems to be missing info class !!! - # should be enum and value - name = "Body Appearance" - desc = "" - color = Output::DEFAULT - when "BP_APPEARANCE_MODIFIER" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Body part appearance" - desc = "value=#{ce.value} change_type_enum?=#{ce.unk_6c}#{get_effect_target(ce.target)}" - color = Output::DEFAULT - when "DISPLAY_NAME" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Set display name" - desc = "#{ce.name}" - color = Output::DEFAULT - else - name = "Unknown" - color = Output::HIGHLIGHT - end - - text = "#{name}#{duration}#{flags} #{desc}" - if ticks > 0 and ((ce.start > 0 and ticks < ce.start) or (ce.end > 0 and ticks > ce.end)) - text = logger.inactive(text) - end - - return text, color -end - -print_syndrome = lambda { |logger, syndrome, showeffects, showdisplayeffects| - rawsyndrome = df.world.raws.syndromes.all[syndrome.type] - duration = rawsyndrome.ce.minmax_by{ |ce| ce.end } - - if duration[0].end == -1 - durationStr = "permanent" - else - if duration[0].end == duration[1].end - durationStr = "#{syndrome.ticks} of #{duration[0].end}" - else - durationStr = "#{syndrome.ticks} of #{duration[0].end}-#{duration[1].end}" - end - end - - effects = rawsyndrome.ce.collect { |effect| get_effect(logger, effect, syndrome.ticks, showdisplayeffects) } - - if effects.any?{ |text, color| color==Output::RED } - color = Output::RED - elsif effects.any?{|text, color| color==Output::GREEN } - color = Output::GREEN - else - color = Output::DEFAULT - end - - logger.indent() - logger.log "#{get_display_name(rawsyndrome.syn_name, "")} [#{durationStr}]", color - - if showeffects - logger.indent() - effects.each{ |text, color| if text!="" then logger.log text, color end } - logger.unindent() - end - logger.unindent() -} - -print_raw_syndrome = lambda { |logger, rawsyndrome, showeffects, showdisplayeffects| - - effects = rawsyndrome.ce.collect { |effect| get_effect(logger, effect, -1, showdisplayeffects) } - - if effects.any?{ |item| item[1]==Output::RED } - color = Output::RED - elsif effects.any?{|item| item[1]==Output::GREEN } - color = Output::GREEN - else - color = Output::DEFAULT - end - - logger.indent() - logger.log get_display_name(rawsyndrome.syn_name, ""), color - - if showeffects - logger.indent() - effects.each{ |text, color| if text!="" then logger.log text, color end } - logger.unindent() - end - logger.unindent() -} - -print_syndromes = lambda { |logger, unit, showrace, showall, showeffects, showhiddencurse, showdisplayeffects| - - if showhiddencurse - syndromes = unit.syndromes.active - else - syndromes = unit.syndromes.active - # TODO: syndromes = unit.syndromes.active.select{ |s| visible_syndrome?(unit, s) } - end - - if !syndromes.empty? or showall - if showrace - logger.log "#{df.world.raws.creatures.all[unit.race].name[0]}#{unit.name == '' ? "" : ": "}#{unit.name}", Output::HIGHLIGHT - else - logger.log "#{unit.name}", Output::HIGHLIGHT - end - end - - syndromes.each { |syndrome| print_syndrome[logger, syndrome, showeffects, showdisplayeffects] } -} - -def starts_with?(str, prefix) - prefix = prefix.to_s - str[0, prefix.length] == prefix -end - -showall = false -showeffects = false -selected = false -dwarves = false -livestock = false -wildanimals = false -hostile = false -world = false -showhiddencurse = false -showdisplayeffects = false - -if $script_args.any?{ |arg| arg == "help" or arg == "?" or arg == "-?" } - print_help() -elsif $script_args.empty? - dwarves = true - showeffects = true -else - if $script_args.any?{ |arg| arg == "showall" } then showall=true end - if $script_args.any?{ |arg| arg == "showeffects" } then showeffects=true end - if $script_args.any?{ |arg| arg == "ignorehiddencurse" } then showhiddencurse=true end - if $script_args.any?{ |arg| arg == "showdisplayeffects" } then showdisplayeffects=true end - if $script_args.any?{ |arg| arg == "selected" } then selected=true end - if $script_args.any?{ |arg| arg == "dwarves" } then dwarves=true end - if $script_args.any?{ |arg| arg == "livestock" } then livestock=true end - if $script_args.any?{ |arg| arg == "wildanimals" } then wildanimals=true end - if $script_args.any?{ |arg| arg == "hostile" } then hostile=true end - if $script_args.any?{ |arg| arg == "world" } then world=true end - if $script_args.any?{ |arg| starts_with?(arg, "export:") } - exportfile = $script_args.find{ |arg| starts_with?(arg, "export:") }.gsub("export:", "") - export=true - end -end - -if export - logger = Output.new(exportfile) -else - logger = Output.new(nil) -end - -if selected - print_syndromes[logger, df.unit_find(), true, showall, showeffects, showhiddencurse, showdisplayeffects] - logger.break() -end - -if dwarves - logger.log "Dwarves", Output::HIGHLIGHT - df.unit_citizens.each { |unit| - print_syndromes[logger, unit, false, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if livestock - logger.log "Livestock", Output::HIGHLIGHT - df.world.units.active.find_all { |u| df.unit_category(u) == :Livestock }.each { |unit| - print_syndromes[logger, unit, true, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if wildanimals - logger.log "Wild animals", Output::HIGHLIGHT - df.world.units.active.find_all { |u| df.unit_category(u) == :Other and df.unit_other_category(u) == :Wild }.each { |unit| - print_syndromes[logger, unit, true, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if hostile - logger.log "Hostile units", Output::HIGHLIGHT - df.unit_hostiles.each { |unit| - print_syndromes[logger, unit, true, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if world - logger.log "All syndromes", Output::HIGHLIGHT - df.world.raws.syndromes.all.each { |syndrome| print_raw_syndrome[logger, syndrome, showeffects, showdisplayeffects] } -end - -logger.close() diff --git a/scripts/siren.lua b/scripts/siren.lua deleted file mode 100644 index 5bf821141..000000000 --- a/scripts/siren.lua +++ /dev/null @@ -1,136 +0,0 @@ --- Wakes up the sleeping, ends breaks and parties ---[[=begin - -siren -===== -Wakes up sleeping units, cancels breaks and stops parties either everywhere, -or in the burrows given as arguments. In return, adds bad thoughts about -noise, tiredness and lack of protection. Also, the units with interrupted -breaks will go on break again a lot sooner. The script is intended for -emergencies, e.g. when a siege appears, and all your military is partying. - -=end]] - -local utils = require 'utils' - -local args = {...} -local burrows = {} -local bnames = {} - -if not dfhack.isMapLoaded() then - qerror('Map is not loaded.') -end - -for _,v in ipairs(args) do - local b = dfhack.burrows.findByName(v) - if not b then - qerror('Unknown burrow: '..v) - end - table.insert(bnames, v) - table.insert(burrows, b) -end - -local in_siege = false - -function is_in_burrows(pos) - if #burrows == 0 then - return true - end - for _,v in ipairs(burrows) do - if dfhack.burrows.isAssignedTile(v, pos) then - return true - end - end -end - -function add_thought(unit, emotion, thought) - unit.status.current_soul.personality.emotions:insert('#', { new = true, - type = emotion, - unk2=1, - strength=1, - thought=thought, - subthought=0, - severity=0, - flags=0, - unk7=0, - year=df.global.cur_year, - year_tick=df.global.cur_year_tick}) -end - -function wake_unit(unit) - local job = unit.job.current_job - if not job or job.job_type ~= df.job_type.Sleep then - return - end - - if job.completion_timer > 0 then - unit.counters.unconscious = 0 - add_thought(unit, df.emotion_type.Grouchiness, df.unit_thought_type.Drowsy) - elseif job.completion_timer < 0 then - add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) - end - - job.pos:assign(unit.pos) - - job.completion_timer = 0 - - unit.path.dest:assign(unit.pos) - unit.path.path.x:resize(0) - unit.path.path.y:resize(0) - unit.path.path.z:resize(0) - - unit.counters.job_counter = 0 -end - -function stop_break(unit) - local counter = dfhack.units.getMiscTrait(unit, df.misc_trait_type.OnBreak) - if counter then - counter.id = df.misc_trait_type.TimeSinceBreak - counter.value = 100800 - 30*1200 - add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) - end -end - --- Check siege for thought purpose -for _,v in ipairs(df.global.ui.invasions.list) do - if v.flags.siege and v.flags.active then - in_siege = true - break - end -end - --- Stop rest -for _,v in ipairs(df.global.world.units.active) do - local x,y,z = dfhack.units.getPosition(v) - if x and dfhack.units.isCitizen(v) and is_in_burrows(xyz2pos(x,y,z)) then - if not in_siege and v.military.squad_id < 0 then - add_thought(v, df.emotion_type.Nervousness, df.unit_thought_type.LackProtection) - end - wake_unit(v) - stop_break(v) - end -end - --- Stop parties -for _,v in ipairs(df.global.ui.parties) do - local pos = utils.getBuildingCenter(v.location) - if is_in_burrows(pos) then - v.timer = 0 - for _, u in ipairs(v.units) do - add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) - end - end -end - -local place = 'the halls and tunnels' -if #bnames > 0 then - if #bnames == 1 then - place = bnames[1] - else - place = table.concat(bnames,', ',1,#bnames-1)..' and '..bnames[#bnames] - end -end -dfhack.gui.showAnnouncement( - 'A loud siren sounds throughout '..place..', waking the sleeping and startling the awake.', - COLOR_BROWN, true -) diff --git a/scripts/source.rb b/scripts/source.rb deleted file mode 100644 index e9d7a64f3..000000000 --- a/scripts/source.rb +++ /dev/null @@ -1,114 +0,0 @@ -# create an infinite source/drain of magma/water -=begin - -source -====== -Create an infinite magma or water source or drain on a tile. -For more complex commands, try the `liquids` plugin. - -This script registers a map tile as a liquid source, and every 12 game ticks -that tile receives or remove 1 new unit of flow based on the configuration. - -Place the game cursor where you want to create the source (must be a -flow-passable tile, and not too high in the sky) and call:: - - source add [magma|water] [0-7] - -The number argument is the target liquid level (0 = drain, 7 = source). - -To add more than 1 unit everytime, call the command again on the same spot. - -To delete one source, place the cursor over its tile and use ``source delete``. -To remove all existing sources, call ``source clear``. - -The ``list`` argument shows all existing sources. - -Examples:: - - source add water - water source - source add magma 7 - magma source - source add water 0 - water drain - -=end - -$sources ||= [] - -cur_source = { - :liquid => 'water', - :amount => 7, - :pos => [df.cursor.x, df.cursor.y, df.cursor.z] -} -cmd = 'help' - -$script_args.each { |a| - case a.downcase - when 'water', 'magma' - cur_source[:liquid] = a.downcase - when /^\d+$/ - cur_source[:amount] = a.to_i - when 'add', 'del', 'delete', 'clear', 'help', 'list' - cmd = a.downcase - else - puts "source: unhandled argument #{a}" - end -} - -case cmd -when 'add' - $sources_onupdate ||= df.onupdate_register('sources', 12) { - # called every 12 game ticks (100x a dwarf day) - $sources.each { |s| - if tile = df.map_tile_at(*s[:pos]) and tile.shape_passableflow - # XXX does not check current liquid_type - des = tile.designation - cur = des.flow_size - if cur != s[:amount] - tile.spawn_liquid((cur > s[:amount] ? cur-1 : cur+1), s[:liquid] == 'magma') - end - end - } - if $sources.empty? - df.onupdate_unregister($sources_onupdate) - $sources_onupdate = nil - end - } - - if cur_source[:pos][0] >= 0 - if tile = df.map_tile_at(*cur_source[:pos]) - if tile.shape_passableflow - $sources << cur_source - else - puts "Impassable tile: I'm afraid I can't do that, Dave" - end - else - puts "Unallocated map block - build something here first" - end - else - puts "Please put the game cursor where you want a source" - end - -when 'del', 'delete' - $sources.delete_if { |s| s[:pos] == cur_source[:pos] } - -when 'clear' - $sources.clear - -when 'list' - puts "Source list:", $sources.map { |s| - " #{s[:pos].inspect} #{s[:liquid]} #{s[:amount]}" - } - puts "Current cursor pos: #{[df.cursor.x, df.cursor.y, df.cursor.z].inspect}" if df.cursor.x >= 0 - -else - puts <= 3 and args[3]:sub(1, 1) ~= '-' then - extend(new_args, {'-nick', args[3]}) - start = 4 - end - for i = start, #args do - table.insert(new_args, args[i]) - end -end -if show_command then - print('modtools/create-unit ' .. table.concat(new_args, ' ')) - return -end -dfhack.run_script('modtools/create-unit', table.unpack(new_args)) diff --git a/scripts/startdwarf.rb b/scripts/startdwarf.rb deleted file mode 100644 index a592d5391..000000000 --- a/scripts/startdwarf.rb +++ /dev/null @@ -1,20 +0,0 @@ -# patch start dwarf count -=begin - -startdwarf -========== -Use at the embark screen to embark with the specified number of dwarves. Eg. -``startdwarf 500`` would lead to a severe food shortage and FPS issues, while -``startdwarf 10`` would just allow a few more warm bodies to dig in. -The number must be 7 or greater. - -=end - -nr = $script_args[0].to_i - -raise 'too low' if nr < 7 - -addr = df.get_global_address('start_dwarf_count') -raise 'patch address not available' if addr == 0 -df.memory_patch(addr, [nr].pack('L')) - diff --git a/scripts/starvingdead.rb b/scripts/starvingdead.rb deleted file mode 100644 index 7ee985a7b..000000000 --- a/scripts/starvingdead.rb +++ /dev/null @@ -1,92 +0,0 @@ -# Weaken and eventually destroy undead over time -=begin - -starvingdead -============ -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. It mostly helps prevent undead cascades in the caverns, -where constant combat leads to hundreds of undead roaming the -caverns and destroying your FPS. - -With this script running, all undead that have been on the map for -one month gradually decay, losing strength, speed, and toughness. -After six months, they collapse upon themselves, never to be reanimated. - -Usage: ``starvingdead (start|stop)`` - -=end - -class StarvingDead - - def initialize - @threshold = 1 - @die_threshold = 6 - end - - def process - return false unless @running - month_length = 67200 - if (@undead_count >= 25) - month_length *= 25 / @undead_count - end - - @undead_count = 0 - df.world.units.active.each { |u| - if (u.enemy.undead and not u.flags1.dead) - @undead_count += 1 - if (u.curse.time_on_site > month_length * @threshold) - u.body.physical_attrs.each { |att| - att.value = att.value - (att.value * 0.02) - } - end - - if (u.curse.time_on_site > month_length * @die_threshold) - u.flags1.dead = true - u.curse.rem_tags2.FIT_FOR_ANIMATION = true - end - end - } - end - - def start - @onupdate = df.onupdate_register('starvingdead', 1200, 1200) { process } - @running = true - @undead_count = 0 - - if ($script_args[1] and $script_args[1].gsub(/[^0-9\.]/,'').to_f > 0) - @threshold = $script_args[1].gsub(/[^0-9\.]/,'').to_f - end - - if ($script_args[2] and $script_args[2].gsub(/[^0-9\.]/,'').to_f > 0) - @die_threshold = $script_args[2].gsub(/[^0-9\.]/,'').to_f - end - - puts "Starving Dead starting...weakness starts at #{@threshold} months, true death at #{@die_threshold} months" - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - def status - @running ? 'Running.' : 'Stopped.' - end -end - -case $script_args[0] -when 'start' - if ($StarvingDead) - $StarvingDead.stop - end - $StarvingDead = StarvingDead.new - $StarvingDead.start - -when 'end', 'stop' - $StarvingDead.stop -else - if $StarvingDead - puts $StarvingDead.status - else - puts 'Not loaded.' - end -end diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb deleted file mode 100644 index 8cb1a0fa4..000000000 --- a/scripts/stripcaged.rb +++ /dev/null @@ -1,227 +0,0 @@ -# mark stuff inside of cages for dumping. -=begin - -stripcaged -========== -For dumping items inside cages. Will mark selected items for dumping, then -a dwarf may come and actually dump them (or you can use `autodump`). - -Arguments: - -:list: display the list of all cages and their item content on the console -:items: dump items in the cage, excluding stuff worn by caged creatures -:weapons: dump equipped weapons -:armor: dump everything worn by caged creatures (including armor and clothing) -:all: dump everything in the cage, on a creature or not - -Without further arguments, all commands work on all cages and animal traps on -the map. With the ``here`` argument, considers only the in-game selected cage -(or the cage under the game cursor). To target only specific cages, you can -alternatively pass cage IDs as arguments:: - - stripcaged weapons 25321 34228 - -=end - -def plural(nr, name) - # '1 cage' / '4 cages' - "#{nr} #{name}#{'s' if nr > 1}" -end - -def cage_dump_items(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) - next if ref.item_tg.flags.dump - count += 1 - ref.item_tg.flags.dump = true - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_armor(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) - ref.unit_tg.inventory.each { |it| - next if it.mode != :Worn - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_weapons(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) - ref.unit_tg.inventory.each { |it| - next if it.mode != :Weapon - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_all(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - case ref - when DFHack::GeneralRefContainsItemst - next if ref.item_tg.flags.dump - count += 1 - ref.item_tg.flags.dump = true - when DFHack::GeneralRefContainsUnitst - ref.unit_tg.inventory.each { |it| - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - end - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" -end - - -def cage_dump_list(list) - count_total = Hash.new(0) - empty_cages = 0 - list.each { |cage| - count = Hash.new(0) - - cage.general_refs.each { |ref| - case ref - when DFHack::GeneralRefContainsItemst - count[ref.item_tg._rtti_classname] += 1 - when DFHack::GeneralRefContainsUnitst - ref.unit_tg.inventory.each { |it| - count[it.item._rtti_classname] += 1 - } - # TODO vermin ? - else - puts "unhandled ref #{ref.inspect}" if $DEBUG - end - } - - type = case cage - when DFHack::ItemCagest; 'Cage' - when DFHack::ItemAnimaltrapst; 'Animal trap' - else cage._rtti_classname - end - - if count.empty? - empty_cages += 1 - else - puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } - end - - count.each { |k, v| count_total[k] += v } - } - - if list.length > 2 - puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } - puts "with #{plural(empty_cages, 'empty cage')}" - end -end - - -# handle magic script arguments -here_only = $script_args.delete 'here' -if here_only - it = df.item_find - list = [it] - if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) - list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) } - end - if list.empty? - puts 'Please select a cage' - throw :script_finished - end - -elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first - list = [] - ids.each { |id| - $script_args.delete id - if not it = df.item_find(id.to_i) - puts "Invalid item id #{id}" - elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) - puts "Item ##{id} is not a cage" - list << it - else - list << it - end - } - if list.empty? - puts 'Please use a valid cage id' - throw :script_finished - end - -else - list = df.world.items.other[:ANY_CAGE_OR_TRAP] -end - - -# act -case $script_args[0] -when /^it/i - cage_dump_items(list) -when /^arm/i - cage_dump_armor(list) -when /^wea/i - cage_dump_weapons(list) -when 'all' - cage_dump_all(list) -when 'list' - cage_dump_list(list) -else - puts < 10000 - u.counters2.sleepiness_timer = 1 - end - - # no break - if b = u.status.misc_traits.find { |t| t.id == :OnBreak } - b.value = 500_000 - end - else - $superdwarf_ids.delete id - end - } - end - } - else - puts "Select a creature using 'v'" - end - -when 'del' - if u = df.unit_find - $superdwarf_ids.delete u.id - else - puts "Select a creature using 'v'" - end - -when 'clear' - $superdwarf_ids.clear - -when 'list' - puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } - -else - puts "Usage:", - " - superdwarf add: give superspeed to currently selected creature", - " - superdwarf del: remove superspeed to current creature", - " - superdwarf clear: remove all superpowers", - " - superdwarf list: list super-dwarves" -end diff --git a/scripts/teleport.lua b/scripts/teleport.lua deleted file mode 100644 index 8c657061c..000000000 --- a/scripts/teleport.lua +++ /dev/null @@ -1,58 +0,0 @@ --- teleports a unit to a location --- author Putnam --- edited by expwnent ---[[=begin - -teleport -======== -Teleports a unit to given coordinates. Examples: - -:teleport -showunitid: prints unitid beneath cursor -:teleport -showpos: prints coordinates beneath cursor -:teleport -unit 1234 -x 56 -y 115 -z 26: - teleports unit 1234 to 56,115,26 - -=end]] - -function teleport(unit,pos) - local unitoccupancy = dfhack.maps.getTileBlock(unit.pos).occupancy[unit.pos.x%16][unit.pos.y%16] - local newoccupancy = dfhack.maps.getTileBlock(pos).occupancy[pos.x%16][pos.y%16] - if newoccupancy.unit then - unit.flags1.on_ground=true - end - unit.pos.x = pos.x - unit.pos.y = pos.y - unit.pos.z = pos.z - if not unit.flags1.on_ground then unitoccupancy.unit = false else unitoccupancy.unit_grounded = false end -end - -utils = require('utils') - -validArgs = validArgs or utils.invert({ - 'unit', - 'x', - 'y', - 'z', - 'showunitid', - 'showpos' -}) - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) - -if args.showunitid or args.showpos then - if args.showunitid then - print(dfhack.gui.getSelectedUnit(true).id) - else - printall(df.global.cursor) - end -else - local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) - local pos = not(not args.x or not args.y or not args.z) and {x=args.x,y=args.y,z=args.z} or {x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} - if not unit then qerror('A unit needs to be selected or specified. Use teleport -showunitid to get a unit\'s ID.') end - if not pos.x or pos.x==-30000 then qerror('A position needs to be highlighted or specified. Use teleport -showpos to get a position\'s exact xyz values.') end - teleport(unit,pos) -end diff --git a/scripts/tidlers.lua b/scripts/tidlers.lua deleted file mode 100644 index 7df509139..000000000 --- a/scripts/tidlers.lua +++ /dev/null @@ -1,10 +0,0 @@ ---Toggle idlers count ---[[=begin - -tidlers -======= -Toggle between all possible positions where the idlers count can be placed. - -=end]] -df.global.d_init.idlers = df.d_init_idlers:next_item(df.global.d_init.idlers) -print('Toggled the display of idlers.') diff --git a/scripts/twaterlvl.lua b/scripts/twaterlvl.lua deleted file mode 100644 index 50f487686..000000000 --- a/scripts/twaterlvl.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Toggle display of water depth ---[[=begin - -twaterlvl -========= -Toggle between displaying/not displaying liquid depth as numbers. - -=end]] - -df.global.d_init.flags1.SHOW_FLOW_AMOUNTS = not df.global.d_init.flags1.SHOW_FLOW_AMOUNTS -print('Water level display toggled.') diff --git a/scripts/undump-buildings.lua b/scripts/undump-buildings.lua deleted file mode 100644 index f4cfe7909..000000000 --- a/scripts/undump-buildings.lua +++ /dev/null @@ -1,36 +0,0 @@ --- Undesignates building base materials for dumping. ---[[=begin - -undump-buildings -================ -Undesignates building base materials for dumping. - -=end]] - -function undump_buildings() - local buildings = df.global.world.buildings.all - local undumped = 0 - for i = 0, #buildings - 1 do - local building = buildings[i] - -- Zones and stockpiles don't have the contained_items field. - if df.building_actual:is_instance(building) then - local items = building.contained_items - for j = 0, #items - 1 do - local contained = items[j] - if contained.use_mode == 2 and contained.item.flags.dump then - -- print(building, contained.item) - undumped = undumped + 1 - contained.item.flags.dump = false - end - end - end - end - - if undumped > 0 then - local s = "s" - if undumped == 1 then s = "" end - print("Undumped "..undumped.." item"..s..".") - end -end - -undump_buildings() diff --git a/scripts/unsuspend.rb b/scripts/unsuspend.rb deleted file mode 100644 index 666969359..000000000 --- a/scripts/unsuspend.rb +++ /dev/null @@ -1,28 +0,0 @@ -# un-suspend construction jobs, one time only -# same as "resume all" -=begin - -unsuspend -========= -Unsuspend jobs in workshops, on a one-off basis. See `autounsuspend` -for regular use. - -=end - -joblist = df.world.job_list.next -count = 0 - -while joblist - job = joblist.item - joblist = joblist.next - - if job.job_type == :ConstructBuilding - if (job.flags.suspend && job.items && job.items[0]) - item = job.items[0].item - job.flags.suspend = false - count += 1 - end - end -end - -puts "Unsuspended #{count} job(s)." diff --git a/scripts/view-item-info.lua b/scripts/view-item-info.lua deleted file mode 100644 index 39614d3bf..000000000 --- a/scripts/view-item-info.lua +++ /dev/null @@ -1,407 +0,0 @@ --- Extended Item Viewscreens --- Shows information on material properties, weapon or armour stats, and more. --- By PeridexisErrant, adapted from nb_item_info by Raidau ---@ enable = true -local help = [[=begin - -view-item-info -============== -A script to extend the item or unit viewscreen with additional information -including a custom description of each item (when available), and properties -such as material statistics, weapon attacks, armor effectiveness, and more. - -The associated script `item-descriptions`.lua supplies custom descriptions -of items. Individual descriptions can be added or overridden by a similar -script :file:`raw/scripts/more-item-descriptions.lua`. Both work as sparse lists, -so missing items simply go undescribed if not defined in the fallback. - -=end]] - -function isInList(list, item, helper) - if not helper then - helper = function(v) return v end - end - for k,v in pairs (list) do - if item == helper(v) then - return true - end - end -end - -if dfhack_flags and dfhack_flags.enable then - enabled = dfhack_flags.enable_state -end - -local args = {...} -local lastframe = df.global.enabler.frame_last -if isInList(args, "help") or isInList(args, "?") then - print(help) - return -elseif isInList(args, "enable") then - enabled = true -elseif isInList(args, "disable") then - enabled = false -end - -function append (list, str, indent) - local str = str or " " - local indent = indent or 0 - table.insert(list, {str, indent * 4}) -end - -function add_lines_to_list(t1,t2) - for i=1,#t2 do - t1[#t1+1] = t2[i] - end - return t1 -end - -function GetMatPlant (item) - if dfhack.matinfo.decode(item).mode == "plant" then - return dfhack.matinfo.decode(item).plant - end -end - -function get_textid (item) - return string.match(tostring(item._type),""):upper() -end - -function compare_iron (mat_prop, iron_prop) - return " "..mat_prop.." ("..math.floor(mat_prop/iron_prop*100).."% of iron)" -end - -function GetMatPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - local deg_U = item.temperature.whole - local deg_C = math.floor((deg_U-10000)*5/9) - append(list,"Temperature: "..deg_C.."\248C ("..deg_U.."U)") - append(list,"Color: "..df.global.world.raws.language.colors[mat.state_color.Solid].name) - local function GetStrainDescription (number) - if tonumber(number) < 1 then return "crystalline" - elseif tonumber(number) < 1000 then return "very stiff" - elseif tonumber(number) < 5001 then return "stiff" - elseif tonumber(number) < 15001 then return "medium" - elseif tonumber(number) < 50000 then return "elastic" - elseif tonumber(number) >= 50000 then return "very elastic" - else return "unknown" end - end - local mat_properties_for = {"BAR", "SMALLGEM", "BOULDER", "ROUGH", - "WOOD", "GEM", "ANVIL", "THREAD", "SHOES", "CLOTH", "ROCK", "WEAPON", "TRAPCOMP", - "ORTHOPEDIC_CAST", "SIEGEAMMO", "SHIELD", "PANTS", "HELM", "GLOVES", "ARMOR", "AMMO"} - if isInList(mat_properties_for, get_textid (item)) then - append(list,"Material name: "..mat.state_name.Solid) - append(list,"Material properties: ") - append(list,"Solid density: "..mat.solid_density..'kg/m^3',1) - local maxedge = mat.strength.max_edge - append(list,"Maximum sharpness: "..maxedge.." ("..maxedge/standard.strength.max_edge*100 .."%)",1) - if mat.molar_mass > 0 then - append(list,"Molar mass: "..mat.molar_mass,1) - end - append(list,"Shear strength:",1) - append(list, "yield:"..compare_iron(mat.strength.yield.SHEAR, standard.strength.yield.SHEAR), 2) - append(list, "fracture:"..compare_iron(mat.strength.fracture.SHEAR, standard.strength.fracture.SHEAR), 2) - local s_strain = mat.strength.strain_at_yield.SHEAR - append(list, "elasticity: "..s_strain.." ("..GetStrainDescription(s_strain)..")", 2) - append(list,"Impact strength:",1) - append(list, "yield:"..compare_iron(mat.strength.yield.IMPACT, standard.strength.yield.IMPACT), 2) - append(list, "fracture:"..compare_iron(mat.strength.fracture.IMPACT, standard.strength.fracture.IMPACT), 2) - local i_strain = mat.strength.strain_at_yield.IMPACT - append(list, "elasticity: "..i_strain.." ("..GetStrainDescription(i_strain)..")", 2) - end - return list -end - -function GetArmorPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - append(list,"Armor properties: ") - append(list,"Thickness: "..item.subtype.props.layer_size,1) - append(list,"Coverage: "..item.subtype.props.coverage.."%",1) - append(list,"Fit for "..df.creature_raw.find(item.maker_race).name[0],1) - return list -end - -function GetShieldPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - append(list,"Shield properties:") - append(list,"Base block chance: "..item.subtype.blockchance,1) - append(list,"Fit for "..df.creature_raw.find(item.maker_race).name[0],1) - return list -end - -function GetWeaponPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - if item._type == df.item_toolst and #item.subtype.attacks < 1 then - return list - end - append(list,"Weapon properties: ") - if item.sharpness > 0 then - append(list,"Sharpness:"..compare_iron(item.sharpness, standard.strength.max_edge),1) - else - append(list,"Not edged",1) - end - if string.len(item.subtype.ranged_ammo) > 0 then - append(list,"Ranged weapon",1) - append(list,"Ammo: "..item.subtype.ranged_ammo:lower(),2) - if item.subtype.shoot_force > 0 then - append(list,"Shoot force: "..item.subtype.shoot_force,2) - end - if item.subtype.shoot_maxvel > 0 then - append(list,"Maximum projectile velocity: "..item.subtype.shoot_maxvel,2) - end - end - append(list,"Required size: "..item.subtype.minimum_size*10,1) - if item.subtype.two_handed*10 > item.subtype.minimum_size*10 then - append(list,"Used as 2-handed until unit size: "..item.subtype.two_handed*10,2) - end - append(list,"Attacks: ",1) - for k,attack in pairs (item.subtype.attacks) do - local name = attack.verb_2nd - if attack.noun ~= "NO_SUB" then - name = name.." with "..attack.noun - end - if attack.edged then - name = name.." (edged)" - else - name = name.." (blunt)" - end - append(list,name,2) - append(list,"Contact area: "..attack.contact,3) - if attack.edged then - append(list,"Penetration: "..attack.penetration,3) - end - append(list,"Velocity multiplier: "..attack.velocity_mult/1000,3) - if attack.flags.bad_multiattack then - append(list,"Bad multiattack",3) - end - append(list,"Prepare "..attack.prepare.." / recover "..attack.recover,3) - end - return list -end - -function GetAmmoPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - if item._type == df.item_toolst and #item.subtype.attacks < 1 then - return list - end - append(list,"Ammo properties: ") - if item.sharpness > 0 then - append(list,"Sharpness: "..item.sharpness,1) - else - append(list,"Not edged",1) - end - append(list,"Attacks: ", 1) - for k,attack in pairs (item.subtype.attacks) do - local name = attack.verb_2nd - if attack.noun ~= "NO_SUB" then - name = name.." with "..attack.noun - end - if attack.edged then - name = name.." (edged)" - else - name = name.." (blunt)" - end - append(list,name,2) - append(list,"Contact area: "..attack.contact,3) - if attack.edged then - append(list,"Penetration: "..attack.penetration,3) - end - append(list,"Velocity multiplier: "..attack.velocity_mult/1000,3) - if attack.flags.bad_multiattack then - append(list,"Bad multiattack",3) - end - append(list,"Prepare "..attack.prepare.." / recover "..attack.recover,3) - end - return list -end - -function edible_string (mat) - local edible_string = "Edible" - if mat.flags.EDIBLE_RAW or mat.flags.EDIBLE_COOKED then - if mat.flags.EDIBLE_RAW then - edible_string = edible_string.." raw" - if mat.flags.EDIBLE_COOKED then - edible_string = edible_string.." and cooked" - end - elseif mat.flags.EDIBLE_COOKED then - edible_string = edible_string.." only when cooked" - end - else - edible_string = "Not edible" - end - return edible_string -end - -function GetReactionProduct (inmat, reaction) - for k,v in pairs (inmat.reaction_product.id) do - if v.value == reaction then - return {inmat.reaction_product.material.mat_type[k], - inmat.reaction_product.material.mat_index[k]} - end - end -end - -function add_react_prod (list, mat, product, str) - local mat_type, mat_index = GetReactionProduct (mat, product) - if mat_type then - local result = dfhack.matinfo.decode(mat_type, mat_index).material.state_name.Liquid - append(list, str..result) - end -end - -function get_plant_reaction_products (mat) - local list = {} - add_react_prod (list, mat, "GROWTH_JUICE_PROD", "Pressed into ") - add_react_prod (list, mat, "PRESS_LIQUID_MAT", "Pressed into ") - add_react_prod (list, mat, "LIQUID_EXTRACTABLE", "Extractable product: ") - add_react_prod (list, mat, "WATER_SOLUTION_PROD", "Can be mixed with water to make ") - add_react_prod (list, mat, "RENDER_MAT", "Rendered into ") - add_react_prod (list, mat, "SOAP_MAT", "Used to make soap") - if GetReactionProduct (mat, "SUGARABLE") then - append(list,"Used to make sugar") - end - if GetReactionProduct (mat, "MILLABLE") or - GetReactionProduct (mat, "GRAIN_MILLABLE") or - GetReactionProduct (mat, "GROWTH_MILLABLE") then - append(list,"Can be milled") - end - if GetReactionProduct(mat, "GRAIN_THRESHABLE") then - append(list,"Grain can be threshed") - end - if GetReactionProduct (mat, "CHEESE_MAT") then - local mat_type, mat_index = GetReactionProduct (mat, "CHEESE_MAT") - append(list,"Used to make "..dfhack.matinfo.decode(mat_type, mat_index).material.state_name.Solid) - end - return list -end - -function GetFoodPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {{" ", 0}} - append(list,edible_string(mat)) - if item._type == df.item_foodst then - append(list,"This is prepared meal") - return list - end - if mat == dfhack.matinfo.find ("WATER") then - append(list,"Water is drinkable") - return list - end - add_lines_to_list(list, get_plant_reaction_products(mat)) - if item._type == df.item_plantst and GetMatPlant (item) then - local plant = GetMatPlant (item) - for k,v in pairs (plant.material_defs) do - if v ~= -1 and k:find("type_") and not (k:find("type_basic") - or k:find("type_seed") or k:find("type_tree")) then - local targetmat = dfhack.matinfo.decode (v, - plant.material_defs["idx_"..k:match("type_(.+)")]) - local state = "Liquid" - local describe = "Made into " - if k:find("type_mill") - then state = "Powder" describe = "Ground into " - elseif k:find("type_thread") - then state = "Solid" describe = "Woven into " - elseif k:find("type_drink") - then describe = "Brewed into " - elseif k:find("type_extract_barrel") - then describe = "Cask-aged into " - elseif k:find("type_extract_vial") - then describe = "Refined into vials of " - elseif k:find("type_extract_still_vial") - then describe = "Distilled into vials of " end - local st_name = targetmat.material.state_name[state] - append(list,describe..targetmat.material.prefix..''..st_name) - end - end - end - return list -end - -function get_all_uses_strings (item) - local all_lines = {} - local FoodsAndPlants = {df.item_meatst, df.item_globst, - df.item_plantst, df.item_plant_growthst, df.item_liquid_miscst, - df.item_powder_miscst, df.item_cheesest, df.item_foodst} - local ArmourTypes = {df.item_armorst, df.item_pantsst, - df.item_helmst, df.item_glovesst, df.item_shoesst} - if df.global.gamemode == df.game_mode.ADVENTURE and item ~= "COIN" then - add_lines_to_list(all_lines, {{"Value: "..dfhack.items.getValue(item),0}}) - elseif isInList(ArmourTypes, item._type) then - add_lines_to_list(all_lines, GetArmorPropertiesStringList(item)) - elseif item._type == df.item_weaponst or item._type == df.item_toolst then - add_lines_to_list(all_lines, GetWeaponPropertiesStringList(item)) - elseif item._type == df.item_ammost then - add_lines_to_list(all_lines, GetAmmoPropertiesStringList(item)) - elseif item._type == df.item_shieldst then - add_lines_to_list(all_lines, GetShieldPropertiesStringList(item)) - elseif item._type == df.item_seedsst then - local str = math.floor(GetMatPlant(item).growdur/12).." days" - add_lines_to_list(all_lines, {{"Growth time: "..str, 0}}) - elseif isInList(FoodsAndPlants, item._type) then - add_lines_to_list(all_lines, GetFoodPropertiesStringList(item)) - end - if not dfhack.items.isCasteMaterial(df.item_type[get_textid(item)]) then - add_lines_to_list(all_lines, GetMatPropertiesStringList(item)) - end - return all_lines -end - -function get_custom_item_desc (item) - local desc - local ID = get_textid (item) - if ID and dfhack.items.getSubtypeCount(df.item_type[ID]) ~= -1 then - ID = item.subtype.id end - if not ID then return nil end - if dfhack.findScript("item-descriptions") then - desc = dfhack.script_environment("item-descriptions").descriptions[ID] - end - if dfhack.findScript("more-item-descriptions") then - desc = dfhack.script_environment("more-item-descriptions").descriptions[ID] or desc - end - if desc then add_lines_to_list(desc, {""}) end - return desc -end - -function AddUsesString (viewscreen,line,indent) - local str = df.new("string") - str.value = tostring(line) - local indent = indent or 0 - viewscreen.entry_ref:insert('#', nil) - viewscreen.entry_indent:insert('#', indent) - viewscreen.unk_34:insert('#', nil) -- TODO: get this into structures, and fix usage! - viewscreen.entry_string:insert('#', str) - viewscreen.entry_reaction:insert('#', -1) -end - -function dfhack.onStateChange.item_info (code) - vi_label = 'More information (DFHack):' - if not enabled then return end - if code == SC_VIEWSCREEN_CHANGED and dfhack.isWorldLoaded() then - standard = dfhack.matinfo.find("INORGANIC:IRON").material - if not standard then return end - local scr = dfhack.gui.getCurViewscreen() - if scr._type == df.viewscreen_itemst then - if isInList(scr.entry_string, vi_label, function(v) return v.value end) then - return - end - if #scr.entry_string > 0 then - AddUsesString(scr, '') - end - AddUsesString(scr, vi_label) - AddUsesString(scr, '') - local description = get_custom_item_desc (scr.item) or "" - for i = 1, #description do - AddUsesString(scr,description[i]) - end - local all_lines = get_all_uses_strings(scr.item) - for i = 1, #all_lines do - AddUsesString(scr,all_lines[i][1],all_lines[i][2]) - end - scr.caption_uses = true - end - end -end diff --git a/scripts/warn-starving.lua b/scripts/warn-starving.lua deleted file mode 100644 index 12334bf99..000000000 --- a/scripts/warn-starving.lua +++ /dev/null @@ -1,140 +0,0 @@ --- Pause and warn if a unit is starving --- By Meneth32, PeridexisErrant, Lethosor ---@ module = true ---[[=begin - -warn-starving -============= -If any (live) units are starving, very thirsty, or very drowsy, the game will -be paused and a warning shown and logged to the console. Use with the -`repeat` command for regular checks. - -Use ``warn-starving all`` to display a list of all problematic units. - -=end]] - -starvingUnits = starvingUnits or {} -dehydratedUnits = dehydratedUnits or {} -sleepyUnits = sleepyUnits or {} - -function clear() - starvingUnits = {} - dehydratedUnits = {} - sleepyUnits = {} -end - -local gui = require 'gui' -local utils = require 'utils' -local units = df.global.world.units.active - -local args = utils.invert({...}) -if args.all or args.clear then - clear() -end - -warning = defclass(warning, gui.FramedScreen) -warning.ATTRS = { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'Warning', - frame_width = 20, - frame_height = 18, - frame_inset = 1, - focus_path = 'warn-starving', -} - -function warning:init(args) - self.start = 1 - self.messages = args.messages - self.frame_height = math.min(18, #self.messages) - self.max_start = #self.messages - self.frame_height + 1 - for _, msg in pairs(self.messages) do - self.frame_width = math.max(self.frame_width, #msg + 2) - end - self.frame_width = math.min(df.global.gps.dimx - 2, self.frame_width) -end - -function warning:onRenderBody(p) - for i = self.start, math.min(self.start + self.frame_height - 1, #self.messages) do - p:string(self.messages[i]):newline() - end - if #self.messages > self.frame_height then - if self.start > 1 then - p:seek(self.frame_width - 1, 0):string(string.char(24), COLOR_LIGHTCYAN) -- up - end - if self.start < self.max_start then - p:seek(self.frame_width - 1, self.frame_height - 1):string(string.char(25), COLOR_LIGHTCYAN) -- down - end - end -end - -function warning:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - elseif keys.CURSOR_UP or keys.STANDARDSCROLL_UP then - self.start = math.max(1, self.start - 1) - elseif keys.CURSOR_DOWN or keys.STANDARDSCROLL_DOWN then - self.start = math.min(self.start + 1, self.max_start) - end -end - -local function findRaceCaste(unit) - local rraw = df.creature_raw.find(unit.race) - return rraw, safe_index(rraw, 'caste', unit.caste) -end - -local function getSexString(sex) - local sexStr = "" - if sex==0 then sexStr=string.char(12) - elseif sex==1 then sexStr=string.char(11) - end - return string.char(40)..sexStr..string.char(41) -end - -local function nameOrSpeciesAndNumber(unit) - if unit.name.has_name then - return dfhack.TranslateName(dfhack.units.getVisibleName(unit))..' '..getSexString(unit.sex),true - else - return 'Unit #'..unit.id..' ('..df.creature_raw.find(unit.race).caste[unit.caste].caste_name[0]..' '..getSexString(unit.sex)..')',false - end -end - -local function checkVariable(var, limit, description, map, unit) - local rraw = findRaceCaste(unit) - local species = rraw.name[0] - local profname = dfhack.units.getProfessionName(unit) - if #profname == 0 then profname = nil end - local name = nameOrSpeciesAndNumber(unit) - if var > limit then - if not map[unit.id] then - map[unit.id] = true - return name .. ", " .. (profname or species) .. " is " .. description .. "!" - end - else - map[unit.id] = false - end - return nil -end - -function doCheck() - local messages = {} - for i=#units-1, 0, -1 do - local unit = units[i] - local rraw = findRaceCaste(unit) - if rraw and not unit.flags1.dead and not dfhack.units.isOpposedToLife(unit) then - table.insert(messages, checkVariable(unit.counters2.hunger_timer, 75000, 'starving', starvingUnits, unit)) - table.insert(messages, checkVariable(unit.counters2.thirst_timer, 50000, 'dehydrated', dehydratedUnits, unit)) - table.insert(messages, checkVariable(unit.counters2.sleepiness_timer, 150000, 'very drowsy', sleepyUnits, unit)) - end - end - if #messages > 0 then - dfhack.color(COLOR_LIGHTMAGENTA) - for _, msg in pairs(messages) do - print(dfhack.df2console(msg)) - end - dfhack.color() - df.global.pause_state = true - warning{messages=messages}:show() - end -end - -if not moduleMode then doCheck() end diff --git a/scripts/weather.lua b/scripts/weather.lua deleted file mode 100644 index a61ab48bd..000000000 --- a/scripts/weather.lua +++ /dev/null @@ -1,46 +0,0 @@ --- 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 = {...} -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 cmd == "c" then - dfhack.world.SetCurrentWeather(df.weather_type.None) - print("The weather has cleared.") -elseif cmd == "r" then - dfhack.world.SetCurrentWeather(df.weather_type.Rain) - print("It is now raining.") -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"} - print("Weather map (C = clear, R = rain, S = snow):") - for y=0, 4 do - s = "" - for x=0, 4 do - local cur = df.global.current_weather[x][y] - s = s .. (kind[cur] or cur) .. ' ' - end - print(s) - end -end From 53440a6f0d1d8c6928a7a80dce7c8f605008c945 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 19:43:06 -0400 Subject: [PATCH 0054/1012] Add scripts submodule --- .gitmodules | 3 +++ scripts | 1 + 2 files changed, 4 insertions(+) create mode 160000 scripts diff --git a/.gitmodules b/.gitmodules index 9f1b48395..e2d5de578 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git +[submodule "scripts"] + path = scripts + url = git://github.com/dfhack/scripts.git diff --git a/scripts b/scripts new file mode 160000 index 000000000..5a09a5316 --- /dev/null +++ b/scripts @@ -0,0 +1 @@ +Subproject commit 5a09a53169663b87d848643e6a50d6d32662090a From 09f310767f6ed8d98a65f13003c1fc34331a0625 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 19:46:13 -0400 Subject: [PATCH 0055/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 5a09a5316..19ea053ca 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 5a09a53169663b87d848643e6a50d6d32662090a +Subproject commit 19ea053caee75ab4f646087d88b2d082fae8cd0b From 36d647a0de4c49328ef4d191266a749d5b174221 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 20:15:42 -0400 Subject: [PATCH 0056/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 19ea053ca..e13b76a79 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 19ea053caee75ab4f646087d88b2d082fae8cd0b +Subproject commit e13b76a79038c6e2fbca5c7c5b0adb009b4afc03 From a947586cf08d31e1d4f2baf5b555704caabd47aa Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 20:16:14 -0400 Subject: [PATCH 0057/1012] Add @hayguen to Authors.rst (clsocket) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index c4462a987..e38d82194 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -35,6 +35,7 @@ expwnent expwnent Feng gchristopher gchristopher Harlan Playford playfordh +Hayati Ayguen hayguen IndigoFenix James Logsdon jlogsdon Japa JapaMala From 022a1bf9e88b51e92f02f3f6a73a12373401d3e8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 21:24:27 -0400 Subject: [PATCH 0058/1012] Wrap script descriptions in `ls` output and remove description length cap --- library/Core.cpp | 31 ++++++++++++++++++++++++------- library/MiscUtils.cpp | 28 ++++++++++++++++++++++++++++ library/include/MiscUtils.h | 4 ++++ travis/script-in-readme.py | 3 --- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index f7f057e19..f5bc9d4e7 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -618,6 +618,27 @@ string getBuiltinCommand(std::string cmd) return builtin; } +void ls_helper(color_ostream &con, const string &name, const string &desc) +{ + const size_t help_line_length = 80 - 22 - 5; + const string padding = string(80 - help_line_length, ' '); + vector lines; + con.print(" %-22s - ", name.c_str()); + word_wrap(&lines, desc, help_line_length); + + // print first line, then any additional lines preceded by padding + for (size_t i = 0; i < lines.size(); i++) + con.print("%s%s\n", i ? padding.c_str() : "", lines[i].c_str()); +} + +void ls_helper(color_ostream &con, const PluginCommand &pcmd) +{ + if (pcmd.isHotkeyCommand()) + con.color(COLOR_CYAN); + ls_helper(con, pcmd.name, pcmd.description); + con.reset_color(); +} + command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) { std::string first = first_; @@ -846,11 +867,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else for (size_t j = 0; j < plug->size();j++) { - const PluginCommand & pcmd = (plug->operator[](j)); - if (pcmd.isHotkeyCommand()) - con.color(COLOR_CYAN); - con.print(" %-22s - %s\n",pcmd.name.c_str(), pcmd.description.c_str()); - con.reset_color(); + ls_helper(con, plug->operator[](j)); } } else @@ -891,7 +908,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { if ((*iter).recolor) con.color(COLOR_CYAN); - con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str()); + ls_helper(con, iter->name, iter->description); con.reset_color(); } std::map scripts; @@ -900,7 +917,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { con.print("\nscripts:\n"); for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) - con.print(" %-22s- %s\n", iter->first.c_str(), iter->second.c_str()); + ls_helper(con, iter->first, iter->second); } } } diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 823b676eb..43fb5ef48 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -125,6 +125,34 @@ std::string toLower(const std::string &str) return rv; } +bool word_wrap(std::vector *out, const std::string &str, size_t line_length) +{ + out->clear(); + std::istringstream input(str); + std::string out_line; + std::string word; + if (input >> word) + { + out_line += word; + // size_t remaining = line_length - std::min(line_length, word.length()); + while (input >> word) + { + if (out_line.length() + word.length() + 1 <= line_length) + { + out_line += ' '; + out_line += word; + } + else + { + out->push_back(out_line); + out_line = word; + } + } + if (out_line.length()) + out->push_back(out_line); + } +} + bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail) { size_t ksize = key.size(); diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 653e37f47..917b67489 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -321,6 +321,10 @@ DFHACK_EXPORT std::string join_strings(const std::string &separator, const std:: DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str); +DFHACK_EXPORT bool word_wrap(std::vector *out, + const std::string &str, + size_t line_length = 80); + inline bool bits_match(unsigned required, unsigned ok, unsigned mask) { return (required & mask) == (required & mask & ok); diff --git a/travis/script-in-readme.py b/travis/script-in-readme.py index 72b7db86a..8227dea8d 100644 --- a/travis/script-in-readme.py +++ b/travis/script-in-readme.py @@ -20,9 +20,6 @@ def check_ls(fname, line): if line.endswith('=begin') or not line.startswith(comment): print('Error: no leading comment in ' + fname) return 1 - if len(line.replace(comment, '').strip()) > 53: - print('Error: leading comment too long in ' + fname) - return 1 return 0 From 7156b3cfe2c6e51d71f80b28e3dc5579823cffac Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 23:42:51 -0400 Subject: [PATCH 0059/1012] Update scripts (documentation) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index e13b76a79..3c3e7a487 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e13b76a79038c6e2fbca5c7c5b0adb009b4afc03 +Subproject commit 3c3e7a487164f416c93f9eaebca25f9db0019ea2 From e1b91866634e2e846d92bec005a12ade4bcd6059 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jun 2016 23:55:34 -0400 Subject: [PATCH 0060/1012] Update scripts (more documentation) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3c3e7a487..64c797b68 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3c3e7a487164f416c93f9eaebca25f9db0019ea2 +Subproject commit 64c797b681bc5f95991af410e2e89a88ec0c1abc From 9da53a056e08cb1f2203460701592566e0f56e29 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 15 Jun 2016 00:03:43 -0400 Subject: [PATCH 0061/1012] Update scripts (devel/send-key) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 64c797b68..40cf4ae2a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 64c797b681bc5f95991af410e2e89a88ec0c1abc +Subproject commit 40cf4ae2a2fd954f1d566c54a1470d92b0d21acf From e6d2f4ea2eb198387c888f55502f662d30443034 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Mon, 13 Jun 2016 14:14:33 +1000 Subject: [PATCH 0062/1012] Move Contributing.rst for Github banner link --- docs/Contributing.rst => Contributing.rst | 0 index.rst | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/Contributing.rst => Contributing.rst (100%) diff --git a/docs/Contributing.rst b/Contributing.rst similarity index 100% rename from docs/Contributing.rst rename to Contributing.rst diff --git a/index.rst b/index.rst index de587e95a..88d3ca6b1 100644 --- a/index.rst +++ b/index.rst @@ -57,7 +57,7 @@ For Developers .. toctree:: :maxdepth: 1 - /docs/Contributing + /Contributing /docs/Compile /docs/Lua API /library/xml/SYNTAX From cfc322fb7f3ba361d3efef64cb76de7f1cc7a161 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Wed, 15 Jun 2016 13:22:04 +1000 Subject: [PATCH 0063/1012] Improve handling of in-script documentation Closes issue #823. This allows for clean unification of html docs and the in-terminal help text for scripts, including handling in core rather than on a per-script basis (see issue #947). --- Contributing.rst | 26 ++--- NEWS.rst | 1 + conf.py | 193 +++++++++---------------------------- docs/Authors.rst | 1 + travis/script-in-readme.py | 15 +-- 5 files changed, 70 insertions(+), 166 deletions(-) diff --git a/Contributing.rst b/Contributing.rst index d29c4acef..2c1e7bb45 100644 --- a/Contributing.rst +++ b/Contributing.rst @@ -148,29 +148,32 @@ as well as getting help, you'll be providing valuable feedback that makes it easier for future readers! Scripts can use a custom autodoc function, based on the Sphinx ``include`` -directive and Ruby docstring conventions - any lines between ``=begin`` and -``=end`` are copied into the appropriate scripts documentation page. -They **must** have a heading which exactly matches the command, underlined +directive - anything between the tokens is copied into the appropriate scripts +documentation page. For Ruby, we follow the built-in docstring convention +(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]`` +- ordinary multi-line strings. It is highly encouraged to reuse this string +as the in-console documentation by (eg.) printing it when a ``-help`` argument +is given. + +The docs **must** have a heading which exactly matches the command, underlined with ``=====`` to the same length. For example, a lua file would have:: - --[[=begin + local helpstr = [====[ add-thought =========== Adds a thought or emotion to the selected unit. Can be used by other scripts, or the gui invoked by running ``add-thought gui`` with a unit selected. - =end]] + ]====] -Ruby scripts use the same syntax, but obviously omit the leading ``--[[`` and -trailing ``]]`` which denote a multiline comment in lua. -``=begin`` and ``=end`` are native syntax (and matched in lua for convenience). Where the heading for a section is also the name of a command, the spelling and case should exactly match the command to enter in the DFHack command line. -Try to keep lines within 80-100 characters, so it's readable in plain text - -Sphinx (our documentation system) will make sure paragraphs flow. +Try to keep lines within 80-100 characters, so it's readable in plain text +in the terminal - Sphinx (our documentation system) will make sure +paragraphs flow. If there aren't many options or examples to show, they can go in a paragraph of text. Use double-backticks to put commands in monospaced font, like this:: @@ -208,8 +211,7 @@ section like this:: ========= Add link targets if you need them, but otherwise plain headings are preferred. -Scripts using the in-source docs option, which should be all of them, have -link targets created automatically. +Scripts have link targets created automatically. Other ways to help ================== diff --git a/NEWS.rst b/NEWS.rst index 781c22b7b..7d9630c32 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -54,6 +54,7 @@ Misc Improvements - `catsplosion`: now a lua script instead of a plugin - `fix/diplomats`: replaces ``fixdiplomats`` - `fix/merchants`: replaces ``fixmerchants`` +- Unified script documentation and in-terminal help options Removed ------- diff --git a/conf.py b/conf.py index 59a220e26..d841eece2 100644 --- a/conf.py +++ b/conf.py @@ -1,21 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# -# DFHack documentation build configuration file -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +""" +DFHack documentation build configuration file + +This file is execfile()d with the current directory set to its +containing dir. + +Note that not all possible configuration values are present in this +autogenerated file. + +All configuration values have a default; values that are commented out +serve to show the default. +""" + +# pylint:disable=redefined-builtin import fnmatch from io import open +from itertools import starmap import os -import shlex +import shlex # pylint:disable=unused-import import sys @@ -29,10 +33,14 @@ def doc_dir(dirname, files): continue with open(os.path.join(dirname, f), 'r', encoding='utf8') as fstream: text = [l.rstrip() for l in fstream.readlines() if l.strip()] + # Some legacy lua files use the ruby tokens (in 3rdparty scripts) + tokens = ('=begin', '=end') + if f[-4:] == '.lua' and any('[====[' in line for line in text): + tokens = ('[====[', ']====]') command = None for line in text: if command and line == len(line) * '=': - yield command, sdir + '/' + f + yield command, sdir + '/' + f, tokens[0], tokens[1] break command = line @@ -40,9 +48,7 @@ def doc_dir(dirname, files): def document_scripts(): """Autodoc for files with the magic script documentation marker strings. - Creates a file for eack kind of script (base/devel/fix/gui/modtools) - with all the ".. include::" directives to pull out docs between the - magic strings. + Returns a dict of script-kinds to lists of .rst include directives. """ # First, we collect the commands and paths to include in our docs scripts = [] @@ -55,15 +61,19 @@ def document_scripts(): if len(k_fname) == 1: kinds['base'].append(s) else: - kinds.get(k_fname[0], []).append(s) - template = ('.. _{}:\n\n' - '.. include:: /{}\n' - ' :start-after: =begin\n' - ' :end-before: =end\n') - for key, value in kinds.items(): - kinds[key] = [template.format(x[0], x[1]) - for x in sorted(value, key=lambda x: x[0])] - # Finally, we write our _auto/* files for each kind of script + kinds[k_fname[0]].append(s) + template = '.. _{}:\n\n.. include:: /{}\n' +\ + ' :start-after: {}\n :end-before: {}\n' + return {key: '\n\n'.join(starmap(template.format, sorted(value))) + for key, value in kinds.items()} + +def write_script_docs(): + """ + Creates a file for eack kind of script (base/devel/fix/gui/modtools) + with all the ".. include::" directives to pull out docs between the + magic strings. + """ + kinds = document_scripts() if not os.path.isdir('docs/_auto'): os.mkdir('docs/_auto') head = { @@ -76,15 +86,18 @@ def document_scripts(): title = ('.. _{k}:\n\n{l}\n{t}\n{l}\n\n' '.. include:: /scripts/{a}about.txt\n\n' '.. contents::\n\n').format( - k=k, t=head[k], l=len(head[k])*'#', a=('' if k=='base' else k+'/')) + k=k, t=head[k], + l=len(head[k])*'#', + a=('' if k == 'base' else k + '/') + ) mode = 'w' if sys.version_info.major > 2 else 'wb' with open('docs/_auto/{}.rst'.format(k), mode) as outfile: outfile.write(title) - outfile.write('\n\n'.join(kinds[k])) + outfile.write(kinds[k]) # Actually call the docs generator -document_scripts() +write_script_docs() # -- General configuration ------------------------------------------------ @@ -135,7 +148,7 @@ author = 'The DFHack Team' def get_version(): """Return the DFHack version string, from CMakeLists.txt""" - version = release = '' + version = release = '' #pylint:disable=redefined-outer-name try: with open('CMakeLists.txt') as f: for s in f.readlines(): @@ -157,11 +170,8 @@ version = release = get_version() # Usually you set "language" from the command line for these cases. language = None -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# strftime format for |today| and 'Last updated on:' timestamp at page bottom +today_fmt = html_last_updated_fmt = '%Y-%m-%d' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -202,9 +212,6 @@ html_theme_options = { 'travis_button': False, } -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None @@ -226,19 +233,6 @@ html_favicon = 'docs/styles/dfhack-icon.ico' # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['docs/styles'] -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%Y-%m-%d' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - # Custom sidebar templates, maps document names to template names. html_sidebars = { '**': [ @@ -249,113 +243,18 @@ html_sidebars = { ] } -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. html_use_index = False -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'DFHackdoc' - # -- Options for LaTeX output --------------------------------------------- -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'DFHack.tex', 'DFHack Documentation', - 'The DFHack Team', 'manual'), + (master_doc, 'DFHack.tex', 'DFHack Documentation', + 'The DFHack Team', 'manual'), ] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'dfhack', 'DFHack Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'DFHack', 'DFHack Documentation', - author, 'DFHack', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/Authors.rst b/docs/Authors.rst index e38d82194..64dec0e9c 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -95,6 +95,7 @@ Sebastian Wolfertz Enkrod Seth Woodworth sethwoodworth simon Simon Jackson sizeak +stolencatkarma sv-esk sv-esk Tacomagic Tim Walberg twalberg diff --git a/travis/script-in-readme.py b/travis/script-in-readme.py index 8227dea8d..154304420 100644 --- a/travis/script-in-readme.py +++ b/travis/script-in-readme.py @@ -17,7 +17,7 @@ def check_ls(fname, line): """Check length & existence of leading comment for "ls" builtin command.""" line = line.strip() comment = '--' if fname.endswith('.lua') else '#' - if line.endswith('=begin') or not line.startswith(comment): + if '[====[' in line or not line.startswith(comment): print('Error: no leading comment in ' + fname) return 1 return 0 @@ -25,13 +25,15 @@ def check_ls(fname, line): def check_file(fname): errors, doclines = 0, [] + tok1, tok2 = ('=begin', '=end') if fname.endswith('.rb') else \ + ('[====[', ']====]') with open(fname, errors='ignore') as f: lines = f.readlines() errors += check_ls(fname, lines[0]) for l in lines: - if doclines or l.strip().endswith('=begin'): + if doclines or l.strip().endswith(tok1): doclines.append(l.rstrip()) - if l.startswith('=end'): + if l.startswith(tok2): break else: if doclines: @@ -39,7 +41,8 @@ def check_file(fname): else: print('Error: no documentation in: ' + fname) return 1 - title, underline = [d for d in doclines if d and '=begin' not in d][:2] + title, underline = [d for d in doclines + if d and '=begin' not in d and '[====[' not in d][:2] if underline != '=' * len(title): print('Error: title/underline mismatch:', fname, title, underline) errors += 1 @@ -55,9 +58,7 @@ def main(): err = 0 for root, _, files in os.walk('scripts'): for f in files: - # TODO: remove 3rdparty exemptions from checks - # Requires reading their CMakeLists to only apply to used scripts - if f[-3:] in {'.rb', 'lua'} and '3rdparty' not in root: + if f[-3:] in {'.rb', 'lua'}: err += check_file(join(root, f)) return err From 9de4550fde4d1a637b33bd8776f7adf51099c8e2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 15 Jun 2016 12:11:20 -0400 Subject: [PATCH 0064/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 5a9ded892..7912dbfd3 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5a9ded89222d87daa006fee96e4605ecf0567531 +Subproject commit 7912dbfd3602b9743e7f000d25bfe8f5ecf93e08 diff --git a/scripts b/scripts index 40cf4ae2a..d4fbac033 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 40cf4ae2a2fd954f1d566c54a1470d92b0d21acf +Subproject commit d4fbac0333fd76829a3eaefad6d326f2136dbf86 From 27a84a82467343dc50999cfa816841269f1612be Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 15 Jun 2016 18:43:13 -0400 Subject: [PATCH 0065/1012] Move scripts to library/scripts git can't cope with replacing scripts/3rdparty/* submodules with a single scripts submodule. --- .gitmodules | 4 ++-- .travis.yml | 2 +- CMakeLists.txt | 10 ++++------ conf.py | 5 ++--- library/CMakeLists.txt | 2 ++ library/scripts | 1 + scripts | 1 - 7 files changed, 12 insertions(+), 13 deletions(-) create mode 160000 library/scripts delete mode 160000 scripts diff --git a/.gitmodules b/.gitmodules index e2d5de578..cbb4c538d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,6 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git -[submodule "scripts"] - path = scripts +[submodule "library/scripts"] + path = library/scripts url = git://github.com/dfhack/scripts.git diff --git a/.travis.yml b/.travis.yml index ec08a8d56..b7ac6ae18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: - python travis/authors-rst.py - python travis/script-in-readme.py - python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" -- python travis/script-syntax.py --ext=rb --cmd="ruby -c" --path scripts/ +- python travis/script-syntax.py --ext=rb --cmd="ruby -c" --path library/scripts/ - mkdir build-travis - cd build-travis - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b9b985c2..4bd89edce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,8 +210,6 @@ IF(BUILD_PLUGINS) add_subdirectory (plugins) endif() -add_subdirectory(scripts) - find_package(Sphinx QUIET) if (BUILD_DOCS) if (NOT SPHINX_FOUND) @@ -223,12 +221,12 @@ if (BUILD_DOCS) "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*" "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/about.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*/about.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/about.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/*/about.txt" ) file(GLOB_RECURSE SPHINX_SCRIPT_DEPS - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.lua" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.rb" + "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/*.lua" + "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/*.rb" ) set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" diff --git a/conf.py b/conf.py index d841eece2..52f715ec5 100644 --- a/conf.py +++ b/conf.py @@ -52,7 +52,7 @@ def document_scripts(): """ # First, we collect the commands and paths to include in our docs scripts = [] - for root, _, files in os.walk('scripts'): + for root, _, files in os.walk('library/scripts'): scripts.extend(doc_dir(root, files)) # Next we split by type and create include directives sorted by command kinds = {'base': [], 'devel': [], 'fix': [], 'gui': [], 'modtools': []} @@ -84,7 +84,7 @@ def write_script_docs(): 'modtools': 'Scripts for Modders'} for k in head: title = ('.. _{k}:\n\n{l}\n{t}\n{l}\n\n' - '.. include:: /scripts/{a}about.txt\n\n' + '.. include:: /library/scripts/{a}about.txt\n\n' '.. contents::\n\n').format( k=k, t=head[k], l=len(head[k])*'#', @@ -179,7 +179,6 @@ exclude_patterns = [ 'README.md', 'docs/html*', 'depends/*', - 'scripts/3rdparty/*', 'build*', ] diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 54300ec8d..3a6cd4dc8 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -434,3 +434,5 @@ if(BUILD_DEVEL) add_subdirectory (doc) ENDIF() endif() + +add_subdirectory(scripts) diff --git a/library/scripts b/library/scripts new file mode 160000 index 000000000..b67885c0c --- /dev/null +++ b/library/scripts @@ -0,0 +1 @@ +Subproject commit b67885c0c07931a402a3b642a4e2468f14782058 diff --git a/scripts b/scripts deleted file mode 160000 index d4fbac033..000000000 --- a/scripts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d4fbac0333fd76829a3eaefad6d326f2136dbf86 From 0918efd02d2ebe161d4170cb37eb505c6b73d535 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 16 Jun 2016 20:52:16 +0530 Subject: [PATCH 0066/1012] fix word_wrap not returning a value. --- library/MiscUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 43fb5ef48..5348086c1 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -151,6 +151,7 @@ bool word_wrap(std::vector *out, const std::string &str, size_t lin if (out_line.length()) out->push_back(out_line); } + return true; } bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail) From 980be6e2af018e8fb5df715c40b6e222f182b01b Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 16 Jun 2016 21:36:35 +0530 Subject: [PATCH 0067/1012] Added a cleaner format for sending over region info. --- plugins/proto/RemoteFortressReader.proto | 25 +++++ plugins/remotefortressreader.cpp | 130 ++++++++++++++++++++++- 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 36ca4fd90..ffb67aabf 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -401,9 +401,34 @@ message WorldMap repeated int32 water_elevation = 24; } +message RegionTile +{ + optional int32 elevation = 1; + optional int32 rainfall = 2; + optional int32 vegetation = 3; + optional int32 temperature = 4; + optional int32 evilness = 5; + optional int32 drainage = 6; + optional int32 volcanism = 7; + optional int32 savagery = 8; + optional int32 salinity = 9; + optional RiverTile river_tiles = 10; + optional int32 water_elevation = 11; +} + +message RegionMap +{ + optional int32 map_x = 1; + optional int32 map_y = 2; + optional string name = 3; + optional string name_english = 4; + repeated RegionTile tiles = 5; +} + message RegionMaps { repeated WorldMap world_maps = 1; + repeated RegionMap region_maps = 2; } message CasteRaw diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6a227cb2a..bb2af88f8 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -176,6 +176,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetBuildingDefList", GetBuildingDefList); svc->addFunction("GetWorldMap", GetWorldMap); svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetRegionMapsNew", GetRegionMaps); svc->addFunction("GetCreatureRaws", GetCreatureRaws); svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); svc->addFunction("GetPlantRaws", GetPlantRaws); @@ -1878,12 +1879,26 @@ static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_ out->add_savagery(e1->savagery); out->add_salinity(e1->salinity); if (region->type == world_region_type::Lake) - { out->add_water_elevation(region->lake_surface); - } else out->add_water_elevation(99); +} +static void AddRegionTiles(RegionTile * out, df::region_map_entry * e1, df::world_data * worldData) +{ + df::world_region * region = worldData->regions[e1->region_id]; + out->set_rainfall(e1->rainfall); + out->set_vegetation(e1->vegetation); + out->set_temperature(e1->temperature); + out->set_evilness(e1->evilness); + out->set_drainage(e1->drainage); + out->set_volcanism(e1->volcanism); + out->set_savagery(e1->savagery); + out->set_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->set_water_elevation(region->lake_surface); + else + out->set_water_elevation(99); } static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) @@ -1899,6 +1914,19 @@ static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * wor AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); } +static void AddRegionTiles(RegionTile * out, df::coord2d pos, df::world_data * worldData) +{ + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); +} + static df::coord2d ShiftCoords(df::coord2d source, int direction) { switch (direction) @@ -2032,6 +2060,86 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w } } +static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, RegionMap * out) +{ + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); + + + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (int i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + auto tile = out->add_tiles(); + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + tile->set_elevation(southEast->elevation[0][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + tile->set_elevation(east->elevation[0][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + tile->set_elevation(south->elevation[xx][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + tile->set_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + auto riverTile = tile->mutable_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } +} static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) { @@ -2051,6 +2159,24 @@ static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *i return CR_OK; } +static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) +{ + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (int i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + WorldMap * regionMap = out->add_world_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; +} + static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) { if (!df::global::world) From a7d5c2132aab224d3f83a9dd991359626779e443 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 16 Jun 2016 23:37:01 -0400 Subject: [PATCH 0068/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 7912dbfd3..b9178de68 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7912dbfd3602b9743e7f000d25bfe8f5ecf93e08 +Subproject commit b9178de68bd67442ff720f18b04d222302ce9f8c From 36aafd550ba5a5d32b6d4df2e9a238455b52c726 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 20 Jun 2016 13:17:30 +0530 Subject: [PATCH 0069/1012] Increased the message size limit in RemoteClient from 8mb to 64mb. --- library/include/RemoteClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 1e8264cec..e1a2c512a 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -66,7 +66,7 @@ namespace DFHack }; struct RPCMessageHeader { - static const int MAX_MESSAGE_SIZE = 8*1048576; + static const int MAX_MESSAGE_SIZE = 64*1048576; int16_t id; int32_t size; From 9ef9a6b519fd696034c7205ae340948f26727ebf Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 20 Jun 2016 13:21:11 +0530 Subject: [PATCH 0070/1012] Send bodypart raws from RemoteFortressReader --- plugins/proto/RemoteFortressReader.proto | 17 ++++ plugins/remotefortressreader.cpp | 100 +++++++++++++++++++++-- 2 files changed, 108 insertions(+), 9 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index ffb67aabf..939858e83 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -431,6 +431,21 @@ message RegionMaps repeated RegionMap region_maps = 2; } +message BodyPartLayerRaw +{ + optional string layer_name = 1; +} + +message BodyPartRaw +{ + optional string token = 1; + optional string category = 2; + optional int32 parent = 3; + repeated bool flags = 4; + repeated BodyPartLayerRaw layers = 5; + optional int32 relsize = 6; +} + message CasteRaw { optional int32 index = 1; @@ -439,6 +454,8 @@ message CasteRaw repeated string baby_name = 4; repeated string child_name = 5; optional int32 gender = 6; + repeated BodyPartRaw body_parts = 7; + optional int32 total_relsize = 8; } message CreatureRaw diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index bb2af88f8..54b3f7d65 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -66,6 +66,9 @@ #include "df/viewscreen_choose_start_sitest.h" +#include "df/bp_appearance_modifier.h" +#include "df/body_part_layer_raw.h" + //DFhack specific headers #include "modules/Maps.h" #include "modules/MapCache.h" @@ -84,6 +87,7 @@ #include #include +#include #include "RemoteFortressReader.pb.h" @@ -143,19 +147,66 @@ const char* growth_locations[] = { }; #define GROWTH_LOCATIONS_SIZE 8 +command_result dump_bp_mods(color_ostream &out, vector & parameters) +{ + remove("bp_appearance_mods.csv"); + ofstream output; + output.open("bp_appearance_mods.csv"); + + output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; + + for (int creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) + { + auto creatureRaw = world->raws.creatures.all[creatureIndex]; + for (int casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) + { + df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; + for (int partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) + { + output << creatureIndex << ";"; + output << creatureRaw->creature_id << ";"; + output << casteRaw->caste_id << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";"; + int layer = casteRaw->bp_appearance.layer_idx[partIndex]; + if (layer < 0) + output << "N/A;"; + else + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; + output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; + auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; + if (appMod->growth_rate > 0) + { + output << appMod->growth_min << " - " << appMod->growth_max << "\n"; + } + else + { + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; + } + } + } + } + + output.close(); + + return CR_OK; +} + + // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { //// Fill the command list with your commands. - //commands.push_back(PluginCommand( - // "isoworldremote", "Dump north-west embark tile to text file for debug purposes.", - // isoWorldRemote, false, /* true means that the command can't be used from non-interactive user interface */ - // // Extended help string. Used by CR_WRONG_USAGE and the help command: - // " This command does nothing at all.\n" - // "Example:\n" - // " isoworldremote\n" - // " Does nothing.\n" - //)); + commands.push_back(PluginCommand( + "dump_bp_mods", "Dump bodypart mods for debugging", + dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " isoworldremote\n" + " Does nothing.\n" + )); return CR_OK; } @@ -2230,6 +2281,37 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_caste->add_child_name(orig_caste->child_name[0]); send_caste->add_child_name(orig_caste->child_name[1]); send_caste->set_gender(orig_caste->gender); + + for (int partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) + { + auto orig_part = orig_caste->body_info.body_parts[partIndex]; + if (!orig_part) + continue; + auto send_part = send_caste->add_body_parts(); + + send_part->set_token(orig_part->token); + send_part->set_category(orig_part->category); + send_part->set_parent(orig_part->con_part_id); + + for (int partFlagIndex = 0; partFlagIndex <= ENUM_LAST_ITEM(body_part_raw_flags); partFlagIndex++) + { + send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); + } + + for (int layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) + { + auto orig_layer = orig_part->layers[layerIndex]; + if (!orig_layer) + continue; + auto send_layer = send_part->add_layers(); + + send_layer->set_layer_name(orig_layer->layer_name); + } + + send_part->set_relsize(orig_part->relsize); + } + + send_caste->set_total_relsize(orig_caste->body_info.total_relsize); } } From 5fff0efec5321109845ae99ddc472aa9fe4739bb Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 20 Jun 2016 22:59:59 +0530 Subject: [PATCH 0071/1012] Send appearance info over remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 21 +++++++++++++++++++++ plugins/remotefortressreader.cpp | 19 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 939858e83..327398c4a 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -253,6 +253,14 @@ message BodySizeInfo optional int32 length_base = 6; /*!< (size_base*10000)^0.333 */ } +message UnitAppearance +{ + repeated int32 body_modifiers = 1; + repeated int32 bp_modifiers = 2; + optional int32 size_modifier = 3; + +} + message UnitDefinition { required int32 id = 1; @@ -270,6 +278,7 @@ message UnitDefinition optional string name = 13; optional int32 blood_max = 14; optional int32 blood_count = 15; + } message UnitList @@ -434,6 +443,9 @@ message RegionMaps message BodyPartLayerRaw { optional string layer_name = 1; + optional int32 tissue_id = 2; + optional int32 layer_depth = 3; + repeated int32 bp_modifiers = 4; } message BodyPartRaw @@ -446,6 +458,11 @@ message BodyPartRaw optional int32 relsize = 6; } +message BpAppearanceModifier +{ + optional string type = 1; +} + message CasteRaw { optional int32 index = 1; @@ -456,6 +473,10 @@ message CasteRaw optional int32 gender = 6; repeated BodyPartRaw body_parts = 7; optional int32 total_relsize = 8; + repeated BpAppearanceModifier modifiers = 9; + repeated int32 modifier_idx = 10; + repeated int32 part_idx = 11; + repeated int32 layer_idx = 12; } message CreatureRaw diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 54b3f7d65..8c7747921 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -2306,12 +2306,31 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage auto send_layer = send_part->add_layers(); send_layer->set_layer_name(orig_layer->layer_name); + send_layer->set_tissue_id(orig_layer->tissue_id); + send_layer->set_layer_depth(orig_layer->layer_depth); + for (int layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) + { + send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); + } } send_part->set_relsize(orig_part->relsize); } send_caste->set_total_relsize(orig_caste->body_info.total_relsize); + + for (int k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) + { + auto send_mod = send_caste->add_modifiers(); + auto orig_mod = orig_caste->bp_appearance.modifiers[k]; + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + } + for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) + { + send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); + send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); + send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); + } } } From 069d6b67d69e723a58a2f4083e99aa22c06ec281 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 21 Jun 2016 22:42:47 +0530 Subject: [PATCH 0072/1012] Send all size modifiers through remotefortressreader. --- plugins/proto/RemoteFortressReader.proto | 37 ++++++++++++++++++++-- plugins/remotefortressreader.cpp | 40 ++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 327398c4a..8f8d1b923 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -258,7 +258,7 @@ message UnitAppearance repeated int32 body_modifiers = 1; repeated int32 bp_modifiers = 2; optional int32 size_modifier = 3; - + repeated int32 colors = 4; } message UnitDefinition @@ -278,7 +278,7 @@ message UnitDefinition optional string name = 13; optional int32 blood_max = 14; optional int32 blood_count = 15; - + optional UnitAppearance appearance = 16; } message UnitList @@ -440,6 +440,33 @@ message RegionMaps repeated RegionMap region_maps = 2; } +enum PatternType +{ + MONOTONE = 0; + STRIPES = 1; + IRIS_EYE = 2; + SPOTS = 3; + PUPIL_EYE = 4; + MOTTLED = 5; +} + +message PatternDescriptor +{ + optional string id = 1; + repeated ColorDefinition colors = 2; + optional PatternType pattern = 3; +} + +message ColorModifierRaw +{ + repeated PatternDescriptor patterns = 1; + repeated int32 body_part_id = 2; + repeated int32 tissue_layer_id = 3; + optional int32 start_date = 4; + optional int32 end_date = 5; + optional string part = 6; +} + message BodyPartLayerRaw { optional string layer_name = 1; @@ -461,6 +488,8 @@ message BodyPartRaw message BpAppearanceModifier { optional string type = 1; + optional int32 mod_min = 2; + optional int32 mod_max = 3; } message CasteRaw @@ -477,6 +506,8 @@ message CasteRaw repeated int32 modifier_idx = 10; repeated int32 part_idx = 11; repeated int32 layer_idx = 12; + repeated BpAppearanceModifier body_appearance_modifiers = 13; + repeated ColorModifierRaw color_modifiers = 14; } message CreatureRaw @@ -580,4 +611,4 @@ message KeyboardEvent optional uint32 sym = 5; optional uint32 mod = 6; optional uint32 unicode = 7; -} \ No newline at end of file +} diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 8c7747921..af39fbe76 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -68,6 +68,7 @@ #include "df/bp_appearance_modifier.h" #include "df/body_part_layer_raw.h" +#include "df/body_appearance_modifier.h" //DFhack specific headers #include "modules/Maps.h" @@ -1512,6 +1513,15 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, { send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); } + + auto appearance = send_unit->mutable_appearance(); + for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) + appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); + for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) + appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); + for (int j = 0; j < unit->appearance.colors.size(); j++) + appearance->add_colors(unit->appearance.colors[j]); + appearance->set_size_modifier(unit->appearance.size_modifier); } return CR_OK; } @@ -2324,6 +2334,18 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage auto send_mod = send_caste->add_modifiers(); auto orig_mod = orig_caste->bp_appearance.modifiers[k]; send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + } for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) { @@ -2331,6 +2353,24 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); } + for (int k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) + { + auto send_mod = send_caste->add_body_appearance_modifiers(); + auto orig_mod = orig_caste->body_appearance_modifiers[k]; + + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + } } } From 74c9abcb4733eac5763dcd384f202717e08a17a1 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 22 Jun 2016 13:36:36 +0530 Subject: [PATCH 0073/1012] Send color modifier raws through remotefortressreader.cpp --- plugins/remotefortressreader.cpp | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index af39fbe76..04e5a994e 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -50,6 +50,8 @@ #include "df/physical_attribute_type.h" #include "df/mental_attribute_type.h" #include "df/color_modifier_raw.h" +#include "df/descriptor_color.h" +#include "df/descriptor_pattern.h" #include "df/region_map_entry.h" #include "df/world_region_details.h" @@ -2371,6 +2373,38 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_mod->set_mod_max(orig_mod->ranges[6]); } } + for (int k = 0; k < orig_caste->color_modifiers.size(); k++) + { + auto send_mod = send_caste->add_color_modifiers(); + auto orig_mod = orig_caste->color_modifiers[k]; + + for (int l = 0; l < orig_mod->pattern_index.size(); l++) + { + auto orig_pattern = world->raws.language.patterns[orig_mod->pattern_index[l]]; + auto send_pattern = send_mod->add_patterns(); + + for (int m = 0; m < orig_pattern->colors.size(); m++) + { + auto send_color = send_pattern->add_colors(); + auto orig_color = world->raws.language.colors[orig_pattern->colors[m]]; + send_color->set_red(orig_color->red * 255.0); + send_color->set_green(orig_color->green * 255.0); + send_color->set_blue(orig_color->blue * 255.0); + } + + send_pattern->set_id(orig_pattern->id); + send_pattern->set_pattern((PatternType)orig_pattern->pattern); + } + + for (int l = 0; l < orig_mod->body_part_id.size(); l++) + { + send_mod->add_body_part_id(orig_mod->body_part_id[l]); + send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); + send_mod->set_start_date(orig_mod->start_date); + send_mod->set_end_date(orig_mod->end_date); + send_mod->set_part(orig_mod->part); + } + } } } From 036c0f787c0feb6246326276862c79f562bd2e1d Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 23 Jun 2016 21:26:00 +0530 Subject: [PATCH 0074/1012] Send creature adult size through remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 3 +++ plugins/remotefortressreader.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 8f8d1b923..b7ca674fb 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -508,6 +508,8 @@ message CasteRaw repeated int32 layer_idx = 12; repeated BpAppearanceModifier body_appearance_modifiers = 13; repeated ColorModifierRaw color_modifiers = 14; + optional string description = 15; + optional int32 adult_size = 16; } message CreatureRaw @@ -612,3 +614,4 @@ message KeyboardEvent optional uint32 mod = 6; optional uint32 unicode = 7; } + \ No newline at end of file diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 04e5a994e..fe8aae53e 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -2405,6 +2405,9 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_mod->set_part(orig_mod->part); } } + + send_caste->set_description(orig_caste->description); + send_caste->set_adult_size(orig_caste->misc.adult_size); } } From 060331de37951d4fa47c793758e195cd8f6eddd6 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 26 Jun 2016 19:22:53 -0600 Subject: [PATCH 0075/1012] Stockflow reaction updates SpinThread jobs now get the proper material category flag, and metal mechanisms can be ordered. --- plugins/lua/stockflow.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 4d7889843..6027bb455 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -396,7 +396,7 @@ function collect_reactions() reaction_entry(result, job_types.MakeCheese) reaction_entry(result, job_types.MilkCreature) reaction_entry(result, job_types.ShearCreature) - reaction_entry(result, job_types.SpinThread) + reaction_entry(result, job_types.SpinThread, {material_category = {strand = true}}) reaction_entry(result, job_types.MakeLye) reaction_entry(result, job_types.ProcessPlants) reaction_entry(result, job_types.ProcessPlantsBag) @@ -538,6 +538,7 @@ function collect_reactions() {job_types.MakeEarring, "Make", "Earring"}, {job_types.MakeBracelet, "Make", "Bracelet"}, {job_types.MakeGem, "Make Large", "Gem"}, + {job_types.ConstructMechanisms, "Construct", "Mechanisms"}, }, mat_flags) end From 261da6666086bdc735aee95fd8c0341c868ccf7a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 26 Jun 2016 23:39:10 -0400 Subject: [PATCH 0076/1012] Update ab9rf's name --- docs/Authors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index 64dec0e9c..ee6f80390 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -46,7 +46,7 @@ John Beisley huin John Shade gsvslto Jonas Ask kane-t kane-t -Kelly Martin ab9rf +Kelly Kinkade ab9rf Kris Parker kaypy Kurik Amudnil Lethosor lethosor From 2072dcc38d9cadf747006223f12018ef16056221 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Jun 2016 12:04:51 -0500 Subject: [PATCH 0077/1012] Rename autolabor2 to labormanager and bring up to date with current --- plugins/devel/CMakeLists.txt | 2 +- .../{autolabor2.cpp => labormanager.cpp} | 487 +++++++++++++----- 2 files changed, 354 insertions(+), 135 deletions(-) rename plugins/devel/{autolabor2.cpp => labormanager.cpp} (86%) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 1f3ff6f75..a1e5b7f14 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -3,13 +3,13 @@ DFHACK_PLUGIN(vectors vectors.cpp) endif() ADD_DEFINITIONS(-DDEV_PLUGIN) -#DFHACK_PLUGIN(autolabor2 autolabor2.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(color-dfhack-text color-dfhack-text.cpp) DFHACK_PLUGIN(counters counters.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) +DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(kittens kittens.cpp) DFHACK_PLUGIN(memview memview.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) diff --git a/plugins/devel/autolabor2.cpp b/plugins/devel/labormanager.cpp similarity index 86% rename from plugins/devel/autolabor2.cpp rename to plugins/devel/labormanager.cpp index 4897832d4..12e4e8ca1 100644 --- a/plugins/devel/autolabor2.cpp +++ b/plugins/devel/labormanager.cpp @@ -1,5 +1,5 @@ /* -* Autolabor 2.0 module for dfhack +* Labor manager (formerly Autolabor 2) module for dfhack * * */ @@ -77,7 +77,7 @@ using df::global::world; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) -DFHACK_PLUGIN_IS_ENABLED(enable_autolabor); +DFHACK_PLUGIN_IS_ENABLED(enable_labormanager); static bool print_debug = 0; @@ -94,11 +94,11 @@ enum ConfigFlags { // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom -command_result autolabor (color_ostream &out, std::vector & parameters); +command_result labormanager (color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - autolabor2.plug.so or autolabor2.plug.dll in this case -DFHACK_PLUGIN("autolabor2"); +// The name string provided must correspond to the filename - labormanager.plug.so or labormanager.plug.dll in this case +DFHACK_PLUGIN("labormanager"); static void generate_labor_to_skill_map(); @@ -486,9 +486,20 @@ static const struct labor_default default_labor_infos[] = { /* PRESSING */ {200, 0, TOOL_NONE}, /* BEEKEEPING */ {200, 0, TOOL_NONE}, /* WAX_WORKING */ {200, 0, TOOL_NONE}, - /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE} + /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE}, + /* HAUL_TRADE */ {200, 0, TOOL_NONE}, + /* PULL_LEVER */ {200, 0, TOOL_NONE}, + /* REMOVE_CONSTRUCTION */ {200, 0, TOOL_NONE}, + /* HAUL_WATER */ {200, 0, TOOL_NONE}, + /* GELD */ {200, 0, TOOL_NONE}, + /* BUILD_ROAD */ {200, 0, TOOL_NONE}, + /* BUILD_CONSTRUCTION */ {200, 0, TOOL_NONE}, + /* PAPERMAKING */ {200, 0, TOOL_NONE}, + /* BOOKBINDING */ {200, 0, TOOL_NONE} }; +void debug (char* fmt, ...); + struct dwarf_info_t { df::unit* dwarf; @@ -506,12 +517,19 @@ struct dwarf_info_t df::unit_labor using_labor; dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), - state(OTHER), high_skill(0), has_children(false), armed(false) + state(OTHER), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) has_tool[e] = false; } + ~dwarf_info_t() + { + if (print_debug) + debug("LABORMANAGER: destroying dwarf %p\n", (void*) this); + } + + void set_labor(df::unit_labor labor) { if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) @@ -554,10 +572,10 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ df::unit_labor::HAUL_ITEM, /* TOY */ df::unit_labor::HAUL_FURNITURE, /* WINDOW */ - df::unit_labor::HAUL_ANIMAL, /* CAGE */ + df::unit_labor::HAUL_ANIMALS, /* CAGE */ df::unit_labor::HAUL_ITEM, /* BARREL */ df::unit_labor::HAUL_ITEM, /* BUCKET */ - df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ + df::unit_labor::HAUL_ANIMALS, /* ANIMALTRAP */ df::unit_labor::HAUL_FURNITURE, /* TABLE */ df::unit_labor::HAUL_FURNITURE, /* COFFIN */ df::unit_labor::HAUL_FURNITURE, /* STATUE */ @@ -850,7 +868,7 @@ private: return df::unit_labor::SIEGECRAFT; } - debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); return df::unit_labor::NONE; @@ -944,7 +962,7 @@ private: return df::unit_labor::SIEGECRAFT; } - debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); return df::unit_labor::NONE; @@ -979,13 +997,13 @@ private: return df::unit_labor::BONE_CARVE; else { - debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); + debug ("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); return df::unit_labor::NONE; } case df::item_type::WOOD: return df::unit_labor::WOOD_CRAFT; default: - debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); return df::unit_labor::NONE; } @@ -1002,7 +1020,7 @@ private: case df::workshop_type::MetalsmithsForge: return metaltype; default: - debug ("AUTOLABOR: Cannot deduce labor for make job, workshop type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", ENUM_KEY_STR(workshop_type, type).c_str()); return df::unit_labor::NONE; } @@ -1016,13 +1034,13 @@ private: case df::furnace_type::GlassFurnace: return df::unit_labor::GLASSMAKER; default: - debug ("AUTOLABOR: Cannot deduce labor for make job, furnace type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", ENUM_KEY_STR(furnace_type, type).c_str()); return df::unit_labor::NONE; } } - debug ("AUTOLABOR: Cannot deduce labor for make job, building type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); return df::unit_labor::NONE; @@ -1147,8 +1165,6 @@ public: job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInChest] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInCabinet] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; @@ -1219,7 +1235,6 @@ public: job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsBag] = jlf_const(df::unit_labor::PROCESS_PLANT); job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); @@ -1256,7 +1271,6 @@ public: job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; - job_to_labor_table[df::job_type::BrewDrink] = jlf_const(df::unit_labor::BREWER) ; job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; @@ -1334,9 +1348,9 @@ public: job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; @@ -1365,7 +1379,14 @@ public: df::unit_labor labor; - labor = job_to_labor_table[j->job_type]->get_labor(j); + if (job_to_labor_table.count(j->job_type) == 0) + { + debug("LABORMANAGER: job has no job to labor table entry: %s\n", ENUM_KEY_STR(job_type, j->job_type).c_str()); + labor = df::unit_labor::NONE; + } else { + + labor = job_to_labor_table[j->job_type]->get_labor(j); + } return labor; } @@ -1375,6 +1396,8 @@ public: static JobLaborMapper* labor_mapper = 0; +static bool initialized = false; + static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -1393,8 +1416,9 @@ static void setOptionEnabled(ConfigFlags flag, bool on) static void cleanup_state() { - enable_autolabor = false; + enable_labormanager = false; labor_infos.clear(); + initialized = false; } static void reset_labor(df::unit_labor labor) @@ -1405,25 +1429,25 @@ static void reset_labor(df::unit_labor labor) static void init_state() { - config = World::GetPersistentData("autolabor/2.0/config"); + config = World::GetPersistentData("labormanager/2.0/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; - enable_autolabor = isOptionEnabled(CF_ENABLED); + enable_labormanager = isOptionEnabled(CF_ENABLED); - if (!enable_autolabor) + if (!enable_labormanager) return; // Load labors from save labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - World::GetPersistentData(&items, "autolabor/2.0/labors/", true); + World::GetPersistentData(&items, "labormanager/2.0/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); - df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/2.0/labors/")).c_str()); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("labormanager/2.0/labors/")).c_str()); if (labor >= 0 && labor <= labor_infos.size()) { labor_infos[labor].config = *p; @@ -1437,7 +1461,7 @@ static void init_state() continue; std::stringstream name; - name << "autolabor/2.0/labors/" << i; + name << "labormanager/2.0/labors/" << i; labor_infos[i].config = World::AddPersistentData(name.str()); labor_infos[i].mark_assigned(); @@ -1445,6 +1469,8 @@ static void init_state() reset_labor((df::unit_labor) i); } + initialized = true; + } static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; @@ -1477,12 +1503,12 @@ static void enable_plugin(color_ostream &out) { if (!config.isValid()) { - config = World::AddPersistentData("autolabor/2.0/config"); + config = World::AddPersistentData("labormanager/2.0/config"); config.ival(0) = 0; } setOptionEnabled(CF_ENABLED, true); - enable_autolabor = true; + enable_labormanager = true; out << "Enabling the plugin." << endl; cleanup_state(); @@ -1497,32 +1523,32 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" + " labormanager max \n" " Set max number of dwarves assigned to a labor.\n" - " autolabor2 max none\n" + " labormanager max none\n" " Unrestrict the number of dwarves assigned to a labor.\n" - " autolabor2 priority \n" + " labormanager priority \n" " Change the assignment priority of a labor (default is 100)\n" - " autolabor2 reset \n" + " labormanager reset \n" " Return a labor to the default handling.\n" - " autolabor2 reset-all\n" + " labormanager reset-all\n" " Return all labors to the default handling.\n" - " autolabor2 list\n" + " labormanager list\n" " List current status of all labors.\n" - " autolabor2 status\n" + " labormanager status\n" " Show basic status information.\n" "Function:\n" - " When enabled, autolabor periodically checks your dwarves and enables or\n" + " When enabled, labormanager periodically checks your dwarves and enables or\n" " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" - " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled. Do not try to run both autolabor and autolabor2 at\n" - " the same time." + " Warning: labormanager will override any manual changes you make to labors\n" + " while it is enabled. Do not try to run both labormanager and labormanager at\n" + " the same time.\n" )); generate_labor_to_skill_map(); @@ -1594,14 +1620,21 @@ private: int need_food_water; + int priority_food; + std::map labor_needed; std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; + std::list busy_dwarfs; private: void scan_buildings() { + has_butchers = false; + has_fishery = false; + trader_requested = false; + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) { df::building *build = *b; @@ -1658,16 +1691,14 @@ private: if (dig != df::enums::tile_dig_designation::No) { df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_material ttm = ENUM_ATTR(tiletype, material, tt); df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); - switch (tts) - { - case df::enums::tiletype_shape::TREE: - tree_count++; break; - case df::enums::tiletype_shape::SHRUB: - plant_count++; break; - default: - dig_count++; break; - } + if (ttm == df::enums::tiletype_material::TREE) + tree_count++; + else if (tts == df::enums::tiletype_shape::SHRUB) + plant_count++; + else + dig_count++; } if (bl->designation[x][y].bits.smooth != 0) detail_count++; @@ -1684,13 +1715,15 @@ private: for (int e = TOOL_NONE; e < TOOLS_MAX; e++) tool_count[e] = 0; + priority_food = 0; + df::item_flags bad_flags; bad_flags.whole = 0; #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); + F(in_building); F(construction); #undef F auto& v = world->items.all; @@ -1704,6 +1737,11 @@ private: if (item->flags.whole & bad_flags.whole) continue; + df::item_type t = item->getType(); + + if (item->materialRots() && t != df::item_type::CORPSEPIECE && t != df::item_type::CORPSE && item->getRotTimer() > 1) + priority_food++; + if (!item->isWeapon()) continue; @@ -1869,14 +1907,20 @@ private: { state = CHILD; } + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) state = MILITARY; + + else if (dwarf->dwarf->burrows.size() > 0) + state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + else if (dwarf->dwarf->job.current_job == NULL) { - if (is_on_break) + if (is_on_break || dwarf->dwarf->flags1.bits.chained || dwarf->dwarf->flags1.bits.caged) + { state = OTHER; - else if (dwarf->dwarf->burrows.size() > 0) - state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + dwarf->clear_all = true; + } else if (dwarf->dwarf->status2.limbs_grasp_count == 0) { state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors @@ -1902,18 +1946,23 @@ private: if (state == BUSY) { df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); + + dwarf->using_labor = labor; + if (labor != df::unit_labor::NONE) { - dwarf->using_labor = labor; + labor_infos[labor].busy_dwarfs++; - if (!dwarf->dwarf->status.labors[labor] && print_debug) + if (!dwarf->dwarf->status.labors[labor]) { - out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", + out.print("LABORMANAGER: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); } } } + if (state == OTHER) + dwarf->clear_all = true; } dwarf->state = state; @@ -1925,8 +1974,6 @@ private: if (dwarf->dwarf->status.labors[l]) if (state == IDLE) labor_infos[l].idle_dwarfs++; - else if (state == BUSY) - labor_infos[l].busy_dwarfs++; } @@ -2022,19 +2069,69 @@ private: dwarf->clear_labor(labor); } + } else { + if (state == IDLE) + available_dwarfs.push_back(dwarf); + + if (state == BUSY) + busy_dwarfs.push_back(dwarf); } - else if (state == IDLE || state == BUSY) - available_dwarfs.push_back(dwarf); + } + + } + } + void release_dwarf_list() + { + while (!dwarf_info.empty()) { + auto d = dwarf_info.begin(); + delete *d; + dwarf_info.erase(d); + } + available_dwarfs.clear(); + busy_dwarfs.clear(); + } + + int score_labor (dwarf_info_t* d, df::unit_labor labor) + { + int skill_level = 0; + int xp = 0; + + if (labor != df::unit_labor::NONE) + { + df::job_skill skill = labor_to_skill[labor]; + if (skill != df::job_skill::NONE) + { + skill_level = Units::getEffectiveSkill(d->dwarf, skill); + xp = Units::getExperience(d->dwarf, skill, false); } + } + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); + + if (labor != df::unit_labor::NONE) + { + if (d->dwarf->status.labors[labor]) + if (labor == df::unit_labor::OPERATE_PUMP) + score += 50000; + else + score += 1000; + if (default_labor_infos[labor].tool != TOOL_NONE && + d->has_tool[default_labor_infos[labor].tool]) + score += 5000; + if (d->has_children && labor_outside[labor]) + score -= 15000; + if (d->armed && labor_outside[labor]) + score += 5000; } + + return score; } public: void process() { - dwarf_info.clear(); + release_dwarf_list(); dig_count = tree_count = plant_count = detail_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = @@ -2106,13 +2203,27 @@ public: labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; - labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; + labor_needed[df::unit_labor::HAUL_ANIMALS] += world->stockpile.num_jobs[9]; + + labor_needed[df::unit_labor::HAUL_STONE] += (world->stockpile.num_jobs[1] >= world->stockpile.num_haulers[1]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_WOOD] += (world->stockpile.num_jobs[2] >= world->stockpile.num_haulers[2]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ITEM] += (world->stockpile.num_jobs[3] >= world->stockpile.num_haulers[3]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_BODY] += (world->stockpile.num_jobs[5] >= world->stockpile.num_haulers[5]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FOOD] += (world->stockpile.num_jobs[6] >= world->stockpile.num_haulers[6]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_REFUSE] += (world->stockpile.num_jobs[7] >= world->stockpile.num_haulers[7]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FURNITURE] += (world->stockpile.num_jobs[8] >= world->stockpile.num_haulers[8]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ANIMALS] += (world->stockpile.num_jobs[9] >= world->stockpile.num_haulers[9]) ? 1 : 0; + + int binjobs = world->stockpile.num_jobs[4] + ((world->stockpile.num_jobs[4] >= world->stockpile.num_haulers[4]) ? 1 : 0); + + labor_needed[df::unit_labor::HAUL_ITEM] += binjobs; + labor_needed[df::unit_labor::HAUL_FOOD] += priority_food; // add entries for vehicle hauling for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) if ((*v)->route_id != -1) - labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + labor_needed[df::unit_labor::HANDLE_VEHICLES]++; // add fishing & hunting @@ -2131,33 +2242,101 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } + /* move idle dwarfs ready to be assigned to busy list */ + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); ) + { + bool busy = false; + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + + if (labor_needed[l] > 0 && (*d)->dwarf->status.labors[l]) + { + busy = true; + labor_needed[l] = max(labor_needed[l]-1, 0); + } + } + + if (busy) + { + busy_dwarfs.push_back(*d); + d = available_dwarfs.erase(d); + } else { + d++; + } + } + /* adjust for over/under */ FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL) - continue; - if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) + if (labor_infos[l].idle_dwarfs > 0) + labor_needed[l] = 0; + else + labor_needed[l] = std::max(labor_needed[l] - labor_infos[l].busy_dwarfs, 0); + } + + /* assign food haulers for rotting food items */ + + if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) + priority_food = 1; + + if (print_debug) + out.print ("priority food count = %d\n", priority_food); + + while (!available_dwarfs.empty() && priority_food > 0) + { + std::list::iterator bestdwarf = available_dwarfs.begin(); + + int best_score = INT_MIN; + + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { - int clawback = labor_infos[l].busy_dwarfs; - if (clawback == 0 && labor_needed[l] > 0) - clawback = 1; + dwarf_info_t* d = (*k); + int score = score_labor(d, df::unit_labor::HAUL_FOOD); + + if (score > best_score) + { + bestdwarf = k; + best_score = score; + } + } + + if (best_score > INT_MIN) + { if (print_debug) - out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), - clawback, - labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); - labor_needed[l] = clawback; + out.print("LABORMANAGER: assign \"%s\" labor %s score=%d (priority food)\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, df::unit_labor::HAUL_FOOD).c_str(), best_score); + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + if (l == df::unit_labor::HAUL_FOOD) + (*bestdwarf)->set_labor(l); + else + (*bestdwarf)->clear_labor(l); + } + + available_dwarfs.erase(bestdwarf); + priority_food--; } + else + break; + } if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, - labor_outside[i->first], labor_infos[i->first].idle_dwarfs); + out.print ("labor_needed [%s] = %d, busy = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_infos[i->first].busy_dwarfs, labor_outside[i->first], labor_infos[i->first].idle_dwarfs); } } @@ -2173,6 +2352,8 @@ public: { int priority = labor_infos[l].priority(); priority += labor_infos[l].time_since_last_assigned()/12; + for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) + priority /= 2; pq.push(make_pair(priority, l)); } } @@ -2212,7 +2393,7 @@ public: (1 << df::unit_labor::HAUL_REFUSE) | (1 << df::unit_labor::HAUL_ITEM) | (1 << df::unit_labor::HAUL_FURNITURE) | - (1 << df::unit_labor::HAUL_ANIMAL); + (1 << df::unit_labor::HAUL_ANIMALS); while (!available_dwarfs.empty()) { @@ -2227,36 +2408,11 @@ public: continue; df::unit_labor labor = j->first; - df::job_skill skill = labor_to_skill[labor]; for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { dwarf_info_t* d = (*k); - int skill_level = 0; - int xp = 0; - if (skill != df::job_skill::NONE) - { - skill_level = Units::getEffectiveSkill(d->dwarf, skill); - xp = Units::getExperience(d->dwarf, skill, false); - } - int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); - if (d->dwarf->status.labors[labor]) - if (labor == df::unit_labor::OPERATE_PUMP) - score += 50000; - else - score += 1000; - if (default_labor_infos[labor].tool != TOOL_NONE && - d->has_tool[default_labor_infos[labor].tool]) - score += 5000; - if (d->has_children && labor_outside[labor]) - score -= 15000; - if (d->armed && labor_outside[labor]) - score += 5000; - if (d->state == BUSY) - if (d->using_labor == labor) - score += 7500; - else - score -= 7500; + int score = score_labor(d, labor); if (score > best_score) { bestdwarf = k; @@ -2289,11 +2445,15 @@ public: (*bestdwarf)->clear_labor(l); } - if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) + if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) + priority_food--; + + if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMALS) canary &= ~(1 << best_labor); if (best_labor != df::unit_labor::NONE) { + busy_dwarfs.push_back(*bestdwarf); labor_infos[best_labor].active_dwarfs++; to_assign[best_labor]--; } @@ -2301,17 +2461,71 @@ public: available_dwarfs.erase(bestdwarf); } - if (canary != 0) + for (auto d = busy_dwarfs.begin(); d != busy_dwarfs.end(); d++) { - dwarf_info_t* d = dwarf_info.front(); + int current_score = score_labor (*d, (*d)->using_labor); + FOR_ENUM_ITEMS (unit_labor, l) { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL && - canary & (1 << l)) - d->set_labor(l); + if (l == df::unit_labor::NONE) + continue; + if (l == (*d)->using_labor) + continue; + if (labor_needed[l] <= 0) + continue; + + int score = score_labor (*d, l); + score += labor_infos[l].time_since_last_assigned()/12; + if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) + score += 1000000; + + if (score > current_score) + { + tools_enum t = default_labor_infos[l].tool; + if (t == TOOL_NONE || (*d)->has_tool[t]) + { + (*d)->set_labor (l); + if (print_debug) + out.print("assign \"%s\" extra labor %s score=%d current %s score=%d\n", + (*d)->dwarf->name.first_name.c_str(), + ENUM_KEY_STR(unit_labor, l).c_str(), score, + ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); + } + } + else + (*d)->clear_labor (l); + } + } + + + if (canary != 0) + { + dwarf_info_t* d = 0; + + for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) + if (!(*di)->clear_all) + { + d = *di; + break; + } + + if (d) + { + + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + d->set_labor(l); + } + if (print_debug) + out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); + } + else + { + if (print_debug) + out.print ("No dwarf available to set as the hauling canary!\n"); } - if (print_debug) - out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); } /* set reequip on any dwarfs who are carrying tools needed by others */ @@ -2320,6 +2534,9 @@ public: { FOR_ENUM_ITEMS (unit_labor, l) { + if (l == df::unit_labor::NONE) + continue; + tools_enum t = default_labor_infos[l].tool; if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) { @@ -2329,6 +2546,8 @@ public: } } + release_dwarf_list(); + *df::global::process_jobs = true; print_debug = 0; @@ -2359,7 +2578,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { static int step_count = 0; // check run conditions - if(!world || !world->map.block_index || !enable_autolabor) + if(!initialized || !world || !world->map.block_index || !enable_labormanager) { // give up if we shouldn't be running' return CR_OK; @@ -2406,26 +2625,26 @@ df::unit_labor lookup_labor_by_name (std::string& name) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) { if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); + out.printerr("World is not loaded: please load a fort first.\n"); return CR_FAILURE; } - if (enable && !enable_autolabor) + if (enable && !enable_labormanager) { enable_plugin(out); } - else if(!enable && enable_autolabor) + else if(!enable && enable_labormanager) { - enable_autolabor = false; + enable_labormanager = false; setOptionEnabled(CF_ENABLED, false); - out << "Autolabor is disabled." << endl; + out << "LaborManager is disabled." << endl; } return CR_OK; } -command_result autolabor (color_ostream &out, std::vector & parameters) +command_result labormanager (color_ostream &out, std::vector & parameters) { CoreSuspender suspend; @@ -2443,7 +2662,7 @@ command_result autolabor (color_ostream &out, std::vector & parame else if (parameters.size() == 3 && (parameters[0] == "max" || parameters[0] == "priority")) { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2474,7 +2693,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 2 && parameters[0] == "reset") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2493,7 +2712,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && (parameters[0] == "allow-fishing" || parameters[0] == "forbid-fishing")) { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2504,7 +2723,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && (parameters[0] == "allow-hunting" || parameters[0] == "forbid-hunting")) { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2515,7 +2734,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && parameters[0] == "reset-all") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2530,7 +2749,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2563,7 +2782,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && parameters[0] == "debug") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2576,8 +2795,8 @@ command_result autolabor (color_ostream &out, std::vector & parame else { out.print("Automatically assigns labors to dwarves.\n" - "Activate with 'autolabor enable', deactivate with 'autolabor disable'.\n" - "Current state: %s.\n", enable_autolabor ? "enabled" : "disabled"); + "Activate with 'labormanager enable', deactivate with 'labormanager disable'.\n" + "Current state: %s.\n", enable_labormanager ? "enabled" : "disabled"); return CR_OK; } From 61bcfd4bf3b74a833ec43e4b03b748eaedbd178e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Jun 2016 20:58:38 -0500 Subject: [PATCH 0078/1012] labormanager improvements Add some debugging facilities. Change some hauling, construction, and deconstruction labors to reflect changes in DF since 34.11. --- plugins/devel/labormanager.cpp | 40 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 12e4e8ca1..ffabde36a 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -80,6 +80,7 @@ using df::global::world; DFHACK_PLUGIN_IS_ENABLED(enable_labormanager); static bool print_debug = 0; +static bool pause_on_error = 1; static std::vector state_count(5); @@ -525,8 +526,6 @@ struct dwarf_info_t ~dwarf_info_t() { - if (print_debug) - debug("LABORMANAGER: destroying dwarf %p\n", (void*) this); } @@ -608,7 +607,7 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_FOOD, /* FISH_RAW */ df::unit_labor::HAUL_REFUSE, /* VERMIN */ df::unit_labor::HAUL_ITEM, /* PET */ - df::unit_labor::HAUL_FOOD, /* SEEDS */ + df::unit_labor::HAUL_ITEM, /* SEEDS */ df::unit_labor::HAUL_FOOD, /* PLANT */ df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ df::unit_labor::HAUL_FOOD, /* LEAVES */ @@ -716,6 +715,14 @@ void debug (char* fmt, ...) } } +void debug_pause () +{ + if (pause_on_error) + { + debug("LABORMANAGER: Game paused so you can investigate the above message.\nUse 'labormanager pause-on-error no' to disable autopausing.\n"); + *df::global::pause_state = true; + } +} class JobLaborMapper { @@ -804,9 +811,10 @@ private: return workshop_build_labor[ws->type]; } break; + case df::building_type::Construction: + return df::unit_labor::BUILD_CONSTRUCTION; case df::building_type::Furnace: case df::building_type::TradeDepot: - case df::building_type::Construction: case df::building_type::Bridge: case df::building_type::ArcheryTarget: case df::building_type::WaterWheel: @@ -870,6 +878,7 @@ private: debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); return df::unit_labor::NONE; } @@ -900,9 +909,10 @@ private: return workshop_build_labor[ws->type]; } break; + case df::building_type::Construction: + return df::unit_labor::REMOVE_CONSTRUCTION; case df::building_type::Furnace: case df::building_type::TradeDepot: - case df::building_type::Construction: case df::building_type::Wagon: case df::building_type::Bridge: case df::building_type::ScrewPump: @@ -964,6 +974,7 @@ private: debug ("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); return df::unit_labor::NONE; } @@ -998,6 +1009,7 @@ private: else { debug ("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); + debug_pause(); return df::unit_labor::NONE; } case df::item_type::WOOD: @@ -1005,6 +1017,7 @@ private: default: debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); + debug_pause(); return df::unit_labor::NONE; } } @@ -1022,6 +1035,7 @@ private: default: debug ("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", ENUM_KEY_STR(workshop_type, type).c_str()); + debug_pause(); return df::unit_labor::NONE; } } @@ -1036,12 +1050,14 @@ private: default: debug ("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", ENUM_KEY_STR(furnace_type, type).c_str()); + debug_pause(); return df::unit_labor::NONE; } } debug ("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); return df::unit_labor::NONE; } @@ -1382,6 +1398,7 @@ public: if (job_to_labor_table.count(j->job_type) == 0) { debug("LABORMANAGER: job has no job to labor table entry: %s\n", ENUM_KEY_STR(job_type, j->job_type).c_str()); + debug_pause(); labor = df::unit_labor::NONE; } else { @@ -1941,6 +1958,7 @@ private: else { out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + debug_pause(); state = OTHER; } if (state == BUSY) @@ -2780,6 +2798,18 @@ command_result labormanager (color_ostream &out, std::vector & par return CR_OK; } + else if (parameters.size() == 2 && parameters[0] == "pause-on-error") + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + pause_on_error = parameters[1] == "yes" || parameters[1] == "true"; + + return CR_OK; + } else if (parameters.size() == 1 && parameters[0] == "debug") { if (!enable_labormanager) From e94804ca9f7a8d594874e942cb66e481e1e46362 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Tue, 28 Jun 2016 06:53:23 -0500 Subject: [PATCH 0079/1012] Convince the build system to generate VS2015 project files Signed-off-by: Michael Casadevall --- CMakeLists.txt | 4 ++-- build/build-debug.bat | 4 ++-- build/build-release.bat | 4 ++-- build/generate-MSVC-all-breakfast.bat | 6 +++--- build/generate-MSVC-all.bat | 6 +++--- build/generate-MSVC-gui.bat | 6 +++--- build/generate-MSVC-minimal.bat | 6 +++--- build/generate-MSVC-release.bat | 6 +++--- build/install-debug.bat | 4 ++-- build/install-release.bat | 4 ++-- build/package-debug.bat | 4 ++-- build/package-release.bat | 4 ++-- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bd89edce..c71cb8c94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,8 +48,8 @@ if(UNIX) endif() if(WIN32) - if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1600)) - message(SEND_ERROR "MSVC 2010 is required") + if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1900)) + message(SEND_ERROR "MSVC 2015 is required") endif() endif() diff --git a/build/build-debug.bat b/build/build-debug.bat index a9492de13..b75676ff4 100644 --- a/build/build-debug.bat +++ b/build/build-debug.bat @@ -1,4 +1,4 @@ -call "%VS100COMNTOOLS%vsvars32.bat" -cd VC2010 +call "%VS140COMNTOOLS%vsvars32.bat" +cd VC2015_32 msbuild /m /p:Platform=Win32 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj cd .. \ No newline at end of file diff --git a/build/build-release.bat b/build/build-release.bat index e1ad315e5..0b7a2a407 100644 --- a/build/build-release.bat +++ b/build/build-release.bat @@ -1,5 +1,5 @@ -call "%VS100COMNTOOLS%vsvars32.bat" -cd VC2010 +call "%VS140COMNTOOLS%vsvars32.bat" +cd VC2015_32 msbuild /m /p:Platform=Win32 /p:Configuration=Release ALL_BUILD.vcxproj cd .. pause \ No newline at end of file diff --git a/build/generate-MSVC-all-breakfast.bat b/build/generate-MSVC-all-breakfast.bat index 1921fba6c..4ef5ed677 100644 --- a/build/generate-MSVC-all-breakfast.bat +++ b/build/generate-MSVC-all-breakfast.bat @@ -1,9 +1,9 @@ @echo off IF EXIST DF_PATH.txt SET /P _DF_PATH= Date: Tue, 28 Jun 2016 07:13:18 -0500 Subject: [PATCH 0080/1012] Backport new hash.h from git upstream to add support for VS2015 Signed-off-by: Michael Casadevall --- depends/protobuf/google/protobuf/stubs/hash.h | 556 ++++++++++++------ 1 file changed, 387 insertions(+), 169 deletions(-) diff --git a/depends/protobuf/google/protobuf/stubs/hash.h b/depends/protobuf/google/protobuf/stubs/hash.h index 822d60501..b4b2da574 100644 --- a/depends/protobuf/google/protobuf/stubs/hash.h +++ b/depends/protobuf/google/protobuf/stubs/hash.h @@ -1,6 +1,6 @@ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. -// http://code.google.com/p/protobuf/ +// https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -37,184 +37,402 @@ #include #include -#include "config.h" -#if defined(HAVE_HASH_MAP) && defined(HAVE_HASH_SET) -#include HASH_MAP_H -#include HASH_SET_H +#define GOOGLE_PROTOBUF_HAVE_HASH_MAP 1 +#define GOOGLE_PROTOBUF_HAVE_HASH_SET 1 + +// Android +#if defined(__ANDROID__) +# undef GOOGLE_PROTOBUF_HAVE_HASH_MAP +# undef GOOGLE_PROTOBUF_HAVE_HASH_MAP + +// Use C++11 unordered_{map|set} if available. +#elif ((_LIBCPP_STD_VER >= 11) || \ + (((__cplusplus >= 201103L) || defined(__GXX_EXPERIMENTAL_CXX0X)) && \ + (__GLIBCXX__ > 20090421))) +# define GOOGLE_PROTOBUF_HAS_CXX11_HASH + +// For XCode >= 4.6: the compiler is clang with libc++. +// For earlier XCode version: the compiler is gcc-4.2.1 with libstdc++. +// libc++ provides and friends even in non C++11 mode, +// and it does not provide the tr1 library. Therefore the following macro +// checks against this special case. +// Note that we should not test the __APPLE_CC__ version number or the +// __clang__ macro, since the new compiler can still use -stdlib=libstdc++, in +// which case is not compilable without -std=c++11 +#elif defined(__APPLE_CC__) +# if __GNUC__ >= 4 +# define GOOGLE_PROTOBUF_HAS_TR1 +# else +// Not tested for gcc < 4... These setting can compile under 4.2.1 though. +# define GOOGLE_PROTOBUF_HASH_NAMESPACE __gnu_cxx +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# endif + +// Version checks for gcc. +#elif defined(__GNUC__) +// For GCC 4.x+, use tr1::unordered_map/set; otherwise, follow the +// instructions from: +// https://gcc.gnu.org/onlinedocs/libstdc++/manual/backwards.html +# if __GNUC__ >= 4 +# define GOOGLE_PROTOBUF_HAS_TR1 +# elif __GNUC__ >= 3 +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# if __GNUC__ == 3 && __GNUC_MINOR__ == 0 +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std // GCC 3.0 +# else +# define GOOGLE_PROTOBUF_HASH_NAMESPACE __gnu_cxx // GCC 3.1 and later +# endif +# else +# define GOOGLE_PROTOBUF_HASH_NAMESPACE +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# endif + +// Version checks for MSC. +// Apparently Microsoft decided to move hash_map *back* to the std namespace in +// MSVC 2010: +// http://blogs.msdn.com/vcblog/archive/2009/05/25/stl-breaking-changes-in-visual-studio-2010-beta-1.aspx +// And.. they are moved back to stdext in MSVC 2013 (haven't checked 2012). That +// said, use unordered_map for MSVC 2010 and beyond is our safest bet. +#elif defined(_MSC_VER) +# if _MSC_VER >= 1600 // Since Visual Studio 2010 +# define GOOGLE_PROTOBUF_HAS_CXX11_HASH +# define GOOGLE_PROTOBUF_HASH_COMPARE std::hash_compare +# elif _MSC_VER >= 1500 // Since Visual Studio 2008 +# define GOOGLE_PROTOBUF_HASH_NAMESPACE stdext +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# define GOOGLE_PROTOBUF_HASH_COMPARE stdext::hash_compare +# define GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE +# elif _MSC_VER >= 1310 +# define GOOGLE_PROTOBUF_HASH_NAMESPACE stdext +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# define GOOGLE_PROTOBUF_HASH_COMPARE stdext::hash_compare +# else +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# define GOOGLE_PROTOBUF_HASH_COMPARE stdext::hash_compare +# endif + +// **ADD NEW COMPILERS SUPPORT HERE.** +// For other compilers, undefine the macro and fallback to use std::map, in +// google/protobuf/stubs/hash.h #else -#define MISSING_HASH -#include -#include +# undef GOOGLE_PROTOBUF_HAVE_HASH_MAP +# undef GOOGLE_PROTOBUF_HAVE_HASH_SET #endif -namespace google { -namespace protobuf { - -#ifdef MISSING_HASH - -// This system doesn't have hash_map or hash_set. Emulate them using map and -// set. - -// Make hash be the same as less. Note that everywhere where custom -// hash functions are defined in the protobuf code, they are also defined such -// that they can be used as "less" functions, which is required by MSVC anyway. -template -struct hash { - // Dummy, just to make derivative hash functions compile. - int operator()(const Key& key) { - GOOGLE_LOG(FATAL) << "Should never be called."; - return 0; - } - - inline bool operator()(const Key& a, const Key& b) const { - return a < b; - } -}; - -// Make sure char* is compared by value. -template <> -struct hash { - // Dummy, just to make derivative hash functions compile. - int operator()(const char* key) { - GOOGLE_LOG(FATAL) << "Should never be called."; - return 0; - } - - inline bool operator()(const char* a, const char* b) const { - return strcmp(a, b) < 0; - } -}; - -template , - typename EqualKey = int > -class hash_map : public std::map { -}; - -template , - typename EqualKey = int > -class hash_set : public std::set { -}; +#if defined(GOOGLE_PROTOBUF_HAS_CXX11_HASH) +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS unordered_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS unordered_set +#elif defined(GOOGLE_PROTOBUF_HAS_TR1) +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std::tr1 +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS unordered_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS unordered_set +#endif -#elif defined(_MSC_VER) && !defined(_STLPORT_VERSION) +# define GOOGLE_PROTOBUF_HASH_NAMESPACE_DECLARATION_START \ + namespace google { \ + namespace protobuf { +# define GOOGLE_PROTOBUF_HASH_NAMESPACE_DECLARATION_END }} -template -struct hash : public HASH_NAMESPACE::hash_compare { -}; - -// MSVC's hash_compare hashes based on the string contents but -// compares based on the string pointer. WTF? -class CstringLess { - public: - inline bool operator()(const char* a, const char* b) const { - return strcmp(a, b) < 0; - } -}; - -template <> -struct hash - : public HASH_NAMESPACE::hash_compare { -}; - -template , - typename EqualKey = int > -class hash_map : public HASH_NAMESPACE::hash_map< - Key, Data, HashFcn> { -}; - -template , - typename EqualKey = int > -class hash_set : public HASH_NAMESPACE::hash_set< - Key, HashFcn> { -}; +#undef GOOGLE_PROTOBUF_HAS_CXX11_HASH +#undef GOOGLE_PROTOBUF_HAS_TR1 +#if defined(GOOGLE_PROTOBUF_HAVE_HASH_MAP) && \ + defined(GOOGLE_PROTOBUF_HAVE_HASH_SET) #else +#define GOOGLE_PROTOBUF_MISSING_HASH +#include +#include +#endif -template -struct hash : public HASH_NAMESPACE::hash { -}; - -template -struct hash { - inline size_t operator()(const Key* key) const { - return reinterpret_cast(key); - } -}; - -// Unlike the old SGI version, the TR1 "hash" does not special-case char*. So, -// we go ahead and provide our own implementation. -template <> -struct hash { - inline size_t operator()(const char* str) const { - size_t result = 0; - for (; *str != '\0'; str++) { - result = 5 * result + *str; - } - return result; - } -}; - -template , - typename EqualKey = std::equal_to > -class hash_map : public HASH_NAMESPACE::HASH_MAP_CLASS< - Key, Data, HashFcn, EqualKey> { -}; - -template , - typename EqualKey = std::equal_to > -class hash_set : public HASH_NAMESPACE::HASH_SET_CLASS< - Key, HashFcn, EqualKey> { -}; +namespace google { + namespace protobuf { + +#ifdef GOOGLE_PROTOBUF_MISSING_HASH +#undef GOOGLE_PROTOBUF_MISSING_HASH + + // This system doesn't have hash_map or hash_set. Emulate them using map and + // set. + + // Make hash be the same as less. Note that everywhere where custom + // hash functions are defined in the protobuf code, they are also defined such + // that they can be used as "less" functions, which is required by MSVC anyway. + template + struct hash { + // Dummy, just to make derivative hash functions compile. + int operator()(const Key& key) { + GOOGLE_LOG(FATAL) << "Should never be called."; + return 0; + } + + inline bool operator()(const Key& a, const Key& b) const { + return a < b; + } + }; + + // Make sure char* is compared by value. + template <> + struct hash { + // Dummy, just to make derivative hash functions compile. + int operator()(const char* key) { + GOOGLE_LOG(FATAL) << "Should never be called."; + return 0; + } + + inline bool operator()(const char* a, const char* b) const { + return strcmp(a, b) < 0; + } + }; + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map : public std::map { + typedef std::map BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), + const Alloc& d = Alloc()) : BaseClass(b, d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set : public std::set { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; -#endif +#elif defined(_MSC_VER) && !defined(_STLPORT_VERSION) -template <> -struct hash { - inline size_t operator()(const string& key) const { - return hash()(key.c_str()); - } - - static const size_t bucket_size = 4; - static const size_t min_buckets = 8; - inline size_t operator()(const string& a, const string& b) const { - return a < b; - } -}; - -template -struct hash > { - inline size_t operator()(const pair& key) const { - size_t first_hash = hash()(key.first); - size_t second_hash = hash()(key.second); - - // FIXME(kenton): What is the best way to compute this hash? I have - // no idea! This seems a bit better than an XOR. - return first_hash * ((1 << 16) - 1) + second_hash; - } - - static const size_t bucket_size = 4; - static const size_t min_buckets = 8; - inline size_t operator()(const pair& a, - const pair& b) const { - return a < b; - } -}; - -// Used by GCC/SGI STL only. (Why isn't this provided by the standard -// library? :( ) -struct streq { - inline bool operator()(const char* a, const char* b) const { - return strcmp(a, b) == 0; - } -}; - -} // namespace protobuf + template + struct hash : public GOOGLE_PROTOBUF_HASH_COMPARE { + }; + + // MSVC's hash_compare hashes based on the string contents but + // compares based on the string pointer. WTF? + class CstringLess { + public: + inline bool operator()(const char* a, const char* b) const { + return strcmp(a, b) < 0; + } + }; + + template <> + struct hash + : public GOOGLE_PROTOBUF_HASH_COMPARE {}; + +#ifdef GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE + + template + struct InternalHashCompare : public GOOGLE_PROTOBUF_HASH_COMPARE { + InternalHashCompare() {} + InternalHashCompare(HashFcn hashfcn, EqualKey equalkey) + : hashfcn_(hashfcn), equalkey_(equalkey) {} + size_t operator()(const Key& key) const { return hashfcn_(key); } + bool operator()(const Key& key1, const Key& key2) const { + return !equalkey_(key1, key2); + } + HashFcn hashfcn_; + EqualKey equalkey_; + }; + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, InternalHashCompare, Alloc> { + typedef GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, InternalHashCompare, Alloc> BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), const Alloc& d = Alloc()) + : BaseClass(InternalHashCompare(b, c), d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_SET_CLASS< + Key, InternalHashCompare > { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + +#else // GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> { + typedef GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), + const Alloc& d = Alloc()) : BaseClass(a, b, c, d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_SET_CLASS< + Key, HashFcn, EqualKey> { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; +#endif // GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE + +#else // defined(_MSC_VER) && !defined(_STLPORT_VERSION) + + template + struct hash : public GOOGLE_PROTOBUF_HASH_NAMESPACE::hash { + }; + + template + struct hash { + inline size_t operator()(const Key* key) const { + return reinterpret_cast(key); + } + }; + + // Unlike the old SGI version, the TR1 "hash" does not special-case char*. So, + // we go ahead and provide our own implementation. + template <> + struct hash { + inline size_t operator()(const char* str) const { + size_t result = 0; + for (; *str != '\0'; str++) { + result = 5 * result + *str; + } + return result; + } + }; + + template<> + struct hash { + size_t operator()(bool x) const { + return static_cast(x); + } + }; + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> { + typedef GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), + const Alloc& d = Alloc()) : BaseClass(a, b, c, d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_SET_CLASS< + Key, HashFcn, EqualKey> { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + +#endif // !GOOGLE_PROTOBUF_MISSING_HASH + + template <> + struct hash { + inline size_t operator()(const string& key) const { + return hash()(key.c_str()); + } + + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + inline bool operator()(const string& a, const string& b) const { + return a < b; + } + }; + + template + struct hash > { + inline size_t operator()(const pair& key) const { + size_t first_hash = hash()(key.first); + size_t second_hash = hash()(key.second); + + // FIXME(kenton): What is the best way to compute this hash? I have + // no idea! This seems a bit better than an XOR. + return first_hash * ((1 << 16) - 1) + second_hash; + } + + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + inline bool operator()(const pair& a, + const pair& b) const { + return a < b; + } + }; + + // Used by GCC/SGI STL only. (Why isn't this provided by the standard + // library? :( ) + struct streq { + inline bool operator()(const char* a, const char* b) const { + return strcmp(a, b) == 0; + } + }; + + } // namespace protobuf } // namespace google -#endif // GOOGLE_PROTOBUF_STUBS_HASH_H__ +#endif // GOOGLE_PROTOBUF_STUBS_HASH_H__ \ No newline at end of file From c3188b1d6cce63b71f7fc5992bb0c47ce2dd9a61 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Tue, 28 Jun 2016 07:13:46 -0500 Subject: [PATCH 0081/1012] Make our build checks relate to the new compiler world order (thanks lethosor) Signed-off-by: Michael Casadevall --- depends/protobuf/CMakeLists.txt | 92 +++++++++++++++------------------ 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 4fec34125..085af746f 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -13,68 +13,62 @@ IF(CMAKE_COMPILER_IS_GNUCC) #ENDIF() SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") - SET(HAVE_HASH_MAP 0) - SET(HASH_MAP_CLASS unordered_map) +ENDIF() + +SET(HAVE_HASH_MAP 0) +SET(HASH_MAP_CLASS unordered_map) - #Check for all of the possible combinations of unordered_map and hash_map +#Check for all of the possible combinations of unordered_map and hash_map - FOREACH(header tr1/unordered_map unordered_map) - FOREACH(namespace std::tr1 std ) - IF(HAVE_HASH_MAP EQUAL 0 AND NOT STL_HASH_OLD_GCC) +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_BINARY_DIR}/testHashMap.cpp") + IF(CMAKE_CROSSCOMPILING) + 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_BINARY_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}) + SET(HASH_NAMESPACE ${namespace}) + SET(HASH_MAP_CLASS unordered_map) + SET(HASH_SET_CLASS unordered_set) + SET(HAVE_HASH_MAP 1) + SET(HAVE_HASH_SET 1) + ENDIF() + ENDIF() + ENDFOREACH(namespace) +ENDFOREACH(header) +IF (HAVE_HASH_MAP EQUAL 0) + SET(HASH_MAP_CLASS hash_map) + 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_BINARY_DIR}/testHashMap.cpp") - IF(CMAKE_CROSSCOMPILING) - 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_BINARY_DIR}/testHashMap.cpp") - ENDIF() - IF (HASH_MAP_COMPILE_RESULT AND HASH_MAP_RUN_RESULT EQUAL 1) + 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}) SET(HASH_NAMESPACE ${namespace}) - SET(HASH_MAP_CLASS unordered_map) - SET(HASH_SET_CLASS unordered_set) + SET(HASH_MAP_CLASS hash_map) + SET(HASH_SET_CLASS hash_set) SET(HAVE_HASH_MAP 1) SET(HAVE_HASH_SET 1) ENDIF() ENDIF() - ENDFOREACH(namespace) - ENDFOREACH(header) - IF (HAVE_HASH_MAP EQUAL 0) - SET(HASH_MAP_CLASS hash_map) - 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_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}) - SET(HASH_NAMESPACE ${namespace}) - SET(HASH_MAP_CLASS hash_map) - SET(HASH_SET_CLASS hash_set) - SET(HAVE_HASH_MAP 1) - SET(HAVE_HASH_SET 1) - ENDIF() - ENDIF() - ENDFOREACH() ENDFOREACH() - ENDIF() + ENDFOREACH() +ENDIF() - IF (HAVE_HASH_MAP EQUAL 0) - MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.") - ENDIF() +IF (HAVE_HASH_MAP EQUAL 0) + MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.") +ENDIF() +IF(UNIX) FIND_PACKAGE(Threads) - -ELSE() - SET(HASH_MAP_H ) - SET(HASH_NAMESPACE std) - SET(HASH_SET_H ) - SET(HAVE_HASH_MAP 1) - SET(HAVE_HASH_SET 1) - SET(HASH_MAP_CLASS hash_map) - SET(HASH_SET_CLASS hash_set) ENDIF() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h") @@ -250,4 +244,4 @@ IF(NOT CMAKE_CROSSCOMPILING) TARGET_LINK_LIBRARIES(protoc-bin protoc) EXPORT(TARGETS protoc-bin FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake ) -ENDIF() +ENDIF() \ No newline at end of file From d49032ef73210942a442b3f1109258178f38fafe Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Tue, 28 Jun 2016 08:34:11 -0500 Subject: [PATCH 0082/1012] Supress a lot of compiler noise in hopes of finding real errors. Right now, a plugin free DFHack can be built with VS2015 --- CMakeLists.txt | 9 +++++++++ library/include/Error.h | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c71cb8c94..ffc379724 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,15 @@ endif() if(MSVC) # disable C4819 code-page warning add_definitions( "/wd4819" ) + +# Disable use of POSIX name warnings +add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS") + +# supress C4503 - VC++ dislikes if a name is too long. If you get +# weird and mysterious linking errors, you can disable this, but you'll have to +# deal with a LOT of compiler noise over it +# see https://msdn.microsoft.com/en-us/library/074af4b6.aspx +add_definitions( "/wd4503") endif() IF(CMAKE_CROSSCOMPILING) diff --git a/library/include/Error.h b/library/include/Error.h index 4e3ff269c..4e3224f49 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -38,8 +38,18 @@ namespace DFHack * our wrapper for the C++ exception. used to differentiate * the whole array of DFHack exceptions from the rest */ - class DFHACK_EXPORT All : public std::exception{}; - +#ifdef _MSC_VER +#pragma push +/** + * C4275 is - The warning officially is non dll-interface class 'std::exception' used as base for + * dll-interface class + * + * Basically, its saying that you might have an ABI problem if you mismatch compilers. We don't + * care since we build all of DFhack at once against whatever Toady is using + */ +#pragma warning(disable: 4275) + class DFHACK_EXPORT All : public std::exception{}; +#endif class DFHACK_EXPORT NullPointer : public All { const char *varname_; public: From d509cf2fb507aa243286f0aac98ac776fdc2163b Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Tue, 28 Jun 2016 09:39:46 -0500 Subject: [PATCH 0083/1012] It helps when you pop pragmas Signed-off-by: Michael Casadevall --- library/include/Error.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/include/Error.h b/library/include/Error.h index 4e3224f49..1c8afe44c 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -48,7 +48,10 @@ namespace DFHack * care since we build all of DFhack at once against whatever Toady is using */ #pragma warning(disable: 4275) +#endif class DFHACK_EXPORT All : public std::exception{}; +#ifdef _MSC_VER +#pragma pop #endif class DFHACK_EXPORT NullPointer : public All { const char *varname_; From b516c8e0dcb18d3f1e8cf2e957351803e14bd509 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 28 Jun 2016 22:36:45 -0500 Subject: [PATCH 0084/1012] labormanager: more tweaks to bring up to date This update fixes some labors and attempts to address changes in the way DF maintains the job list. --- plugins/devel/labormanager.cpp | 155 +++++++++++++++------------------ 1 file changed, 72 insertions(+), 83 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index ffabde36a..256841cb7 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1154,7 +1154,7 @@ public: job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); - job_to_labor_table[df::job_type::BringItemToDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; job_to_labor_table[df::job_type::Eat] = jlf_no_labor; job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; @@ -1178,13 +1178,13 @@ public: job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_const(df::unit_labor::HAUL_ITEM); + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1293,14 +1293,14 @@ public: job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::UnchainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::CageLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; @@ -1640,6 +1640,7 @@ private: int priority_food; std::map labor_needed; + std::map labor_in_use; std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; @@ -1668,6 +1669,7 @@ private: { df::building_tradedepotst* depot = (df::building_tradedepotst*) build; trader_requested = depot->trade_flags.bits.trader_requested; + if (print_debug) { if (trader_requested) @@ -1819,20 +1821,23 @@ private: } df::unit_labor labor = labor_mapper->find_job_labor (j); + labor_needed[labor]++; if (labor != df::unit_labor::NONE) { - labor_needed[labor]++; - - if (worker != -1) + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } else { labor_infos[labor].mark_assigned(); + labor_in_use[labor]++; + } - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } } } @@ -1979,8 +1984,6 @@ private: } } } - if (state == OTHER) - dwarf->clear_all = true; } dwarf->state = state; @@ -2199,7 +2202,7 @@ public: // add job entries for health care labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; - labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; + labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; @@ -2260,43 +2263,14 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } - /* move idle dwarfs ready to be assigned to busy list */ - for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); ) - { - bool busy = false; + /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - - - if (labor_needed[l] > 0 && (*d)->dwarf->status.labors[l]) - { - busy = true; - labor_needed[l] = max(labor_needed[l]-1, 0); - } - } - - if (busy) - { - busy_dwarfs.push_back(*d); - d = available_dwarfs.erase(d); - } else { - d++; - } - } - - /* adjust for over/under */ - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - if (labor_infos[l].idle_dwarfs > 0) - labor_needed[l] = 0; - else - labor_needed[l] = std::max(labor_needed[l] - labor_infos[l].busy_dwarfs, 0); - } + FOR_ENUM_ITEMS(unit_labor, l) { + if (labor_infos[l].idle_dwarfs > 0) + labor_needed[l] = 0; + else + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + } /* assign food haulers for rotting food items */ @@ -2401,7 +2375,6 @@ public: priority /= 2; pq.push(make_pair(priority, labor)); } - } int canary = (1 << df::unit_labor::HAUL_STONE) | @@ -2476,6 +2449,7 @@ public: to_assign[best_labor]--; } + busy_dwarfs.push_back(*bestdwarf); available_dwarfs.erase(bestdwarf); } @@ -2510,42 +2484,57 @@ public: ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } } - else - (*d)->clear_labor (l); } } - if (canary != 0) - { - dwarf_info_t* d = 0; - - for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) - if (!(*di)->clear_all) - { - d = *di; - break; - } + dwarf_info_t* canary_dwarf = 0; - if (d) + for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) + if (!(*di)->clear_all) { - - FOR_ENUM_ITEMS (unit_labor, l) - { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && - canary & (1 << l)) - d->set_labor(l); - } - if (print_debug) - out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); + canary_dwarf = *di; + break; } - else + + if (canary_dwarf) + { + + FOR_ENUM_ITEMS (unit_labor, l) { - if (print_debug) - out.print ("No dwarf available to set as the hauling canary!\n"); + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + canary_dwarf->set_labor(l); } + + /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ + + canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + + if (print_debug) + out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); + } + else + { + if (print_debug) + out.print ("No dwarf available to set as the hauling canary!\n"); } + /* Assign any leftover dwarfs to "standard" labors */ + + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) + { + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + (*d)->set_labor(l); + } + + (*d)->set_labor(df::unit_labor::CLEAN); + (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + } + /* set reequip on any dwarfs who are carrying tools needed by others */ for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) From 1927eda6f33aa813da94eaeceb1bfc1db4111d3d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 28 Jun 2016 22:57:12 -0500 Subject: [PATCH 0085/1012] labormanager: whitespace MSVC is evil. --- plugins/devel/labormanager.cpp | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 256841cb7..8acae7524 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1299,7 +1299,7 @@ public: job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); @@ -1640,7 +1640,7 @@ private: int priority_food; std::map labor_needed; - std::map labor_in_use; + std::map labor_in_use; std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; @@ -1821,22 +1821,22 @@ private: } df::unit_labor labor = labor_mapper->find_job_labor (j); - labor_needed[labor]++; + labor_needed[labor]++; if (labor != df::unit_labor::NONE) { - if (worker == -1) - { - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } - } else { + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } else { labor_infos[labor].mark_assigned(); - labor_in_use[labor]++; - } + labor_in_use[labor]++; + } } } @@ -2263,14 +2263,14 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } - /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ + /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ - FOR_ENUM_ITEMS(unit_labor, l) { - if (labor_infos[l].idle_dwarfs > 0) - labor_needed[l] = 0; - else - labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); - } + FOR_ENUM_ITEMS(unit_labor, l) { + if (labor_infos[l].idle_dwarfs > 0) + labor_needed[l] = 0; + else + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + } /* assign food haulers for rotting food items */ @@ -2449,7 +2449,7 @@ public: to_assign[best_labor]--; } - busy_dwarfs.push_back(*bestdwarf); + busy_dwarfs.push_back(*bestdwarf); available_dwarfs.erase(bestdwarf); } @@ -2507,9 +2507,9 @@ public: canary_dwarf->set_labor(l); } - /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ + /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ - canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); if (print_debug) out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); @@ -2520,10 +2520,10 @@ public: out.print ("No dwarf available to set as the hauling canary!\n"); } - /* Assign any leftover dwarfs to "standard" labors */ + /* Assign any leftover dwarfs to "standard" labors */ - for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) - { + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) + { FOR_ENUM_ITEMS (unit_labor, l) { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && @@ -2531,9 +2531,9 @@ public: (*d)->set_labor(l); } - (*d)->set_labor(df::unit_labor::CLEAN); - (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); - } + (*d)->set_labor(df::unit_labor::CLEAN); + (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + } /* set reequip on any dwarfs who are carrying tools needed by others */ From b45fb7c5640b1ddc6ef8360ec965876a3f5f6f2e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 28 Jun 2016 23:28:44 -0500 Subject: [PATCH 0086/1012] labormanager: fix stupid --- plugins/devel/labormanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 8acae7524..d78d06b97 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -2337,6 +2337,9 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { df::unit_labor l = i->first; + if (l == df::unit_labor::NONE) + continue; + if (labor_infos[l].maximum_dwarfs() > 0 && i->second > labor_infos[l].maximum_dwarfs()) i->second = labor_infos[l].maximum_dwarfs(); From 857058752ba56321024e08244dd004347ad55cf5 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 29 Jun 2016 14:54:03 -0500 Subject: [PATCH 0087/1012] labormanager: rework for better behavior with 43.03 The main thing here is that the process loop exits if the DF process_job or process_dig flags are set since if these are set the job list is going to change soon anyway. The plugin also sets these flags when it changes any labors, which has the side effect of effectively disabling the process loop while DF is paused, which prevents flapping while editing job preferences in-game, and also allows changing job preferences in game (although such changes may not last when the clock starts up again). --- plugins/devel/labormanager.cpp | 78 +++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index d78d06b97..8d614ed36 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -528,23 +528,6 @@ struct dwarf_info_t { } - - void set_labor(df::unit_labor labor) - { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) - { - dwarf->status.labors[labor] = true; - } - } - - void clear_labor(df::unit_labor labor) - { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) - { - dwarf->status.labors[labor] = false; - } - } - }; /* @@ -1621,6 +1604,8 @@ private: int plant_count; int detail_count; + bool labors_changed; + int tool_count[TOOLS_MAX]; bool reequip_needed[TOOLS_MAX]; @@ -1647,11 +1632,23 @@ private: std::list busy_dwarfs; private: + void set_labor (dwarf_info_t* dwarf, df::unit_labor labor, bool value) + { + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + bool old = dwarf->dwarf->status.labors[labor]; + dwarf->dwarf->status.labors[labor] = value; + if (old != value) + labors_changed = true; + } + } + void scan_buildings() { has_butchers = false; has_fishery = false; trader_requested = false; + labors_changed = false; for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) { @@ -1821,10 +1818,10 @@ private: } df::unit_labor labor = labor_mapper->find_job_labor (j); - labor_needed[labor]++; if (labor != df::unit_labor::NONE) { + labor_needed[labor]++; if (worker == -1) { if (j->pos.isValid()) @@ -2088,7 +2085,7 @@ private: if (labor == unit_labor::NONE) continue; - dwarf->clear_labor(labor); + set_labor(dwarf, labor, false); } } else { if (state == IDLE) @@ -2152,6 +2149,9 @@ private: public: void process() { + if (*df::global::process_dig || *df::global::process_jobs) + return; + release_dwarf_list(); dig_count = tree_count = plant_count = detail_count = 0; @@ -2266,6 +2266,9 @@ public: /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ FOR_ENUM_ITEMS(unit_labor, l) { + if (l == df::unit_labor::NONE) + continue; + if (labor_infos[l].idle_dwarfs > 0) labor_needed[l] = 0; else @@ -2309,10 +2312,7 @@ public: if (l == df::unit_labor::NONE) continue; - if (l == df::unit_labor::HAUL_FOOD) - (*bestdwarf)->set_labor(l); - else - (*bestdwarf)->clear_labor(l); + set_labor (*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); } available_dwarfs.erase(bestdwarf); @@ -2337,7 +2337,7 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { df::unit_labor l = i->first; - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE) continue; if (labor_infos[l].maximum_dwarfs() > 0 && @@ -2394,7 +2394,7 @@ public: std::list::iterator bestdwarf = available_dwarfs.begin(); int best_score = INT_MIN; - df::unit_labor best_labor = df::unit_labor::CLEAN; + df::unit_labor best_labor = df::unit_labor::NONE; for (auto j = to_assign.begin(); j != to_assign.end(); j++) { @@ -2416,6 +2416,9 @@ public: } } + if (best_labor == df::unit_labor::NONE) + break; + if (print_debug) out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); @@ -2426,7 +2429,7 @@ public: if (l == best_labor) { - (*bestdwarf)->set_labor(l); + set_labor(*bestdwarf, l, true); tools_enum t = default_labor_infos[l].tool; if (t != TOOL_NONE) { @@ -2436,7 +2439,7 @@ public: } } else if ((*bestdwarf)->state == IDLE) - (*bestdwarf)->clear_labor(l); + set_labor(*bestdwarf, l, false); } if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) @@ -2479,7 +2482,7 @@ public: tools_enum t = default_labor_infos[l].tool; if (t == TOOL_NONE || (*d)->has_tool[t]) { - (*d)->set_labor (l); + set_labor(*d, l, true); if (print_debug) out.print("assign \"%s\" extra labor %s score=%d current %s score=%d\n", (*d)->dwarf->name.first_name.c_str(), @@ -2507,12 +2510,12 @@ public: { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) - canary_dwarf->set_labor(l); + set_labor(canary_dwarf, l, true); } /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ - canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); if (print_debug) out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); @@ -2531,11 +2534,12 @@ public: { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) - (*d)->set_labor(l); + set_labor(*d, l, true); + else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION) + set_labor(*d, l, true); + else + set_labor(*d, l, false); } - - (*d)->set_labor(df::unit_labor::CLEAN); - (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); } /* set reequip on any dwarfs who are carrying tools needed by others */ @@ -2558,7 +2562,11 @@ public: release_dwarf_list(); - *df::global::process_jobs = true; + if (labors_changed) + { + *df::global::process_dig = true; + *df::global::process_jobs = true; + } print_debug = 0; From ae55d2d526908636edd75e14a24e7e271c602bd8 Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Sun, 26 Jun 2016 19:48:55 -0400 Subject: [PATCH 0088/1012] Support linking against an external tinyxml if EXTERNAL_TINYXML is set As best as I can tell, the copy of tinyxml dfhack uses is unmodified from whenever it was first bundled. This commit adds an option to CMake, EXTERNAL_TINYXML, that if set to ON, will attempt to link against a system tinyxml instead of using the dfhack-bundled one. It defaults to OFF, so there is no change in default behavior. The DFHACK_TINYXML variable is then set to either "tinyxml" or "dfhack-tinyxml" so the library (and any plugins that need updating) can link against one or the other. The FindTinyXML.cmake script was taken from https://github.com/ros/cmake_modules (licensed under the 3-clause BSD license). Add license text to new CMake file. --- CMake/Modules/FindTinyXML.cmake | 107 ++++++++++++++++++++++++++++++++ CMakeLists.txt | 17 ++++- depends/CMakeLists.txt | 7 ++- depends/tinyxml/CMakeLists.txt | 8 ++- library/CMakeLists.txt | 6 +- 5 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 CMake/Modules/FindTinyXML.cmake diff --git a/CMake/Modules/FindTinyXML.cmake b/CMake/Modules/FindTinyXML.cmake new file mode 100644 index 000000000..b0466fbd5 --- /dev/null +++ b/CMake/Modules/FindTinyXML.cmake @@ -0,0 +1,107 @@ +# Sourced from: +# https://raw.githubusercontent.com/ros/cmake_modules/0.4-devel/cmake/Modules/FindTinyXML.cmake +# under the following license: https://github.com/ros/cmake_modules/blob/0.4-devel/LICENSE, +# reproduced here: + +# Copyright (c) 2013, Open Source Robotics Foundation +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: + +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. + +# Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. + +# Neither the name of the {organization} nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################################## +# +# CMake script for finding TinyXML. +# +# Input variables: +# +# - TinyXML_ROOT_DIR (optional): When specified, header files and libraries will be searched for in +# ${TinyXML_ROOT_DIR}/include +# ${TinyXML_ROOT_DIR}/libs +# respectively, and the default CMake search order will be ignored. When unspecified, the default +# CMake search order is used. +# This variable can be specified either as a CMake or environment variable. If both are set, +# preference is given to the CMake variable. +# Use this variable for finding packages installed in a nonstandard location, or for enforcing +# that one of multiple package installations is picked up. +# +# +# Cache variables (not intended to be used in CMakeLists.txt files) +# +# - TinyXML_INCLUDE_DIR: Absolute path to package headers. +# - TinyXML_LIBRARY: Absolute path to library. +# +# +# Output variables: +# +# - TinyXML_FOUND: Boolean that indicates if the package was found +# - TinyXML_INCLUDE_DIRS: Paths to the necessary header files +# - TinyXML_LIBRARIES: Package libraries +# +# +# Example usage: +# +# find_package(TinyXML) +# if(NOT TinyXML_FOUND) +# # Error handling +# endif() +# ... +# include_directories(${TinyXML_INCLUDE_DIRS} ...) +# ... +# target_link_libraries(my_target ${TinyXML_LIBRARIES}) +# +################################################################################################## + +# Get package location hint from environment variable (if any) +if(NOT TinyXML_ROOT_DIR AND DEFINED ENV{TinyXML_ROOT_DIR}) + set(TinyXML_ROOT_DIR "$ENV{TinyXML_ROOT_DIR}" CACHE PATH + "TinyXML base directory location (optional, used for nonstandard installation paths)") +endif() + +# Search path for nonstandard package locations +if(TinyXML_ROOT_DIR) + set(TinyXML_INCLUDE_PATH PATHS "${TinyXML_ROOT_DIR}/include" NO_DEFAULT_PATH) + set(TinyXML_LIBRARY_PATH PATHS "${TinyXML_ROOT_DIR}/lib" NO_DEFAULT_PATH) +endif() + +# Find headers and libraries +find_path(TinyXML_INCLUDE_DIR NAMES tinyxml.h PATH_SUFFIXES "tinyxml" ${TinyXML_INCLUDE_PATH}) +find_library(TinyXML_LIBRARY NAMES tinyxml PATH_SUFFIXES "tinyxml" ${TinyXML_LIBRARY_PATH}) + +mark_as_advanced(TinyXML_INCLUDE_DIR + TinyXML_LIBRARY) + +# Output variables generation +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(TinyXML DEFAULT_MSG TinyXML_LIBRARY + TinyXML_INCLUDE_DIR) + +set(TinyXML_FOUND ${TINYXML_FOUND}) # Enforce case-correctness: Set appropriately cased variable... +unset(TINYXML_FOUND) # ...and unset uppercase variable generated by find_package_handle_standard_args + +if(TinyXML_FOUND) + set(TinyXML_INCLUDE_DIRS ${TinyXML_INCLUDE_DIR}) + set(TinyXML_LIBRARIES ${TinyXML_LIBRARY}) +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bd89edce..533fba9ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,22 @@ include_directories(depends/protobuf) include_directories(depends/lua/include) include_directories(depends/md5) include_directories(depends/jsoncpp) -include_directories(depends/tinyxml) + +# Support linking against external tinyxml +# If we find an external tinyxml, set the DFHACK_TINYXML variable to "tinyxml" +# Otherwise, set it to "dfhack-tinyxml" +option(EXTERNAL_TINYXML "Choose to link against external TinyXML" OFF) +if(EXTERNAL_TINYXML) + find_package(TinyXML REQUIRED) + if(NOT TinyXML_FOUND) + message(SEND_ERROR "Could not find an external TinyXML, consider setting EXTERNAL_TINYXML to OFF.") + endif() + SET(DFHACK_TINYXML "tinyxml") +else() + include_directories(depends/tinyxml) + SET(DFHACK_TINYXML "dfhack-tinyxml") +endif() + include_directories(depends/tthread) include_directories(${ZLIB_INCLUDE_DIRS}) include_directories(depends/clsocket/src) diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index bf0345bfb..d8442b12a 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -2,7 +2,12 @@ add_subdirectory(lua) add_subdirectory(md5) add_subdirectory(protobuf) -add_subdirectory(tinyxml) + +# Don't build tinyxml if it's being externally linked against. +if(NOT TinyXML_FOUND) + add_subdirectory(tinyxml) +endif() + add_subdirectory(tthread) add_subdirectory(jsoncpp) # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. diff --git a/depends/tinyxml/CMakeLists.txt b/depends/tinyxml/CMakeLists.txt index 7d924924f..313837cac 100644 --- a/depends/tinyxml/CMakeLists.txt +++ b/depends/tinyxml/CMakeLists.txt @@ -1,3 +1,5 @@ -project(dfhack-tinyxml) -ADD_LIBRARY(dfhack-tinyxml STATIC EXCLUDE_FROM_ALL tinystr.cpp tinyxml.cpp tinyxmlerror.cpp tinyxmlparser.cpp) -IDE_FOLDER(dfhack-tinyxml "Depends") \ No newline at end of file +if(NOT TinyXML_FOUND) + project(dfhack-tinyxml) + ADD_LIBRARY(dfhack-tinyxml STATIC EXCLUDE_FROM_ALL tinystr.cpp tinyxml.cpp tinyxmlerror.cpp tinyxmlparser.cpp) + IDE_FOLDER(dfhack-tinyxml "Depends") +endif() diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 3a6cd4dc8..346776865 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -269,12 +269,12 @@ IF(UNIX) ENDIF() IF(APPLE) - SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS dl dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) ELSEIF(UNIX) - SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS rt dl dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) ELSE(WIN32) #FIXME: do we really need psapi? - SET(PROJECT_LIBS psapi dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS psapi dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) ENDIF() ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp) From af906d6582622dd2599cf992feb963df704afdbb Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jun 2016 19:06:00 -0400 Subject: [PATCH 0089/1012] Remove library/scripts submodule --- .gitmodules | 3 --- library/scripts | 1 - 2 files changed, 4 deletions(-) delete mode 160000 library/scripts diff --git a/.gitmodules b/.gitmodules index cbb4c538d..9f1b48395 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,3 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git -[submodule "library/scripts"] - path = library/scripts - url = git://github.com/dfhack/scripts.git diff --git a/library/scripts b/library/scripts deleted file mode 160000 index b67885c0c..000000000 --- a/library/scripts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b67885c0c07931a402a3b642a4e2468f14782058 From 85a920d1df167d48da89aaadb4679a60788f1225 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jun 2016 19:07:45 -0400 Subject: [PATCH 0090/1012] Re-add the scripts submodule, with a different internal name This will break existing clones when using git versions before 1.8.1, which do not support the --name option of "git submodule add", unless steps are taken to remove the old scripts submodules in .git/modules (see Compile.rst). --- .gitmodules | 3 +++ scripts | 1 + 2 files changed, 4 insertions(+) create mode 160000 scripts diff --git a/.gitmodules b/.gitmodules index 9f1b48395..687dae006 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git +[submodule "scripts2"] + path = scripts + url = git://github.com/dfhack/scripts.git diff --git a/scripts b/scripts new file mode 160000 index 000000000..b67885c0c --- /dev/null +++ b/scripts @@ -0,0 +1 @@ +Subproject commit b67885c0c07931a402a3b642a4e2468f14782058 From cfaba3ec71b0b88a4f01f434f539784349231385 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jun 2016 19:38:15 -0400 Subject: [PATCH 0091/1012] Update various references to scripts/ and mention old git issues in Compile.rst --- .travis.yml | 2 +- CMakeLists.txt | 10 ++++++---- conf.py | 4 ++-- docs/Compile.rst | 18 +++++++++++++++++- library/CMakeLists.txt | 9 --------- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7ac6ae18..1455044aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: - python travis/authors-rst.py - python travis/script-in-readme.py - python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" -- python travis/script-syntax.py --ext=rb --cmd="ruby -c" --path library/scripts/ +- python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bd89edce..9b9b985c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,8 @@ IF(BUILD_PLUGINS) add_subdirectory (plugins) endif() +add_subdirectory(scripts) + find_package(Sphinx QUIET) if (BUILD_DOCS) if (NOT SPHINX_FOUND) @@ -221,12 +223,12 @@ if (BUILD_DOCS) "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*" "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" - "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/about.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/*/about.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/about.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*/about.txt" ) file(GLOB_RECURSE SPHINX_SCRIPT_DEPS - "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/*.lua" - "${CMAKE_CURRENT_SOURCE_DIR}/library/scripts/*.rb" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.lua" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.rb" ) set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" diff --git a/conf.py b/conf.py index 52f715ec5..10df1b551 100644 --- a/conf.py +++ b/conf.py @@ -52,7 +52,7 @@ def document_scripts(): """ # First, we collect the commands and paths to include in our docs scripts = [] - for root, _, files in os.walk('library/scripts'): + for root, _, files in os.walk('scripts'): scripts.extend(doc_dir(root, files)) # Next we split by type and create include directives sorted by command kinds = {'base': [], 'devel': [], 'fix': [], 'gui': [], 'modtools': []} @@ -84,7 +84,7 @@ def write_script_docs(): 'modtools': 'Scripts for Modders'} for k in head: title = ('.. _{k}:\n\n{l}\n{t}\n{l}\n\n' - '.. include:: /library/scripts/{a}about.txt\n\n' + '.. include:: /scripts/{a}about.txt\n\n' '.. contents::\n\n').format( k=k, t=head[k], l=len(head[k])*'#', diff --git a/docs/Compile.rst b/docs/Compile.rst index 006c1d263..650abbfca 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -35,7 +35,21 @@ To get the latest development code (develop branch), clone as above and then:: You must run ``git submodule update`` every time you change Git branch, for example when switching between master and develop branches and back. - +If a submodule only exists on the newer branch, you also need to run +``git submodule update --init``. Failure to do this may result in strange +build errors or "not a known DF version" errors. + +**Important note regarding very old git versions** + +If you are using git 1.8.0 or older, and cloned DFHack before commit 85a920d +(around DFHack v0.43.03-alpha1), you may run into fatal git errors when updating +submodules after switching branches. This is due to those versions of git being +unable to handle our change from "scripts/3rdparty/name" submodules to a single +"scripts" submodule. This may be fixable by renaming .git/modules/scripts to +something else and re-running ``git submodule update --init`` on the branch with +the single scripts submodule (and running it again when switching back to the +one with multiple submodules, if necessary), but it is usually much simpler to +upgrade your git version. Contributing to DFHack ====================== @@ -47,6 +61,8 @@ and whenever you need help. .. _IRC: https://webchat.freenode.net/?channels=dfhack +(Note: for submodule issues, please see the above instructions first!) + For lots more details on contributing to DFHack, including pull requests, code format, and more, please see `contributing-code`. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 3a6cd4dc8..116f0c4b3 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -407,13 +407,6 @@ install(DIRECTORY lua/ DESTINATION ${DFHACK_LUA_DESTINATION} FILES_MATCHING PATTERN "*.lua") -#install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts -# DESTINATION ${DFHACK_DATA_DESTINATION} -# FILES_MATCHING PATTERN "*.lua" -# PATTERN "*.rb" -# PATTERN "3rdparty" EXCLUDE -# ) - install(DIRECTORY ${dfhack_SOURCE_DIR}/patches DESTINATION ${DFHACK_DATA_DESTINATION} FILES_MATCHING PATTERN "*.dif") @@ -434,5 +427,3 @@ if(BUILD_DEVEL) add_subdirectory (doc) ENDIF() endif() - -add_subdirectory(scripts) From b196ecf351aebdf860b9475c85f36e60cd9e9d14 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 30 Jun 2016 14:33:15 +0530 Subject: [PATCH 0092/1012] Send over material tissues through RemoteFortressReader --- plugins/proto/RemoteFortressReader.proto | 9 +++++++++ plugins/remotefortressreader.cpp | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index b7ca674fb..96007da7a 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -492,6 +492,14 @@ message BpAppearanceModifier optional int32 mod_max = 3; } +message TissueRaw +{ + optional string id = 1; + optional string name = 2; + optional MatPair material = 3; + optional string subordinate_to_tissue = 4; +} + message CasteRaw { optional int32 index = 1; @@ -524,6 +532,7 @@ message CreatureRaw optional ColorDefinition color = 8; optional int32 adultsize = 9; repeated CasteRaw caste = 10; + repeated TissueRaw tissues = 11; } message CreatureRawList diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index fe8aae53e..9a751102f 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -62,6 +62,7 @@ #include "df/unit.h" #include "df/creature_raw.h" #include "df/caste_raw.h" +#include "df/tissue.h" #include "df/enabler.h" #include "df/graphic.h" @@ -2409,7 +2410,22 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_caste->set_description(orig_caste->description); send_caste->set_adult_size(orig_caste->misc.adult_size); } - } + + for (int j = 0; j < orig_creature->tissue.size(); j++) + { + auto orig_tissue = orig_creature->tissue[j]; + auto send_tissue = send_creature->add_tissues(); + + send_tissue->set_id(orig_tissue->id); + send_tissue->set_name(orig_tissue->tissue_name_singular); + send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); + + auto send_mat = send_tissue->mutable_material(); + + send_mat->set_mat_index(orig_tissue->mat_index); + send_mat->set_mat_type(orig_tissue->mat_type); + } +} return CR_OK; } From b37afa459118a52fe6fb27ca94f778dc1c0b7744 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 30 Jun 2016 15:12:28 +0530 Subject: [PATCH 0093/1012] Remove tabs --- plugins/proto/RemoteFortressReader.proto | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 96007da7a..c5c778b1f 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -494,10 +494,10 @@ message BpAppearanceModifier message TissueRaw { - optional string id = 1; - optional string name = 2; - optional MatPair material = 3; - optional string subordinate_to_tissue = 4; + optional string id = 1; + optional string name = 2; + optional MatPair material = 3; + optional string subordinate_to_tissue = 4; } message CasteRaw @@ -532,7 +532,7 @@ message CreatureRaw optional ColorDefinition color = 8; optional int32 adultsize = 9; repeated CasteRaw caste = 10; - repeated TissueRaw tissues = 11; + repeated TissueRaw tissues = 11; } message CreatureRawList @@ -623,4 +623,4 @@ message KeyboardEvent optional uint32 mod = 6; optional uint32 unicode = 7; } - \ No newline at end of file + From 13b328beebae8557d1a6da1c3bf73be00b45dbb4 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 30 Jun 2016 15:14:20 +0530 Subject: [PATCH 0094/1012] Remove tabs --- plugins/remotefortressreader.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 9a751102f..7a853db82 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -2411,20 +2411,20 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_caste->set_adult_size(orig_caste->misc.adult_size); } - for (int j = 0; j < orig_creature->tissue.size(); j++) - { - auto orig_tissue = orig_creature->tissue[j]; - auto send_tissue = send_creature->add_tissues(); + for (int j = 0; j < orig_creature->tissue.size(); j++) + { + auto orig_tissue = orig_creature->tissue[j]; + auto send_tissue = send_creature->add_tissues(); - send_tissue->set_id(orig_tissue->id); - send_tissue->set_name(orig_tissue->tissue_name_singular); - send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); + send_tissue->set_id(orig_tissue->id); + send_tissue->set_name(orig_tissue->tissue_name_singular); + send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); - auto send_mat = send_tissue->mutable_material(); + auto send_mat = send_tissue->mutable_material(); - send_mat->set_mat_index(orig_tissue->mat_index); - send_mat->set_mat_type(orig_tissue->mat_type); - } + send_mat->set_mat_index(orig_tissue->mat_index); + send_mat->set_mat_type(orig_tissue->mat_type); + } } return CR_OK; From 163207e362dbd850e356d9e21051cc049958c5fb Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 30 Jun 2016 20:42:27 -0400 Subject: [PATCH 0095/1012] Give more feedback for DF detection failures with mismatched XML versions This may help address issues where people forget to read the documentation, don't run "git submodule update", and end up with a df-structures version that's too old (which is somewhat common, it turns out). --- library/Core.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index f5bc9d4e7..fb1dd80bb 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1485,7 +1485,34 @@ bool Core::Init() if(!vinfo || !p->isIdentified()) { - fatal("Not a known DF version.\n"); + if (!Version::git_xml_match()) + { + const char *msg = ( + "*******************************************************\n" + "* BIG, UGLY ERROR MESSAGE *\n" + "*******************************************************\n" + "\n" + "This DF version is missing from hack/symbols.xml, and\n" + "you have compiled DFHack with a df-structures (xml)\n" + "version that does *not* match the version tracked in git.\n" + "\n" + "If you are not actively working on df-structures and you\n" + "expected DFHack to work, you probably forgot to run\n" + "\n" + " git submodule update\n" + "\n" + "If this does not sound familiar, read Compile.rst and \n" + "recompile.\n" + "More details can be found in stderr.log in this folder.\n" + ); + cout << msg << endl; + cerr << msg << endl; + fatal("Not a known DF version - XML version mismatch (see console or stderr.log)"); + } + else + { + fatal("Not a known DF version.\n"); + } errorstate = true; delete p; p = NULL; From d8f4d79b977c126d18201f3875f724731d40acc0 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 30 Jun 2016 23:58:56 -0500 Subject: [PATCH 0096/1012] labormanager: significant restructuring to use job posting list Updated here to get potential jobs off the job posting lists, which is apparently where certain map-designated live after being designated but before they move to the actual job list. Also changes to how tools are handled, and lever pulling is assigned by default to all idle dwarfs. --- plugins/devel/labormanager.cpp | 273 ++++++++++++++++++++------------- 1 file changed, 169 insertions(+), 104 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 8d614ed36..545ca7f1c 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -184,7 +184,7 @@ static const dwarf_state dwarf_states[] = { OTHER /* GoShopping2 */, BUSY /* Clean */, OTHER /* Rest */, - BUSY /* PickupEquipment */, + OTHER /* PickupEquipment */, BUSY /* DumpItem */, OTHER /* StrangeMoodCrafter */, OTHER /* StrangeMoodJeweller */, @@ -414,7 +414,7 @@ struct labor_default static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {200, 0, TOOL_PICK}, + /* MINEa */ {200, 0, TOOL_PICK}, /* HAUL_STONE */ {100, 0, TOOL_NONE}, /* HAUL_WOOD */ {100, 0, TOOL_NONE}, /* HAUL_BODY */ {200, 0, TOOL_NONE}, @@ -1607,7 +1607,7 @@ private: bool labors_changed; int tool_count[TOOLS_MAX]; - bool reequip_needed[TOOLS_MAX]; + int tool_in_use[TOOLS_MAX]; int cnt_recover_wounded; int cnt_diagnosis; @@ -1639,7 +1639,69 @@ private: bool old = dwarf->dwarf->status.labors[labor]; dwarf->dwarf->status.labors[labor] = value; if (old != value) + { labors_changed = true; + + tools_enum tool = default_labor_infos[labor].tool; + if (tool != TOOL_NONE) + tool_in_use[tool] += value ? 1 : -1; + } + } + } + + void process_job (df::job* j) + { + if (j->flags.bits.suspend || j->flags.bits.item_lost) + return; + + int worker = -1; + int bld = -1; + + for (int r = 0; r < j->general_refs.size(); ++r) + { + if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; + if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) + bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; + } + + if (bld != -1) + { + df::building* b = binsearch_in_vector(world->buildings.all, bld); + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + // check if this job is the first nonsuspended job on this building; if not, ignore it + // (except for farms and trade depots) + if (fjid != j->id && + b->getType() != df::building_type::FarmPlot && + b->getType() != df::building_type::TradeDepot) + return; + } + + df::unit_labor labor = labor_mapper->find_job_labor (j); + + if (labor != df::unit_labor::NONE) + { + labor_needed[labor]++; + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } else { + labor_infos[labor].mark_assigned(); + labor_in_use[labor]++; + } + } } @@ -1674,6 +1736,7 @@ private: else out.print("Trade depot found but trader is not requested.\n"); } + } } } @@ -1729,7 +1792,10 @@ private: void count_tools() { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + { tool_count[e] = 0; + tool_in_use[e] = 0; + } priority_food = 0; @@ -1776,66 +1842,20 @@ private: void collect_job_list() { - labor_needed.clear(); - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) { df::job* j = jll->item; if (!j) continue; + process_job(j); + } - if (j->flags.bits.suspend || j->flags.bits.item_lost) + for (auto jp = world->job_postings.begin(); jp != world->job_postings.end(); jp++) + { + if ((*jp)->flags.bits.dead) continue; - int worker = -1; - int bld = -1; - - for (int r = 0; r < j->general_refs.size(); ++r) - { - if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; - if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) - bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; - } - - if (bld != -1) - { - df::building* b = binsearch_in_vector(world->buildings.all, bld); - int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) - { - if (b->jobs[jn]->flags.bits.suspend) - continue; - fjid = b->jobs[jn]->id; - break; - } - // check if this job is the first nonsuspended job on this building; if not, ignore it - // (except for farms) - if (fjid != j->id && b->getType() != df::building_type::FarmPlot) { - continue; - } - - } - - df::unit_labor labor = labor_mapper->find_job_labor (j); - - if (labor != df::unit_labor::NONE) - { - labor_needed[labor]++; - if (worker == -1) - { - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } - } else { - labor_infos[labor].mark_assigned(); - labor_in_use[labor]++; - } - - } + process_job((*jp)->job); } } @@ -1909,6 +1929,32 @@ private: } } + // check if dwarf has an axe, pick, or crossbow + + for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + dwarf->armed = true; + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_tool[TOOL_AXE] = true; + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_tool[TOOL_PICK] = true; + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_tool[TOOL_CROSSBOW] = true; + } + } + } + // Find the activity state for each dwarf bool is_on_break = false; @@ -1972,12 +2018,9 @@ private: if (labor != df::unit_labor::NONE) { labor_infos[labor].busy_dwarfs++; - - if (!dwarf->dwarf->status.labors[labor]) + if (default_labor_infos[labor].tool != TOOL_NONE) { - out.print("LABORMANAGER: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", - dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, - ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); + tool_in_use[default_labor_infos[labor].tool]++; } } } @@ -2044,37 +2087,7 @@ private: } dwarf->high_skill = high_skill; - // check if dwarf has an axe, pick, or crossbow - for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) - { - df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; - if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) - { - dwarf->armed = true; - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; - if (weaponsk == df::job_skill::AXE) - { - dwarf->has_tool[TOOL_AXE] = true; - if (state != IDLE) - tool_count[TOOL_AXE]--; - } - else if (weaponsk == df::job_skill::MINING) - { - dwarf->has_tool[TOOL_PICK] = 1; - if (state != IDLE) - tool_count[TOOL_PICK]--; - } - else if (rangesk == df::job_skill::CROSSBOW) - { - dwarf->has_tool[TOOL_CROSSBOW] = 1; - if (state != IDLE) - tool_count[TOOL_CROSSBOW]--; - } - } - } // clear labors of dwarfs with clear_all set @@ -2136,7 +2149,10 @@ private: score += 1000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 5000; + score += 30000; + if (default_labor_infos[labor].tool != TOOL_NONE && + !d->has_tool[default_labor_infos[labor].tool]) + score -= 30000; if (d->has_children && labor_outside[labor]) score -= 15000; if (d->armed && labor_outside[labor]) @@ -2159,6 +2175,8 @@ public: cnt_setting = cnt_traction = cnt_crutch = 0; need_food_water = 0; + labor_needed.clear(); + for (int e = 0; e < TOOLS_MAX; e++) tool_count[e] = 0; @@ -2194,8 +2212,8 @@ public: // add job entries for designation-related jobs - labor_needed[df::unit_labor::MINE] += std::min(tool_count[TOOL_PICK], dig_count); - labor_needed[df::unit_labor::CUTWOOD] += std::min(tool_count[TOOL_AXE], tree_count); + labor_needed[df::unit_labor::MINE] += dig_count; + labor_needed[df::unit_labor::CUTWOOD] += tree_count; labor_needed[df::unit_labor::DETAIL] += detail_count; labor_needed[df::unit_labor::HERBALIST] += plant_count; @@ -2269,10 +2287,19 @@ public: if (l == df::unit_labor::NONE) continue; + int before = labor_needed[l]; + if (labor_infos[l].idle_dwarfs > 0) labor_needed[l] = 0; else labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + + if (default_labor_infos[l].tool != TOOL_NONE) + labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); + + if (print_debug && before != labor_needed[l]) + out.print ("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); + } /* assign food haulers for rotting food items */ @@ -2427,19 +2454,30 @@ public: if (l == df::unit_labor::NONE) continue; - if (l == best_labor) + tools_enum t = default_labor_infos[l].tool; + + if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) { set_labor(*bestdwarf, l, true); - tools_enum t = default_labor_infos[l].tool; - if (t != TOOL_NONE) + if (t != TOOL_NONE && (*bestdwarf)->has_tool[t]) { - tool_count[t]--; - if (!(*bestdwarf)->has_tool[t]) - (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + df::job_type j; + j = df::job_type::NONE; + + if ((*bestdwarf)->dwarf->job.current_job) + j = (*bestdwarf)->dwarf->job.current_job->job_type; + + if (print_debug) + out.print("LABORMANAGER: asking %s to pick up tools, current job %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(job_type, j).c_str()); + + (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + labors_changed = true; } } else if ((*bestdwarf)->state == IDLE) + { set_labor(*bestdwarf, l, false); + } } if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) @@ -2450,7 +2488,6 @@ public: if (best_labor != df::unit_labor::NONE) { - busy_dwarfs.push_back(*bestdwarf); labor_infos[best_labor].active_dwarfs++; to_assign[best_labor]--; } @@ -2489,6 +2526,8 @@ public: ENUM_KEY_STR(unit_labor, l).c_str(), score, ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } + if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000) + set_labor(*d, (*d)->using_labor, false); } } } @@ -2535,7 +2574,7 @@ public: if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) set_labor(*d, l, true); - else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION) + else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER) set_labor(*d, l, true); else set_labor(*d, l, false); @@ -2546,16 +2585,42 @@ public: for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) { + if ((*d)->dwarf->job.current_job && (*d)->dwarf->job.current_job->job_type == df::job_type::PickupEquipment) + continue; + + if ((*d)->dwarf->military.pickup_flags.bits.update) + continue; + FOR_ENUM_ITEMS (unit_labor, l) { if (l == df::unit_labor::NONE) continue; tools_enum t = default_labor_infos[l].tool; - if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) + if (t == TOOL_NONE) + continue; + + bool has_tool = (*d)->has_tool[t]; + bool needs_tool = (*d)->dwarf->status.labors[l]; + + if (has_tool != needs_tool) { - tool_count[t]++; - (*d)->dwarf->military.pickup_flags.bits.update = 1; + if (has_tool && tool_count[t] > tool_in_use[t]) + continue; + + df::job_type j = df::job_type::NONE; + + if ((*d)->dwarf->job.current_job) + j = (*d)->dwarf->job.current_job->job_type; + + if (print_debug) + out.print("LABORMANAGER: asking %s to %s tools, current job %s, %d %d \n", (*d)->dwarf->name.first_name.c_str(), (has_tool) ? "drop" : "pick up", ENUM_KEY_STR(job_type, j).c_str(), has_tool, needs_tool); + + (*d)->dwarf->military.pickup_flags.bits.update = true; + labors_changed = true; + + if (needs_tool) + tool_in_use[t]++; } } } From 233a5e9c10d3838110f4b2db912bef54d6edbec7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 3 Jul 2016 23:30:03 -0400 Subject: [PATCH 0097/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index b67885c0c..022cb9ef7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b67885c0c07931a402a3b642a4e2468f14782058 +Subproject commit 022cb9ef78f13786a3ff6122c49b16090edf8aa5 From 2455e36510a10ccac291c78d9db747ad631702d2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 3 Jul 2016 23:32:43 -0400 Subject: [PATCH 0098/1012] Initial 64-bit support --- CMakeLists.txt | 23 ++++++- depends/lua/CMakeLists.txt | 8 ++- library/DataDefs.cpp | 8 +-- library/DataStaticsFields.cpp | 2 + library/LuaApi.cpp | 16 ++--- library/LuaWrapper.cpp | 2 +- library/Process-darwin.cpp | 18 ++++- library/Process-linux.cpp | 4 +- library/VTableInterpose.cpp | 2 +- library/VersionInfoFactory.cpp | 4 +- library/include/DataIdentity.h | 2 + library/include/VersionInfo.h | 24 +++---- library/include/VersionInfoFactory.h | 2 +- library/include/modules/EventManager.h | 2 +- library/modules/Buildings.cpp | 92 +++++++++++++------------- plugins/Plugins.cmake | 15 +++-- 16 files changed, 136 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b9b985c2..b74f94b39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,18 @@ if(MSVC) add_definitions( "/wd4819" ) endif() +SET(DFHACK_BUILD_ARCH "32" CACHE STRING "Architecture to build ('32' or '64')") +IF("${DFHACK_BUILD_ARCH}" STREQUAL "32") + SET(DFHACK_BUILD_32 1) + SET(DFHACK_BUILD_64 0) +ELSEIF("${DFHACK_BUILD_ARCH}" STREQUAL "64") + SET(DFHACK_BUILD_32 0) + SET(DFHACK_BUILD_64 1) + ADD_DEFINITIONS(-DDFHACK64) +ELSE() + MESSAGE(SEND_ERROR "Invalid build architecture (should be 32/64): ${DFHACK_BUILD_ARCH}") +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") @@ -152,8 +164,15 @@ IF(UNIX) # enable C++11 features add_definitions(-DLINUX_BUILD) SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -Wall -Wno-unused-variable") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -m32 -march=i686 -mtune=generic -std=c++0x") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -m32 -march=i686 -mtune=generic") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -mtune=generic -std=c++0x") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -mtune=generic") + IF(DFHACK_BUILD_64) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -mno-avx") + ELSE() + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32 -march=i686") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") + ENDIF() ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index b77bce6f7..782bb5e96 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -11,8 +11,12 @@ ELSE() ENDIF() IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(CMAKE_C_FLAGS "-m32") + ADD_DEFINITIONS(-DLINUX_BUILD) + IF(DFHACK_BUILD_64) + SET(CMAKE_C_FLAGS "-m64 -mno-avx") + ELSE() + SET(CMAKE_C_FLAGS "-m32") + ENDIF() ENDIF() SET (HDR_LIBLUA diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index f9187ff09..be381d860 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -276,11 +276,11 @@ virtual_identity *virtual_identity::find(void *vtable) if (p->vtable_ptr && p->vtable_ptr != vtable) { std::cerr << "Conflicting vtable ptr for class '" << p->getName() - << "': found 0x" << std::hex << unsigned(vtable) - << ", previous 0x" << unsigned(p->vtable_ptr) << std::dec << std::endl; + << "': found 0x" << std::hex << uintptr_t(vtable) + << ", previous 0x" << uintptr_t(p->vtable_ptr) << std::dec << std::endl; abort(); } else if (!p->vtable_ptr) { - uint32_t pv = unsigned(vtable); + uintptr_t pv = uintptr_t(vtable); pv -= Core::getInstance().vinfo->getRebaseDelta(); std::cerr << "" << std::endl; @@ -292,7 +292,7 @@ virtual_identity *virtual_identity::find(void *vtable) } std::cerr << "UNKNOWN CLASS '" << name << "': vtable = 0x" - << std::hex << unsigned(vtable) << std::dec << std::endl; + << std::hex << uintptr_t(vtable) << std::dec << std::endl; known[vtable] = NULL; return NULL; diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 583e4d6a4..96f91dd66 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -30,6 +30,8 @@ namespace df { NUMBER_IDENTITY_TRAITS(uint32_t); NUMBER_IDENTITY_TRAITS(int64_t); NUMBER_IDENTITY_TRAITS(uint64_t); + NUMBER_IDENTITY_TRAITS(intptr_t); + NUMBER_IDENTITY_TRAITS(uintptr_t); NUMBER_IDENTITY_TRAITS(float); NUMBER_IDENTITY_TRAITS(double); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 712ccb541..74019c4e1 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2310,8 +2310,8 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) return rv; } -static uint32_t getImageBase() { return Core::getInstance().p->getBase(); } -static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } +static uintptr_t getImageBase() { return Core::getInstance().p->getBase(); } +static intptr_t getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static int8_t getModstate() { return Core::getInstance().getModstate(); } static std::string internal_strerror(int n) { return strerror(n); } @@ -2344,7 +2344,7 @@ static int internal_getPE(lua_State *L) static int internal_getAddress(lua_State *L) { const char *name = luaL_checkstring(L, 1); - uint32_t addr = Core::getInstance().vinfo->getAddress(name); + uintptr_t addr = Core::getInstance().vinfo->getAddress(name); if (addr) lua_pushnumber(L, addr); else @@ -2355,7 +2355,7 @@ static int internal_getAddress(lua_State *L) static int internal_setAddress(lua_State *L) { std::string name = luaL_checkstring(L, 1); - uint32_t addr = (uint32_t)checkaddr(L, 2, true); + uintptr_t addr = (uintptr_t)checkaddr(L, 2, true); internal_getAddress(L); // Set the address @@ -2372,7 +2372,7 @@ static int internal_setAddress(lua_State *L) } // Print via printerr, so that it is definitely logged to stderr.log. - uint32_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta(); + uintptr_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta(); fprintf(stderr, "Setting global '%s' to %x (%x)\n", name.c_str(), addr, iaddr); fflush(stderr); @@ -2382,7 +2382,7 @@ static int internal_setAddress(lua_State *L) static int internal_getVTable(lua_State *L) { const char *name = luaL_checkstring(L, 1); - uint32_t addr = (uint32_t)Core::getInstance().vinfo->getVTable(name); + uintptr_t addr = (uintptr_t)Core::getInstance().vinfo->getVTable(name); if (addr) lua_pushnumber(L, addr); else @@ -2412,9 +2412,9 @@ static int internal_getMemRanges(lua_State *L) for(size_t i = 0; i < ranges.size(); i++) { lua_newtable(L); - lua_pushnumber(L, (uint32_t)ranges[i].start); + lua_pushnumber(L, (uintptr_t)ranges[i].start); lua_setfield(L, -2, "start_addr"); - lua_pushnumber(L, (uint32_t)ranges[i].end); + lua_pushnumber(L, (uintptr_t)ranges[i].end); lua_setfield(L, -2, "end_addr"); lua_pushstring(L, ranges[i].name); lua_setfield(L, -2, "name"); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index dac458709..08af93e78 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -993,7 +993,7 @@ static int meta_ptr_tostring(lua_State *state) lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); - lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (unsigned)ptr).c_str()); + lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (uintptr_t)ptr).c_str()); return 1; } diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 6f159e5fe..95c347368 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -118,8 +118,8 @@ Process::~Process() string Process::doReadClassName (void * vptr) { //FIXME: BAD!!!!! - char * typeinfo = Process::readPtr(((char *)vptr - 0x4)); - char * typestring = Process::readPtr(typeinfo + 0x4); + char * typeinfo = Process::readPtr(((char *)vptr - sizeof(void*))); + char * typestring = Process::readPtr(typeinfo + sizeof(void*)); string raw = readCString(typestring); size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers size_t end = raw.length(); @@ -151,9 +151,15 @@ void Process::getMemRanges( vector & ranges ) the_task = mach_task_self(); +#ifdef DFHACK64 + mach_vm_size_t vmsize; + mach_vm_address_t address; + vm_region_basic_info_data_64_t info; +#else vm_size_t vmsize; vm_address_t address; vm_region_basic_info_data_t info; +#endif mach_msg_type_number_t info_count; vm_region_flavor_t flavor; memory_object_name_t object; @@ -162,10 +168,18 @@ void Process::getMemRanges( vector & ranges ) address = 0; do { +#ifdef DFHACK64 + flavor = VM_REGION_BASIC_INFO_64; + info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = mach_vm_region(the_task, &address, &vmsize, flavor, + (vm_region_info_64_t)&info, &info_count, &object); +#else flavor = VM_REGION_BASIC_INFO; info_count = VM_REGION_BASIC_INFO_COUNT; kr = vm_region(the_task, &address, &vmsize, flavor, (vm_region_info_t)&info, &info_count, &object); +#endif + if (kr == KERN_SUCCESS) { if (info.reserved==1) { address += vmsize; diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 1a1e754d0..14c70a802 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -122,8 +122,8 @@ Process::~Process() string Process::doReadClassName (void * vptr) { //FIXME: BAD!!!!! - char * typeinfo = Process::readPtr(((char *)vptr - 0x4)); - char * typestring = Process::readPtr(typeinfo + 0x4); + char * typeinfo = Process::readPtr(((char *)vptr - sizeof(void*))); + char * typestring = Process::readPtr(typeinfo + sizeof(void*)); string raw = readCString(typestring); size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers size_t end = raw.length(); diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index eb8dec861..d81c6d730 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -305,7 +305,7 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v */ fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x (%s)\n", - vmethod_idx, unsigned(interpose_method), name_str); + vmethod_idx, uintptr_t(interpose_method), name_str); fflush(stderr); abort(); } diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index f6bd5cba9..02dac568c 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -69,7 +69,7 @@ VersionInfo * VersionInfoFactory::getVersionInfoByMD5(string hash) return 0; } -VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uint32_t timestamp) +VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) { for(size_t i = 0; i < versions.size();i++) { @@ -140,7 +140,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) } if ((is_vtable && no_vtables) || (!is_vtable && no_globals)) continue; - uint32_t addr = strtol(cstr_value, 0, 0); + uintptr_t addr = strtol(cstr_value, 0, 0); if (is_vtable) mem->setVTable(cstr_key, addr); else diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index e6dfc6a18..c94129f9b 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -487,6 +487,8 @@ namespace df NUMBER_IDENTITY_TRAITS(uint32_t); NUMBER_IDENTITY_TRAITS(int64_t); NUMBER_IDENTITY_TRAITS(uint64_t); + NUMBER_IDENTITY_TRAITS(intptr_t); + NUMBER_IDENTITY_TRAITS(uintptr_t); NUMBER_IDENTITY_TRAITS(float); NUMBER_IDENTITY_TRAITS(double); diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index cf7dd60c0..58ecb2c28 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -49,10 +49,10 @@ namespace DFHack { private: std::vector md5_list; - std::vector PE_list; - std::map Addresses; - std::map VTables; - uint32_t base; + std::vector PE_list; + std::map Addresses; + std::map VTables; + uintptr_t base; int rebase_delta; std::string version; OSType OS; @@ -75,10 +75,10 @@ namespace DFHack OS = rhs.OS; }; - uint32_t getBase () const { return base; }; + uintptr_t getBase () const { return base; }; int getRebaseDelta() const { return rebase_delta; } - void setBase (const uint32_t _base) { base = _base; }; - void rebaseTo(const uint32_t new_base) + void setBase (const uintptr_t _base) { base = _base; }; + void rebaseTo(const uintptr_t new_base) { int64_t old = base; int64_t newx = new_base; @@ -100,11 +100,11 @@ namespace DFHack return std::find(md5_list.begin(), md5_list.end(), _md5) != md5_list.end(); }; - void addPE (uint32_t PE_) + void addPE (uintptr_t PE_) { PE_list.push_back(PE_); }; - bool hasPE (uint32_t PE_) const + bool hasPE (uintptr_t PE_) const { return std::find(PE_list.begin(), PE_list.end(), PE_) != PE_list.end(); }; @@ -115,7 +115,7 @@ namespace DFHack }; std::string getVersion() const { return version; }; - void setAddress (const std::string& key, const uint32_t value) + void setAddress (const std::string& key, const uintptr_t value) { Addresses[key] = value; }; @@ -129,7 +129,7 @@ namespace DFHack return true; }; - uint32_t getAddress (const std::string& key) const + uintptr_t getAddress (const std::string& key) const { auto i = Addresses.find(key); if(i == Addresses.end()) @@ -137,7 +137,7 @@ namespace DFHack return (*i).second; } - void setVTable (const std::string& key, const uint32_t value) + void setVTable (const std::string& key, const uintptr_t value) { VTables[key] = value; }; diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index f69f37fee..8a2cabdc3 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -40,7 +40,7 @@ namespace DFHack bool loadFile( std::string path_to_xml); bool isInErrorState() const {return error;}; VersionInfo * getVersionInfoByMD5(std::string md5string); - VersionInfo * getVersionInfoByPETimestamp(uint32_t timestamp); + VersionInfo * getVersionInfoByPETimestamp(uintptr_t timestamp); std::vector versions; // trash existing list void clear(); diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 6e4be47be..d22fa2310 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -116,7 +116,7 @@ namespace std { std::size_t operator()(const DFHack::EventManager::EventHandler& h) const { size_t r = 17; const size_t m = 65537; - r = m*(r+(int32_t)h.eventHandler); + r = m*(r+(intptr_t)h.eventHandler); r = m*(r+h.freq); return r; } diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index a6589bac1..d819d358c 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1173,52 +1173,52 @@ void Buildings::clearBuildings(color_ostream& out) { void Buildings::updateBuildings(color_ostream& out, void* ptr) { - int32_t id = (int32_t)ptr; - auto building = df::building::find(id); - - if (building) - { - // Already cached -> weird, so bail out - if (corner1.count(id)) - return; - // Civzones cannot be cached because they can - // overlap each other and normal buildings. - if (!building->isSettingOccupancy()) - return; - - df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); - df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); - - corner1[id] = p1; - corner2[id] = p2; - - for ( int32_t x = p1.x; x <= p2.x; x++ ) { - for ( int32_t y = p1.y; y <= p2.y; y++ ) { - df::coord pt(x,y,building->z); - if (containsTile(building, pt, false)) - locationToBuilding[pt] = id; - } - } - } - else if (corner1.count(id)) - { - //existing building: destroy it - df::coord p1 = corner1[id]; - df::coord p2 = corner2[id]; - - for ( int32_t x = p1.x; x <= p2.x; x++ ) { - for ( int32_t y = p1.y; y <= p2.y; y++ ) { - df::coord pt(x,y,p1.z); - - auto cur = locationToBuilding.find(pt); - if (cur != locationToBuilding.end() && cur->second == id) - locationToBuilding.erase(cur); - } - } - - corner1.erase(id); - corner2.erase(id); - } + // int32_t id = (int32_t)ptr; + // auto building = df::building::find(id); + + // if (building) + // { + // // Already cached -> weird, so bail out + // if (corner1.count(id)) + // return; + // // Civzones cannot be cached because they can + // // overlap each other and normal buildings. + // if (!building->isSettingOccupancy()) + // return; + + // df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); + // df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); + + // corner1[id] = p1; + // corner2[id] = p2; + + // for ( int32_t x = p1.x; x <= p2.x; x++ ) { + // for ( int32_t y = p1.y; y <= p2.y; y++ ) { + // df::coord pt(x,y,building->z); + // if (containsTile(building, pt, false)) + // locationToBuilding[pt] = id; + // } + // } + // } + // else if (corner1.count(id)) + // { + // //existing building: destroy it + // df::coord p1 = corner1[id]; + // df::coord p2 = corner2[id]; + + // for ( int32_t x = p1.x; x <= p2.x; x++ ) { + // for ( int32_t y = p1.y; y <= p2.y; y++ ) { + // df::coord pt(x,y,p1.z); + + // auto cur = locationToBuilding.find(pt); + // if (cur != locationToBuilding.end() && cur->second == id) + // locationToBuilding.erase(cur); + // } + // } + + // corner1.erase(id); + // corner2.erase(id); + // } } void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector *items) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index c801570db..4871c1f0c 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -1,8 +1,15 @@ IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall") - SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -m32 -std=c++0x") - SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32") + add_definitions(-DLINUX_BUILD) + SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall") + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -std=c++0x") + SET(CMAKE_C_FLAGS "-fvisibility=hidden") + IF(DFHACK_BUILD_64) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -mno-avx") + ELSE() + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") + ENDIF() ENDIF() include_directories("${dfhack_SOURCE_DIR}/library/include") From b4063352cf29dfecb5b2f878e18310049fe7c297 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 3 Jul 2016 23:33:07 -0400 Subject: [PATCH 0099/1012] Fix crash in vprinterr due to va_list misuse --- library/ColorText.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/library/ColorText.cpp b/library/ColorText.cpp index 359b717e6..7f417e0a8 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -126,10 +126,18 @@ void color_ostream::vprinterr(const char *format, va_list args) color_value save = cur_color; if (log_errors_to_stderr) - vfprintf(stderr, format, args); + { + va_list args1; + va_copy(args1, args); + vfprintf(stderr, format, args1); + va_end(args1); + } color(COLOR_LIGHTRED); - vprint(format, args); + va_list args2; + va_copy(args2, args); + vprint(format, args2); + va_end(args2); color(save); } From a386228f0ede21bdd4a72cea9c9b5d9b3008c0b3 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 5 Jul 2016 13:16:34 -0500 Subject: [PATCH 0100/1012] labormanager: add StoreItemInLocation labor, reduce tool churn Note: this commit requires updated df-structures (77968973b28d0e828f880d119a700abb079f3521 or later) --- library/xml | 2 +- plugins/devel/labormanager.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index b9178de68..77968973b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9178de68bd67442ff720f18b04d222302ce9f8c +Subproject commit 77968973b28d0e828f880d119a700abb079f3521 diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 545ca7f1c..98b7f0f6f 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1359,6 +1359,8 @@ public: job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; + + job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation }; df::unit_labor find_job_labor(df::job* j) @@ -1380,7 +1382,7 @@ public: df::unit_labor labor; if (job_to_labor_table.count(j->job_type) == 0) { - debug("LABORMANAGER: job has no job to labor table entry: %s\n", ENUM_KEY_STR(job_type, j->job_type).c_str()); + debug("LABORMANAGER: job has no job to labor table entry: %s (%d)\n", ENUM_KEY_STR(job_type, j->job_type).c_str(), j->job_type); debug_pause(); labor = df::unit_labor::NONE; } else { @@ -2526,7 +2528,7 @@ public: ENUM_KEY_STR(unit_labor, l).c_str(), score, ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } - if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000) + if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } } From 2de2e6a983964cbfd81455b63e9a6b4e9722acd3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 6 Jul 2016 20:30:28 -0400 Subject: [PATCH 0101/1012] Update CMakeLists and submodules --- CMakeLists.txt | 2 +- library/xml | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b9b985c2..3f3e24fee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.03") -SET(DFHACK_RELEASE "alpha1") +SET(DFHACK_RELEASE "r1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/library/xml b/library/xml index b9178de68..68d8a108b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9178de68bd67442ff720f18b04d222302ce9f8c +Subproject commit 68d8a108bdb3d0f38b4a8862bc9d8c9d9362645a diff --git a/scripts b/scripts index 022cb9ef7..7574e08b9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 022cb9ef78f13786a3ff6122c49b16090edf8aa5 +Subproject commit 7574e08b9af0397d6830949c49a344cd5d0134de From 39aa143cfac00dcfa87abd7af7ff1aaafdd48d58 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 6 Jul 2016 20:59:49 -0400 Subject: [PATCH 0102/1012] Update NEWS and submodules --- NEWS.rst | 23 ++++++++++++++++++++--- library/xml | 2 +- scripts | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 7d9630c32..dcc87b04a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -30,8 +30,8 @@ Changelog .. contents:: :depth: 2 -DFHack future -============= +DFHack 0.43.03-r1 +================= Lua --- @@ -39,18 +39,35 @@ Lua New Features ------------ -- `gui/gm-editor` it's now possible to insert default types to containers. For primitive types leave the type entry empty, for references use ``*``. +- `add-thought`: allow syndrome name as ``-thought`` argument +- `gui/gm-editor` + + - Added ability to insert default types into containers. For primitive types leave the type entry empty, and for references use ``*``. + - Added ``shift-esc`` binding to fully exit from editor + - Added ``gui/gm-editor toggle`` command to toggle editor visibility (saving position) + +- `modtools/create-unit`: + + - Added an option to attach units to an existing wild animal population + - Added an option to attach units to a map feature Fixes ----- +- `autofarm`: Can now handle crops that grow for more than a season +- `combine-plants`: Fixed recursion into sub-containers - `createitem`: Now moves multiple created items to cursor correctly - `exportlegends`: Improved handling of unknown enum items (fixes many errors) - `gui/create-item`: Fixed quality when creating multiple items - `gui/mod-manager`: Fixed error when mods folder doesn't exist - `modtools/item-trigger`: Fixed handling of items with subtypes +- `stockflow`: + + - Can order metal mechanisms + - Fixed material category of thread-spinning jobs Misc Improvements ----------------- +- The built-in ``ls`` command now wraps the descriptions of commands - `catsplosion`: now a lua script instead of a plugin - `fix/diplomats`: replaces ``fixdiplomats`` - `fix/merchants`: replaces ``fixmerchants`` diff --git a/library/xml b/library/xml index 68d8a108b..7d312334c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 68d8a108bdb3d0f38b4a8862bc9d8c9d9362645a +Subproject commit 7d312334c320022cd6275cddcdb8a64d4ed8d722 diff --git a/scripts b/scripts index 7574e08b9..b9f223612 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7574e08b9af0397d6830949c49a344cd5d0134de +Subproject commit b9f223612bbcebfa8aa3de368fd5deadc1d4589d From 98893793bd19d359b75771279d694bff7dd34ede Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 6 Jul 2016 21:27:54 -0400 Subject: [PATCH 0103/1012] Turn off prerelease flag --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d12c27179..10a831c28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.03") SET(DFHACK_RELEASE "r1") -SET(DFHACK_PRERELEASE TRUE) +SET(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 7f1d4506d4c59d6bfc9b6e6c1bda61099acf5a60 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 7 Jul 2016 02:10:43 -0500 Subject: [PATCH 0104/1012] labormanager: fix several job-to-labor mappings --- plugins/devel/labormanager.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 98b7f0f6f..46322a90c 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1135,7 +1135,7 @@ public: job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); - job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; + job_to_labor_table[df::job_type::RemoveConstruction] = jlf_const(df::unit_labor::REMOVE_CONSTRUCTION); job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; @@ -1269,13 +1269,13 @@ public: job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; + job_to_labor_table[df::job_type::PullLever] = jlf_const(df::unit_labor::PULL_LEVER); job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; - job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::ChainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); @@ -1335,7 +1335,7 @@ public: job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); - job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; job_to_labor_table[df::job_type::MakeTool] = jlf_make_furniture; job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); @@ -2291,10 +2291,7 @@ public: int before = labor_needed[l]; - if (labor_infos[l].idle_dwarfs > 0) - labor_needed[l] = 0; - else - labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); if (default_labor_infos[l].tool != TOOL_NONE) labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); From ed6fb690121d0a433a37a29251de447c2f68c0f8 Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Mon, 11 Jul 2016 18:54:03 -0500 Subject: [PATCH 0105/1012] Fix whitespace spacing Signed-off-by: Michael Casadevall --- library/include/Error.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/include/Error.h b/library/include/Error.h index 1c8afe44c..6b4781028 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -38,19 +38,19 @@ namespace DFHack * our wrapper for the C++ exception. used to differentiate * the whole array of DFHack exceptions from the rest */ -#ifdef _MSC_VER +#ifdef _MSC_VER #pragma push /** - * C4275 is - The warning officially is non dll-interface class 'std::exception' used as base for - * dll-interface class + * C4275 is - The warning officially is non dll-interface class 'std::exception' used as base for + * dll-interface class * * Basically, its saying that you might have an ABI problem if you mismatch compilers. We don't * care since we build all of DFhack at once against whatever Toady is using */ -#pragma warning(disable: 4275) +#pragma warning(disable: 4275) #endif class DFHACK_EXPORT All : public std::exception{}; -#ifdef _MSC_VER +#ifdef _MSC_VER #pragma pop #endif class DFHACK_EXPORT NullPointer : public All { From bcffc53d5a2e589a7cdf5a129e99c75b46d53c95 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 11 Jul 2016 22:29:38 -0500 Subject: [PATCH 0106/1012] labormanager: add labors for bookcase (de)construct --- plugins/devel/labormanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 46322a90c..cb5f11dfc 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -840,6 +840,7 @@ private: case df::building_type::BarsFloor: case df::building_type::BarsVertical: case df::building_type::GrateWall: + case df::building_type::Bookcase: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -942,6 +943,7 @@ private: case df::building_type::BarsVertical: case df::building_type::GrateFloor: case df::building_type::GrateWall: + case df::building_type::Bookcase: return df::unit_labor::HAUL_FURNITURE; case df::building_type::AnimalTrap: return df::unit_labor::TRAPPER; From 824275b23b6ca6c1b155c3c0fac3625def90aa98 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Jun 2016 12:04:51 -0500 Subject: [PATCH 0107/1012] Rename autolabor2 to labormanager and bring up to date with current --- plugins/devel/CMakeLists.txt | 2 +- .../{autolabor2.cpp => labormanager.cpp} | 487 +++++++++++++----- 2 files changed, 354 insertions(+), 135 deletions(-) rename plugins/devel/{autolabor2.cpp => labormanager.cpp} (86%) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 1f3ff6f75..a1e5b7f14 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -3,13 +3,13 @@ DFHACK_PLUGIN(vectors vectors.cpp) endif() ADD_DEFINITIONS(-DDEV_PLUGIN) -#DFHACK_PLUGIN(autolabor2 autolabor2.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(color-dfhack-text color-dfhack-text.cpp) DFHACK_PLUGIN(counters counters.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) +DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(kittens kittens.cpp) DFHACK_PLUGIN(memview memview.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) diff --git a/plugins/devel/autolabor2.cpp b/plugins/devel/labormanager.cpp similarity index 86% rename from plugins/devel/autolabor2.cpp rename to plugins/devel/labormanager.cpp index 4897832d4..12e4e8ca1 100644 --- a/plugins/devel/autolabor2.cpp +++ b/plugins/devel/labormanager.cpp @@ -1,5 +1,5 @@ /* -* Autolabor 2.0 module for dfhack +* Labor manager (formerly Autolabor 2) module for dfhack * * */ @@ -77,7 +77,7 @@ using df::global::world; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) -DFHACK_PLUGIN_IS_ENABLED(enable_autolabor); +DFHACK_PLUGIN_IS_ENABLED(enable_labormanager); static bool print_debug = 0; @@ -94,11 +94,11 @@ enum ConfigFlags { // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom -command_result autolabor (color_ostream &out, std::vector & parameters); +command_result labormanager (color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - autolabor2.plug.so or autolabor2.plug.dll in this case -DFHACK_PLUGIN("autolabor2"); +// The name string provided must correspond to the filename - labormanager.plug.so or labormanager.plug.dll in this case +DFHACK_PLUGIN("labormanager"); static void generate_labor_to_skill_map(); @@ -486,9 +486,20 @@ static const struct labor_default default_labor_infos[] = { /* PRESSING */ {200, 0, TOOL_NONE}, /* BEEKEEPING */ {200, 0, TOOL_NONE}, /* WAX_WORKING */ {200, 0, TOOL_NONE}, - /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE} + /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE}, + /* HAUL_TRADE */ {200, 0, TOOL_NONE}, + /* PULL_LEVER */ {200, 0, TOOL_NONE}, + /* REMOVE_CONSTRUCTION */ {200, 0, TOOL_NONE}, + /* HAUL_WATER */ {200, 0, TOOL_NONE}, + /* GELD */ {200, 0, TOOL_NONE}, + /* BUILD_ROAD */ {200, 0, TOOL_NONE}, + /* BUILD_CONSTRUCTION */ {200, 0, TOOL_NONE}, + /* PAPERMAKING */ {200, 0, TOOL_NONE}, + /* BOOKBINDING */ {200, 0, TOOL_NONE} }; +void debug (char* fmt, ...); + struct dwarf_info_t { df::unit* dwarf; @@ -506,12 +517,19 @@ struct dwarf_info_t df::unit_labor using_labor; dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), - state(OTHER), high_skill(0), has_children(false), armed(false) + state(OTHER), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) has_tool[e] = false; } + ~dwarf_info_t() + { + if (print_debug) + debug("LABORMANAGER: destroying dwarf %p\n", (void*) this); + } + + void set_labor(df::unit_labor labor) { if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) @@ -554,10 +572,10 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ df::unit_labor::HAUL_ITEM, /* TOY */ df::unit_labor::HAUL_FURNITURE, /* WINDOW */ - df::unit_labor::HAUL_ANIMAL, /* CAGE */ + df::unit_labor::HAUL_ANIMALS, /* CAGE */ df::unit_labor::HAUL_ITEM, /* BARREL */ df::unit_labor::HAUL_ITEM, /* BUCKET */ - df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ + df::unit_labor::HAUL_ANIMALS, /* ANIMALTRAP */ df::unit_labor::HAUL_FURNITURE, /* TABLE */ df::unit_labor::HAUL_FURNITURE, /* COFFIN */ df::unit_labor::HAUL_FURNITURE, /* STATUE */ @@ -850,7 +868,7 @@ private: return df::unit_labor::SIEGECRAFT; } - debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); return df::unit_labor::NONE; @@ -944,7 +962,7 @@ private: return df::unit_labor::SIEGECRAFT; } - debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); return df::unit_labor::NONE; @@ -979,13 +997,13 @@ private: return df::unit_labor::BONE_CARVE; else { - debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); + debug ("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); return df::unit_labor::NONE; } case df::item_type::WOOD: return df::unit_labor::WOOD_CRAFT; default: - debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); return df::unit_labor::NONE; } @@ -1002,7 +1020,7 @@ private: case df::workshop_type::MetalsmithsForge: return metaltype; default: - debug ("AUTOLABOR: Cannot deduce labor for make job, workshop type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", ENUM_KEY_STR(workshop_type, type).c_str()); return df::unit_labor::NONE; } @@ -1016,13 +1034,13 @@ private: case df::furnace_type::GlassFurnace: return df::unit_labor::GLASSMAKER; default: - debug ("AUTOLABOR: Cannot deduce labor for make job, furnace type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", ENUM_KEY_STR(furnace_type, type).c_str()); return df::unit_labor::NONE; } } - debug ("AUTOLABOR: Cannot deduce labor for make job, building type %s\n", + debug ("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); return df::unit_labor::NONE; @@ -1147,8 +1165,6 @@ public: job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInChest] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInCabinet] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; @@ -1219,7 +1235,6 @@ public: job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsBag] = jlf_const(df::unit_labor::PROCESS_PLANT); job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); @@ -1256,7 +1271,6 @@ public: job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; - job_to_labor_table[df::job_type::BrewDrink] = jlf_const(df::unit_labor::BREWER) ; job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; @@ -1334,9 +1348,9 @@ public: job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; @@ -1365,7 +1379,14 @@ public: df::unit_labor labor; - labor = job_to_labor_table[j->job_type]->get_labor(j); + if (job_to_labor_table.count(j->job_type) == 0) + { + debug("LABORMANAGER: job has no job to labor table entry: %s\n", ENUM_KEY_STR(job_type, j->job_type).c_str()); + labor = df::unit_labor::NONE; + } else { + + labor = job_to_labor_table[j->job_type]->get_labor(j); + } return labor; } @@ -1375,6 +1396,8 @@ public: static JobLaborMapper* labor_mapper = 0; +static bool initialized = false; + static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -1393,8 +1416,9 @@ static void setOptionEnabled(ConfigFlags flag, bool on) static void cleanup_state() { - enable_autolabor = false; + enable_labormanager = false; labor_infos.clear(); + initialized = false; } static void reset_labor(df::unit_labor labor) @@ -1405,25 +1429,25 @@ static void reset_labor(df::unit_labor labor) static void init_state() { - config = World::GetPersistentData("autolabor/2.0/config"); + config = World::GetPersistentData("labormanager/2.0/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; - enable_autolabor = isOptionEnabled(CF_ENABLED); + enable_labormanager = isOptionEnabled(CF_ENABLED); - if (!enable_autolabor) + if (!enable_labormanager) return; // Load labors from save labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - World::GetPersistentData(&items, "autolabor/2.0/labors/", true); + World::GetPersistentData(&items, "labormanager/2.0/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); - df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/2.0/labors/")).c_str()); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("labormanager/2.0/labors/")).c_str()); if (labor >= 0 && labor <= labor_infos.size()) { labor_infos[labor].config = *p; @@ -1437,7 +1461,7 @@ static void init_state() continue; std::stringstream name; - name << "autolabor/2.0/labors/" << i; + name << "labormanager/2.0/labors/" << i; labor_infos[i].config = World::AddPersistentData(name.str()); labor_infos[i].mark_assigned(); @@ -1445,6 +1469,8 @@ static void init_state() reset_labor((df::unit_labor) i); } + initialized = true; + } static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; @@ -1477,12 +1503,12 @@ static void enable_plugin(color_ostream &out) { if (!config.isValid()) { - config = World::AddPersistentData("autolabor/2.0/config"); + config = World::AddPersistentData("labormanager/2.0/config"); config.ival(0) = 0; } setOptionEnabled(CF_ENABLED, true); - enable_autolabor = true; + enable_labormanager = true; out << "Enabling the plugin." << endl; cleanup_state(); @@ -1497,32 +1523,32 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" + " labormanager max \n" " Set max number of dwarves assigned to a labor.\n" - " autolabor2 max none\n" + " labormanager max none\n" " Unrestrict the number of dwarves assigned to a labor.\n" - " autolabor2 priority \n" + " labormanager priority \n" " Change the assignment priority of a labor (default is 100)\n" - " autolabor2 reset \n" + " labormanager reset \n" " Return a labor to the default handling.\n" - " autolabor2 reset-all\n" + " labormanager reset-all\n" " Return all labors to the default handling.\n" - " autolabor2 list\n" + " labormanager list\n" " List current status of all labors.\n" - " autolabor2 status\n" + " labormanager status\n" " Show basic status information.\n" "Function:\n" - " When enabled, autolabor periodically checks your dwarves and enables or\n" + " When enabled, labormanager periodically checks your dwarves and enables or\n" " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" - " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled. Do not try to run both autolabor and autolabor2 at\n" - " the same time." + " Warning: labormanager will override any manual changes you make to labors\n" + " while it is enabled. Do not try to run both labormanager and labormanager at\n" + " the same time.\n" )); generate_labor_to_skill_map(); @@ -1594,14 +1620,21 @@ private: int need_food_water; + int priority_food; + std::map labor_needed; std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; + std::list busy_dwarfs; private: void scan_buildings() { + has_butchers = false; + has_fishery = false; + trader_requested = false; + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) { df::building *build = *b; @@ -1658,16 +1691,14 @@ private: if (dig != df::enums::tile_dig_designation::No) { df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_material ttm = ENUM_ATTR(tiletype, material, tt); df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); - switch (tts) - { - case df::enums::tiletype_shape::TREE: - tree_count++; break; - case df::enums::tiletype_shape::SHRUB: - plant_count++; break; - default: - dig_count++; break; - } + if (ttm == df::enums::tiletype_material::TREE) + tree_count++; + else if (tts == df::enums::tiletype_shape::SHRUB) + plant_count++; + else + dig_count++; } if (bl->designation[x][y].bits.smooth != 0) detail_count++; @@ -1684,13 +1715,15 @@ private: for (int e = TOOL_NONE; e < TOOLS_MAX; e++) tool_count[e] = 0; + priority_food = 0; + df::item_flags bad_flags; bad_flags.whole = 0; #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); + F(in_building); F(construction); #undef F auto& v = world->items.all; @@ -1704,6 +1737,11 @@ private: if (item->flags.whole & bad_flags.whole) continue; + df::item_type t = item->getType(); + + if (item->materialRots() && t != df::item_type::CORPSEPIECE && t != df::item_type::CORPSE && item->getRotTimer() > 1) + priority_food++; + if (!item->isWeapon()) continue; @@ -1869,14 +1907,20 @@ private: { state = CHILD; } + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) state = MILITARY; + + else if (dwarf->dwarf->burrows.size() > 0) + state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + else if (dwarf->dwarf->job.current_job == NULL) { - if (is_on_break) + if (is_on_break || dwarf->dwarf->flags1.bits.chained || dwarf->dwarf->flags1.bits.caged) + { state = OTHER; - else if (dwarf->dwarf->burrows.size() > 0) - state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + dwarf->clear_all = true; + } else if (dwarf->dwarf->status2.limbs_grasp_count == 0) { state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors @@ -1902,18 +1946,23 @@ private: if (state == BUSY) { df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); + + dwarf->using_labor = labor; + if (labor != df::unit_labor::NONE) { - dwarf->using_labor = labor; + labor_infos[labor].busy_dwarfs++; - if (!dwarf->dwarf->status.labors[labor] && print_debug) + if (!dwarf->dwarf->status.labors[labor]) { - out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", + out.print("LABORMANAGER: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); } } } + if (state == OTHER) + dwarf->clear_all = true; } dwarf->state = state; @@ -1925,8 +1974,6 @@ private: if (dwarf->dwarf->status.labors[l]) if (state == IDLE) labor_infos[l].idle_dwarfs++; - else if (state == BUSY) - labor_infos[l].busy_dwarfs++; } @@ -2022,19 +2069,69 @@ private: dwarf->clear_labor(labor); } + } else { + if (state == IDLE) + available_dwarfs.push_back(dwarf); + + if (state == BUSY) + busy_dwarfs.push_back(dwarf); } - else if (state == IDLE || state == BUSY) - available_dwarfs.push_back(dwarf); + } + + } + } + void release_dwarf_list() + { + while (!dwarf_info.empty()) { + auto d = dwarf_info.begin(); + delete *d; + dwarf_info.erase(d); + } + available_dwarfs.clear(); + busy_dwarfs.clear(); + } + + int score_labor (dwarf_info_t* d, df::unit_labor labor) + { + int skill_level = 0; + int xp = 0; + + if (labor != df::unit_labor::NONE) + { + df::job_skill skill = labor_to_skill[labor]; + if (skill != df::job_skill::NONE) + { + skill_level = Units::getEffectiveSkill(d->dwarf, skill); + xp = Units::getExperience(d->dwarf, skill, false); } + } + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); + + if (labor != df::unit_labor::NONE) + { + if (d->dwarf->status.labors[labor]) + if (labor == df::unit_labor::OPERATE_PUMP) + score += 50000; + else + score += 1000; + if (default_labor_infos[labor].tool != TOOL_NONE && + d->has_tool[default_labor_infos[labor].tool]) + score += 5000; + if (d->has_children && labor_outside[labor]) + score -= 15000; + if (d->armed && labor_outside[labor]) + score += 5000; } + + return score; } public: void process() { - dwarf_info.clear(); + release_dwarf_list(); dig_count = tree_count = plant_count = detail_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = @@ -2106,13 +2203,27 @@ public: labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; - labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; + labor_needed[df::unit_labor::HAUL_ANIMALS] += world->stockpile.num_jobs[9]; + + labor_needed[df::unit_labor::HAUL_STONE] += (world->stockpile.num_jobs[1] >= world->stockpile.num_haulers[1]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_WOOD] += (world->stockpile.num_jobs[2] >= world->stockpile.num_haulers[2]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ITEM] += (world->stockpile.num_jobs[3] >= world->stockpile.num_haulers[3]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_BODY] += (world->stockpile.num_jobs[5] >= world->stockpile.num_haulers[5]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FOOD] += (world->stockpile.num_jobs[6] >= world->stockpile.num_haulers[6]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_REFUSE] += (world->stockpile.num_jobs[7] >= world->stockpile.num_haulers[7]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FURNITURE] += (world->stockpile.num_jobs[8] >= world->stockpile.num_haulers[8]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ANIMALS] += (world->stockpile.num_jobs[9] >= world->stockpile.num_haulers[9]) ? 1 : 0; + + int binjobs = world->stockpile.num_jobs[4] + ((world->stockpile.num_jobs[4] >= world->stockpile.num_haulers[4]) ? 1 : 0); + + labor_needed[df::unit_labor::HAUL_ITEM] += binjobs; + labor_needed[df::unit_labor::HAUL_FOOD] += priority_food; // add entries for vehicle hauling for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) if ((*v)->route_id != -1) - labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + labor_needed[df::unit_labor::HANDLE_VEHICLES]++; // add fishing & hunting @@ -2131,33 +2242,101 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } + /* move idle dwarfs ready to be assigned to busy list */ + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); ) + { + bool busy = false; + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + + if (labor_needed[l] > 0 && (*d)->dwarf->status.labors[l]) + { + busy = true; + labor_needed[l] = max(labor_needed[l]-1, 0); + } + } + + if (busy) + { + busy_dwarfs.push_back(*d); + d = available_dwarfs.erase(d); + } else { + d++; + } + } + /* adjust for over/under */ FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL) - continue; - if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) + if (labor_infos[l].idle_dwarfs > 0) + labor_needed[l] = 0; + else + labor_needed[l] = std::max(labor_needed[l] - labor_infos[l].busy_dwarfs, 0); + } + + /* assign food haulers for rotting food items */ + + if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) + priority_food = 1; + + if (print_debug) + out.print ("priority food count = %d\n", priority_food); + + while (!available_dwarfs.empty() && priority_food > 0) + { + std::list::iterator bestdwarf = available_dwarfs.begin(); + + int best_score = INT_MIN; + + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { - int clawback = labor_infos[l].busy_dwarfs; - if (clawback == 0 && labor_needed[l] > 0) - clawback = 1; + dwarf_info_t* d = (*k); + int score = score_labor(d, df::unit_labor::HAUL_FOOD); + + if (score > best_score) + { + bestdwarf = k; + best_score = score; + } + } + + if (best_score > INT_MIN) + { if (print_debug) - out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), - clawback, - labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); - labor_needed[l] = clawback; + out.print("LABORMANAGER: assign \"%s\" labor %s score=%d (priority food)\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, df::unit_labor::HAUL_FOOD).c_str(), best_score); + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + if (l == df::unit_labor::HAUL_FOOD) + (*bestdwarf)->set_labor(l); + else + (*bestdwarf)->clear_labor(l); + } + + available_dwarfs.erase(bestdwarf); + priority_food--; } + else + break; + } if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, - labor_outside[i->first], labor_infos[i->first].idle_dwarfs); + out.print ("labor_needed [%s] = %d, busy = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_infos[i->first].busy_dwarfs, labor_outside[i->first], labor_infos[i->first].idle_dwarfs); } } @@ -2173,6 +2352,8 @@ public: { int priority = labor_infos[l].priority(); priority += labor_infos[l].time_since_last_assigned()/12; + for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) + priority /= 2; pq.push(make_pair(priority, l)); } } @@ -2212,7 +2393,7 @@ public: (1 << df::unit_labor::HAUL_REFUSE) | (1 << df::unit_labor::HAUL_ITEM) | (1 << df::unit_labor::HAUL_FURNITURE) | - (1 << df::unit_labor::HAUL_ANIMAL); + (1 << df::unit_labor::HAUL_ANIMALS); while (!available_dwarfs.empty()) { @@ -2227,36 +2408,11 @@ public: continue; df::unit_labor labor = j->first; - df::job_skill skill = labor_to_skill[labor]; for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { dwarf_info_t* d = (*k); - int skill_level = 0; - int xp = 0; - if (skill != df::job_skill::NONE) - { - skill_level = Units::getEffectiveSkill(d->dwarf, skill); - xp = Units::getExperience(d->dwarf, skill, false); - } - int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); - if (d->dwarf->status.labors[labor]) - if (labor == df::unit_labor::OPERATE_PUMP) - score += 50000; - else - score += 1000; - if (default_labor_infos[labor].tool != TOOL_NONE && - d->has_tool[default_labor_infos[labor].tool]) - score += 5000; - if (d->has_children && labor_outside[labor]) - score -= 15000; - if (d->armed && labor_outside[labor]) - score += 5000; - if (d->state == BUSY) - if (d->using_labor == labor) - score += 7500; - else - score -= 7500; + int score = score_labor(d, labor); if (score > best_score) { bestdwarf = k; @@ -2289,11 +2445,15 @@ public: (*bestdwarf)->clear_labor(l); } - if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) + if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) + priority_food--; + + if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMALS) canary &= ~(1 << best_labor); if (best_labor != df::unit_labor::NONE) { + busy_dwarfs.push_back(*bestdwarf); labor_infos[best_labor].active_dwarfs++; to_assign[best_labor]--; } @@ -2301,17 +2461,71 @@ public: available_dwarfs.erase(bestdwarf); } - if (canary != 0) + for (auto d = busy_dwarfs.begin(); d != busy_dwarfs.end(); d++) { - dwarf_info_t* d = dwarf_info.front(); + int current_score = score_labor (*d, (*d)->using_labor); + FOR_ENUM_ITEMS (unit_labor, l) { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL && - canary & (1 << l)) - d->set_labor(l); + if (l == df::unit_labor::NONE) + continue; + if (l == (*d)->using_labor) + continue; + if (labor_needed[l] <= 0) + continue; + + int score = score_labor (*d, l); + score += labor_infos[l].time_since_last_assigned()/12; + if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) + score += 1000000; + + if (score > current_score) + { + tools_enum t = default_labor_infos[l].tool; + if (t == TOOL_NONE || (*d)->has_tool[t]) + { + (*d)->set_labor (l); + if (print_debug) + out.print("assign \"%s\" extra labor %s score=%d current %s score=%d\n", + (*d)->dwarf->name.first_name.c_str(), + ENUM_KEY_STR(unit_labor, l).c_str(), score, + ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); + } + } + else + (*d)->clear_labor (l); + } + } + + + if (canary != 0) + { + dwarf_info_t* d = 0; + + for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) + if (!(*di)->clear_all) + { + d = *di; + break; + } + + if (d) + { + + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + d->set_labor(l); + } + if (print_debug) + out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); + } + else + { + if (print_debug) + out.print ("No dwarf available to set as the hauling canary!\n"); } - if (print_debug) - out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); } /* set reequip on any dwarfs who are carrying tools needed by others */ @@ -2320,6 +2534,9 @@ public: { FOR_ENUM_ITEMS (unit_labor, l) { + if (l == df::unit_labor::NONE) + continue; + tools_enum t = default_labor_infos[l].tool; if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) { @@ -2329,6 +2546,8 @@ public: } } + release_dwarf_list(); + *df::global::process_jobs = true; print_debug = 0; @@ -2359,7 +2578,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { static int step_count = 0; // check run conditions - if(!world || !world->map.block_index || !enable_autolabor) + if(!initialized || !world || !world->map.block_index || !enable_labormanager) { // give up if we shouldn't be running' return CR_OK; @@ -2406,26 +2625,26 @@ df::unit_labor lookup_labor_by_name (std::string& name) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) { if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); + out.printerr("World is not loaded: please load a fort first.\n"); return CR_FAILURE; } - if (enable && !enable_autolabor) + if (enable && !enable_labormanager) { enable_plugin(out); } - else if(!enable && enable_autolabor) + else if(!enable && enable_labormanager) { - enable_autolabor = false; + enable_labormanager = false; setOptionEnabled(CF_ENABLED, false); - out << "Autolabor is disabled." << endl; + out << "LaborManager is disabled." << endl; } return CR_OK; } -command_result autolabor (color_ostream &out, std::vector & parameters) +command_result labormanager (color_ostream &out, std::vector & parameters) { CoreSuspender suspend; @@ -2443,7 +2662,7 @@ command_result autolabor (color_ostream &out, std::vector & parame else if (parameters.size() == 3 && (parameters[0] == "max" || parameters[0] == "priority")) { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2474,7 +2693,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 2 && parameters[0] == "reset") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2493,7 +2712,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && (parameters[0] == "allow-fishing" || parameters[0] == "forbid-fishing")) { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2504,7 +2723,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && (parameters[0] == "allow-hunting" || parameters[0] == "forbid-hunting")) { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2515,7 +2734,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && parameters[0] == "reset-all") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2530,7 +2749,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2563,7 +2782,7 @@ command_result autolabor (color_ostream &out, std::vector & parame } else if (parameters.size() == 1 && parameters[0] == "debug") { - if (!enable_autolabor) + if (!enable_labormanager) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; @@ -2576,8 +2795,8 @@ command_result autolabor (color_ostream &out, std::vector & parame else { out.print("Automatically assigns labors to dwarves.\n" - "Activate with 'autolabor enable', deactivate with 'autolabor disable'.\n" - "Current state: %s.\n", enable_autolabor ? "enabled" : "disabled"); + "Activate with 'labormanager enable', deactivate with 'labormanager disable'.\n" + "Current state: %s.\n", enable_labormanager ? "enabled" : "disabled"); return CR_OK; } From 07e1c819691622b450bbc4bb9dd0fb141008b38e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Jun 2016 20:58:38 -0500 Subject: [PATCH 0108/1012] labormanager improvements Add some debugging facilities. Change some hauling, construction, and deconstruction labors to reflect changes in DF since 34.11. --- plugins/devel/labormanager.cpp | 40 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 12e4e8ca1..ffabde36a 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -80,6 +80,7 @@ using df::global::world; DFHACK_PLUGIN_IS_ENABLED(enable_labormanager); static bool print_debug = 0; +static bool pause_on_error = 1; static std::vector state_count(5); @@ -525,8 +526,6 @@ struct dwarf_info_t ~dwarf_info_t() { - if (print_debug) - debug("LABORMANAGER: destroying dwarf %p\n", (void*) this); } @@ -608,7 +607,7 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_FOOD, /* FISH_RAW */ df::unit_labor::HAUL_REFUSE, /* VERMIN */ df::unit_labor::HAUL_ITEM, /* PET */ - df::unit_labor::HAUL_FOOD, /* SEEDS */ + df::unit_labor::HAUL_ITEM, /* SEEDS */ df::unit_labor::HAUL_FOOD, /* PLANT */ df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ df::unit_labor::HAUL_FOOD, /* LEAVES */ @@ -716,6 +715,14 @@ void debug (char* fmt, ...) } } +void debug_pause () +{ + if (pause_on_error) + { + debug("LABORMANAGER: Game paused so you can investigate the above message.\nUse 'labormanager pause-on-error no' to disable autopausing.\n"); + *df::global::pause_state = true; + } +} class JobLaborMapper { @@ -804,9 +811,10 @@ private: return workshop_build_labor[ws->type]; } break; + case df::building_type::Construction: + return df::unit_labor::BUILD_CONSTRUCTION; case df::building_type::Furnace: case df::building_type::TradeDepot: - case df::building_type::Construction: case df::building_type::Bridge: case df::building_type::ArcheryTarget: case df::building_type::WaterWheel: @@ -870,6 +878,7 @@ private: debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); return df::unit_labor::NONE; } @@ -900,9 +909,10 @@ private: return workshop_build_labor[ws->type]; } break; + case df::building_type::Construction: + return df::unit_labor::REMOVE_CONSTRUCTION; case df::building_type::Furnace: case df::building_type::TradeDepot: - case df::building_type::Construction: case df::building_type::Wagon: case df::building_type::Bridge: case df::building_type::ScrewPump: @@ -964,6 +974,7 @@ private: debug ("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); return df::unit_labor::NONE; } @@ -998,6 +1009,7 @@ private: else { debug ("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); + debug_pause(); return df::unit_labor::NONE; } case df::item_type::WOOD: @@ -1005,6 +1017,7 @@ private: default: debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); + debug_pause(); return df::unit_labor::NONE; } } @@ -1022,6 +1035,7 @@ private: default: debug ("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", ENUM_KEY_STR(workshop_type, type).c_str()); + debug_pause(); return df::unit_labor::NONE; } } @@ -1036,12 +1050,14 @@ private: default: debug ("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", ENUM_KEY_STR(furnace_type, type).c_str()); + debug_pause(); return df::unit_labor::NONE; } } debug ("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); return df::unit_labor::NONE; } @@ -1382,6 +1398,7 @@ public: if (job_to_labor_table.count(j->job_type) == 0) { debug("LABORMANAGER: job has no job to labor table entry: %s\n", ENUM_KEY_STR(job_type, j->job_type).c_str()); + debug_pause(); labor = df::unit_labor::NONE; } else { @@ -1941,6 +1958,7 @@ private: else { out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + debug_pause(); state = OTHER; } if (state == BUSY) @@ -2780,6 +2798,18 @@ command_result labormanager (color_ostream &out, std::vector & par return CR_OK; } + else if (parameters.size() == 2 && parameters[0] == "pause-on-error") + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + pause_on_error = parameters[1] == "yes" || parameters[1] == "true"; + + return CR_OK; + } else if (parameters.size() == 1 && parameters[0] == "debug") { if (!enable_labormanager) From f095e139aaafbe7207439a2bd3eadc4e90683352 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 28 Jun 2016 22:36:45 -0500 Subject: [PATCH 0109/1012] labormanager: more tweaks to bring up to date This update fixes some labors and attempts to address changes in the way DF maintains the job list. --- plugins/devel/labormanager.cpp | 155 +++++++++++++++------------------ 1 file changed, 72 insertions(+), 83 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index ffabde36a..256841cb7 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1154,7 +1154,7 @@ public: job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); - job_to_labor_table[df::job_type::BringItemToDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; job_to_labor_table[df::job_type::Eat] = jlf_no_labor; job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; @@ -1178,13 +1178,13 @@ public: job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_const(df::unit_labor::HAUL_ITEM); + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1293,14 +1293,14 @@ public: job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::UnchainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::CageLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; @@ -1640,6 +1640,7 @@ private: int priority_food; std::map labor_needed; + std::map labor_in_use; std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; @@ -1668,6 +1669,7 @@ private: { df::building_tradedepotst* depot = (df::building_tradedepotst*) build; trader_requested = depot->trade_flags.bits.trader_requested; + if (print_debug) { if (trader_requested) @@ -1819,20 +1821,23 @@ private: } df::unit_labor labor = labor_mapper->find_job_labor (j); + labor_needed[labor]++; if (labor != df::unit_labor::NONE) { - labor_needed[labor]++; - - if (worker != -1) + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } else { labor_infos[labor].mark_assigned(); + labor_in_use[labor]++; + } - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } } } @@ -1979,8 +1984,6 @@ private: } } } - if (state == OTHER) - dwarf->clear_all = true; } dwarf->state = state; @@ -2199,7 +2202,7 @@ public: // add job entries for health care labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; - labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; + labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; @@ -2260,43 +2263,14 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } - /* move idle dwarfs ready to be assigned to busy list */ - for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); ) - { - bool busy = false; + /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - - - if (labor_needed[l] > 0 && (*d)->dwarf->status.labors[l]) - { - busy = true; - labor_needed[l] = max(labor_needed[l]-1, 0); - } - } - - if (busy) - { - busy_dwarfs.push_back(*d); - d = available_dwarfs.erase(d); - } else { - d++; - } - } - - /* adjust for over/under */ - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - if (labor_infos[l].idle_dwarfs > 0) - labor_needed[l] = 0; - else - labor_needed[l] = std::max(labor_needed[l] - labor_infos[l].busy_dwarfs, 0); - } + FOR_ENUM_ITEMS(unit_labor, l) { + if (labor_infos[l].idle_dwarfs > 0) + labor_needed[l] = 0; + else + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + } /* assign food haulers for rotting food items */ @@ -2401,7 +2375,6 @@ public: priority /= 2; pq.push(make_pair(priority, labor)); } - } int canary = (1 << df::unit_labor::HAUL_STONE) | @@ -2476,6 +2449,7 @@ public: to_assign[best_labor]--; } + busy_dwarfs.push_back(*bestdwarf); available_dwarfs.erase(bestdwarf); } @@ -2510,42 +2484,57 @@ public: ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } } - else - (*d)->clear_labor (l); } } - if (canary != 0) - { - dwarf_info_t* d = 0; - - for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) - if (!(*di)->clear_all) - { - d = *di; - break; - } + dwarf_info_t* canary_dwarf = 0; - if (d) + for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) + if (!(*di)->clear_all) { - - FOR_ENUM_ITEMS (unit_labor, l) - { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && - canary & (1 << l)) - d->set_labor(l); - } - if (print_debug) - out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); + canary_dwarf = *di; + break; } - else + + if (canary_dwarf) + { + + FOR_ENUM_ITEMS (unit_labor, l) { - if (print_debug) - out.print ("No dwarf available to set as the hauling canary!\n"); + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + canary_dwarf->set_labor(l); } + + /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ + + canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + + if (print_debug) + out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); + } + else + { + if (print_debug) + out.print ("No dwarf available to set as the hauling canary!\n"); } + /* Assign any leftover dwarfs to "standard" labors */ + + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) + { + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + (*d)->set_labor(l); + } + + (*d)->set_labor(df::unit_labor::CLEAN); + (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + } + /* set reequip on any dwarfs who are carrying tools needed by others */ for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) From 705134975d492a00f76e33c6549924f7c7c738f3 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 28 Jun 2016 22:57:12 -0500 Subject: [PATCH 0110/1012] labormanager: whitespace MSVC is evil. --- plugins/devel/labormanager.cpp | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 256841cb7..8acae7524 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1299,7 +1299,7 @@ public: job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); @@ -1640,7 +1640,7 @@ private: int priority_food; std::map labor_needed; - std::map labor_in_use; + std::map labor_in_use; std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; @@ -1821,22 +1821,22 @@ private: } df::unit_labor labor = labor_mapper->find_job_labor (j); - labor_needed[labor]++; + labor_needed[labor]++; if (labor != df::unit_labor::NONE) { - if (worker == -1) - { - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } - } else { + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } else { labor_infos[labor].mark_assigned(); - labor_in_use[labor]++; - } + labor_in_use[labor]++; + } } } @@ -2263,14 +2263,14 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } - /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ + /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ - FOR_ENUM_ITEMS(unit_labor, l) { - if (labor_infos[l].idle_dwarfs > 0) - labor_needed[l] = 0; - else - labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); - } + FOR_ENUM_ITEMS(unit_labor, l) { + if (labor_infos[l].idle_dwarfs > 0) + labor_needed[l] = 0; + else + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + } /* assign food haulers for rotting food items */ @@ -2449,7 +2449,7 @@ public: to_assign[best_labor]--; } - busy_dwarfs.push_back(*bestdwarf); + busy_dwarfs.push_back(*bestdwarf); available_dwarfs.erase(bestdwarf); } @@ -2507,9 +2507,9 @@ public: canary_dwarf->set_labor(l); } - /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ + /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ - canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); if (print_debug) out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); @@ -2520,10 +2520,10 @@ public: out.print ("No dwarf available to set as the hauling canary!\n"); } - /* Assign any leftover dwarfs to "standard" labors */ + /* Assign any leftover dwarfs to "standard" labors */ - for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) - { + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) + { FOR_ENUM_ITEMS (unit_labor, l) { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && @@ -2531,9 +2531,9 @@ public: (*d)->set_labor(l); } - (*d)->set_labor(df::unit_labor::CLEAN); - (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); - } + (*d)->set_labor(df::unit_labor::CLEAN); + (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + } /* set reequip on any dwarfs who are carrying tools needed by others */ From dbc46c510ff83c877ca36e9b0e4a976001eff733 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 28 Jun 2016 23:28:44 -0500 Subject: [PATCH 0111/1012] labormanager: fix stupid --- plugins/devel/labormanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 8acae7524..d78d06b97 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -2337,6 +2337,9 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { df::unit_labor l = i->first; + if (l == df::unit_labor::NONE) + continue; + if (labor_infos[l].maximum_dwarfs() > 0 && i->second > labor_infos[l].maximum_dwarfs()) i->second = labor_infos[l].maximum_dwarfs(); From 3a0ba332d9764dfbaa45c1bc0db4dca0abeead43 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 29 Jun 2016 14:54:03 -0500 Subject: [PATCH 0112/1012] labormanager: rework for better behavior with 43.03 The main thing here is that the process loop exits if the DF process_job or process_dig flags are set since if these are set the job list is going to change soon anyway. The plugin also sets these flags when it changes any labors, which has the side effect of effectively disabling the process loop while DF is paused, which prevents flapping while editing job preferences in-game, and also allows changing job preferences in game (although such changes may not last when the clock starts up again). --- plugins/devel/labormanager.cpp | 78 +++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index d78d06b97..8d614ed36 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -528,23 +528,6 @@ struct dwarf_info_t { } - - void set_labor(df::unit_labor labor) - { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) - { - dwarf->status.labors[labor] = true; - } - } - - void clear_labor(df::unit_labor labor) - { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) - { - dwarf->status.labors[labor] = false; - } - } - }; /* @@ -1621,6 +1604,8 @@ private: int plant_count; int detail_count; + bool labors_changed; + int tool_count[TOOLS_MAX]; bool reequip_needed[TOOLS_MAX]; @@ -1647,11 +1632,23 @@ private: std::list busy_dwarfs; private: + void set_labor (dwarf_info_t* dwarf, df::unit_labor labor, bool value) + { + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + bool old = dwarf->dwarf->status.labors[labor]; + dwarf->dwarf->status.labors[labor] = value; + if (old != value) + labors_changed = true; + } + } + void scan_buildings() { has_butchers = false; has_fishery = false; trader_requested = false; + labors_changed = false; for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) { @@ -1821,10 +1818,10 @@ private: } df::unit_labor labor = labor_mapper->find_job_labor (j); - labor_needed[labor]++; if (labor != df::unit_labor::NONE) { + labor_needed[labor]++; if (worker == -1) { if (j->pos.isValid()) @@ -2088,7 +2085,7 @@ private: if (labor == unit_labor::NONE) continue; - dwarf->clear_labor(labor); + set_labor(dwarf, labor, false); } } else { if (state == IDLE) @@ -2152,6 +2149,9 @@ private: public: void process() { + if (*df::global::process_dig || *df::global::process_jobs) + return; + release_dwarf_list(); dig_count = tree_count = plant_count = detail_count = 0; @@ -2266,6 +2266,9 @@ public: /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ FOR_ENUM_ITEMS(unit_labor, l) { + if (l == df::unit_labor::NONE) + continue; + if (labor_infos[l].idle_dwarfs > 0) labor_needed[l] = 0; else @@ -2309,10 +2312,7 @@ public: if (l == df::unit_labor::NONE) continue; - if (l == df::unit_labor::HAUL_FOOD) - (*bestdwarf)->set_labor(l); - else - (*bestdwarf)->clear_labor(l); + set_labor (*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); } available_dwarfs.erase(bestdwarf); @@ -2337,7 +2337,7 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { df::unit_labor l = i->first; - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE) continue; if (labor_infos[l].maximum_dwarfs() > 0 && @@ -2394,7 +2394,7 @@ public: std::list::iterator bestdwarf = available_dwarfs.begin(); int best_score = INT_MIN; - df::unit_labor best_labor = df::unit_labor::CLEAN; + df::unit_labor best_labor = df::unit_labor::NONE; for (auto j = to_assign.begin(); j != to_assign.end(); j++) { @@ -2416,6 +2416,9 @@ public: } } + if (best_labor == df::unit_labor::NONE) + break; + if (print_debug) out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); @@ -2426,7 +2429,7 @@ public: if (l == best_labor) { - (*bestdwarf)->set_labor(l); + set_labor(*bestdwarf, l, true); tools_enum t = default_labor_infos[l].tool; if (t != TOOL_NONE) { @@ -2436,7 +2439,7 @@ public: } } else if ((*bestdwarf)->state == IDLE) - (*bestdwarf)->clear_labor(l); + set_labor(*bestdwarf, l, false); } if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) @@ -2479,7 +2482,7 @@ public: tools_enum t = default_labor_infos[l].tool; if (t == TOOL_NONE || (*d)->has_tool[t]) { - (*d)->set_labor (l); + set_labor(*d, l, true); if (print_debug) out.print("assign \"%s\" extra labor %s score=%d current %s score=%d\n", (*d)->dwarf->name.first_name.c_str(), @@ -2507,12 +2510,12 @@ public: { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) - canary_dwarf->set_labor(l); + set_labor(canary_dwarf, l, true); } /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ - canary_dwarf->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); + set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); if (print_debug) out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); @@ -2531,11 +2534,12 @@ public: { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) - (*d)->set_labor(l); + set_labor(*d, l, true); + else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION) + set_labor(*d, l, true); + else + set_labor(*d, l, false); } - - (*d)->set_labor(df::unit_labor::CLEAN); - (*d)->set_labor(df::unit_labor::REMOVE_CONSTRUCTION); } /* set reequip on any dwarfs who are carrying tools needed by others */ @@ -2558,7 +2562,11 @@ public: release_dwarf_list(); - *df::global::process_jobs = true; + if (labors_changed) + { + *df::global::process_dig = true; + *df::global::process_jobs = true; + } print_debug = 0; From 0509c455ddd2bb3fae6fbea4bd77a642a590e57d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 30 Jun 2016 23:58:56 -0500 Subject: [PATCH 0113/1012] labormanager: significant restructuring to use job posting list Updated here to get potential jobs off the job posting lists, which is apparently where certain map-designated live after being designated but before they move to the actual job list. Also changes to how tools are handled, and lever pulling is assigned by default to all idle dwarfs. --- plugins/devel/labormanager.cpp | 273 ++++++++++++++++++++------------- 1 file changed, 169 insertions(+), 104 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 8d614ed36..545ca7f1c 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -184,7 +184,7 @@ static const dwarf_state dwarf_states[] = { OTHER /* GoShopping2 */, BUSY /* Clean */, OTHER /* Rest */, - BUSY /* PickupEquipment */, + OTHER /* PickupEquipment */, BUSY /* DumpItem */, OTHER /* StrangeMoodCrafter */, OTHER /* StrangeMoodJeweller */, @@ -414,7 +414,7 @@ struct labor_default static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {200, 0, TOOL_PICK}, + /* MINEa */ {200, 0, TOOL_PICK}, /* HAUL_STONE */ {100, 0, TOOL_NONE}, /* HAUL_WOOD */ {100, 0, TOOL_NONE}, /* HAUL_BODY */ {200, 0, TOOL_NONE}, @@ -1607,7 +1607,7 @@ private: bool labors_changed; int tool_count[TOOLS_MAX]; - bool reequip_needed[TOOLS_MAX]; + int tool_in_use[TOOLS_MAX]; int cnt_recover_wounded; int cnt_diagnosis; @@ -1639,7 +1639,69 @@ private: bool old = dwarf->dwarf->status.labors[labor]; dwarf->dwarf->status.labors[labor] = value; if (old != value) + { labors_changed = true; + + tools_enum tool = default_labor_infos[labor].tool; + if (tool != TOOL_NONE) + tool_in_use[tool] += value ? 1 : -1; + } + } + } + + void process_job (df::job* j) + { + if (j->flags.bits.suspend || j->flags.bits.item_lost) + return; + + int worker = -1; + int bld = -1; + + for (int r = 0; r < j->general_refs.size(); ++r) + { + if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; + if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) + bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; + } + + if (bld != -1) + { + df::building* b = binsearch_in_vector(world->buildings.all, bld); + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + // check if this job is the first nonsuspended job on this building; if not, ignore it + // (except for farms and trade depots) + if (fjid != j->id && + b->getType() != df::building_type::FarmPlot && + b->getType() != df::building_type::TradeDepot) + return; + } + + df::unit_labor labor = labor_mapper->find_job_labor (j); + + if (labor != df::unit_labor::NONE) + { + labor_needed[labor]++; + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } else { + labor_infos[labor].mark_assigned(); + labor_in_use[labor]++; + } + } } @@ -1674,6 +1736,7 @@ private: else out.print("Trade depot found but trader is not requested.\n"); } + } } } @@ -1729,7 +1792,10 @@ private: void count_tools() { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + { tool_count[e] = 0; + tool_in_use[e] = 0; + } priority_food = 0; @@ -1776,66 +1842,20 @@ private: void collect_job_list() { - labor_needed.clear(); - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) { df::job* j = jll->item; if (!j) continue; + process_job(j); + } - if (j->flags.bits.suspend || j->flags.bits.item_lost) + for (auto jp = world->job_postings.begin(); jp != world->job_postings.end(); jp++) + { + if ((*jp)->flags.bits.dead) continue; - int worker = -1; - int bld = -1; - - for (int r = 0; r < j->general_refs.size(); ++r) - { - if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; - if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) - bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; - } - - if (bld != -1) - { - df::building* b = binsearch_in_vector(world->buildings.all, bld); - int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) - { - if (b->jobs[jn]->flags.bits.suspend) - continue; - fjid = b->jobs[jn]->id; - break; - } - // check if this job is the first nonsuspended job on this building; if not, ignore it - // (except for farms) - if (fjid != j->id && b->getType() != df::building_type::FarmPlot) { - continue; - } - - } - - df::unit_labor labor = labor_mapper->find_job_labor (j); - - if (labor != df::unit_labor::NONE) - { - labor_needed[labor]++; - if (worker == -1) - { - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } - } else { - labor_infos[labor].mark_assigned(); - labor_in_use[labor]++; - } - - } + process_job((*jp)->job); } } @@ -1909,6 +1929,32 @@ private: } } + // check if dwarf has an axe, pick, or crossbow + + for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + dwarf->armed = true; + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_tool[TOOL_AXE] = true; + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_tool[TOOL_PICK] = true; + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_tool[TOOL_CROSSBOW] = true; + } + } + } + // Find the activity state for each dwarf bool is_on_break = false; @@ -1972,12 +2018,9 @@ private: if (labor != df::unit_labor::NONE) { labor_infos[labor].busy_dwarfs++; - - if (!dwarf->dwarf->status.labors[labor]) + if (default_labor_infos[labor].tool != TOOL_NONE) { - out.print("LABORMANAGER: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", - dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, - ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); + tool_in_use[default_labor_infos[labor].tool]++; } } } @@ -2044,37 +2087,7 @@ private: } dwarf->high_skill = high_skill; - // check if dwarf has an axe, pick, or crossbow - for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) - { - df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; - if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) - { - dwarf->armed = true; - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; - if (weaponsk == df::job_skill::AXE) - { - dwarf->has_tool[TOOL_AXE] = true; - if (state != IDLE) - tool_count[TOOL_AXE]--; - } - else if (weaponsk == df::job_skill::MINING) - { - dwarf->has_tool[TOOL_PICK] = 1; - if (state != IDLE) - tool_count[TOOL_PICK]--; - } - else if (rangesk == df::job_skill::CROSSBOW) - { - dwarf->has_tool[TOOL_CROSSBOW] = 1; - if (state != IDLE) - tool_count[TOOL_CROSSBOW]--; - } - } - } // clear labors of dwarfs with clear_all set @@ -2136,7 +2149,10 @@ private: score += 1000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 5000; + score += 30000; + if (default_labor_infos[labor].tool != TOOL_NONE && + !d->has_tool[default_labor_infos[labor].tool]) + score -= 30000; if (d->has_children && labor_outside[labor]) score -= 15000; if (d->armed && labor_outside[labor]) @@ -2159,6 +2175,8 @@ public: cnt_setting = cnt_traction = cnt_crutch = 0; need_food_water = 0; + labor_needed.clear(); + for (int e = 0; e < TOOLS_MAX; e++) tool_count[e] = 0; @@ -2194,8 +2212,8 @@ public: // add job entries for designation-related jobs - labor_needed[df::unit_labor::MINE] += std::min(tool_count[TOOL_PICK], dig_count); - labor_needed[df::unit_labor::CUTWOOD] += std::min(tool_count[TOOL_AXE], tree_count); + labor_needed[df::unit_labor::MINE] += dig_count; + labor_needed[df::unit_labor::CUTWOOD] += tree_count; labor_needed[df::unit_labor::DETAIL] += detail_count; labor_needed[df::unit_labor::HERBALIST] += plant_count; @@ -2269,10 +2287,19 @@ public: if (l == df::unit_labor::NONE) continue; + int before = labor_needed[l]; + if (labor_infos[l].idle_dwarfs > 0) labor_needed[l] = 0; else labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + + if (default_labor_infos[l].tool != TOOL_NONE) + labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); + + if (print_debug && before != labor_needed[l]) + out.print ("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); + } /* assign food haulers for rotting food items */ @@ -2427,19 +2454,30 @@ public: if (l == df::unit_labor::NONE) continue; - if (l == best_labor) + tools_enum t = default_labor_infos[l].tool; + + if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) { set_labor(*bestdwarf, l, true); - tools_enum t = default_labor_infos[l].tool; - if (t != TOOL_NONE) + if (t != TOOL_NONE && (*bestdwarf)->has_tool[t]) { - tool_count[t]--; - if (!(*bestdwarf)->has_tool[t]) - (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + df::job_type j; + j = df::job_type::NONE; + + if ((*bestdwarf)->dwarf->job.current_job) + j = (*bestdwarf)->dwarf->job.current_job->job_type; + + if (print_debug) + out.print("LABORMANAGER: asking %s to pick up tools, current job %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(job_type, j).c_str()); + + (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + labors_changed = true; } } else if ((*bestdwarf)->state == IDLE) + { set_labor(*bestdwarf, l, false); + } } if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) @@ -2450,7 +2488,6 @@ public: if (best_labor != df::unit_labor::NONE) { - busy_dwarfs.push_back(*bestdwarf); labor_infos[best_labor].active_dwarfs++; to_assign[best_labor]--; } @@ -2489,6 +2526,8 @@ public: ENUM_KEY_STR(unit_labor, l).c_str(), score, ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } + if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000) + set_labor(*d, (*d)->using_labor, false); } } } @@ -2535,7 +2574,7 @@ public: if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) set_labor(*d, l, true); - else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION) + else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER) set_labor(*d, l, true); else set_labor(*d, l, false); @@ -2546,16 +2585,42 @@ public: for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) { + if ((*d)->dwarf->job.current_job && (*d)->dwarf->job.current_job->job_type == df::job_type::PickupEquipment) + continue; + + if ((*d)->dwarf->military.pickup_flags.bits.update) + continue; + FOR_ENUM_ITEMS (unit_labor, l) { if (l == df::unit_labor::NONE) continue; tools_enum t = default_labor_infos[l].tool; - if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) + if (t == TOOL_NONE) + continue; + + bool has_tool = (*d)->has_tool[t]; + bool needs_tool = (*d)->dwarf->status.labors[l]; + + if (has_tool != needs_tool) { - tool_count[t]++; - (*d)->dwarf->military.pickup_flags.bits.update = 1; + if (has_tool && tool_count[t] > tool_in_use[t]) + continue; + + df::job_type j = df::job_type::NONE; + + if ((*d)->dwarf->job.current_job) + j = (*d)->dwarf->job.current_job->job_type; + + if (print_debug) + out.print("LABORMANAGER: asking %s to %s tools, current job %s, %d %d \n", (*d)->dwarf->name.first_name.c_str(), (has_tool) ? "drop" : "pick up", ENUM_KEY_STR(job_type, j).c_str(), has_tool, needs_tool); + + (*d)->dwarf->military.pickup_flags.bits.update = true; + labors_changed = true; + + if (needs_tool) + tool_in_use[t]++; } } } From 808afca9f01553888e82cec1ec69b25534a36fd8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 5 Jul 2016 13:16:34 -0500 Subject: [PATCH 0114/1012] labormanager: add StoreItemInLocation labor, reduce tool churn Note: this commit requires updated df-structures (77968973b28d0e828f880d119a700abb079f3521 or later) --- plugins/devel/labormanager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 545ca7f1c..98b7f0f6f 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1359,6 +1359,8 @@ public: job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; + + job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation }; df::unit_labor find_job_labor(df::job* j) @@ -1380,7 +1382,7 @@ public: df::unit_labor labor; if (job_to_labor_table.count(j->job_type) == 0) { - debug("LABORMANAGER: job has no job to labor table entry: %s\n", ENUM_KEY_STR(job_type, j->job_type).c_str()); + debug("LABORMANAGER: job has no job to labor table entry: %s (%d)\n", ENUM_KEY_STR(job_type, j->job_type).c_str(), j->job_type); debug_pause(); labor = df::unit_labor::NONE; } else { @@ -2526,7 +2528,7 @@ public: ENUM_KEY_STR(unit_labor, l).c_str(), score, ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } - if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000) + if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } } From 874a97ed9f8daf33c98c88d166c09fd183b6191b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 7 Jul 2016 02:10:43 -0500 Subject: [PATCH 0115/1012] labormanager: fix several job-to-labor mappings --- plugins/devel/labormanager.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 98b7f0f6f..46322a90c 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1135,7 +1135,7 @@ public: job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); - job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; + job_to_labor_table[df::job_type::RemoveConstruction] = jlf_const(df::unit_labor::REMOVE_CONSTRUCTION); job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; @@ -1269,13 +1269,13 @@ public: job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; + job_to_labor_table[df::job_type::PullLever] = jlf_const(df::unit_labor::PULL_LEVER); job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; - job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::ChainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); @@ -1335,7 +1335,7 @@ public: job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); - job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; job_to_labor_table[df::job_type::MakeTool] = jlf_make_furniture; job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); @@ -2291,10 +2291,7 @@ public: int before = labor_needed[l]; - if (labor_infos[l].idle_dwarfs > 0) - labor_needed[l] = 0; - else - labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); if (default_labor_infos[l].tool != TOOL_NONE) labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); From 74f6f3d416bc785fa4d961fb6ce9cd244051d0cd Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 11 Jul 2016 22:29:38 -0500 Subject: [PATCH 0116/1012] labormanager: add labors for bookcase (de)construct --- plugins/devel/labormanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 46322a90c..cb5f11dfc 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -840,6 +840,7 @@ private: case df::building_type::BarsFloor: case df::building_type::BarsVertical: case df::building_type::GrateWall: + case df::building_type::Bookcase: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -942,6 +943,7 @@ private: case df::building_type::BarsVertical: case df::building_type::GrateFloor: case df::building_type::GrateWall: + case df::building_type::Bookcase: return df::unit_labor::HAUL_FURNITURE; case df::building_type::AnimalTrap: return df::unit_labor::TRAPPER; From ed96725293671eb2f42763c9f0dc6a6355382e44 Mon Sep 17 00:00:00 2001 From: Matthew Lindner Date: Wed, 29 Jun 2016 09:44:42 -0400 Subject: [PATCH 0117/1012] Add protection from spaces in path and add warning in documentation --- docs/Introduction.rst | 2 ++ package/darwin/dfhack | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 64b6b36a5..9c4a276d2 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -52,6 +52,8 @@ into your DF folder, so that: * On Windows, ``SDL.dll`` is replaced * On Linux or OSX, the ``dfhack`` script is placed in the same folder as the ``df`` script +Additionally, on OSX, make sure that the directories in the DF folder path do not contain any '/' or ':' characters. + Uninstalling is basically the same, in reverse: * On Windows, replace ``SDL.dll`` with ``SDLreal.dll``, then remove the DFHack files. diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 66b574cf8..a97970801 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -4,11 +4,11 @@ cd "${PWD}" #thanks to Iriel for figuring this out OSREV=`uname -r | cut -d. -f1` if [ "$OSREV" -ge 11 ] ; then - export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs - export DYLD_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs + export DYLD_LIBRARY_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" + export DYLD_FRAMEWORK_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" else - export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs - export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs + export DYLD_FALLBACK_LIBRARY_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" + export DYLD_FALLBACK_FRAMEWORK_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" fi old_tty_settings=$(stty -g) From af6fed58f0cacccc8c379cbab625ef90dc0e13b5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 12 Jul 2016 14:51:31 -0400 Subject: [PATCH 0118/1012] Add @mlindner to Authors.rst --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index ee6f80390..5e919a846 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -52,6 +52,7 @@ Kurik Amudnil Lethosor lethosor Mason11987 Mason11987 Matthew Cline +Matthew Lindner mlindner Max maxthyme Max^TM melkor217 melkor217 Meneth32 From e357d9e40b5ea62d0ee1bdd960bdcbc18d6d6c6e Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 12 Jul 2016 14:53:22 -0400 Subject: [PATCH 0119/1012] OSX -> OS X --- docs/Introduction.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 9c4a276d2..68eb4844f 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -50,14 +50,14 @@ Installing DFhack involves copying files from a release archive into your DF folder, so that: * On Windows, ``SDL.dll`` is replaced -* On Linux or OSX, the ``dfhack`` script is placed in the same folder as the ``df`` script Additionally, on OSX, make sure that the directories in the DF folder path do not contain any '/' or ':' characters. +* On Linux or OS X, the ``dfhack`` script is placed in the same folder as the ``df`` script Uninstalling is basically the same, in reverse: * On Windows, replace ``SDL.dll`` with ``SDLreal.dll``, then remove the DFHack files. -* On Linux or OSX, remove the DFHack files. +* On Linux or OS X, remove the DFHack files. New players may wish to :wiki:`get a pack ` with DFHack preinstalled. From eef66b0db1ac6d3ca4578762e864b6b9903f7a48 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 12 Jul 2016 14:54:06 -0400 Subject: [PATCH 0120/1012] Get rid of OS X path restrictions (see package/linux/dfhack) --- docs/Introduction.rst | 2 -- package/darwin/dfhack | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 68eb4844f..3a36aa538 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -50,8 +50,6 @@ Installing DFhack involves copying files from a release archive into your DF folder, so that: * On Windows, ``SDL.dll`` is replaced - -Additionally, on OSX, make sure that the directories in the DF folder path do not contain any '/' or ':' characters. * On Linux or OS X, the ``dfhack`` script is placed in the same folder as the ``df`` script Uninstalling is basically the same, in reverse: diff --git a/package/darwin/dfhack b/package/darwin/dfhack index a97970801..445b600f7 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -4,11 +4,11 @@ cd "${PWD}" #thanks to Iriel for figuring this out OSREV=`uname -r | cut -d. -f1` if [ "$OSREV" -ge 11 ] ; then - export DYLD_LIBRARY_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" - export DYLD_FRAMEWORK_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" + export DYLD_LIBRARY_PATH="./hack:./libs:./hack/libs" + export DYLD_FRAMEWORK_PATH="./hack:./libs:./hack/libs" else - export DYLD_FALLBACK_LIBRARY_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" - export DYLD_FALLBACK_FRAMEWORK_PATH="${PWD}/hack:${PWD}/libs:${PWD}/hack/libs" + export DYLD_FALLBACK_LIBRARY_PATH="./hack:./libs:./hack/libs" + export DYLD_FALLBACK_FRAMEWORK_PATH="./hack:./libs:./hack/libs" fi old_tty_settings=$(stty -g) From 29963f4b67b9edae74cd69e159bb63730b524d27 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 26 Jul 2016 22:51:42 -0400 Subject: [PATCH 0121/1012] Rename script-in-readme to script-docs --- .travis.yml | 2 +- travis/{script-in-readme.py => script-docs.py} | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) rename travis/{script-in-readme.py => script-docs.py} (90%) diff --git a/.travis.yml b/.travis.yml index 1455044aa..5e60a1461 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ script: - python travis/pr-check-base.py - python travis/lint.py - python travis/authors-rst.py -- python travis/script-in-readme.py +- python travis/script-docs.py - python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" - python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis diff --git a/travis/script-in-readme.py b/travis/script-docs.py similarity index 90% rename from travis/script-in-readme.py rename to travis/script-docs.py index 154304420..2bce4bfd2 100644 --- a/travis/script-in-readme.py +++ b/travis/script-docs.py @@ -4,6 +4,7 @@ import os from os.path import basename, dirname, join, splitext import sys +SCRIPT_PATH = sys.argv[1] if len(sys.argv) > 1 else 'scripts' def expected_cmd(path): """Get the command from the name of a script.""" @@ -54,9 +55,9 @@ def check_file(fname): def main(): - """Check that all DFHack scripts include documentation (not 3rdparty)""" + """Check that all DFHack scripts include documentation""" err = 0 - for root, _, files in os.walk('scripts'): + for root, _, files in os.walk(SCRIPT_PATH): for f in files: if f[-3:] in {'.rb', 'lua'}: err += check_file(join(root, f)) @@ -64,4 +65,4 @@ def main(): if __name__ == '__main__': - sys.exit(bool(main())) + sys.exit(min(100, main())) From e2c63509788fa3109ea0a6b3e06fe2a13e5f7cd7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 26 Jul 2016 23:23:26 -0400 Subject: [PATCH 0122/1012] Update Lua to 5.3.3 --- depends/lua/include/lapi.h | 2 +- depends/lua/include/lauxlib.h | 96 ++- depends/lua/include/lcode.h | 15 +- depends/lua/include/lctype.h | 2 +- depends/lua/include/ldebug.h | 19 +- depends/lua/include/ldo.h | 28 +- depends/lua/include/lfunc.h | 38 +- depends/lua/include/lgc.h | 112 ++- depends/lua/include/llex.h | 17 +- depends/lua/include/llimits.h | 280 +++---- depends/lua/include/lmem.h | 30 +- depends/lua/include/lobject.h | 424 +++++------ depends/lua/include/lopcodes.h | 39 +- depends/lua/include/lparser.h | 68 +- depends/lua/include/lprefix.h | 45 ++ depends/lua/include/lstate.h | 168 +++-- depends/lua/include/lstring.h | 21 +- depends/lua/include/ltable.h | 25 +- depends/lua/include/ltm.h | 29 +- depends/lua/include/lua.h | 178 +++-- depends/lua/include/luaconf.h | 712 +++++++++++------- depends/lua/include/lualib.h | 5 +- depends/lua/include/lundump.h | 26 +- depends/lua/include/lvm.h | 99 ++- depends/lua/include/lzio.h | 11 +- depends/lua/src/lapi.c | 582 ++++++++------- depends/lua/src/lauxlib.c | 370 ++++++---- depends/lua/src/lbaselib.c | 226 +++--- depends/lua/src/lbitlib.c | 111 +-- depends/lua/src/lcode.c | 828 ++++++++++++++------- depends/lua/src/lcorolib.c | 33 +- depends/lua/src/lctype.c | 5 +- depends/lua/src/ldblib.c | 230 +++--- depends/lua/src/ldebug.c | 240 ++++-- depends/lua/src/ldo.c | 387 ++++++---- depends/lua/src/ldump.c | 300 ++++---- depends/lua/src/lfunc.c | 118 ++- depends/lua/src/lgc.c | 936 +++++++++++------------ depends/lua/src/linit.c | 41 +- depends/lua/src/liolib.c | 298 +++++--- depends/lua/src/llex.c | 297 ++++---- depends/lua/src/lmathlib.c | 336 ++++++--- depends/lua/src/lmem.c | 23 +- depends/lua/src/loadlib.c | 258 ++++--- depends/lua/src/lobject.c | 382 ++++++++-- depends/lua/src/lopcodes.c | 25 +- depends/lua/src/loslib.c | 260 ++++--- depends/lua/src/lparser.c | 224 +++--- depends/lua/src/lstate.c | 120 +-- depends/lua/src/lstring.c | 217 ++++-- depends/lua/src/lstrlib.c | 921 ++++++++++++++++++----- depends/lua/src/ltable.c | 427 ++++++----- depends/lua/src/ltablib.c | 401 +++++++--- depends/lua/src/ltm.c | 112 ++- depends/lua/src/lua.c | 518 ++++++++----- depends/lua/src/luac.c | 49 +- depends/lua/src/lundump.c | 427 ++++++----- depends/lua/src/lutf8lib.c | 256 +++++++ depends/lua/src/lvm.c | 1271 ++++++++++++++++++++++---------- depends/lua/src/lzio.c | 20 +- 60 files changed, 8743 insertions(+), 4995 deletions(-) create mode 100644 depends/lua/include/lprefix.h create mode 100644 depends/lua/src/lutf8lib.c diff --git a/depends/lua/include/lapi.h b/depends/lua/include/lapi.h index c7d34ad84..6d36dee3f 100644 --- a/depends/lua/include/lapi.h +++ b/depends/lua/include/lapi.h @@ -1,5 +1,5 @@ /* -** $Id: lapi.h,v 2.7.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lapi.h,v 2.9 2015/03/06 19:49:50 roberto Exp $ ** Auxiliary functions from Lua API ** See Copyright Notice in lua.h */ diff --git a/depends/lua/include/lauxlib.h b/depends/lua/include/lauxlib.h index 0fb023b8e..ddb7c2283 100644 --- a/depends/lua/include/lauxlib.h +++ b/depends/lua/include/lauxlib.h @@ -1,5 +1,5 @@ /* -** $Id: lauxlib.h,v 1.120.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lauxlib.h,v 1.129 2015/11/23 11:29:43 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ @@ -16,7 +16,7 @@ -/* extra error code for `luaL_load' */ +/* extra error code for 'luaL_load' */ #define LUA_ERRFILE (LUA_ERRERR+1) @@ -26,30 +26,30 @@ typedef struct luaL_Reg { } luaL_Reg; -LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver); -#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM) +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); -LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); -LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, size_t *l); -LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, const char *def, size_t *l); -LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); -LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); -LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); -LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, lua_Integer def); -LUALIB_API lua_Unsigned (luaL_checkunsigned) (lua_State *L, int numArg); -LUALIB_API lua_Unsigned (luaL_optunsigned) (lua_State *L, int numArg, - lua_Unsigned def); LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); -LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); -LUALIB_API void (luaL_checkany) (lua_State *L, int narg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); @@ -59,13 +59,13 @@ LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); LUALIB_API void (luaL_where) (lua_State *L, int lvl); LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); -LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, const char *const lst[]); LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); LUALIB_API int (luaL_execresult) (lua_State *L, int stat); -/* pre-defined references */ +/* predefined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) @@ -83,7 +83,7 @@ LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); LUALIB_API lua_State *(luaL_newstate) (void); -LUALIB_API int (luaL_len) (lua_State *L, int idx); +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, const char *r); @@ -108,16 +108,13 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, #define luaL_newlibtable(L,l) \ lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) -#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) -#define luaL_argcheck(L, cond,numarg,extramsg) \ - ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) #define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) #define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) -#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) -#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) -#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) -#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) #define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) @@ -207,6 +204,53 @@ LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, #endif +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + #endif diff --git a/depends/lua/include/lcode.h b/depends/lua/include/lcode.h index 6a1424cf5..cd306d573 100644 --- a/depends/lua/include/lcode.h +++ b/depends/lua/include/lcode.h @@ -1,5 +1,5 @@ /* -** $Id: lcode.h,v 1.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lcode.h,v 1.64 2016/01/05 16:22:37 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ @@ -24,7 +24,11 @@ ** grep "ORDER OPR" if you change these enums (ORDER OP) */ typedef enum BinOpr { - OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, + OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW, + OPR_DIV, + OPR_IDIV, + OPR_BAND, OPR_BOR, OPR_BXOR, + OPR_SHL, OPR_SHR, OPR_CONCAT, OPR_EQ, OPR_LT, OPR_LE, OPR_NE, OPR_GT, OPR_GE, @@ -33,10 +37,11 @@ typedef enum BinOpr { } BinOpr; -typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; +typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; -#define getcode(fs,e) ((fs)->f->code[(e)->u.info]) +/* get (pointer to) instruction of given 'expdesc' */ +#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info]) #define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) @@ -52,7 +57,7 @@ LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); -LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r); +LUAI_FUNC int luaK_intK (FuncState *fs, lua_Integer n); LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); diff --git a/depends/lua/include/lctype.h b/depends/lua/include/lctype.h index b09b21a33..99c7d1223 100644 --- a/depends/lua/include/lctype.h +++ b/depends/lua/include/lctype.h @@ -1,5 +1,5 @@ /* -** $Id: lctype.h,v 1.12.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lctype.h,v 1.12 2011/07/15 12:50:29 roberto Exp $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ diff --git a/depends/lua/include/ldebug.h b/depends/lua/include/ldebug.h index 6445c763e..0e31546b1 100644 --- a/depends/lua/include/ldebug.h +++ b/depends/lua/include/ldebug.h @@ -1,5 +1,5 @@ /* -** $Id: ldebug.h,v 2.7.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldebug.h,v 2.14 2015/05/22 17:45:56 roberto Exp $ ** Auxiliary functions from Debug Interface module ** See Copyright Notice in lua.h */ @@ -13,22 +13,27 @@ #define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) -#define getfuncline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0) +#define getfuncline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : -1) #define resethookcount(L) (L->hookcount = L->basehookcount) -/* Active Lua function (given call info) */ -#define ci_func(ci) (clLvalue((ci)->func)) - LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *opname); -LUAI_FUNC l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2); -LUAI_FUNC l_noret luaG_aritherror (lua_State *L, const TValue *p1, +LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, + const char *msg); +LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); +LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, + TString *src, int line); LUAI_FUNC l_noret luaG_errormsg (lua_State *L); +LUAI_FUNC void luaG_traceexec (lua_State *L); + #endif diff --git a/depends/lua/include/ldo.h b/depends/lua/include/ldo.h index d3d3082c9..4f5d51c3c 100644 --- a/depends/lua/include/ldo.h +++ b/depends/lua/include/ldo.h @@ -1,5 +1,5 @@ /* -** $Id: ldo.h,v 2.20.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldo.h,v 2.29 2015/12/21 13:02:14 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ @@ -13,31 +13,43 @@ #include "lzio.h" -#define luaD_checkstack(L,n) if (L->stack_last - L->top <= (n)) \ - luaD_growstack(L, n); else condmovestack(L); +/* +** Macro to check stack size and grow stack if needed. Parameters +** 'pre'/'pos' allow the macro to preserve a pointer into the +** stack across reallocations, doing the work only when needed. +** 'condmovestack' is used in heavy tests to force a stack reallocation +** at every check. +*/ +#define luaD_checkstackaux(L,n,pre,pos) \ + if (L->stack_last - L->top <= (n)) \ + { pre; luaD_growstack(L, n); pos; } else { condmovestack(L,pre,pos); } + +/* In general, 'pre'/'pos' are empty (nothing to save) */ +#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0) -#define incr_top(L) {L->top++; luaD_checkstack(L,0);} #define savestack(L,p) ((char *)(p) - (char *)L->stack) #define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) -/* type of protected functions, to be ran by `runprotected' */ +/* type of protected functions, to be ran by 'runprotected' */ typedef void (*Pfunc) (lua_State *L, void *ud); LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, const char *mode); LUAI_FUNC void luaD_hook (lua_State *L, int event, int line); LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); -LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults, - int allowyield); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); -LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); +LUAI_FUNC int luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult, + int nres); LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); LUAI_FUNC void luaD_growstack (lua_State *L, int n); LUAI_FUNC void luaD_shrinkstack (lua_State *L); +LUAI_FUNC void luaD_inctop (lua_State *L); LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); diff --git a/depends/lua/include/lfunc.h b/depends/lua/include/lfunc.h index ca0d3a3e0..2eeb0d5a4 100644 --- a/depends/lua/include/lfunc.h +++ b/depends/lua/include/lfunc.h @@ -1,5 +1,5 @@ /* -** $Id: lfunc.h,v 2.8.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lfunc.h,v 2.15 2015/01/13 15:49:11 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ @@ -18,14 +18,42 @@ cast(int, sizeof(TValue *)*((n)-1))) +/* test whether thread is in 'twups' list */ +#define isintwups(L) (L->twups != L) + + +/* +** maximum number of upvalues in a closure (both C and Lua). (Value +** must fit in a VM register.) +*/ +#define MAXUPVAL 255 + + +/* +** Upvalues for Lua closures +*/ +struct UpVal { + TValue *v; /* points to stack or to its own value */ + lu_mem refcount; /* reference counter */ + union { + struct { /* (when open) */ + UpVal *next; /* linked list */ + int touched; /* mark to avoid cycles with dead threads */ + } open; + TValue value; /* the value (when closed) */ + } u; +}; + +#define upisopen(up) ((up)->v != &(up)->u.value) + + LUAI_FUNC Proto *luaF_newproto (lua_State *L); -LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems); -LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems); -LUAI_FUNC UpVal *luaF_newupval (lua_State *L); +LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems); +LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); +LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); LUAI_FUNC void luaF_close (lua_State *L, StkId level); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); -LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, int pc); diff --git a/depends/lua/include/lgc.h b/depends/lua/include/lgc.h index 84bb1cdf9..aed3e18a5 100644 --- a/depends/lua/include/lgc.h +++ b/depends/lua/include/lgc.h @@ -1,5 +1,5 @@ /* -** $Id: lgc.h,v 2.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lgc.h,v 2.91 2015/12/21 13:02:14 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ @@ -38,36 +38,27 @@ */ #define GCSpropagate 0 #define GCSatomic 1 -#define GCSsweepstring 2 -#define GCSsweepudata 3 -#define GCSsweep 4 -#define GCSpause 5 +#define GCSswpallgc 2 +#define GCSswpfinobj 3 +#define GCSswptobefnz 4 +#define GCSswpend 5 +#define GCScallfin 6 +#define GCSpause 7 #define issweepphase(g) \ - (GCSsweepstring <= (g)->gcstate && (g)->gcstate <= GCSsweep) + (GCSswpallgc <= (g)->gcstate && (g)->gcstate <= GCSswpend) -#define isgenerational(g) ((g)->gckind == KGC_GEN) /* -** macros to tell when main invariant (white objects cannot point to black -** ones) must be kept. During a non-generational collection, the sweep +** macro to tell when main invariant (white objects cannot point to black +** ones) must be kept. During a collection, the sweep ** phase may break the invariant, as objects turned white may point to ** still-black objects. The invariant is restored when sweep ends and -** all objects are white again. During a generational collection, the -** invariant must be kept all times. +** all objects are white again. */ -#define keepinvariant(g) (isgenerational(g) || g->gcstate <= GCSatomic) - - -/* -** Outside the collector, the state in generational mode is kept in -** 'propagate', so 'keepinvariant' is always true. -*/ -#define keepinvariantout(g) \ - check_exp(g->gcstate == GCSpropagate || !isgenerational(g), \ - g->gcstate <= GCSatomic) +#define keepinvariant(g) ((g)->gcstate <= GCSatomic) /* @@ -83,75 +74,74 @@ #define testbit(x,b) testbits(x, bitmask(b)) -/* Layout for bit use in `marked' field: */ +/* Layout for bit use in 'marked' field: */ #define WHITE0BIT 0 /* object is white (type 0) */ #define WHITE1BIT 1 /* object is white (type 1) */ #define BLACKBIT 2 /* object is black */ -#define FINALIZEDBIT 3 /* object has been separated for finalization */ -#define SEPARATED 4 /* object is in 'finobj' list or in 'tobefnz' */ -#define FIXEDBIT 5 /* object is fixed (should not be collected) */ -#define OLDBIT 6 /* object is old (only in generational mode) */ +#define FINALIZEDBIT 3 /* object has been marked for finalization */ /* bit 7 is currently used by tests (luaL_checkmemory) */ #define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) -#define iswhite(x) testbits((x)->gch.marked, WHITEBITS) -#define isblack(x) testbit((x)->gch.marked, BLACKBIT) +#define iswhite(x) testbits((x)->marked, WHITEBITS) +#define isblack(x) testbit((x)->marked, BLACKBIT) #define isgray(x) /* neither white nor black */ \ - (!testbits((x)->gch.marked, WHITEBITS | bitmask(BLACKBIT))) - -#define isold(x) testbit((x)->gch.marked, OLDBIT) + (!testbits((x)->marked, WHITEBITS | bitmask(BLACKBIT))) -/* MOVE OLD rule: whenever an object is moved to the beginning of - a GC list, its old bit must be cleared */ -#define resetoldbit(o) resetbit((o)->gch.marked, OLDBIT) +#define tofinalize(x) testbit((x)->marked, FINALIZEDBIT) -#define otherwhite(g) (g->currentwhite ^ WHITEBITS) +#define otherwhite(g) ((g)->currentwhite ^ WHITEBITS) #define isdeadm(ow,m) (!(((m) ^ WHITEBITS) & (ow))) -#define isdead(g,v) isdeadm(otherwhite(g), (v)->gch.marked) +#define isdead(g,v) isdeadm(otherwhite(g), (v)->marked) -#define changewhite(x) ((x)->gch.marked ^= WHITEBITS) -#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) - -#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) +#define changewhite(x) ((x)->marked ^= WHITEBITS) +#define gray2black(x) l_setbit((x)->marked, BLACKBIT) #define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) -#define luaC_condGC(L,c) \ - {if (G(L)->GCdebt > 0) {c;}; condchangemem(L);} -#define luaC_checkGC(L) luaC_condGC(L, luaC_step(L);) +/* +** Does one step of collection when debt becomes positive. 'pre'/'pos' +** allows some adjustments to be done only when needed. macro +** 'condchangemem' is used only for heavy tests (forcing a full +** GC cycle on every opportunity) +*/ +#define luaC_condGC(L,pre,pos) \ + { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \ + condchangemem(L,pre,pos); } +/* more often than not, 'pre'/'pos' are empty */ +#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) -#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ - luaC_barrier_(L,obj2gco(p),gcvalue(v)); } -#define luaC_barrierback(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ - luaC_barrierback_(L,p); } +#define luaC_barrier(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrier_(L,obj2gco(p),gcvalue(v)) : cast_void(0)) -#define luaC_objbarrier(L,p,o) \ - { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ - luaC_barrier_(L,obj2gco(p),obj2gco(o)); } +#define luaC_barrierback(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrierback_(L,p) : cast_void(0)) -#define luaC_objbarrierback(L,p,o) \ - { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) luaC_barrierback_(L,p); } +#define luaC_objbarrier(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) -#define luaC_barrierproto(L,p,c) \ - { if (isblack(obj2gco(p))) luaC_barrierproto_(L,p,c); } +#define luaC_upvalbarrier(L,uv) ( \ + (iscollectable((uv)->v) && !upisopen(uv)) ? \ + luaC_upvalbarrier_(L,uv) : cast_void(0)) +LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); LUAI_FUNC void luaC_freeallobjects (lua_State *L); LUAI_FUNC void luaC_step (lua_State *L); -LUAI_FUNC void luaC_forcestep (lua_State *L); LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); -LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, - GCObject **list, int offset); +LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); -LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); -LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c); +LUAI_FUNC void luaC_barrierback_ (lua_State *L, Table *o); +LUAI_FUNC void luaC_upvalbarrier_ (lua_State *L, UpVal *uv); LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); -LUAI_FUNC void luaC_checkupvalcolor (global_State *g, UpVal *uv); -LUAI_FUNC void luaC_changemode (lua_State *L, int mode); +LUAI_FUNC void luaC_upvdeccount (lua_State *L, UpVal *uv); + #endif diff --git a/depends/lua/include/llex.h b/depends/lua/include/llex.h index a4acdd302..2363d87e4 100644 --- a/depends/lua/include/llex.h +++ b/depends/lua/include/llex.h @@ -1,5 +1,5 @@ /* -** $Id: llex.h,v 1.72.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: llex.h,v 1.79 2016/05/02 14:02:12 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ @@ -14,6 +14,10 @@ #define FIRST_RESERVED 257 +#if !defined(LUA_ENV) +#define LUA_ENV "_ENV" +#endif + /* * WARNING: if you change the order of this enumeration, @@ -26,8 +30,10 @@ enum RESERVED { TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* other terminal symbols */ - TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_DBCOLON, TK_EOS, - TK_NUMBER, TK_NAME, TK_STRING + TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, + TK_SHL, TK_SHR, + TK_DBCOLON, TK_EOS, + TK_FLT, TK_INT, TK_NAME, TK_STRING }; /* number of reserved words */ @@ -36,6 +42,7 @@ enum RESERVED { typedef union { lua_Number r; + lua_Integer i; TString *ts; } SemInfo; /* semantics information */ @@ -51,17 +58,17 @@ typedef struct Token { typedef struct LexState { int current; /* current character (charint) */ int linenumber; /* input line counter */ - int lastline; /* line of last token `consumed' */ + int lastline; /* line of last token 'consumed' */ Token t; /* current token */ Token lookahead; /* look ahead token */ struct FuncState *fs; /* current function (parser) */ struct lua_State *L; ZIO *z; /* input stream */ Mbuffer *buff; /* buffer for tokens */ + Table *h; /* to avoid collection/reuse strings */ struct Dyndata *dyd; /* dynamic structures used by the parser */ TString *source; /* current source name */ TString *envn; /* environment variable name */ - char decpoint; /* locale decimal point */ } LexState; diff --git a/depends/lua/include/llimits.h b/depends/lua/include/llimits.h index 152dd0551..f21377fef 100644 --- a/depends/lua/include/llimits.h +++ b/depends/lua/include/llimits.h @@ -1,6 +1,6 @@ /* -** $Id: llimits.h,v 1.103.1.1 2013/04/12 18:48:47 roberto Exp $ -** Limits, basic types, and some other `installation-dependent' definitions +** $Id: llimits.h,v 1.141 2015/11/19 19:16:22 roberto Exp $ +** Limits, basic types, and some other 'installation-dependent' definitions ** See Copyright Notice in lua.h */ @@ -14,54 +14,77 @@ #include "lua.h" - -typedef unsigned LUA_INT32 lu_int32; - +/* +** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count +** the total memory used by Lua (in bytes). Usually, 'size_t' and +** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines. +*/ +#if defined(LUAI_MEM) /* { external definitions? */ typedef LUAI_UMEM lu_mem; - typedef LUAI_MEM l_mem; +#elif LUAI_BITSINT >= 32 /* }{ */ +typedef size_t lu_mem; +typedef ptrdiff_t l_mem; +#else /* 16-bit ints */ /* }{ */ +typedef unsigned long lu_mem; +typedef long l_mem; +#endif /* } */ - -/* chars used as small naturals (so that `char' is reserved for characters) */ +/* chars used as small naturals (so that 'char' is reserved for characters) */ typedef unsigned char lu_byte; -#define MAX_SIZET ((size_t)(~(size_t)0)-2) +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +/* maximum size visible for Lua (must be representable in a lua_Integer */ +#define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ + : (size_t)(LUA_MAXINTEGER)) + -#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)) -#define MAX_LMEM ((l_mem) ((MAX_LUMEM >> 1) - 2)) +#define MAX_LMEM ((l_mem)(MAX_LUMEM >> 1)) -#define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ +#define MAX_INT INT_MAX /* maximum value of an int */ + /* -** conversion of pointer to integer +** conversion of pointer to unsigned integer: ** this is for hashing only; there is no problem if the integer ** cannot hold the whole pointer value */ -#define IntPoint(p) ((unsigned int)(lu_mem)(p)) +#define point2uint(p) ((unsigned int)((size_t)(p) & UINT_MAX)) /* type to ensure maximum alignment */ -#if !defined(LUAI_USER_ALIGNMENT_T) -#define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; } +#if defined(LUAI_USER_ALIGNMENT_T) +typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; +#else +typedef union { + lua_Number n; + double u; + void *s; + lua_Integer i; + long l; +} L_Umaxalign; #endif -typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; -/* result of a `usual argument conversion' over lua_Number */ +/* types of 'usual argument conversions' for lua_Number and lua_Integer */ typedef LUAI_UACNUMBER l_uacNumber; +typedef LUAI_UACINT l_uacInt; /* internal assertions for in-house debugging */ #if defined(lua_assert) #define check_exp(c,e) (lua_assert(c), (e)) /* to avoid problems with conditions too long */ -#define lua_longassert(c) { if (!(c)) lua_assert(0); } +#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0)) #else #define lua_assert(c) ((void)0) #define check_exp(c,e) (e) @@ -72,38 +95,49 @@ typedef LUAI_UACNUMBER l_uacNumber; ** assertion for checking API calls */ #if !defined(luai_apicheck) - -#if defined(LUA_USE_APICHECK) -#include -#define luai_apicheck(L,e) assert(e) -#else -#define luai_apicheck(L,e) lua_assert(e) -#endif - +#define luai_apicheck(l,e) lua_assert(e) #endif #define api_check(l,e,msg) luai_apicheck(l,(e) && msg) +/* macro to avoid warnings about unused variables */ #if !defined(UNUSED) -#define UNUSED(x) ((void)(x)) /* to avoid warnings */ +#define UNUSED(x) ((void)(x)) #endif +/* type casts (a macro highlights casts in the code) */ #define cast(t, exp) ((t)(exp)) +#define cast_void(i) cast(void, (i)) #define cast_byte(i) cast(lu_byte, (i)) #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) #define cast_uchar(i) cast(unsigned char, (i)) +/* cast a signed lua_Integer to lua_Unsigned */ +#if !defined(l_castS2U) +#define l_castS2U(i) ((lua_Unsigned)(i)) +#endif + +/* +** cast a lua_Unsigned to a signed lua_Integer; this cast is +** not strict ISO C, but two-complement architectures should +** work fine. +*/ +#if !defined(l_castU2S) +#define l_castU2S(i) ((lua_Integer)(i)) +#endif + + /* ** non-return type */ #if defined(__GNUC__) #define l_noret void __attribute__((noreturn)) -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && _MSC_VER >= 1200 #define l_noret void __declspec(noreturn) #else #define l_noret void @@ -119,29 +153,50 @@ typedef LUAI_UACNUMBER l_uacNumber; #define LUAI_MAXCCALLS 200 #endif -/* -** maximum number of upvalues in a closure (both C and Lua). (Value -** must fit in an unsigned char.) -*/ -#define MAXUPVAL UCHAR_MAX /* -** type for virtual-machine instructions +** type for virtual-machine instructions; ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) */ -typedef lu_int32 Instruction; - +#if LUAI_BITSINT >= 32 +typedef unsigned int Instruction; +#else +typedef unsigned long Instruction; +#endif -/* maximum stack for a Lua function */ -#define MAXSTACK 250 +/* +** Maximum length for short strings, that is, strings that are +** internalized. (Cannot be smaller than reserved words or tags for +** metamethods, as these strings must be internalized; +** #("function") = 8, #("__newindex") = 10.) +*/ +#if !defined(LUAI_MAXSHORTLEN) +#define LUAI_MAXSHORTLEN 40 +#endif -/* minimum size for the string table (must be power of 2) */ +/* +** Initial size for the string table (must be power of 2). +** The Lua core alone registers ~50 strings (reserved words + +** metaevent keys + a few others). Libraries would typically add +** a few dozens more. +*/ #if !defined(MINSTRTABSIZE) -#define MINSTRTABSIZE 32 +#define MINSTRTABSIZE 128 +#endif + + +/* +** Size of cache for strings in the API. 'N' is the number of +** sets (better be a prime) and "M" is the size of each set (M == 1 +** makes a direct cache.) +*/ +#if !defined(STRCACHE_N) +#define STRCACHE_N 53 +#define STRCACHE_M 2 #endif @@ -151,13 +206,21 @@ typedef lu_int32 Instruction; #endif +/* +** macros that are executed whenever program enters the Lua core +** ('lua_lock') and leaves the core ('lua_unlock') +*/ #if !defined(lua_lock) -#define lua_lock(L) ((void) 0) -#define lua_unlock(L) ((void) 0) +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) #endif +/* +** macro executed during Lua functions at points where the +** function can yield. +*/ #if !defined(luai_threadyield) -#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} #endif @@ -183,127 +246,78 @@ typedef lu_int32 Instruction; #endif #if !defined(luai_userstateresume) -#define luai_userstateresume(L,n) ((void)L) +#define luai_userstateresume(L,n) ((void)L) #endif #if !defined(luai_userstateyield) -#define luai_userstateyield(L,n) ((void)L) +#define luai_userstateyield(L,n) ((void)L) #endif -/* -** lua_number2int is a macro to convert lua_Number to int. -** lua_number2integer is a macro to convert lua_Number to lua_Integer. -** lua_number2unsigned is a macro to convert a lua_Number to a lua_Unsigned. -** lua_unsigned2number is a macro to convert a lua_Unsigned to a lua_Number. -** luai_hashnum is a macro to hash a lua_Number value into an integer. -** The hash must be deterministic and give reasonable values for -** both small and large values (outside the range of integers). -*/ - -#if defined(MS_ASMTRICK) || defined(LUA_MSASMTRICK) /* { */ -/* trick with Microsoft assembler for X86 */ - -#define lua_number2int(i,n) __asm {__asm fld n __asm fistp i} -#define lua_number2integer(i,n) lua_number2int(i, n) -#define lua_number2unsigned(i,n) \ - {__int64 l; __asm {__asm fld n __asm fistp l} i = (unsigned int)l;} - - -#elif defined(LUA_IEEE754TRICK) /* }{ */ -/* the next trick should work on any machine using IEEE754 with - a 32-bit int type */ - -union luai_Cast { double l_d; LUA_INT32 l_p[2]; }; - -#if !defined(LUA_IEEEENDIAN) /* { */ -#define LUAI_EXTRAIEEE \ - static const union luai_Cast ieeeendian = {-(33.0 + 6755399441055744.0)}; -#define LUA_IEEEENDIANLOC (ieeeendian.l_p[1] == 33) -#else -#define LUA_IEEEENDIANLOC LUA_IEEEENDIAN -#define LUAI_EXTRAIEEE /* empty */ -#endif /* } */ -#define lua_number2int32(i,n,t) \ - { LUAI_EXTRAIEEE \ - volatile union luai_Cast u; u.l_d = (n) + 6755399441055744.0; \ - (i) = (t)u.l_p[LUA_IEEEENDIANLOC]; } -#define luai_hashnum(i,n) \ - { volatile union luai_Cast u; u.l_d = (n) + 1.0; /* avoid -0 */ \ - (i) = u.l_p[0]; (i) += u.l_p[1]; } /* add double bits for his hash */ - -#define lua_number2int(i,n) lua_number2int32(i, n, int) -#define lua_number2unsigned(i,n) lua_number2int32(i, n, lua_Unsigned) +/* +** The luai_num* macros define the primitive operations over numbers. +*/ -/* the trick can be expanded to lua_Integer when it is a 32-bit value */ -#if defined(LUA_IEEELL) -#define lua_number2integer(i,n) lua_number2int32(i, n, lua_Integer) +/* floor division (defined as 'floor(a/b)') */ +#if !defined(luai_numidiv) +#define luai_numidiv(L,a,b) ((void)L, l_floor(luai_numdiv(L,a,b))) #endif -#endif /* } */ - - -/* the following definitions always work, but may be slow */ - -#if !defined(lua_number2int) -#define lua_number2int(i,n) ((i)=(int)(n)) +/* float division */ +#if !defined(luai_numdiv) +#define luai_numdiv(L,a,b) ((a)/(b)) #endif -#if !defined(lua_number2integer) -#define lua_number2integer(i,n) ((i)=(lua_Integer)(n)) +/* +** modulo: defined as 'a - floor(a/b)*b'; this definition gives NaN when +** 'b' is huge, but the result should be 'a'. 'fmod' gives the result of +** 'a - trunc(a/b)*b', and therefore must be corrected when 'trunc(a/b) +** ~= floor(a/b)'. That happens when the division has a non-integer +** negative result, which is equivalent to the test below. +*/ +#if !defined(luai_nummod) +#define luai_nummod(L,a,b,m) \ + { (m) = l_mathop(fmod)(a,b); if ((m)*(b) < 0) (m) += (b); } #endif -#if !defined(lua_number2unsigned) /* { */ -/* the following definition assures proper modulo behavior */ -#if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) -#include -#define SUPUNSIGNED ((lua_Number)(~(lua_Unsigned)0) + 1) -#define lua_number2unsigned(i,n) \ - ((i)=(lua_Unsigned)((n) - floor((n)/SUPUNSIGNED)*SUPUNSIGNED)) -#else -#define lua_number2unsigned(i,n) ((i)=(lua_Unsigned)(n)) +/* exponentiation */ +#if !defined(luai_numpow) +#define luai_numpow(L,a,b) ((void)L, l_mathop(pow)(a,b)) #endif -#endif /* } */ - -#if !defined(lua_unsigned2number) -/* on several machines, coercion from unsigned to double is slow, - so it may be worth to avoid */ -#define lua_unsigned2number(u) \ - (((u) <= (lua_Unsigned)INT_MAX) ? (lua_Number)(int)(u) : (lua_Number)(u)) +/* the others are quite standard operations */ +#if !defined(luai_numadd) +#define luai_numadd(L,a,b) ((a)+(b)) +#define luai_numsub(L,a,b) ((a)-(b)) +#define luai_nummul(L,a,b) ((a)*(b)) +#define luai_numunm(L,a) (-(a)) +#define luai_numeq(a,b) ((a)==(b)) +#define luai_numlt(a,b) ((a)<(b)) +#define luai_numle(a,b) ((a)<=(b)) +#define luai_numisnan(a) (!luai_numeq((a), (a))) #endif -#if defined(ltable_c) && !defined(luai_hashnum) - -#include -#include - -#define luai_hashnum(i,n) { int e; \ - n = l_mathop(frexp)(n, &e) * (lua_Number)(INT_MAX - DBL_MAX_EXP); \ - lua_number2int(i, n); i += e; } - -#endif - /* ** macro to control inclusion of some hard tests on stack reallocation */ #if !defined(HARDSTACKTESTS) -#define condmovestack(L) ((void)0) +#define condmovestack(L,pre,pos) ((void)0) #else /* realloc stack keeping its size */ -#define condmovestack(L) luaD_reallocstack((L), (L)->stacksize) +#define condmovestack(L,pre,pos) \ + { int sz_ = (L)->stacksize; pre; luaD_reallocstack((L), sz_); pos; } #endif #if !defined(HARDMEMTESTS) -#define condchangemem(L) condmovestack(L) +#define condchangemem(L,pre,pos) ((void)0) #else -#define condchangemem(L) \ - ((void)(!(G(L)->gcrunning) || (luaC_fullgc(L, 0), 1))) +#define condchangemem(L,pre,pos) \ + { if (G(L)->gcrunning) { pre; luaC_fullgc(L, 0); pos; } } #endif #endif diff --git a/depends/lua/include/lmem.h b/depends/lua/include/lmem.h index bd4f4e072..30f484895 100644 --- a/depends/lua/include/lmem.h +++ b/depends/lua/include/lmem.h @@ -1,5 +1,5 @@ /* -** $Id: lmem.h,v 1.40.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lmem.h,v 1.43 2014/12/19 17:26:14 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ @@ -15,20 +15,32 @@ /* -** This macro avoids the runtime division MAX_SIZET/(e), as 'e' is -** always constant. -** The macro is somewhat complex to avoid warnings: -** +1 avoids warnings of "comparison has constant result"; -** cast to 'void' avoids warnings of "value unused". +** This macro reallocs a vector 'b' from 'on' to 'n' elements, where +** each element has size 'e'. In case of arithmetic overflow of the +** product 'n'*'e', it raises an error (calling 'luaM_toobig'). Because +** 'e' is always constant, it avoids the runtime division MAX_SIZET/(e). +** +** (The macro is somewhat complex to avoid warnings: The 'sizeof' +** comparison avoids a runtime comparison when overflow cannot occur. +** The compiler should be able to optimize the real test by itself, but +** when it does it, it may give a warning about "comparison is always +** false due to limited range of data type"; the +1 tricks the compiler, +** avoiding this warning but also this optimization.) */ #define luaM_reallocv(L,b,on,n,e) \ - (cast(void, \ - (cast(size_t, (n)+1) > MAX_SIZET/(e)) ? (luaM_toobig(L), 0) : 0), \ + (((sizeof(n) >= sizeof(size_t) && cast(size_t, (n)) + 1 > MAX_SIZET/(e)) \ + ? luaM_toobig(L) : cast_void(0)) , \ luaM_realloc_(L, (b), (on)*(e), (n)*(e))) +/* +** Arrays of chars do not need any test +*/ +#define luaM_reallocvchar(L,b,on,n) \ + cast(char *, luaM_realloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char))) + #define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) #define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) -#define luaM_freearray(L, b, n) luaM_reallocv(L, (b), n, 0, sizeof((b)[0])) +#define luaM_freearray(L, b, n) luaM_realloc_(L, (b), (n)*sizeof(*(b)), 0) #define luaM_malloc(L,s) luaM_realloc_(L, NULL, 0, (s)) #define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) diff --git a/depends/lua/include/lobject.h b/depends/lua/include/lobject.h index 3a630b944..2d52b4159 100644 --- a/depends/lua/include/lobject.h +++ b/depends/lua/include/lobject.h @@ -1,5 +1,5 @@ /* -** $Id: lobject.h,v 2.71.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lobject.h,v 2.116 2015/11/03 18:33:10 roberto Exp $ ** Type definitions for Lua objects ** See Copyright Notice in lua.h */ @@ -19,14 +19,13 @@ /* ** Extra tags for non-values */ -#define LUA_TPROTO LUA_NUMTAGS -#define LUA_TUPVAL (LUA_NUMTAGS+1) -#define LUA_TDEADKEY (LUA_NUMTAGS+2) +#define LUA_TPROTO LUA_NUMTAGS /* function prototypes */ +#define LUA_TDEADKEY (LUA_NUMTAGS+1) /* removed keys in tables */ /* ** number of all possible tags (including LUA_TNONE but excluding DEADKEY) */ -#define LUA_TOTALTAGS (LUA_TUPVAL+2) +#define LUA_TOTALTAGS (LUA_TPROTO + 2) /* @@ -36,8 +35,6 @@ ** bit 6: whether value is collectable */ -#define VARBITS (3 << 4) - /* ** LUA_TFUNCTION variants: @@ -57,6 +54,11 @@ #define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */ +/* Variant tags for numbers */ +#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) /* float numbers */ +#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) /* integer numbers */ + + /* Bit mark for collectable types */ #define BIT_ISCOLLECTABLE (1 << 6) @@ -65,9 +67,9 @@ /* -** Union of all collectable objects +** Common type for all collectable objects */ -typedef union GCObject GCObject; +typedef struct GCObject GCObject; /* @@ -78,21 +80,12 @@ typedef union GCObject GCObject; /* -** Common header in struct form +** Common type has only the common header */ -typedef struct GCheader { +struct GCObject { CommonHeader; -} GCheader; - - - -/* -** Union of all Lua values -*/ -typedef union Value Value; - +}; -#define numfield lua_Number n; /* numbers */ @@ -101,9 +94,26 @@ typedef union Value Value; ** an actual value plus a tag with its type. */ +/* +** Union of all Lua values +*/ +typedef union Value { + GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + int b; /* booleans */ + lua_CFunction f; /* light C functions */ + lua_Integer i; /* integer numbers */ + lua_Number n; /* float numbers */ +} Value; + + #define TValuefields Value value_; int tt_ -typedef struct lua_TValue TValue; + +typedef struct lua_TValue { + TValuefields; +} TValue; + /* macro defining a nil value */ @@ -111,7 +121,6 @@ typedef struct lua_TValue TValue; #define val_(o) ((o)->value_) -#define num_(o) (val_(o).n) /* raw type tag of a TValue */ @@ -124,13 +133,15 @@ typedef struct lua_TValue TValue; #define ttype(o) (rttype(o) & 0x3F) /* type tag of a TValue with no variants (bits 0-3) */ -#define ttypenv(o) (novariant(rttype(o))) +#define ttnov(o) (novariant(rttype(o))) /* Macros to test type */ #define checktag(o,t) (rttype(o) == (t)) -#define checktype(o,t) (ttypenv(o) == (t)) -#define ttisnumber(o) checktag((o), LUA_TNUMBER) +#define checktype(o,t) (ttnov(o) == (t)) +#define ttisnumber(o) checktype((o), LUA_TNUMBER) +#define ttisfloat(o) checktag((o), LUA_TNUMFLT) +#define ttisinteger(o) checktag((o), LUA_TNUMINT) #define ttisnil(o) checktag((o), LUA_TNIL) #define ttisboolean(o) checktag((o), LUA_TBOOLEAN) #define ttislightuserdata(o) checktag((o), LUA_TLIGHTUSERDATA) @@ -143,27 +154,27 @@ typedef struct lua_TValue TValue; #define ttisCclosure(o) checktag((o), ctb(LUA_TCCL)) #define ttisLclosure(o) checktag((o), ctb(LUA_TLCL)) #define ttislcf(o) checktag((o), LUA_TLCF) -#define ttisuserdata(o) checktag((o), ctb(LUA_TUSERDATA)) +#define ttisfulluserdata(o) checktag((o), ctb(LUA_TUSERDATA)) #define ttisthread(o) checktag((o), ctb(LUA_TTHREAD)) #define ttisdeadkey(o) checktag((o), LUA_TDEADKEY) -#define ttisequal(o1,o2) (rttype(o1) == rttype(o2)) /* Macros to access values */ -#define nvalue(o) check_exp(ttisnumber(o), num_(o)) +#define ivalue(o) check_exp(ttisinteger(o), val_(o).i) +#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n) +#define nvalue(o) check_exp(ttisnumber(o), \ + (ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o))) #define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) #define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) -#define rawtsvalue(o) check_exp(ttisstring(o), &val_(o).gc->ts) -#define tsvalue(o) (&rawtsvalue(o)->tsv) -#define rawuvalue(o) check_exp(ttisuserdata(o), &val_(o).gc->u) -#define uvalue(o) (&rawuvalue(o)->uv) -#define clvalue(o) check_exp(ttisclosure(o), &val_(o).gc->cl) -#define clLvalue(o) check_exp(ttisLclosure(o), &val_(o).gc->cl.l) -#define clCvalue(o) check_exp(ttisCclosure(o), &val_(o).gc->cl.c) +#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc)) +#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc)) +#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc)) +#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc)) +#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc)) #define fvalue(o) check_exp(ttislcf(o), val_(o).f) -#define hvalue(o) check_exp(ttistable(o), &val_(o).gc->h) +#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc)) #define bvalue(o) check_exp(ttisboolean(o), val_(o).b) -#define thvalue(o) check_exp(ttisthread(o), &val_(o).gc->th) +#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc)) /* a dead value may get the 'gc' field, but cannot access its contents */ #define deadvalue(o) check_exp(ttisdeadkey(o), cast(void *, val_(o).gc)) @@ -174,18 +185,27 @@ typedef struct lua_TValue TValue; /* Macros for internal tests */ -#define righttt(obj) (ttype(obj) == gcvalue(obj)->gch.tt) +#define righttt(obj) (ttype(obj) == gcvalue(obj)->tt) -#define checkliveness(g,obj) \ +#define checkliveness(L,obj) \ lua_longassert(!iscollectable(obj) || \ - (righttt(obj) && !isdead(g,gcvalue(obj)))) + (righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj))))) /* Macros to set values */ #define settt_(o,t) ((o)->tt_=(t)) -#define setnvalue(obj,x) \ - { TValue *io=(obj); num_(io)=(x); settt_(io, LUA_TNUMBER); } +#define setfltvalue(obj,x) \ + { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_TNUMFLT); } + +#define chgfltvalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); } + +#define setivalue(obj,x) \ + { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_TNUMINT); } + +#define chgivalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); } #define setnilvalue(obj) settt_(obj, LUA_TNIL) @@ -199,48 +219,46 @@ typedef struct lua_TValue TValue; { TValue *io=(obj); val_(io).b=(x); settt_(io, LUA_TBOOLEAN); } #define setgcovalue(L,obj,x) \ - { TValue *io=(obj); GCObject *i_g=(x); \ - val_(io).gc=i_g; settt_(io, ctb(gch(i_g)->tt)); } + { TValue *io = (obj); GCObject *i_g=(x); \ + val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); } #define setsvalue(L,obj,x) \ - { TValue *io=(obj); \ - TString *x_ = (x); \ - val_(io).gc=cast(GCObject *, x_); settt_(io, ctb(x_->tsv.tt)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); TString *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ + checkliveness(L,io); } #define setuvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TUSERDATA)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); Udata *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TUSERDATA)); \ + checkliveness(L,io); } #define setthvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TTHREAD)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); lua_State *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTHREAD)); \ + checkliveness(L,io); } #define setclLvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TLCL)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); LClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TLCL)); \ + checkliveness(L,io); } #define setclCvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TCCL)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); CClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TCCL)); \ + checkliveness(L,io); } #define sethvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TTABLE)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); Table *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTABLE)); \ + checkliveness(L,io); } #define setdeadvalue(obj) settt_(obj, LUA_TDEADKEY) #define setobj(L,obj1,obj2) \ - { const TValue *io2=(obj2); TValue *io1=(obj1); \ - io1->value_ = io2->value_; io1->tt_ = io2->tt_; \ - checkliveness(G(L),io1); } + { TValue *io1=(obj1); *io1 = *(obj2); \ + (void)L; checkliveness(L,io1); } /* @@ -256,188 +274,110 @@ typedef struct lua_TValue TValue; #define setptvalue2s setptvalue /* from table to same table */ #define setobjt2t setobj -/* to table */ -#define setobj2t setobj /* to new object */ #define setobj2n setobj #define setsvalue2n setsvalue +/* to table (define it as an expression to be used in macros) */ +#define setobj2t(L,o1,o2) ((void)L, *(o1)=*(o2), checkliveness(L,(o1))) + -/* check whether a number is valid (useful only for NaN trick) */ -#define luai_checknum(L,o,c) { /* empty */ } /* ** {====================================================== -** NaN Trick +** types and prototypes ** ======================================================= */ -#if defined(LUA_NANTRICK) - -/* -** numbers are represented in the 'd_' field. All other values have the -** value (NNMARK | tag) in 'tt__'. A number with such pattern would be -** a "signaled NaN", which is never generated by regular operations by -** the CPU (nor by 'strtod') -*/ - -/* allows for external implementation for part of the trick */ -#if !defined(NNMARK) /* { */ - - -#if !defined(LUA_IEEEENDIAN) -#error option 'LUA_NANTRICK' needs 'LUA_IEEEENDIAN' -#endif - - -#define NNMARK 0x7FF7A500 -#define NNMASK 0x7FFFFF00 - -#undef TValuefields -#undef NILCONSTANT - -#if (LUA_IEEEENDIAN == 0) /* { */ - -/* little endian */ -#define TValuefields \ - union { struct { Value v__; int tt__; } i; double d__; } u -#define NILCONSTANT {{{NULL}, tag2tt(LUA_TNIL)}} -/* field-access macros */ -#define v_(o) ((o)->u.i.v__) -#define d_(o) ((o)->u.d__) -#define tt_(o) ((o)->u.i.tt__) - -#else /* }{ */ - -/* big endian */ -#define TValuefields \ - union { struct { int tt__; Value v__; } i; double d__; } u -#define NILCONSTANT {{tag2tt(LUA_TNIL), {NULL}}} -/* field-access macros */ -#define v_(o) ((o)->u.i.v__) -#define d_(o) ((o)->u.d__) -#define tt_(o) ((o)->u.i.tt__) - -#endif /* } */ - -#endif /* } */ -/* correspondence with standard representation */ -#undef val_ -#define val_(o) v_(o) -#undef num_ -#define num_(o) d_(o) - - -#undef numfield -#define numfield /* no such field; numbers are the entire struct */ - -/* basic check to distinguish numbers from non-numbers */ -#undef ttisnumber -#define ttisnumber(o) ((tt_(o) & NNMASK) != NNMARK) - -#define tag2tt(t) (NNMARK | (t)) - -#undef rttype -#define rttype(o) (ttisnumber(o) ? LUA_TNUMBER : tt_(o) & 0xff) - -#undef settt_ -#define settt_(o,t) (tt_(o) = tag2tt(t)) +typedef TValue *StkId; /* index to stack elements */ -#undef setnvalue -#define setnvalue(obj,x) \ - { TValue *io_=(obj); num_(io_)=(x); lua_assert(ttisnumber(io_)); } -#undef setobj -#define setobj(L,obj1,obj2) \ - { const TValue *o2_=(obj2); TValue *o1_=(obj1); \ - o1_->u = o2_->u; \ - checkliveness(G(L),o1_); } /* -** these redefinitions are not mandatory, but these forms are more efficient +** Header for string value; string bytes follow the end of this structure +** (aligned according to 'UTString'; see next). */ - -#undef checktag -#undef checktype -#define checktag(o,t) (tt_(o) == tag2tt(t)) -#define checktype(o,t) (ctb(tt_(o) | VARBITS) == ctb(tag2tt(t) | VARBITS)) - -#undef ttisequal -#define ttisequal(o1,o2) \ - (ttisnumber(o1) ? ttisnumber(o2) : (tt_(o1) == tt_(o2))) - - -#undef luai_checknum -#define luai_checknum(L,o,c) { if (!ttisnumber(o)) c; } - -#endif -/* }====================================================== */ - +typedef struct TString { + CommonHeader; + lu_byte extra; /* reserved words for short strings; "has hash" for longs */ + lu_byte shrlen; /* length for short strings */ + unsigned int hash; + union { + size_t lnglen; /* length for long strings */ + struct TString *hnext; /* linked list for hash table */ + } u; +} TString; /* -** {====================================================== -** types and prototypes -** ======================================================= +** Ensures that address after this type is always fully aligned. */ +typedef union UTString { + L_Umaxalign dummy; /* ensures maximum alignment for strings */ + TString tsv; +} UTString; -union Value { - GCObject *gc; /* collectable objects */ - void *p; /* light userdata */ - int b; /* booleans */ - lua_CFunction f; /* light C functions */ - numfield /* numbers */ -}; - - -struct lua_TValue { - TValuefields; -}; +/* +** Get the actual string (array of bytes) from a 'TString'. +** (Access to 'extra' ensures that value is really a 'TString'.) +*/ +#define getstr(ts) \ + check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString)) -typedef TValue *StkId; /* index to stack elements */ +/* get the actual string (array of bytes) from a Lua value */ +#define svalue(o) getstr(tsvalue(o)) +/* get string length from 'TString *s' */ +#define tsslen(s) ((s)->tt == LUA_TSHRSTR ? (s)->shrlen : (s)->u.lnglen) +/* get string length from 'TValue *o' */ +#define vslen(o) tsslen(tsvalue(o)) /* -** Header for string value; string bytes follow the end of this structure +** Header for userdata; memory area follows the end of this structure +** (aligned according to 'UUdata'; see next). */ -typedef union TString { - L_Umaxalign dummy; /* ensures maximum alignment for strings */ - struct { - CommonHeader; - lu_byte extra; /* reserved words for short strings; "has hash" for longs */ - unsigned int hash; - size_t len; /* number of characters in string */ - } tsv; -} TString; - +typedef struct Udata { + CommonHeader; + lu_byte ttuv_; /* user value's tag */ + struct Table *metatable; + size_t len; /* number of bytes */ + union Value user_; /* user value */ +} Udata; -/* get the actual string (array of bytes) from a TString */ -#define getstr(ts) cast(const char *, (ts) + 1) -/* get the actual string (array of bytes) from a Lua value */ -#define svalue(o) getstr(rawtsvalue(o)) +/* +** Ensures that address after this type is always fully aligned. +*/ +typedef union UUdata { + L_Umaxalign dummy; /* ensures maximum alignment for 'local' udata */ + Udata uv; +} UUdata; /* -** Header for userdata; memory area follows the end of this structure +** Get the address of memory block inside 'Udata'. +** (Access to 'ttuv_' ensures that value is really a 'Udata'.) */ -typedef union Udata { - L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ - struct { - CommonHeader; - struct Table *metatable; - struct Table *env; - size_t len; /* number of bytes */ - } uv; -} Udata; +#define getudatamem(u) \ + check_exp(sizeof((u)->ttuv_), (cast(char*, (u)) + sizeof(UUdata))) + +#define setuservalue(L,u,o) \ + { const TValue *io=(o); Udata *iu = (u); \ + iu->user_ = io->value_; iu->ttuv_ = rttype(io); \ + checkliveness(L,io); } + +#define getuservalue(L,u,o) \ + { TValue *io=(o); const Udata *iu = (u); \ + io->value_ = iu->user_; settt_(io, iu->ttuv_); \ + checkliveness(L,io); } /* @@ -445,7 +385,7 @@ typedef union Udata { */ typedef struct Upvaldesc { TString *name; /* upvalue name (for debug information) */ - lu_byte instack; /* whether it is in stack */ + lu_byte instack; /* whether it is in stack (register) */ lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ } Upvaldesc; @@ -466,26 +406,26 @@ typedef struct LocVar { */ typedef struct Proto { CommonHeader; + lu_byte numparams; /* number of fixed parameters */ + lu_byte is_vararg; /* 2: declared vararg; 1: uses vararg */ + lu_byte maxstacksize; /* number of registers needed by this function */ + int sizeupvalues; /* size of 'upvalues' */ + int sizek; /* size of 'k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of 'p' */ + int sizelocvars; + int linedefined; /* debug information */ + int lastlinedefined; /* debug information */ TValue *k; /* constants used by the function */ - Instruction *code; + Instruction *code; /* opcodes */ struct Proto **p; /* functions defined inside the function */ int *lineinfo; /* map from opcodes to source lines (debug information) */ LocVar *locvars; /* information about local variables (debug information) */ Upvaldesc *upvalues; /* upvalue information */ - union Closure *cache; /* last created closure with this prototype */ + struct LClosure *cache; /* last-created closure with this prototype */ TString *source; /* used for debug information */ - int sizeupvalues; /* size of 'upvalues' */ - int sizek; /* size of `k' */ - int sizecode; - int sizelineinfo; - int sizep; /* size of `p' */ - int sizelocvars; - int linedefined; - int lastlinedefined; GCObject *gclist; - lu_byte numparams; /* number of fixed parameters */ - lu_byte is_vararg; - lu_byte maxstacksize; /* maximum stack used by this function */ } Proto; @@ -493,17 +433,7 @@ typedef struct Proto { /* ** Lua Upvalues */ -typedef struct UpVal { - CommonHeader; - TValue *v; /* points to stack or to its own value */ - union { - TValue value; /* the value (when closed) */ - struct { /* double linked list (when open) */ - struct UpVal *prev; - struct UpVal *next; - } l; - } u; -} UpVal; +typedef struct UpVal UpVal; /* @@ -545,12 +475,19 @@ typedef union Closure { typedef union TKey { struct { TValuefields; - struct Node *next; /* for chaining */ + int next; /* for chaining (offset for next node) */ } nk; TValue tvk; } TKey; +/* copy a value into a key without messing up field 'next' */ +#define setnodekey(L,key,obj) \ + { TKey *k_=(key); const TValue *io_=(obj); \ + k_->nk.value_ = io_->value_; k_->nk.tt_ = io_->tt_; \ + (void)L; checkliveness(L,io_); } + + typedef struct Node { TValue i_val; TKey i_key; @@ -560,19 +497,19 @@ typedef struct Node { typedef struct Table { CommonHeader; lu_byte flags; /* 1<

    >1) /* `sBx' is signed */ +#define MAXARG_sBx (MAXARG_Bx>>1) /* 'sBx' is signed */ #else #define MAXARG_Bx MAX_INT #define MAXARG_sBx MAX_INT @@ -76,10 +76,10 @@ enum OpMode {iABC, iABx, iAsBx, iAx}; /* basic instruction format */ #define MAXARG_C ((1<> RK(C) */ OP_UNM,/* A B R(A) := -R(B) */ +OP_BNOT,/* A B R(A) := ~R(B) */ OP_NOT,/* A B R(A) := not R(B) */ OP_LEN,/* A B R(A) := length of R(B) */ OP_CONCAT,/* A B C R(A) := R(B).. ... ..R(C) */ -OP_JMP,/* A sBx pc+=sBx; if (A) close all upvalues >= R(A) + 1 */ +OP_JMP,/* A sBx pc+=sBx; if (A) close all upvalues >= R(A - 1) */ OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ @@ -231,16 +238,16 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ /*=========================================================================== Notes: - (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then `top' is + (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then 'top' is set to last_result+1, so next open instruction (OP_CALL, OP_RETURN, - OP_SETLIST) may use `top'. + OP_SETLIST) may use 'top'. (*) In OP_VARARG, if (B == 0) then use actual number of varargs and set top (like in OP_CALL with C == 0). - (*) In OP_RETURN, if (B == 0) then return up to `top'. + (*) In OP_RETURN, if (B == 0) then return up to 'top'. - (*) In OP_SETLIST, if (B == 0) then B = `top'; if (C == 0) then next + (*) In OP_SETLIST, if (B == 0) then B = 'top'; if (C == 0) then next 'instruction' is EXTRAARG(real C). (*) In OP_LOADKX, the next 'instruction' is always EXTRAARG. @@ -248,7 +255,7 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ (*) For comparisons, A specifies what condition the test should accept (true or false). - (*) All `skips' (pc++) assume that next instruction is a jump. + (*) All 'skips' (pc++) assume that next instruction is a jump. ===========================================================================*/ diff --git a/depends/lua/include/lparser.h b/depends/lua/include/lparser.h index 0346e3c41..02e9b03ae 100644 --- a/depends/lua/include/lparser.h +++ b/depends/lua/include/lparser.h @@ -1,5 +1,5 @@ /* -** $Id: lparser.h,v 1.70.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lparser.h,v 1.76 2015/12/30 18:16:13 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ @@ -13,24 +13,38 @@ /* -** Expression descriptor +** Expression and variable descriptor. +** Code generation for variables and expressions can be delayed to allow +** optimizations; An 'expdesc' structure describes a potentially-delayed +** variable/expression. It has a description of its "main" value plus a +** list of conditional jumps that can also produce its value (generated +** by short-circuit operators 'and'/'or'). */ +/* kinds of variables/expressions */ typedef enum { - VVOID, /* no value */ - VNIL, - VTRUE, - VFALSE, - VK, /* info = index of constant in `k' */ - VKNUM, /* nval = numerical value */ - VNONRELOC, /* info = result register */ - VLOCAL, /* info = local register */ - VUPVAL, /* info = index of upvalue in 'upvalues' */ - VINDEXED, /* t = table register/upvalue; idx = index R/K */ - VJMP, /* info = instruction pc */ - VRELOCABLE, /* info = instruction pc */ - VCALL, /* info = instruction pc */ - VVARARG /* info = instruction pc */ + VVOID, /* when 'expdesc' describes the last expression a list, + this kind means an empty list (so, no expression) */ + VNIL, /* constant nil */ + VTRUE, /* constant true */ + VFALSE, /* constant false */ + VK, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT, /* floating constant; nval = numerical float value */ + VKINT, /* integer constant; nval = numerical integer value */ + VNONRELOC, /* expression has its value in a fixed register; + info = result register */ + VLOCAL, /* local variable; info = local register */ + VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VINDEXED, /* indexed variable; + ind.vt = whether 't' is register or upvalue; + ind.t = table register or upvalue; + ind.idx = key's R/K index */ + VJMP, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOCABLE, /* expression can put result in any register; + info = instruction pc */ + VCALL, /* expression is a function call; info = instruction pc */ + VVARARG /* vararg expression; info = instruction pc */ } expkind; @@ -40,16 +54,17 @@ typedef enum { typedef struct expdesc { expkind k; union { + lua_Integer ival; /* for VKINT */ + lua_Number nval; /* for VKFLT */ + int info; /* for generic use */ struct { /* for indexed variables (VINDEXED) */ short idx; /* index (R/K) */ lu_byte t; /* table (register or upvalue) */ lu_byte vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ } ind; - int info; /* for generic use */ - lua_Number nval; /* for VKNUM */ } u; - int t; /* patch list of `exit when true' */ - int f; /* patch list of `exit when false' */ + int t; /* patch list of 'exit when true' */ + int f; /* patch list of 'exit when false' */ } expdesc; @@ -95,15 +110,14 @@ struct BlockCnt; /* defined in lparser.c */ /* state needed to generate code for a given function */ typedef struct FuncState { Proto *f; /* current function header */ - Table *h; /* table to find (and reuse) elements in `k' */ struct FuncState *prev; /* enclosing function */ struct LexState *ls; /* lexical state */ struct BlockCnt *bl; /* chain of current blocks */ - int pc; /* next position to code (equivalent to `ncode') */ + int pc; /* next position to code (equivalent to 'ncode') */ int lasttarget; /* 'label' of last 'jump label' */ - int jpc; /* list of pending jumps to `pc' */ - int nk; /* number of elements in `k' */ - int np; /* number of elements in `p' */ + int jpc; /* list of pending jumps to 'pc' */ + int nk; /* number of elements in 'k' */ + int np; /* number of elements in 'p' */ int firstlocal; /* index of first local var (in Dyndata array) */ short nlocvars; /* number of elements in 'f->locvars' */ lu_byte nactvar; /* number of active local variables */ @@ -112,8 +126,8 @@ typedef struct FuncState { } FuncState; -LUAI_FUNC Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, - Dyndata *dyd, const char *name, int firstchar); +LUAI_FUNC LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar); #endif diff --git a/depends/lua/include/lprefix.h b/depends/lua/include/lprefix.h new file mode 100644 index 000000000..02daa837f --- /dev/null +++ b/depends/lua/include/lprefix.h @@ -0,0 +1,45 @@ +/* +** $Id: lprefix.h,v 1.2 2014/12/29 16:54:13 roberto Exp $ +** Definitions for Lua code that must come before any other header file +** See Copyright Notice in lua.h +*/ + +#ifndef lprefix_h +#define lprefix_h + + +/* +** Allows POSIX/XSI stuff +*/ +#if !defined(LUA_USE_C89) /* { */ + +#if !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 600 +#elif _XOPEN_SOURCE == 0 +#undef _XOPEN_SOURCE /* use -D_XOPEN_SOURCE=0 to undefine it */ +#endif + +/* +** Allows manipulation of large files in gcc and some other compilers +*/ +#if !defined(LUA_32BITS) && !defined(_FILE_OFFSET_BITS) +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#endif + +#endif /* } */ + + +/* +** Windows stuff +*/ +#if defined(_WIN32) /* { */ + +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS /* avoid warnings about ISO C functions */ +#endif + +#endif /* } */ + +#endif + diff --git a/depends/lua/include/lstate.h b/depends/lua/include/lstate.h index daffd9aac..b3033bee5 100644 --- a/depends/lua/include/lstate.h +++ b/depends/lua/include/lstate.h @@ -1,5 +1,5 @@ /* -** $Id: lstate.h,v 2.82.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstate.h,v 2.130 2015/12/16 16:39:38 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ @@ -16,25 +16,16 @@ /* -** Some notes about garbage-collected objects: All objects in Lua must -** be kept somehow accessible until being freed. +** Some notes about garbage-collected objects: All objects in Lua must +** be kept somehow accessible until being freed, so all objects always +** belong to one (and only one) of these lists, using field 'next' of +** the 'CommonHeader' for the link: ** -** Lua keeps most objects linked in list g->allgc. The link uses field -** 'next' of the CommonHeader. -** -** Strings are kept in several lists headed by the array g->strt.hash. -** -** Open upvalues are not subject to independent garbage collection. They -** are collected together with their respective threads. Lua keeps a -** double-linked list with all open upvalues (g->uvhead) so that it can -** mark objects referred by them. (They are always gray, so they must -** be remarked in the atomic step. Usually their contents would be marked -** when traversing the respective threads, but the thread may already be -** dead, while the upvalue is still accessible through closures.) -** -** Objects with finalizers are kept in the list g->finobj. -** -** The list g->tobefnz links all objects being finalized. +** 'allgc': all objects not marked for finalization; +** 'finobj': all objects marked for finalization; +** 'tobefnz': all objects ready to be finalized; +** 'fixedgc': all objects that are not to be collected (currently +** only small strings, such as reserved words). */ @@ -42,6 +33,15 @@ struct lua_longjmp; /* defined in ldo.c */ +/* +** Atomic type (relative to signals) to better ensure that 'lua_sethook' +** is thread safe +*/ +#if !defined(l_signalT) +#include +#define l_signalT sig_atomic_t +#endif + /* extra stack space to handle TM calls and some other extras */ #define EXTRA_STACK 5 @@ -53,66 +53,72 @@ struct lua_longjmp; /* defined in ldo.c */ /* kinds of Garbage Collection */ #define KGC_NORMAL 0 #define KGC_EMERGENCY 1 /* gc was forced by an allocation failure */ -#define KGC_GEN 2 /* generational collection */ typedef struct stringtable { - GCObject **hash; - lu_int32 nuse; /* number of elements */ + TString **hash; + int nuse; /* number of elements */ int size; } stringtable; /* -** information about a call +** Information about a call. +** When a thread yields, 'func' is adjusted to pretend that the +** top function has only the yielded values in its stack; in that +** case, the actual 'func' value is saved in field 'extra'. +** When a function calls another with a continuation, 'extra' keeps +** the function index so that, in case of errors, the continuation +** function can be called with the correct top. */ typedef struct CallInfo { StkId func; /* function index in the stack */ StkId top; /* top for this function */ struct CallInfo *previous, *next; /* dynamic call link */ - short nresults; /* expected number of results from this function */ - lu_byte callstatus; - ptrdiff_t extra; union { struct { /* only for Lua functions */ StkId base; /* base for this function */ const Instruction *savedpc; } l; struct { /* only for C functions */ - int ctx; /* context info. in case of yields */ - lua_CFunction k; /* continuation in case of yields */ + lua_KFunction k; /* continuation in case of yields */ ptrdiff_t old_errfunc; - lu_byte old_allowhook; - lu_byte status; + lua_KContext ctx; /* context info. in case of yields */ } c; } u; + ptrdiff_t extra; + short nresults; /* expected number of results from this function */ + lu_byte callstatus; } CallInfo; /* ** Bits in CallInfo status */ -#define CIST_LUA (1<<0) /* call is running a Lua function */ -#define CIST_HOOKED (1<<1) /* call is running a debug hook */ -#define CIST_REENTRY (1<<2) /* call is running on same invocation of - luaV_execute of previous call */ -#define CIST_YIELDED (1<<3) /* call reentered after suspension */ +#define CIST_OAH (1<<0) /* original value of 'allowhook' */ +#define CIST_LUA (1<<1) /* call is running a Lua function */ +#define CIST_HOOKED (1<<2) /* call is running a debug hook */ +#define CIST_FRESH (1<<3) /* call is running on a fresh invocation + of luaV_execute */ #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ -#define CIST_STAT (1<<5) /* call has an error status (pcall) */ -#define CIST_TAIL (1<<6) /* call was tail called */ -#define CIST_HOOKYIELD (1<<7) /* last hook called yielded */ - +#define CIST_TAIL (1<<5) /* call was tail called */ +#define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ +#define CIST_LEQ (1<<7) /* using __lt for __le */ #define isLua(ci) ((ci)->callstatus & CIST_LUA) +/* assume that CIST_OAH has offset 0 and that 'v' is strictly 0/1 */ +#define setoah(st,v) ((st) = ((st) & ~CIST_OAH) | (v)) +#define getoah(st) ((st) & CIST_OAH) + /* -** `global state', shared by all threads of this state +** 'global state', shared by all threads of this state */ typedef struct global_State { lua_Alloc frealloc; /* function to reallocate memory */ - void *ud; /* auxiliary data to `frealloc' */ - lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + void *ud; /* auxiliary data to 'frealloc' */ + l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ lu_mem GCmemtrav; /* memory traversed by the GC */ lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ @@ -123,36 +129,36 @@ typedef struct global_State { lu_byte gcstate; /* state of garbage collector */ lu_byte gckind; /* kind of GC running */ lu_byte gcrunning; /* true if GC is running */ - int sweepstrgc; /* position of sweep in `strt' */ GCObject *allgc; /* list of all collectable objects */ + GCObject **sweepgc; /* current position of sweep in list */ GCObject *finobj; /* list of collectable objects with finalizers */ - GCObject **sweepgc; /* current position of sweep in list 'allgc' */ - GCObject **sweepfin; /* current position of sweep in list 'finobj' */ GCObject *gray; /* list of gray objects */ GCObject *grayagain; /* list of objects to be traversed atomically */ GCObject *weak; /* list of tables with weak values */ GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ GCObject *allweak; /* list of all-weak tables */ GCObject *tobefnz; /* list of userdata to be GC */ - UpVal uvhead; /* head of double-linked list of all open upvalues */ - Mbuffer buff; /* temporary buffer for string concatenation */ + GCObject *fixedgc; /* list of objects not to be collected */ + struct lua_State *twups; /* list of threads with open upvalues */ + unsigned int gcfinnum; /* number of finalizers to call in each GC step */ int gcpause; /* size of pause between successive GCs */ - int gcmajorinc; /* pause between major collections (only in gen. mode) */ - int gcstepmul; /* GC `granularity' */ + int gcstepmul; /* GC 'granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ struct lua_State *mainthread; const lua_Number *version; /* pointer to version number */ TString *memerrmsg; /* memory-error message */ TString *tmname[TM_N]; /* array with tag-method names */ struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ + TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ } global_State; /* -** `per thread' state +** 'per thread' state */ struct lua_State { CommonHeader; + unsigned short nci; /* number of items in 'ci' list */ lu_byte status; StkId top; /* first free slot in the stack */ global_State *l_G; @@ -160,19 +166,20 @@ struct lua_State { const Instruction *oldpc; /* last pc traced */ StkId stack_last; /* last free slot in the stack */ StkId stack; /* stack base */ + UpVal *openupval; /* list of open upvalues in this stack */ + GCObject *gclist; + struct lua_State *twups; /* list of threads with open upvalues */ + struct lua_longjmp *errorJmp; /* current error recover point */ + CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ + volatile lua_Hook hook; + ptrdiff_t errfunc; /* current error handling function (stack index) */ int stacksize; + int basehookcount; + int hookcount; unsigned short nny; /* number of non-yieldable calls in stack */ unsigned short nCcalls; /* number of nested C calls */ - lu_byte hookmask; + l_signalT hookmask; lu_byte allowhook; - int basehookcount; - int hookcount; - lua_Hook hook; - GCObject *openupval; /* list of open upvalues in this stack */ - GCObject *gclist; - struct lua_longjmp *errorJmp; /* current error recover point */ - ptrdiff_t errfunc; /* current error handling function (stack index) */ - CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ }; @@ -180,48 +187,47 @@ struct lua_State { /* -** Union of all collectable objects +** Union of all collectable objects (only for conversions) */ -union GCObject { - GCheader gch; /* common header */ - union TString ts; - union Udata u; +union GCUnion { + GCObject gc; /* common header */ + struct TString ts; + struct Udata u; union Closure cl; struct Table h; struct Proto p; - struct UpVal uv; struct lua_State th; /* thread */ }; -#define gch(o) (&(o)->gch) +#define cast_u(o) cast(union GCUnion *, (o)) /* macros to convert a GCObject into a specific value */ -#define rawgco2ts(o) \ - check_exp(novariant((o)->gch.tt) == LUA_TSTRING, &((o)->ts)) -#define gco2ts(o) (&rawgco2ts(o)->tsv) -#define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) -#define gco2u(o) (&rawgco2u(o)->uv) -#define gco2lcl(o) check_exp((o)->gch.tt == LUA_TLCL, &((o)->cl.l)) -#define gco2ccl(o) check_exp((o)->gch.tt == LUA_TCCL, &((o)->cl.c)) +#define gco2ts(o) \ + check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts)) +#define gco2u(o) check_exp((o)->tt == LUA_TUSERDATA, &((cast_u(o))->u)) +#define gco2lcl(o) check_exp((o)->tt == LUA_TLCL, &((cast_u(o))->cl.l)) +#define gco2ccl(o) check_exp((o)->tt == LUA_TCCL, &((cast_u(o))->cl.c)) #define gco2cl(o) \ - check_exp(novariant((o)->gch.tt) == LUA_TFUNCTION, &((o)->cl)) -#define gco2t(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) -#define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) -#define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) -#define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) + check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) +#define gco2t(o) check_exp((o)->tt == LUA_TTABLE, &((cast_u(o))->h)) +#define gco2p(o) check_exp((o)->tt == LUA_TPROTO, &((cast_u(o))->p)) +#define gco2th(o) check_exp((o)->tt == LUA_TTHREAD, &((cast_u(o))->th)) + -/* macro to convert any Lua object into a GCObject */ -#define obj2gco(v) (cast(GCObject *, (v))) +/* macro to convert a Lua object into a GCObject */ +#define obj2gco(v) \ + check_exp(novariant((v)->tt) < LUA_TDEADKEY, (&(cast_u(v)->gc))) /* actual number of total bytes allocated */ -#define gettotalbytes(g) ((g)->totalbytes + (g)->GCdebt) +#define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); LUAI_FUNC void luaE_freeCI (lua_State *L); +LUAI_FUNC void luaE_shrinkCI (lua_State *L); #endif diff --git a/depends/lua/include/lstring.h b/depends/lua/include/lstring.h index 260e7f169..27efd2077 100644 --- a/depends/lua/include/lstring.h +++ b/depends/lua/include/lstring.h @@ -1,5 +1,5 @@ /* -** $Id: lstring.h,v 1.49.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstring.h,v 1.61 2015/11/03 15:36:01 roberto Exp $ ** String table (keep all strings handled by Lua) ** See Copyright Notice in lua.h */ @@ -12,35 +12,38 @@ #include "lstate.h" -#define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) +#define sizelstring(l) (sizeof(union UTString) + ((l) + 1) * sizeof(char)) -#define sizeudata(u) (sizeof(union Udata)+(u)->len) +#define sizeludata(l) (sizeof(union UUdata) + (l)) +#define sizeudata(u) sizeludata((u)->len) #define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ (sizeof(s)/sizeof(char))-1)) -#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) - /* ** test whether a string is a reserved word */ -#define isreserved(s) ((s)->tsv.tt == LUA_TSHRSTR && (s)->tsv.extra > 0) +#define isreserved(s) ((s)->tt == LUA_TSHRSTR && (s)->extra > 0) /* ** equality for short strings, which are always internalized */ -#define eqshrstr(a,b) check_exp((a)->tsv.tt == LUA_TSHRSTR, (a) == (b)) +#define eqshrstr(a,b) check_exp((a)->tt == LUA_TSHRSTR, (a) == (b)) LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC unsigned int luaS_hashlongstr (TString *ts); LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); -LUAI_FUNC int luaS_eqstr (TString *a, TString *b); LUAI_FUNC void luaS_resize (lua_State *L, int newsize); -LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); +LUAI_FUNC void luaS_clearcache (global_State *g); +LUAI_FUNC void luaS_init (lua_State *L); +LUAI_FUNC void luaS_remove (lua_State *L, TString *ts); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s); LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); +LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); #endif diff --git a/depends/lua/include/ltable.h b/depends/lua/include/ltable.h index d69449b2b..213cc1398 100644 --- a/depends/lua/include/ltable.h +++ b/depends/lua/include/ltable.h @@ -1,5 +1,5 @@ /* -** $Id: ltable.h,v 2.16.1.2 2013/08/30 15:49:41 roberto Exp $ +** $Id: ltable.h,v 2.21 2015/11/03 15:47:30 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ @@ -11,26 +11,39 @@ #define gnode(t,i) (&(t)->node[i]) -#define gkey(n) (&(n)->i_key.tvk) #define gval(n) (&(n)->i_val) #define gnext(n) ((n)->i_key.nk.next) + +/* 'const' to avoid wrong writings that can mess up field 'next' */ +#define gkey(n) cast(const TValue*, (&(n)->i_key.tvk)) + +/* +** writable version of 'gkey'; allows updates to individual fields, +** but not to the whole (which has incompatible type) +*/ +#define wgkey(n) (&(n)->i_key.nk) + #define invalidateTMcache(t) ((t)->flags = 0) + /* returns the key, given the value of a table entry */ #define keyfromval(v) \ (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, i_val)))) -LUAI_FUNC const TValue *luaH_getint (Table *t, int key); -LUAI_FUNC void luaH_setint (lua_State *L, Table *t, int key, TValue *value); +LUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key); +LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, + TValue *value); +LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); LUAI_FUNC TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key); LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); LUAI_FUNC Table *luaH_new (lua_State *L); -LUAI_FUNC void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize); -LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize); +LUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize, + unsigned int nhsize); +LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); LUAI_FUNC int luaH_getn (Table *t); diff --git a/depends/lua/include/ltm.h b/depends/lua/include/ltm.h index 7f89c841f..63db7269b 100644 --- a/depends/lua/include/ltm.h +++ b/depends/lua/include/ltm.h @@ -1,5 +1,5 @@ /* -** $Id: ltm.h,v 2.11.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltm.h,v 2.22 2016/02/26 19:20:15 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ @@ -13,7 +13,7 @@ /* * WARNING: if you change the order of this enumeration, -* grep "ORDER TM" +* grep "ORDER TM" and "ORDER OP" */ typedef enum { TM_INDEX, @@ -21,14 +21,21 @@ typedef enum { TM_GC, TM_MODE, TM_LEN, - TM_EQ, /* last tag method with `fast' access */ + TM_EQ, /* last tag method with fast access */ TM_ADD, TM_SUB, TM_MUL, - TM_DIV, TM_MOD, TM_POW, + TM_DIV, + TM_IDIV, + TM_BAND, + TM_BOR, + TM_BXOR, + TM_SHL, + TM_SHR, TM_UNM, + TM_BNOT, TM_LT, TM_LE, TM_CONCAT, @@ -44,14 +51,26 @@ typedef enum { #define fasttm(l,et,e) gfasttm(G(l), et, e) #define ttypename(x) luaT_typenames_[(x) + 1] -#define objtypename(x) ttypename(ttypenv(x)) LUAI_DDEC const char *const luaT_typenames_[LUA_TOTALTAGS]; +LUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o); + LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event); LUAI_FUNC void luaT_init (lua_State *L); +LUAI_FUNC void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, TValue *p3, int hasres); +LUAI_FUNC int luaT_callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event); +LUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event); +LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1, + const TValue *p2, TMS event); + + + #endif diff --git a/depends/lua/include/lua.h b/depends/lua/include/lua.h index 149a2c37b..f78899fc5 100644 --- a/depends/lua/include/lua.h +++ b/depends/lua/include/lua.h @@ -1,5 +1,5 @@ /* -** $Id: lua.h,v 1.285.1.2 2013/11/11 12:09:16 roberto Exp $ +** $Id: lua.h,v 1.331 2016/05/30 15:53:28 roberto Exp $ ** Lua - A Scripting Language ** Lua.org, PUC-Rio, Brazil (http://www.lua.org) ** See Copyright Notice at the end of this file @@ -17,27 +17,29 @@ #define LUA_VERSION_MAJOR "5" -#define LUA_VERSION_MINOR "2" -#define LUA_VERSION_NUM 502 +#define LUA_VERSION_MINOR "3" +#define LUA_VERSION_NUM 503 #define LUA_VERSION_RELEASE "3" #define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2013 Lua.org, PUC-Rio" +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2016 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" /* mark for precompiled code ('Lua') */ -#define LUA_SIGNATURE "\033Lua" +#define LUA_SIGNATURE "\x1bLua" /* option for multiple returns in 'lua_pcall' and 'lua_call' */ #define LUA_MULTRET (-1) /* -** pseudo-indices +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) */ -#define LUA_REGISTRYINDEX LUAI_FIRSTPSEUDOIDX +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) #define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) @@ -53,22 +55,6 @@ typedef struct lua_State lua_State; -typedef int (*lua_CFunction) (lua_State *L); - - -/* -** functions that read/write blocks when loading/dumping Lua chunks -*/ -typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); - -typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); - - -/* -** prototype for memory-allocation functions -*/ -typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); - /* ** basic types @@ -109,6 +95,34 @@ typedef LUA_INTEGER lua_Integer; /* unsigned integer type */ typedef LUA_UNSIGNED lua_Unsigned; +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + /* @@ -145,11 +159,9 @@ LUA_API int (lua_absindex) (lua_State *L, int idx); LUA_API int (lua_gettop) (lua_State *L); LUA_API void (lua_settop) (lua_State *L, int idx); LUA_API void (lua_pushvalue) (lua_State *L, int idx); -LUA_API void (lua_remove) (lua_State *L, int idx); -LUA_API void (lua_insert) (lua_State *L, int idx); -LUA_API void (lua_replace) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); -LUA_API int (lua_checkstack) (lua_State *L, int sz); +LUA_API int (lua_checkstack) (lua_State *L, int n); LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); @@ -161,13 +173,13 @@ LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); LUA_API int (lua_isnumber) (lua_State *L, int idx); LUA_API int (lua_isstring) (lua_State *L, int idx); LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); LUA_API int (lua_isuserdata) (lua_State *L, int idx); LUA_API int (lua_type) (lua_State *L, int idx); LUA_API const char *(lua_typename) (lua_State *L, int tp); LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); -LUA_API lua_Unsigned (lua_tounsignedx) (lua_State *L, int idx, int *isnum); LUA_API int (lua_toboolean) (lua_State *L, int idx); LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); LUA_API size_t (lua_rawlen) (lua_State *L, int idx); @@ -181,13 +193,20 @@ LUA_API const void *(lua_topointer) (lua_State *L, int idx); ** Comparison and arithmetic functions */ -#define LUA_OPADD 0 /* ORDER TM */ +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ #define LUA_OPSUB 1 #define LUA_OPMUL 2 -#define LUA_OPDIV 3 -#define LUA_OPMOD 4 -#define LUA_OPPOW 5 -#define LUA_OPUNM 6 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 LUA_API void (lua_arith) (lua_State *L, int op); @@ -205,8 +224,7 @@ LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); LUA_API void (lua_pushnil) (lua_State *L); LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); -LUA_API void (lua_pushunsigned) (lua_State *L, lua_Unsigned n); -LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t l); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp); @@ -220,26 +238,29 @@ LUA_API int (lua_pushthread) (lua_State *L); /* ** get functions (Lua -> stack) */ -LUA_API void (lua_getglobal) (lua_State *L, const char *var); -LUA_API void (lua_gettable) (lua_State *L, int idx); -LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); -LUA_API void (lua_rawget) (lua_State *L, int idx); -LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); -LUA_API void (lua_rawgetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); -LUA_API void (lua_getuservalue) (lua_State *L, int idx); +LUA_API int (lua_getuservalue) (lua_State *L, int idx); /* ** set functions (stack -> Lua) */ -LUA_API void (lua_setglobal) (lua_State *L, const char *var); +LUA_API void (lua_setglobal) (lua_State *L, const char *name); LUA_API void (lua_settable) (lua_State *L, int idx); LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); LUA_API void (lua_rawset) (lua_State *L, int idx); -LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); LUA_API int (lua_setmetatable) (lua_State *L, int objindex); LUA_API void (lua_setuservalue) (lua_State *L, int idx); @@ -248,31 +269,31 @@ LUA_API void (lua_setuservalue) (lua_State *L, int idx); /* ** 'load' and 'call' functions (load and run Lua code) */ -LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, int ctx, - lua_CFunction k); +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); #define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) -LUA_API int (lua_getctx) (lua_State *L, int *ctx); - LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, - int ctx, lua_CFunction k); + lua_KContext ctx, lua_KFunction k); #define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, - const char *chunkname, - const char *mode); + const char *chunkname, const char *mode); -LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); /* ** coroutine functions */ -LUA_API int (lua_yieldk) (lua_State *L, int nresults, int ctx, - lua_CFunction k); +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + #define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) -LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); -LUA_API int (lua_status) (lua_State *L); + /* ** garbage-collection function and options @@ -286,10 +307,7 @@ LUA_API int (lua_status) (lua_State *L); #define LUA_GCSTEP 5 #define LUA_GCSETPAUSE 6 #define LUA_GCSETSTEPMUL 7 -#define LUA_GCSETMAJORINC 8 #define LUA_GCISRUNNING 9 -#define LUA_GCGEN 10 -#define LUA_GCINC 11 LUA_API int (lua_gc) (lua_State *L, int what, int data); @@ -305,20 +323,23 @@ LUA_API int (lua_next) (lua_State *L, int idx); LUA_API void (lua_concat) (lua_State *L, int n); LUA_API void (lua_len) (lua_State *L, int idx); +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); /* -** =============================================================== +** {============================================================== ** some useful macros ** =============================================================== */ -#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL) -#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL) -#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL) +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) #define lua_pop(L,n) lua_settop(L, -(n)-1) @@ -337,15 +358,36 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); #define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) -#define lua_pushliteral(L, s) \ - lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) #define lua_pushglobaltable(L) \ - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL) +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros for unsigned conversions +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif +/* }============================================================== */ /* ** {====================================================================== @@ -390,7 +432,7 @@ LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, int fidx2, int n2); -LUA_API int (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); LUA_API lua_Hook (lua_gethook) (lua_State *L); LUA_API int (lua_gethookmask) (lua_State *L); LUA_API int (lua_gethookcount) (lua_State *L); @@ -418,7 +460,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2013 Lua.org, PUC-Rio. +* Copyright (C) 1994-2016 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index af09ffb94..fd447ccb9 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -1,113 +1,204 @@ /* -** $Id: luaconf.h,v 1.176.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: luaconf.h,v 1.255 2016/05/01 20:06:09 roberto Exp $ ** Configuration file for Lua ** See Copyright Notice in lua.h */ -#ifndef lconfig_h -#define lconfig_h +#ifndef luaconf_h +#define luaconf_h #include #include /* -** ================================================================== +** =================================================================== ** Search for "@@" to find all configurable definitions. ** =================================================================== */ /* -@@ LUA_ANSI controls the use of non-ansi features. -** CHANGE it (define it) if you want Lua to avoid the use of any -** non-ansi feature or library. +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance compiling it with 32-bit numbers or +** restricting it to C89. +** ===================================================================== */ -#if !defined(LUA_ANSI) && defined(__STRICT_ANSI__) -#define LUA_ANSI -#endif + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You +** can also define LUA_32BITS in the make file, but changing here you +** ensure that all software connected to Lua will be compiled with the +** same configuration. +*/ +/* #define LUA_32BITS */ -#if !defined(LUA_ANSI) && defined(_WIN32) && !defined(_WIN32_WCE) -#define LUA_WIN /* enable goodies for regular Windows platforms */ -#endif +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ -#if defined(LUA_WIN) -#define LUA_DL_DLL -#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ #endif +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + #if defined(LUA_USE_LINUX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* needs an extra library: -ldl */ #define LUA_USE_READLINE /* needs some extra libraries */ -#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */ -#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ -#define LUA_USE_LONGLONG /* assume support for long long */ #endif + #if defined(LUA_USE_MACOSX) #define LUA_USE_POSIX -#define LUA_USE_DLOPEN /* does not need -ldl */ +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ #define LUA_USE_READLINE /* needs an extra library: -lreadline */ -#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */ -#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ -#define LUA_USE_LONGLONG /* assume support for long long */ +#endif + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS #endif /* -@@ LUA_USE_POSIX includes all functionality listed as X/Open System -@* Interfaces Extension (XSI). -** CHANGE it (define it) if your system is XSI compatible. +@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'. */ -#if defined(LUA_USE_POSIX) -#define LUA_USE_MKSTEMP -#define LUA_USE_ISATTY -#define LUA_USE_POPEN -#define LUA_USE_ULONGJMP -#define LUA_USE_GMTIME_R +/* avoid undefined shifts */ +#if ((INT_MAX >> 15) >> 15) >= 1 +#define LUAI_BITSINT 32 +#else +/* 'int' always must have at least 16 bits */ +#define LUAI_BITSINT 16 #endif +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options (if supported +** by your C compiler). The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +#if defined(LUA_32BITS) /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_BITSINT >= 32 /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif defined(LUA_C89_NUMBERS) /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#endif /* } */ + + +/* +** default configuration for 64-bit Lua ('long long' and 'double') +*/ +#if !defined(LUA_INT_TYPE) +#define LUA_INT_TYPE LUA_INT_LONGLONG +#endif + +#if !defined(LUA_FLOAT_TYPE) +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE +#endif + +/* }================================================================== */ + + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ /* @@ LUA_PATH_DEFAULT is the default path that Lua uses to look for -@* Lua libraries. +** Lua libraries. @@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for -@* C libraries. +** C libraries. ** CHANGE them if your machine has a non-conventional directory ** hierarchy or if you want to install your libraries in ** non-conventional directories. */ +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #if defined(_WIN32) /* { */ /* ** In Windows, any exclamation mark ('!') in the path is replaced by the ** path of the directory of the executable file of the current process. */ -#define LUA_LDIR "!\\hack\\lua\\" +#define LUA_LDIR "!\\lua\\" #define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" #define LUA_PATH_DEFAULT \ - LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" ".\\?.lua" + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.dll;" ".\\?.dll" + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll" #else /* }{ */ -#define LUA_LDIR "./hack/lua/" -#define LUA_CDIR "./hack/" +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" #define LUA_PATH_DEFAULT \ - LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" "./?.lua" + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.so;" "./?.so" + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" #endif /* } */ -#define LUA_PATH "DFHACK_LUA_PATH" -#define LUA_CPATH "DFHACK_LUA_CPATH" /* @@ LUA_DIRSEP is the directory separator (for submodules). @@ -120,14 +211,14 @@ #define LUA_DIRSEP "/" #endif +/* }================================================================== */ + /* -@@ LUA_ENV is the name of the variable that holds the current -@@ environment, used to access global names. -** CHANGE it if you do not like this name. +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== */ -#define LUA_ENV "_ENV" - /* @@ LUA_API is a mark for all core API functions. @@ -140,15 +231,11 @@ */ #if defined(LUA_BUILD_AS_DLL) /* { */ -#if defined(_MSC_VER) #if defined(LUA_CORE) || defined(LUA_LIB) /* { */ #define LUA_API __declspec(dllexport) #else /* }{ */ #define LUA_API __declspec(dllimport) #endif /* } */ -#else -#define LUA_API __attribute__ ((visibility("default"))) -#endif #else /* }{ */ @@ -164,10 +251,10 @@ /* @@ LUAI_FUNC is a mark for all extern functions that are not to be -@* exported to outside modules. +** exported to outside modules. @@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables -@* that are not to be exported to outside modules (LUAI_DDEF for -@* definitions and LUAI_DDEC for declarations). +** that are not to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). ** CHANGE them if you need to mark them in some special way. Elf/gcc ** (versions 3.2 and later) mark them as "hidden" to optimize access ** when Lua is compiled as a shared library. Not all elf targets support @@ -179,74 +266,61 @@ #if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ defined(__ELF__) /* { */ #define LUAI_FUNC __attribute__((visibility("hidden"))) extern -#define LUAI_DDEC LUAI_FUNC -#define LUAI_DDEF /* empty */ - #else /* }{ */ #define LUAI_FUNC extern -#define LUAI_DDEC extern -#define LUAI_DDEF /* empty */ #endif /* } */ +#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ /* -@@ LUA_QL describes how error messages quote program elements. -** CHANGE it if you want a different appearance. +** {================================================================== +** Compatibility with previous versions +** =================================================================== */ -#define LUA_QL(x) "'" x "'" -#define LUA_QS LUA_QL("%s") - /* -@@ LUA_IDSIZE gives the maximum size for the description of the source -@* of a function in debug information. -** CHANGE it if you want a different size. +@@ LUA_COMPAT_5_2 controls other macros for compatibility with Lua 5.2. +@@ LUA_COMPAT_5_1 controls other macros for compatibility with Lua 5.1. +** You can define it to get all options, or change specific options +** to fit your specific needs. */ -#define LUA_IDSIZE 60 - +#if defined(LUA_COMPAT_5_2) /* { */ /* -@@ luai_writestring/luai_writeline define how 'print' prints its results. -** They are only used in libraries and the stand-alone program. (The #if -** avoids including 'stdio.h' everywhere.) +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. */ -#if defined(LUA_LIB) || defined(lua_c) -#include -#define luai_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) -#define luai_writeline() (luai_writestring("\n", 1), fflush(stdout)) -#endif +#define LUA_COMPAT_MATHLIB /* -@@ luai_writestringerror defines how to print error messages. -** (A format string with one argument is enough for Lua...) +@@ LUA_COMPAT_BITLIB controls the presence of library 'bit32'. */ -#define luai_writestringerror(s,p) \ - (fprintf(stderr, (s), (p)), fflush(stderr)) +#define LUA_COMPAT_BITLIB +/* +@@ LUA_COMPAT_IPAIRS controls the effectiveness of the __ipairs metamethod. +*/ +#define LUA_COMPAT_IPAIRS /* -@@ LUAI_MAXSHORTLEN is the maximum length for short strings, that is, -** strings that are internalized. (Cannot be smaller than reserved words -** or tags for metamethods, as these strings must be internalized; -** #("function") = 8, #("__newindex") = 10.) +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) */ -#define LUAI_MAXSHORTLEN 40 +#define LUA_COMPAT_APIINTCASTS +#endif /* } */ -/* -** {================================================================== -** Compatibility with previous versions -** =================================================================== -*/ +#if defined(LUA_COMPAT_5_1) /* { */ -/* -@@ LUA_COMPAT_ALL controls all compatibility options. -** You can define it to get all options, or change specific options -** to fit your specific needs. -*/ -#if defined(LUA_COMPAT_ALL) /* { */ +/* Incompatibilities from 5.2 -> 5.3 */ +#define LUA_COMPAT_MATHLIB +#define LUA_COMPAT_APIINTCASTS /* @@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. @@ -307,237 +381,375 @@ #endif /* } */ + +/* +@@ LUA_COMPAT_FLOATSTRING makes Lua format integral floats without a +@@ a float mark ('.0'). +** This macro is not on by default even in compatibility mode, +** because this is not really an incompatibility. +*/ +/* #define LUA_COMPAT_FLOATSTRING */ + /* }================================================================== */ /* -@@ LUAI_BITSINT defines the number of bits in an int. -** CHANGE here if Lua cannot automatically detect the number of bits of -** your machine. Probably you do not need to change this. +** {================================================================== +** Configuration for Numbers. +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== */ -/* avoid overflows in comparison */ -#if INT_MAX-20 < 32760 /* { */ -#define LUAI_BITSINT 16 -#elif INT_MAX > 2147483640L /* }{ */ -/* int has at least 32 bits */ -#define LUAI_BITSINT 32 -#else /* }{ */ -#error "you must define LUA_BITSINT with number of bits in an integer" -#endif /* } */ +/* +@@ LUA_NUMBER is the floating-point type used by Lua. +@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@@ over a floating number. +@@ l_mathlim(x) corrects limit name 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeric string to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) l_sprintf((s), sz, LUA_NUMBER_FMT, (n)) /* -@@ LUA_INT32 is an signed integer with exactly 32 bits. -@@ LUAI_UMEM is an unsigned integer big enough to count the total -@* memory used by Lua. -@@ LUAI_MEM is a signed integer big enough to count the total memory -@* used by Lua. -** CHANGE here if for some weird reason the default definitions are not -** good enough for your machine. Probably you do not need to change -** this. +@@ lua_numbertointeger converts a float number to an integer, or +** returns 0 if float is not within the range of a lua_Integer. +** (The range comparisons are tricky because of rounding. The tests +** here assume a two-complement representation, where MININTEGER always +** has an exact representation as a float; MAXINTEGER may not have one, +** and therefore its conversion to float may have an ill-defined value.) */ -#if LUAI_BITSINT >= 32 /* { */ -#define LUA_INT32 int -#define LUAI_UMEM size_t -#define LUAI_MEM ptrdiff_t -#else /* }{ */ -/* 16-bit ints */ -#define LUA_INT32 long -#define LUAI_UMEM unsigned long -#define LUAI_MEM long -#endif /* } */ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_mathlim(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_mathlim(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_mathlim(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + /* -@@ LUAI_MAXSTACK limits the size of the Lua stack. -** CHANGE it if you need a different limit. This limit is arbitrary; -** its only purpose is to stop Lua to consume unlimited stack -** space (and to reserve some numbers for pseudo-indices). +@@ LUA_INTEGER is the integer type used by Lua. +** +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +** +@@ LUAI_UACINT is the result of an 'usual argument conversion' +@@ over a lUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ lua_integer2str converts an integer to a string. */ -#if LUAI_BITSINT >= 32 -#define LUAI_MAXSTACK 1000000 -#else -#define LUAI_MAXSTACK 15000 -#endif -/* reserve some space for error handling */ -#define LUAI_FIRSTPSEUDOIDX (-LUAI_MAXSTACK - 1000) +/* The following definitions are good for most cases here */ +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" +#define lua_integer2str(s,sz,n) l_sprintf((s), sz, LUA_INTEGER_FMT, (n)) +#define LUAI_UACINT LUA_INTEGER /* -@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. -** CHANGE it if it uses too much C-stack space. +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) */ -#define LUAL_BUFFERSIZE BUFSIZ +#define LUA_UNSIGNED unsigned LUAI_UACINT +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ /* ** {================================================================== -@@ LUA_NUMBER is the type of numbers in Lua. -** CHANGE the following definitions only if you want to build Lua -** with a number type different from double. You may also need to -** change lua_number2int & lua_number2integer. +** Dependencies with C99 and other C details ** =================================================================== */ -#define LUA_NUMBER_DOUBLE -#define LUA_NUMBER double - /* -@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' -@* over a number. +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) */ -#define LUAI_UACNUMBER double +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif /* -@@ LUA_NUMBER_SCAN is the format for reading numbers. -@@ LUA_NUMBER_FMT is the format for writing numbers. -@@ lua_number2str converts a number to a string. -@@ LUAI_MAXNUMBER2STR is maximum size of previous conversion. +@@ lua_strx2number converts an hexadecimal numeric string to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. */ -#define LUA_NUMBER_SCAN "%lf" -#define LUA_NUMBER_FMT "%.14g" -#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) -#define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif /* -@@ l_mathop allows the addition of an 'l' or 'f' to all math operations +@@ lua_number2strx converts a float to an hexadecimal numeric string. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. */ -#define l_mathop(x) (x) +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) ((void)L, l_sprintf(b,sz,f,n)) +#endif /* -@@ lua_str2number converts a decimal numeric string to a number. -@@ lua_strx2number converts an hexadecimal numeric string to a number. -** In C99, 'strtod' does both conversions. C89, however, has no function -** to convert floating hexadecimal strings to numbers. For these -** systems, you can leave 'lua_strx2number' undefined and Lua will -** provide its own implementation. +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) */ -#define lua_str2number(s,p) strtod((s), (p)) - -#if defined(LUA_USE_STRTODHEX) -#define lua_strx2number(s,p) strtod((s), (p)) +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) #endif /* -@@ The luai_num* macros define the primitive operations over numbers. +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) */ +#define LUA_KCONTEXT ptrdiff_t -/* the following operations need the math library */ -#if defined(lobject_c) || defined(lvm_c) -#include -#define luai_nummod(L,a,b) ((a) - l_mathop(floor)((a)/(b))*(b)) -#define luai_numpow(L,a,b) (l_mathop(pow)(a,b)) +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t #endif - -/* these are quite standard operations */ -#if defined(LUA_CORE) -#define luai_numadd(L,a,b) ((a)+(b)) -#define luai_numsub(L,a,b) ((a)-(b)) -#define luai_nummul(L,a,b) ((a)*(b)) -#define luai_numdiv(L,a,b) ((a)/(b)) -#define luai_numunm(L,a) (-(a)) -#define luai_numeq(a,b) ((a)==(b)) -#define luai_numlt(L,a,b) ((a)<(b)) -#define luai_numle(L,a,b) ((a)<=(b)) -#define luai_numisnan(L,a) (!luai_numeq((a), (a))) #endif - /* -@@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger. -** CHANGE that if ptrdiff_t is not adequate on your machine. (On most -** machines, ptrdiff_t gives a good choice between int or long.) +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include header 'locale.h'.) */ -#define LUA_INTEGER ptrdiff_t - -/* -@@ LUA_UNSIGNED is the integral type used by lua_pushunsigned/lua_tounsigned. -** It must have at least 32 bits. -*/ -#define LUA_UNSIGNED unsigned LUA_INT32 +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif +/* }================================================================== */ /* -** Some tricks with doubles +** {================================================================== +** Language Variations +** ===================================================================== */ -#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) /* { */ /* -** The next definitions activate some tricks to speed up the -** conversion from doubles to integer types, mainly to LUA_UNSIGNED. -** -@@ LUA_MSASMTRICK uses Microsoft assembler to avoid clashes with a -** DirectX idiosyncrasy. -** -@@ LUA_IEEE754TRICK uses a trick that should work on any machine -** using IEEE754 with a 32-bit integer type. -** -@@ LUA_IEEELL extends the trick to LUA_INTEGER; should only be -** defined when LUA_INTEGER is a 32-bit integer. -** -@@ LUA_IEEEENDIAN is the endianness of doubles in your machine -** (0 for little endian, 1 for big endian); if not defined, Lua will -** check it dynamically for LUA_IEEE754TRICK (but not for LUA_NANTRICK). -** -@@ LUA_NANTRICK controls the use of a trick to pack all types into -** a single double value, using NaN values to represent non-number -** values. The trick only works on 32-bit machines (ints and pointers -** are 32-bit values) with numbers represented as IEEE 754-2008 doubles -** with conventional endianess (12345678 or 87654321), in CPUs that do -** not produce signaling NaN values (all NaNs are quiet). +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. */ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ -/* Microsoft compiler on a Pentium (32 bit) ? */ -#if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86) /* { */ - -#define LUA_MSASMTRICK -#define LUA_IEEEENDIAN 0 -#define LUA_NANTRICK +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif -/* pentium 32 bits? */ -#elif defined(__i386__) || defined(__i386) || defined(__X86__) /* }{ */ +/* }================================================================== */ -#define LUA_IEEE754TRICK -#define LUA_IEEELL -#define LUA_IEEEENDIAN 0 -#define LUA_NANTRICK -/* pentium 64 bits? */ -#elif defined(__x86_64) /* }{ */ +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). You probably do not want/need to change them. +** ===================================================================== +*/ -#define LUA_IEEE754TRICK -#define LUA_IEEEENDIAN 0 +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif -#elif defined(__POWERPC__) || defined(__ppc__) /* }{ */ -#define LUA_IEEE754TRICK -#define LUA_IEEEENDIAN 1 +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) -#else /* }{ */ -/* assume IEEE754 and a 32-bit integer type */ -#define LUA_IEEE754TRICK +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 -#endif /* } */ -#endif /* } */ +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +** CHANGE it if it uses too much C-stack space. (For long double, +** 'string.format("%.99f", 1e4932)' needs ~5030 bytes, so a +** smaller buffer would force a memory allocation for each call to +** 'string.format'.) +*/ +#if defined(LUA_FLOAT_LONGDOUBLE) +#define LUAL_BUFFERSIZE 8192 +#else +#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) +#endif /* }================================================================== */ +/* +@@ LUA_QL describes how error messages quote program elements. +** Lua does not use these macros anymore; they are here for +** compatibility only. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + /* =================================================================== */ @@ -549,5 +761,7 @@ + + #endif diff --git a/depends/lua/include/lualib.h b/depends/lua/include/lualib.h index da82005c9..5165c0fb3 100644 --- a/depends/lua/include/lualib.h +++ b/depends/lua/include/lualib.h @@ -1,5 +1,5 @@ /* -** $Id: lualib.h,v 1.43.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lualib.h,v 1.44 2014/02/06 17:32:33 roberto Exp $ ** Lua standard libraries ** See Copyright Notice in lua.h */ @@ -29,6 +29,9 @@ LUAMOD_API int (luaopen_os) (lua_State *L); #define LUA_STRLIBNAME "string" LUAMOD_API int (luaopen_string) (lua_State *L); +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + #define LUA_BITLIBNAME "bit32" LUAMOD_API int (luaopen_bit32) (lua_State *L); diff --git a/depends/lua/include/lundump.h b/depends/lua/include/lundump.h index 5255db259..aa5cc82f1 100644 --- a/depends/lua/include/lundump.h +++ b/depends/lua/include/lundump.h @@ -1,5 +1,5 @@ /* -** $Id: lundump.h,v 1.39.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lundump.h,v 1.45 2015/09/08 15:41:05 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -7,22 +7,26 @@ #ifndef lundump_h #define lundump_h +#include "llimits.h" #include "lobject.h" #include "lzio.h" -/* load one chunk; from lundump.c */ -LUAI_FUNC Closure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name); -/* make header; from lundump.c */ -LUAI_FUNC void luaU_header (lu_byte* h); +/* data to catch conversion errors */ +#define LUAC_DATA "\x19\x93\r\n\x1a\n" -/* dump one chunk; from ldump.c */ -LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip); +#define LUAC_INT 0x5678 +#define LUAC_NUM cast_num(370.5) -/* data to catch conversion errors */ -#define LUAC_TAIL "\x19\x93\r\n\x1a\n" +#define MYINT(s) (s[0]-'0') +#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR)) +#define LUAC_FORMAT 0 /* this is the official format */ -/* size in bytes of header of binary files */ -#define LUAC_HEADERSIZE (sizeof(LUA_SIGNATURE)-sizeof(char)+2+6+sizeof(LUAC_TAIL)-sizeof(char)) +/* load one chunk; from lundump.c */ +LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name); + +/* dump one chunk; from ldump.c */ +LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, + void* data, int strip); #endif diff --git a/depends/lua/include/lvm.h b/depends/lua/include/lvm.h index 5380270da..bcf52d20a 100644 --- a/depends/lua/include/lvm.h +++ b/depends/lua/include/lvm.h @@ -1,5 +1,5 @@ /* -** $Id: lvm.h,v 2.18.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lvm.h,v 2.40 2016/01/05 16:07:21 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ @@ -13,32 +13,101 @@ #include "ltm.h" -#define tostring(L,o) (ttisstring(o) || (luaV_tostring(L, o))) +#if !defined(LUA_NOCVTN2S) +#define cvt2str(o) ttisnumber(o) +#else +#define cvt2str(o) 0 /* no conversion from numbers to strings */ +#endif + + +#if !defined(LUA_NOCVTS2N) +#define cvt2num(o) ttisstring(o) +#else +#define cvt2num(o) 0 /* no conversion from strings to numbers */ +#endif + + +/* +** You can define LUA_FLOORN2I if you want to convert floats to integers +** by flooring them (instead of raising an error if they are not +** integral values) +*/ +#if !defined(LUA_FLOORN2I) +#define LUA_FLOORN2I 0 +#endif + + +#define tonumber(o,n) \ + (ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n)) -#define tonumber(o,n) (ttisnumber(o) || (((o) = luaV_tonumber(o,n)) != NULL)) +#define tointeger(o,i) \ + (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I)) -#define equalobj(L,o1,o2) (ttisequal(o1, o2) && luaV_equalobj_(L, o1, o2)) +#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) -#define luaV_rawequalobj(o1,o2) equalobj(NULL,o1,o2) +#define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) + + +/* +** fast track for 'gettable': if 't' is a table and 't[k]' is not nil, +** return 1 with 'slot' pointing to 't[k]' (final result). Otherwise, +** return 0 (meaning it will have to check metamethod) with 'slot' +** pointing to a nil 't[k]' (if 't' is a table) or NULL (otherwise). +** 'f' is the raw get function to use. +*/ +#define luaV_fastget(L,t,k,slot,f) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = f(hvalue(t), k), /* else, do raw access */ \ + !ttisnil(slot))) /* result not nil? */ + +/* +** standard implementation for 'gettable' +*/ +#define luaV_gettable(L,t,k,v) { const TValue *slot; \ + if (luaV_fastget(L,t,k,slot,luaH_get)) { setobj2s(L, v, slot); } \ + else luaV_finishget(L,t,k,v,slot); } + + +/* +** Fast track for set table. If 't' is a table and 't[k]' is not nil, +** call GC barrier, do a raw 't[k]=v', and return true; otherwise, +** return false with 'slot' equal to NULL (if 't' is not a table) or +** 'nil'. (This is needed by 'luaV_finishget'.) Note that, if the macro +** returns true, there is no need to 'invalidateTMcache', because the +** call is not creating a new entry. +*/ +#define luaV_fastset(L,t,k,slot,f,v) \ + (!ttistable(t) \ + ? (slot = NULL, 0) \ + : (slot = f(hvalue(t), k), \ + ttisnil(slot) ? 0 \ + : (luaC_barrierback(L, hvalue(t), v), \ + setobj2t(L, cast(TValue *,slot), v), \ + 1))) -/* not to called directly */ -LUAI_FUNC int luaV_equalobj_ (lua_State *L, const TValue *t1, const TValue *t2); +#define luaV_settable(L,t,k,v) { const TValue *slot; \ + if (!luaV_fastset(L,t,k,slot,luaH_get,v)) \ + luaV_finishset(L,t,k,v,slot); } + +LUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2); LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); -LUAI_FUNC const TValue *luaV_tonumber (const TValue *obj, TValue *n); -LUAI_FUNC int luaV_tostring (lua_State *L, StkId obj); -LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, - StkId val); -LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, - StkId val); +LUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n); +LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode); +LUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); +LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); LUAI_FUNC void luaV_finishOp (lua_State *L); LUAI_FUNC void luaV_execute (lua_State *L); LUAI_FUNC void luaV_concat (lua_State *L, int total); -LUAI_FUNC void luaV_arith (lua_State *L, StkId ra, const TValue *rb, - const TValue *rc, TMS op); +LUAI_FUNC lua_Integer luaV_div (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y); LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb); #endif diff --git a/depends/lua/include/lzio.h b/depends/lua/include/lzio.h index 441f7479c..e7b6f34b1 100644 --- a/depends/lua/include/lzio.h +++ b/depends/lua/include/lzio.h @@ -1,5 +1,5 @@ /* -** $Id: lzio.h,v 1.26.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lzio.h,v 1.31 2015/09/08 15:41:05 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ @@ -32,20 +32,21 @@ typedef struct Mbuffer { #define luaZ_sizebuffer(buff) ((buff)->buffsize) #define luaZ_bufflen(buff) ((buff)->n) +#define luaZ_buffremove(buff,i) ((buff)->n -= (i)) #define luaZ_resetbuffer(buff) ((buff)->n = 0) #define luaZ_resizebuffer(L, buff, size) \ - (luaM_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ + ((buff)->buffer = luaM_reallocvchar(L, (buff)->buffer, \ + (buff)->buffsize, size), \ (buff)->buffsize = size) #define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) -LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data); -LUAI_FUNC size_t luaZ_read (ZIO* z, void* b, size_t n); /* read next n bytes */ +LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n); /* read next n bytes */ @@ -55,7 +56,7 @@ struct Zio { size_t n; /* bytes still unread */ const char *p; /* current position in buffer */ lua_Reader reader; /* reader function */ - void* data; /* additional data */ + void *data; /* additional data */ lua_State *L; /* Lua state (for reader) */ }; diff --git a/depends/lua/src/lapi.c b/depends/lua/src/lapi.c index d011431ea..c9455a5d8 100644 --- a/depends/lua/src/lapi.c +++ b/depends/lua/src/lapi.c @@ -1,16 +1,18 @@ /* -** $Id: lapi.c,v 2.171.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lapi.c,v 2.259 2016/02/29 14:27:14 roberto Exp $ ** Lua API ** See Copyright Notice in lua.h */ +#define lapi_c +#define LUA_CORE + +#include "lprefix.h" + #include #include -#define lapi_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -43,13 +45,16 @@ const char lua_ident[] = /* test for pseudo index */ #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) +/* test for upvalue */ +#define isupvalue(i) ((i) < LUA_REGISTRYINDEX) + /* test for valid but not pseudo index */ #define isstackindex(i, o) (isvalid(o) && !ispseudo(i)) -#define api_checkvalidindex(L, o) api_check(L, isvalid(o), "invalid index") +#define api_checkvalidindex(l,o) api_check(l, isvalid(o), "invalid index") -#define api_checkstackindex(L, i, o) \ - api_check(L, isstackindex(i, o), "index not in the stack") +#define api_checkstackindex(l, i, o) \ + api_check(l, isstackindex(i, o), "index not in the stack") static TValue *index2addr (lua_State *L, int idx) { @@ -89,21 +94,22 @@ static void growstack (lua_State *L, void *ud) { } -LUA_API int lua_checkstack (lua_State *L, int size) { +LUA_API int lua_checkstack (lua_State *L, int n) { int res; CallInfo *ci = L->ci; lua_lock(L); - if (L->stack_last - L->top > size) /* stack large enough? */ + api_check(L, n >= 0, "negative 'n'"); + if (L->stack_last - L->top > n) /* stack large enough? */ res = 1; /* yes; check is OK */ else { /* no; need to grow stack */ int inuse = cast_int(L->top - L->stack) + EXTRA_STACK; - if (inuse > LUAI_MAXSTACK - size) /* can grow without overflow? */ + if (inuse > LUAI_MAXSTACK - n) /* can grow without overflow? */ res = 0; /* no */ else /* try to grow stack */ - res = (luaD_rawrunprotected(L, &growstack, &size) == LUA_OK); + res = (luaD_rawrunprotected(L, &growstack, &n) == LUA_OK); } - if (res && ci->top < L->top + size) - ci->top = L->top + size; /* adjust frame top */ + if (res && ci->top < L->top + n) + ci->top = L->top + n; /* adjust frame top */ lua_unlock(L); return res; } @@ -115,10 +121,11 @@ LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { lua_lock(to); api_checknelems(from, n); api_check(from, G(from) == G(to), "moving among independent states"); - api_check(from, to->ci->top - to->top >= n, "not enough elements to move"); + api_check(from, to->ci->top - to->top >= n, "stack overflow"); from->top -= n; for (i = 0; i < n; i++) { - setobj2s(to, to->top++, from->top + i); + setobj2s(to, to->top, from->top + i); + to->top++; /* stack already checked by previous 'api_check' */ } lua_unlock(to); } @@ -153,7 +160,7 @@ LUA_API const lua_Number *lua_version (lua_State *L) { LUA_API int lua_absindex (lua_State *L, int idx) { return (idx > 0 || ispseudo(idx)) ? idx - : cast_int(L->top - L->ci->func + idx); + : cast_int(L->top - L->ci->func) + idx; } @@ -173,61 +180,56 @@ LUA_API void lua_settop (lua_State *L, int idx) { } else { api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top"); - L->top += idx+1; /* `subtract' index (index is negative) */ + L->top += idx+1; /* 'subtract' index (index is negative) */ } lua_unlock(L); } -LUA_API void lua_remove (lua_State *L, int idx) { - StkId p; - lua_lock(L); - p = index2addr(L, idx); - api_checkstackindex(L, idx, p); - while (++p < L->top) setobjs2s(L, p-1, p); - L->top--; - lua_unlock(L); +/* +** Reverse the stack segment from 'from' to 'to' +** (auxiliary to 'lua_rotate') +*/ +static void reverse (lua_State *L, StkId from, StkId to) { + for (; from < to; from++, to--) { + TValue temp; + setobj(L, &temp, from); + setobjs2s(L, from, to); + setobj2s(L, to, &temp); + } } -LUA_API void lua_insert (lua_State *L, int idx) { - StkId p; - StkId q; +/* +** Let x = AB, where A is a prefix of length 'n'. Then, +** rotate x n == BA. But BA == (A^r . B^r)^r. +*/ +LUA_API void lua_rotate (lua_State *L, int idx, int n) { + StkId p, t, m; lua_lock(L); - p = index2addr(L, idx); + t = L->top - 1; /* end of stack segment being rotated */ + p = index2addr(L, idx); /* start of segment */ api_checkstackindex(L, idx, p); - for (q = L->top; q > p; q--) /* use L->top as a temporary */ - setobjs2s(L, q, q - 1); - setobjs2s(L, p, L->top); + api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); + m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ + reverse(L, p, m); /* reverse the prefix with length 'n' */ + reverse(L, m + 1, t); /* reverse the suffix */ + reverse(L, p, t); /* reverse the entire segment */ lua_unlock(L); } -static void moveto (lua_State *L, TValue *fr, int idx) { - TValue *to = index2addr(L, idx); +LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { + TValue *fr, *to; + lua_lock(L); + fr = index2addr(L, fromidx); + to = index2addr(L, toidx); api_checkvalidindex(L, to); setobj(L, to, fr); - if (idx < LUA_REGISTRYINDEX) /* function upvalue? */ + if (isupvalue(toidx)) /* function upvalue? */ luaC_barrier(L, clCvalue(L->ci->func), fr); /* LUA_REGISTRYINDEX does not need gc barrier (collector revisits it before finishing collection) */ -} - - -LUA_API void lua_replace (lua_State *L, int idx) { - lua_lock(L); - api_checknelems(L, 1); - moveto(L, L->top - 1, idx); - L->top--; - lua_unlock(L); -} - - -LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { - TValue *fr; - lua_lock(L); - fr = index2addr(L, fromidx); - moveto(L, fr, toidx); lua_unlock(L); } @@ -248,12 +250,13 @@ LUA_API void lua_pushvalue (lua_State *L, int idx) { LUA_API int lua_type (lua_State *L, int idx) { StkId o = index2addr(L, idx); - return (isvalid(o) ? ttypenv(o) : LUA_TNONE); + return (isvalid(o) ? ttnov(o) : LUA_TNONE); } LUA_API const char *lua_typename (lua_State *L, int t) { UNUSED(L); + api_check(L, LUA_TNONE <= t && t < LUA_NUMTAGS, "invalid tag"); return ttypename(t); } @@ -264,22 +267,28 @@ LUA_API int lua_iscfunction (lua_State *L, int idx) { } +LUA_API int lua_isinteger (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + return ttisinteger(o); +} + + LUA_API int lua_isnumber (lua_State *L, int idx) { - TValue n; + lua_Number n; const TValue *o = index2addr(L, idx); return tonumber(o, &n); } LUA_API int lua_isstring (lua_State *L, int idx) { - int t = lua_type(L, idx); - return (t == LUA_TSTRING || t == LUA_TNUMBER); + const TValue *o = index2addr(L, idx); + return (ttisstring(o) || cvt2str(o)); } LUA_API int lua_isuserdata (lua_State *L, int idx) { const TValue *o = index2addr(L, idx); - return (ttisuserdata(o) || ttislightuserdata(o)); + return (ttisfulluserdata(o) || ttislightuserdata(o)); } @@ -291,24 +300,17 @@ LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { LUA_API void lua_arith (lua_State *L, int op) { - StkId o1; /* 1st operand */ - StkId o2; /* 2nd operand */ lua_lock(L); - if (op != LUA_OPUNM) /* all other operations expect two operands */ - api_checknelems(L, 2); - else { /* for unary minus, add fake 2nd operand */ + if (op != LUA_OPUNM && op != LUA_OPBNOT) + api_checknelems(L, 2); /* all other operations expect two operands */ + else { /* for unary operations, add fake 2nd operand */ api_checknelems(L, 1); setobjs2s(L, L->top, L->top - 1); - L->top++; - } - o1 = L->top - 2; - o2 = L->top - 1; - if (ttisnumber(o1) && ttisnumber(o2)) { - setnvalue(o1, luaO_arith(op, nvalue(o1), nvalue(o2))); + api_incr_top(L); } - else - luaV_arith(L, o1, o1, o2, cast(TMS, op - LUA_OPADD + TM_ADD)); - L->top--; + /* first operand at top - 2, second at top - 1; result go to top - 2 */ + luaO_arith(L, op, L->top - 2, L->top - 1, L->top - 2); + L->top--; /* remove second operand */ lua_unlock(L); } @@ -321,7 +323,7 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { o2 = index2addr(L, index2); if (isvalid(o1) && isvalid(o2)) { switch (op) { - case LUA_OPEQ: i = equalobj(L, o1, o2); break; + case LUA_OPEQ: i = luaV_equalobj(L, o1, o2); break; case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break; case LUA_OPLE: i = luaV_lessequal(L, o1, o2); break; default: api_check(L, 0, "invalid option"); @@ -332,51 +334,33 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { } -LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum) { - TValue n; - const TValue *o = index2addr(L, idx); - if (tonumber(o, &n)) { - if (isnum) *isnum = 1; - return nvalue(o); - } - else { - if (isnum) *isnum = 0; - return 0; - } +LUA_API size_t lua_stringtonumber (lua_State *L, const char *s) { + size_t sz = luaO_str2num(s, L->top); + if (sz != 0) + api_incr_top(L); + return sz; } -LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum) { - TValue n; +LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum) { + lua_Number n; const TValue *o = index2addr(L, idx); - if (tonumber(o, &n)) { - lua_Integer res; - lua_Number num = nvalue(o); - lua_number2integer(res, num); - if (isnum) *isnum = 1; - return res; - } - else { - if (isnum) *isnum = 0; - return 0; - } + int isnum = tonumber(o, &n); + if (!isnum) + n = 0; /* call to 'tonumber' may change 'n' even if it fails */ + if (pisnum) *pisnum = isnum; + return n; } -LUA_API lua_Unsigned lua_tounsignedx (lua_State *L, int idx, int *isnum) { - TValue n; +LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) { + lua_Integer res; const TValue *o = index2addr(L, idx); - if (tonumber(o, &n)) { - lua_Unsigned res; - lua_Number num = nvalue(o); - lua_number2unsigned(res, num); - if (isnum) *isnum = 1; - return res; - } - else { - if (isnum) *isnum = 0; - return 0; - } + int isnum = tointeger(o, &res); + if (!isnum) + res = 0; /* call to 'tointeger' may change 'n' even if it fails */ + if (pisnum) *pisnum = isnum; + return res; } @@ -389,25 +373,27 @@ LUA_API int lua_toboolean (lua_State *L, int idx) { LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { StkId o = index2addr(L, idx); if (!ttisstring(o)) { - lua_lock(L); /* `luaV_tostring' may create a new string */ - if (!luaV_tostring(L, o)) { /* conversion failed? */ + if (!cvt2str(o)) { /* not convertible? */ if (len != NULL) *len = 0; - lua_unlock(L); return NULL; } + lua_lock(L); /* 'luaO_tostring' may create a new string */ + luaO_tostring(L, o); luaC_checkGC(L); o = index2addr(L, idx); /* previous call may reallocate the stack */ lua_unlock(L); } - if (len != NULL) *len = tsvalue(o)->len; + if (len != NULL) + *len = vslen(o); return svalue(o); } LUA_API size_t lua_rawlen (lua_State *L, int idx) { StkId o = index2addr(L, idx); - switch (ttypenv(o)) { - case LUA_TSTRING: return tsvalue(o)->len; + switch (ttype(o)) { + case LUA_TSHRSTR: return tsvalue(o)->shrlen; + case LUA_TLNGSTR: return tsvalue(o)->u.lnglen; case LUA_TUSERDATA: return uvalue(o)->len; case LUA_TTABLE: return luaH_getn(hvalue(o)); default: return 0; @@ -426,8 +412,8 @@ LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { LUA_API void *lua_touserdata (lua_State *L, int idx) { StkId o = index2addr(L, idx); - switch (ttypenv(o)) { - case LUA_TUSERDATA: return (rawuvalue(o) + 1); + switch (ttnov(o)) { + case LUA_TUSERDATA: return getudatamem(uvalue(o)); case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; } @@ -448,9 +434,8 @@ LUA_API const void *lua_topointer (lua_State *L, int idx) { case LUA_TCCL: return clCvalue(o); case LUA_TLCF: return cast(void *, cast(size_t, fvalue(o))); case LUA_TTHREAD: return thvalue(o); - case LUA_TUSERDATA: - case LUA_TLIGHTUSERDATA: - return lua_touserdata(L, idx); + case LUA_TUSERDATA: return getudatamem(uvalue(o)); + case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; } } @@ -472,9 +457,7 @@ LUA_API void lua_pushnil (lua_State *L) { LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { lua_lock(L); - setnvalue(L->top, n); - luai_checknum(L, L->top, - luaG_runerror(L, "C API - attempt to push a signaling NaN")); + setfltvalue(L->top, n); api_incr_top(L); lua_unlock(L); } @@ -482,49 +465,43 @@ LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { lua_lock(L); - setnvalue(L->top, cast_num(n)); - api_incr_top(L); - lua_unlock(L); -} - - -LUA_API void lua_pushunsigned (lua_State *L, lua_Unsigned u) { - lua_Number n; - lua_lock(L); - n = lua_unsigned2number(u); - setnvalue(L->top, n); + setivalue(L->top, n); api_incr_top(L); lua_unlock(L); } +/* +** Pushes on the stack a string with given length. Avoid using 's' when +** 'len' == 0 (as 's' can be NULL in that case), due to later use of +** 'memcmp' and 'memcpy'. +*/ LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { TString *ts; lua_lock(L); - luaC_checkGC(L); - ts = luaS_newlstr(L, s, len); + ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len); setsvalue2s(L, L->top, ts); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return getstr(ts); } LUA_API const char *lua_pushstring (lua_State *L, const char *s) { - if (s == NULL) { - lua_pushnil(L); - return NULL; - } + lua_lock(L); + if (s == NULL) + setnilvalue(L->top); else { TString *ts; - lua_lock(L); - luaC_checkGC(L); ts = luaS_new(L, s); setsvalue2s(L, L->top, ts); - api_incr_top(L); - lua_unlock(L); - return getstr(ts); + s = getstr(ts); /* internal copy's address */ } + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return s; } @@ -532,8 +509,8 @@ LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { const char *ret; lua_lock(L); - luaC_checkGC(L); ret = luaO_pushvfstring(L, fmt, argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -543,10 +520,10 @@ LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); - luaC_checkGC(L); va_start(argp, fmt); ret = luaO_pushvfstring(L, fmt, argp); va_end(argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -558,18 +535,20 @@ LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { setfvalue(L->top, fn); } else { - Closure *cl; + CClosure *cl; api_checknelems(L, n); api_check(L, n <= MAXUPVAL, "upvalue index too large"); - luaC_checkGC(L); cl = luaF_newCclosure(L, n); - cl->c.f = fn; + cl->f = fn; L->top -= n; - while (n--) - setobj2n(L, &cl->c.upvalue[n], L->top + n); + while (n--) { + setobj2n(L, &cl->upvalue[n], L->top + n); + /* does not need barrier because closure is white */ + } setclCvalue(L, L->top, cl); } api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); } @@ -605,48 +584,77 @@ LUA_API int lua_pushthread (lua_State *L) { */ -LUA_API void lua_getglobal (lua_State *L, const char *var) { +static int auxgetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setsvalue2s(L, L->top, str); + api_incr_top(L); + luaV_finishget(L, t, L->top - 1, L->top - 1, slot); + } + lua_unlock(L); + return ttnov(L->top - 1); +} + + +LUA_API int lua_getglobal (lua_State *L, const char *name) { Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ lua_lock(L); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top++, luaS_new(L, var)); - luaV_gettable(L, gt, L->top - 1, L->top - 1); - lua_unlock(L); + return auxgetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); } -LUA_API void lua_gettable (lua_State *L, int idx) { +LUA_API int lua_gettable (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2addr(L, idx); luaV_gettable(L, t, L->top - 1, L->top - 1); lua_unlock(L); + return ttnov(L->top - 1); +} + + +LUA_API int lua_getfield (lua_State *L, int idx, const char *k) { + lua_lock(L); + return auxgetstr(L, index2addr(L, idx), k); } -LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { +LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) { StkId t; + const TValue *slot; lua_lock(L); t = index2addr(L, idx); - setsvalue2s(L, L->top, luaS_new(L, k)); - api_incr_top(L); - luaV_gettable(L, t, L->top - 1, L->top - 1); + if (luaV_fastget(L, t, n, slot, luaH_getint)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setivalue(L->top, n); + api_incr_top(L); + luaV_finishget(L, t, L->top - 1, L->top - 1, slot); + } lua_unlock(L); + return ttnov(L->top - 1); } -LUA_API void lua_rawget (lua_State *L, int idx) { +LUA_API int lua_rawget (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2addr(L, idx); api_check(L, ttistable(t), "table expected"); setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); lua_unlock(L); + return ttnov(L->top - 1); } -LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { +LUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n) { StkId t; lua_lock(L); t = index2addr(L, idx); @@ -654,10 +662,11 @@ LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { setobj2s(L, L->top, luaH_getint(hvalue(t), n)); api_incr_top(L); lua_unlock(L); + return ttnov(L->top - 1); } -LUA_API void lua_rawgetp (lua_State *L, int idx, const void *p) { +LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { StkId t; TValue k; lua_lock(L); @@ -667,29 +676,30 @@ LUA_API void lua_rawgetp (lua_State *L, int idx, const void *p) { setobj2s(L, L->top, luaH_get(hvalue(t), &k)); api_incr_top(L); lua_unlock(L); + return ttnov(L->top - 1); } LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { Table *t; lua_lock(L); - luaC_checkGC(L); t = luaH_new(L); sethvalue(L, L->top, t); api_incr_top(L); if (narray > 0 || nrec > 0) luaH_resize(L, t, narray, nrec); + luaC_checkGC(L); lua_unlock(L); } LUA_API int lua_getmetatable (lua_State *L, int objindex) { const TValue *obj; - Table *mt = NULL; - int res; + Table *mt; + int res = 0; lua_lock(L); obj = index2addr(L, objindex); - switch (ttypenv(obj)) { + switch (ttnov(obj)) { case LUA_TTABLE: mt = hvalue(obj)->metatable; break; @@ -697,12 +707,10 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { mt = uvalue(obj)->metatable; break; default: - mt = G(L)->mt[ttypenv(obj)]; + mt = G(L)->mt[ttnov(obj)]; break; } - if (mt == NULL) - res = 0; - else { + if (mt != NULL) { sethvalue(L, L->top, mt); api_incr_top(L); res = 1; @@ -712,17 +720,15 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { } -LUA_API void lua_getuservalue (lua_State *L, int idx) { +LUA_API int lua_getuservalue (lua_State *L, int idx) { StkId o; lua_lock(L); o = index2addr(L, idx); - api_check(L, ttisuserdata(o), "userdata expected"); - if (uvalue(o)->env) { - sethvalue(L, L->top, uvalue(o)->env); - } else - setnilvalue(L->top); + api_check(L, ttisfulluserdata(o), "full userdata expected"); + getuservalue(L, uvalue(o), L->top); api_incr_top(L); lua_unlock(L); + return ttnov(L->top - 1); } @@ -730,17 +736,29 @@ LUA_API void lua_getuservalue (lua_State *L, int idx) { ** set functions (stack -> Lua) */ +/* +** t[k] = value at the top of the stack (where 'k' is a string) +*/ +static void auxsetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + api_checknelems(L, 1); + if (luaV_fastset(L, t, str, slot, luaH_getstr, L->top - 1)) + L->top--; /* pop value */ + else { + setsvalue2s(L, L->top, str); /* push 'str' (to make it a TValue) */ + api_incr_top(L); + luaV_finishset(L, t, L->top - 1, L->top - 2, slot); + L->top -= 2; /* pop value and key */ + } + lua_unlock(L); /* lock done by caller */ +} + -LUA_API void lua_setglobal (lua_State *L, const char *var) { +LUA_API void lua_setglobal (lua_State *L, const char *name) { Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ - lua_lock(L); - api_checknelems(L, 1); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top++, luaS_new(L, var)); - luaV_settable(L, gt, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ - lua_unlock(L); + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); } @@ -756,54 +774,69 @@ LUA_API void lua_settable (lua_State *L, int idx) { LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, index2addr(L, idx), k); +} + + +LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { StkId t; + const TValue *slot; lua_lock(L); api_checknelems(L, 1); t = index2addr(L, idx); - setsvalue2s(L, L->top++, luaS_new(L, k)); - luaV_settable(L, t, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ + if (luaV_fastset(L, t, n, slot, luaH_getint, L->top - 1)) + L->top--; /* pop value */ + else { + setivalue(L->top, n); + api_incr_top(L); + luaV_finishset(L, t, L->top - 1, L->top - 2, slot); + L->top -= 2; /* pop value and key */ + } lua_unlock(L); } LUA_API void lua_rawset (lua_State *L, int idx) { - StkId t; + StkId o; + TValue *slot; lua_lock(L); api_checknelems(L, 2); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1); - invalidateTMcache(hvalue(t)); - luaC_barrierback(L, gcvalue(t), L->top-1); + o = index2addr(L, idx); + api_check(L, ttistable(o), "table expected"); + slot = luaH_set(L, hvalue(o), L->top - 2); + setobj2t(L, slot, L->top - 1); + invalidateTMcache(hvalue(o)); + luaC_barrierback(L, hvalue(o), L->top-1); L->top -= 2; lua_unlock(L); } -LUA_API void lua_rawseti (lua_State *L, int idx, int n) { - StkId t; +LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { + StkId o; lua_lock(L); api_checknelems(L, 1); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - luaH_setint(L, hvalue(t), n, L->top - 1); - luaC_barrierback(L, gcvalue(t), L->top-1); + o = index2addr(L, idx); + api_check(L, ttistable(o), "table expected"); + luaH_setint(L, hvalue(o), n, L->top - 1); + luaC_barrierback(L, hvalue(o), L->top-1); L->top--; lua_unlock(L); } LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { - StkId t; - TValue k; + StkId o; + TValue k, *slot; lua_lock(L); api_checknelems(L, 1); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); + o = index2addr(L, idx); + api_check(L, ttistable(o), "table expected"); setpvalue(&k, cast(void *, p)); - setobj2t(L, luaH_set(L, hvalue(t), &k), L->top - 1); - luaC_barrierback(L, gcvalue(t), L->top - 1); + slot = luaH_set(L, hvalue(o), &k); + setobj2t(L, slot, L->top - 1); + luaC_barrierback(L, hvalue(o), L->top - 1); L->top--; lua_unlock(L); } @@ -821,11 +854,11 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { api_check(L, ttistable(L->top - 1), "table expected"); mt = hvalue(L->top - 1); } - switch (ttypenv(obj)) { + switch (ttnov(obj)) { case LUA_TTABLE: { hvalue(obj)->metatable = mt; if (mt) { - luaC_objbarrierback(L, gcvalue(obj), mt); + luaC_objbarrier(L, gcvalue(obj), mt); luaC_checkfinalizer(L, gcvalue(obj), mt); } break; @@ -833,13 +866,13 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { case LUA_TUSERDATA: { uvalue(obj)->metatable = mt; if (mt) { - luaC_objbarrier(L, rawuvalue(obj), mt); + luaC_objbarrier(L, uvalue(obj), mt); luaC_checkfinalizer(L, gcvalue(obj), mt); } break; } default: { - G(L)->mt[ttypenv(obj)] = mt; + G(L)->mt[ttnov(obj)] = mt; break; } } @@ -854,21 +887,16 @@ LUA_API void lua_setuservalue (lua_State *L, int idx) { lua_lock(L); api_checknelems(L, 1); o = index2addr(L, idx); - api_check(L, ttisuserdata(o), "userdata expected"); - if (ttisnil(L->top - 1)) - uvalue(o)->env = NULL; - else { - api_check(L, ttistable(L->top - 1), "table expected"); - uvalue(o)->env = hvalue(L->top - 1); - luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); - } + api_check(L, ttisfulluserdata(o), "full userdata expected"); + setuservalue(L, uvalue(o), L->top - 1); + luaC_barrier(L, gcvalue(o), L->top - 1); L->top--; lua_unlock(L); } /* -** `load' and `call' functions (run Lua code) +** 'load' and 'call' functions (run Lua code) */ @@ -877,17 +905,8 @@ LUA_API void lua_setuservalue (lua_State *L, int idx) { "results from function overflow current stack size") -LUA_API int lua_getctx (lua_State *L, int *ctx) { - if (L->ci->callstatus & CIST_YIELDED) { - if (ctx) *ctx = L->ci->u.c.ctx; - return L->ci->u.c.status; - } - else return LUA_OK; -} - - -LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, - lua_CFunction k) { +LUA_API void lua_callk (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k) { StkId func; lua_lock(L); api_check(L, k == NULL || !isLua(L->ci), @@ -899,10 +918,10 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, if (k != NULL && L->nny == 0) { /* need to prepare continuation? */ L->ci->u.c.k = k; /* save continuation */ L->ci->u.c.ctx = ctx; /* save context */ - luaD_call(L, func, nresults, 1); /* do the call */ + luaD_call(L, func, nresults); /* do the call */ } else /* no continuation or no yieldable */ - luaD_call(L, func, nresults, 0); /* just do the call */ + luaD_callnoyield(L, func, nresults); /* just do the call */ adjustresults(L, nresults); lua_unlock(L); } @@ -912,7 +931,7 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, /* ** Execute a protected call. */ -struct CallS { /* data to `f_call' */ +struct CallS { /* data to 'f_call' */ StkId func; int nresults; }; @@ -920,13 +939,13 @@ struct CallS { /* data to `f_call' */ static void f_call (lua_State *L, void *ud) { struct CallS *c = cast(struct CallS *, ud); - luaD_call(L, c->func, c->nresults, 0); + luaD_callnoyield(L, c->func, c->nresults); } LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, - int ctx, lua_CFunction k) { + lua_KContext ctx, lua_KFunction k) { struct CallS c; int status; ptrdiff_t func; @@ -954,12 +973,11 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, ci->u.c.ctx = ctx; /* save context */ /* save information for error recovery */ ci->extra = savestack(L, c.func); - ci->u.c.old_allowhook = L->allowhook; ci->u.c.old_errfunc = L->errfunc; L->errfunc = func; - /* mark that function may do error recovery */ - ci->callstatus |= CIST_YPCALL; - luaD_call(L, c.func, nresults, 1); /* do the call */ + setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */ + ci->callstatus |= CIST_YPCALL; /* function can do error recovery */ + luaD_call(L, c.func, nresults); /* do the call */ ci->callstatus &= ~CIST_YPCALL; L->errfunc = ci->u.c.old_errfunc; status = LUA_OK; /* if it is here, there were no errors */ @@ -980,13 +998,13 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, status = luaD_protectedparser(L, &z, chunkname, mode); if (status == LUA_OK) { /* no errors? */ LClosure *f = clLvalue(L->top - 1); /* get newly created function */ - if (f->nupvalues == 1) { /* does it have one upvalue? */ + if (f->nupvalues >= 1) { /* does it have an upvalue? */ /* get global table from registry */ Table *reg = hvalue(&G(L)->l_registry); const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS); /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */ setobj(L, f->upvals[0]->v, gt); - luaC_barrier(L, f->upvals[0], gt); + luaC_upvalbarrier(L, f->upvals[0]); } } lua_unlock(L); @@ -994,14 +1012,14 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, } -LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) { +LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { int status; TValue *o; lua_lock(L); api_checknelems(L, 1); o = L->top - 1; if (isLfunction(o)) - status = luaU_dump(L, getproto(o), writer, data, 0); + status = luaU_dump(L, getproto(o), writer, data, strip); else status = 1; lua_unlock(L); @@ -1047,19 +1065,21 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { break; } case LUA_GCSTEP: { - if (g->gckind == KGC_GEN) { /* generational mode? */ - res = (g->GCestimate == 0); /* true if it will do major collection */ - luaC_forcestep(L); /* do a single step */ + l_mem debt = 1; /* =1 to signal that it did an actual step */ + lu_byte oldrunning = g->gcrunning; + g->gcrunning = 1; /* allow GC to run */ + if (data == 0) { + luaE_setdebt(g, -GCSTEPSIZE); /* to do a "small" step */ + luaC_step(L); } - else { - lu_mem debt = cast(lu_mem, data) * 1024 - GCSTEPSIZE; - if (g->gcrunning) - debt += g->GCdebt; /* include current debt */ - luaE_setdebt(g, debt); - luaC_forcestep(L); - if (g->gcstate == GCSpause) /* end of cycle? */ - res = 1; /* signal it */ + else { /* add 'data' to total debt */ + debt = cast(l_mem, data) * 1024 + g->GCdebt; + luaE_setdebt(g, debt); + luaC_checkGC(L); } + g->gcrunning = oldrunning; /* restore previous state */ + if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ + res = 1; /* signal it */ break; } case LUA_GCSETPAUSE: { @@ -1067,13 +1087,9 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { g->gcpause = data; break; } - case LUA_GCSETMAJORINC: { - res = g->gcmajorinc; - g->gcmajorinc = data; - break; - } case LUA_GCSETSTEPMUL: { res = g->gcstepmul; + if (data < 40) data = 40; /* avoid ridiculous low values (and 0) */ g->gcstepmul = data; break; } @@ -1081,14 +1097,6 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { res = g->gcrunning; break; } - case LUA_GCGEN: { /* change collector to generational mode */ - luaC_changemode(L, KGC_GEN); - break; - } - case LUA_GCINC: { /* change collector to incremental mode */ - luaC_changemode(L, KGC_NORMAL); - break; - } default: res = -1; /* invalid option */ } lua_unlock(L); @@ -1132,7 +1140,6 @@ LUA_API void lua_concat (lua_State *L, int n) { lua_lock(L); api_checknelems(L, n); if (n >= 2) { - luaC_checkGC(L); luaV_concat(L, n); } else if (n == 0) { /* push empty string */ @@ -1140,6 +1147,7 @@ LUA_API void lua_concat (lua_State *L, int n) { api_incr_top(L); } /* else n == 1; nothing to do */ + luaC_checkGC(L); lua_unlock(L); } @@ -1175,24 +1183,24 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { LUA_API void *lua_newuserdata (lua_State *L, size_t size) { Udata *u; lua_lock(L); - luaC_checkGC(L); - u = luaS_newudata(L, size, NULL); + u = luaS_newudata(L, size); setuvalue(L, L->top, u); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); - return u + 1; + return getudatamem(u); } static const char *aux_upvalue (StkId fi, int n, TValue **val, - GCObject **owner) { + CClosure **owner, UpVal **uv) { switch (ttype(fi)) { case LUA_TCCL: { /* C closure */ CClosure *f = clCvalue(fi); if (!(1 <= n && n <= f->nupvalues)) return NULL; *val = &f->upvalue[n-1]; - if (owner) *owner = obj2gco(f); + if (owner) *owner = f; return ""; } case LUA_TLCL: { /* Lua closure */ @@ -1201,9 +1209,9 @@ static const char *aux_upvalue (StkId fi, int n, TValue **val, Proto *p = f->p; if (!(1 <= n && n <= p->sizeupvalues)) return NULL; *val = f->upvals[n-1]->v; - if (owner) *owner = obj2gco(f->upvals[n - 1]); + if (uv) *uv = f->upvals[n - 1]; name = p->upvalues[n-1].name; - return (name == NULL) ? "" : getstr(name); + return (name == NULL) ? "(*no name)" : getstr(name); } default: return NULL; /* not a closure */ } @@ -1214,7 +1222,7 @@ LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val = NULL; /* to avoid warnings */ lua_lock(L); - name = aux_upvalue(index2addr(L, funcindex), n, &val, NULL); + name = aux_upvalue(index2addr(L, funcindex), n, &val, NULL, NULL); if (name) { setobj2s(L, L->top, val); api_incr_top(L); @@ -1227,16 +1235,18 @@ LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val = NULL; /* to avoid warnings */ - GCObject *owner = NULL; /* to avoid warnings */ + CClosure *owner = NULL; + UpVal *uv = NULL; StkId fi; lua_lock(L); fi = index2addr(L, funcindex); api_checknelems(L, 1); - name = aux_upvalue(fi, n, &val, &owner); + name = aux_upvalue(fi, n, &val, &owner, &uv); if (name) { L->top--; setobj(L, val, L->top); - luaC_barrier(L, owner, L->top); + if (owner) { luaC_barrier(L, owner, L->top); } + else if (uv) { luaC_upvalbarrier(L, uv); } } lua_unlock(L); return name; @@ -1278,7 +1288,11 @@ LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, LClosure *f1; UpVal **up1 = getupvalref(L, fidx1, n1, &f1); UpVal **up2 = getupvalref(L, fidx2, n2, NULL); + luaC_upvdeccount(L, *up1); *up1 = *up2; - luaC_objbarrier(L, f1, *up2); + (*up1)->refcount++; + if (upisopen(*up1)) (*up1)->u.open.touched = 1; + luaC_upvalbarrier(L, *up1); } + diff --git a/depends/lua/src/lauxlib.c b/depends/lua/src/lauxlib.c index b00f8c709..bacf43b3e 100644 --- a/depends/lua/src/lauxlib.c +++ b/depends/lua/src/lauxlib.c @@ -1,9 +1,14 @@ /* -** $Id: lauxlib.c,v 1.248.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lauxlib.c,v 1.286 2016/01/08 15:33:09 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ +#define lauxlib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include @@ -12,13 +17,11 @@ #include -/* This file uses only the official API of Lua. +/* +** This file uses only the official API of Lua. ** Any function declared here could be written as an application function. */ -#define lauxlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -31,8 +34,8 @@ */ -#define LEVELS1 12 /* size of the first part of the stack */ -#define LEVELS2 10 /* size of the second part of the stack */ +#define LEVELS1 10 /* size of the first part of the stack */ +#define LEVELS2 11 /* size of the second part of the stack */ @@ -64,11 +67,20 @@ static int findfield (lua_State *L, int objidx, int level) { } +/* +** Search for a name for a function in all loaded modules +** (registry._LOADED). +*/ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { int top = lua_gettop(L); lua_getinfo(L, "f", ar); /* push function */ - lua_pushglobaltable(L); + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); if (findfield(L, top + 1, 2)) { + const char *name = lua_tostring(L, -1); + if (strncmp(name, "_G.", 3) == 0) { /* name start with '_G.'? */ + lua_pushstring(L, name + 3); /* push name without prefix */ + lua_remove(L, -2); /* remove original name */ + } lua_copy(L, -1, top + 1); /* move name to proper place */ lua_pop(L, 2); /* remove pushed values */ return 1; @@ -81,24 +93,22 @@ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { static void pushfuncname (lua_State *L, lua_Debug *ar) { - if (*ar->namewhat != '\0') /* is there a name? */ - lua_pushfstring(L, "function " LUA_QS, ar->name); + if (pushglobalfuncname(L, ar)) { /* try first a global name */ + lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); + lua_remove(L, -2); /* remove name */ + } + else if (*ar->namewhat != '\0') /* is there a name from code? */ + lua_pushfstring(L, "%s '%s'", ar->namewhat, ar->name); /* use it */ else if (*ar->what == 'm') /* main? */ lua_pushliteral(L, "main chunk"); - else if (*ar->what == 'C') { - if (pushglobalfuncname(L, ar)) { - lua_pushfstring(L, "function " LUA_QS, lua_tostring(L, -1)); - lua_remove(L, -2); /* remove name */ - } - else - lua_pushliteral(L, "?"); - } - else + else if (*ar->what != 'C') /* for Lua functions, use */ lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); + else /* nothing left... */ + lua_pushliteral(L, "?"); } -static int countlevels (lua_State *L) { +static int lastlevel (lua_State *L) { lua_Debug ar; int li = 1, le = 1; /* find an upper bound */ @@ -117,14 +127,16 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { lua_Debug ar; int top = lua_gettop(L); - int numlevels = countlevels(L1); - int mark = (numlevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; - if (msg) lua_pushfstring(L, "%s\n", msg); + int last = lastlevel(L1); + int n1 = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; + if (msg) + lua_pushfstring(L, "%s\n", msg); + luaL_checkstack(L, 10, NULL); lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { - if (level == mark) { /* too many levels? */ + if (n1-- == 0) { /* too many levels? */ lua_pushliteral(L, "\n\t..."); /* add a '...' */ - level = numlevels - LEVELS2; /* and skip to last ones */ + level = last - LEVELS2 + 1; /* and skip to last ones */ } else { lua_getinfo(L1, "Slnt", &ar); @@ -150,36 +162,47 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, ** ======================================================= */ -LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) { +LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { lua_Debug ar; if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ - return luaL_error(L, "bad argument #%d (%s)", narg, extramsg); + return luaL_error(L, "bad argument #%d (%s)", arg, extramsg); lua_getinfo(L, "n", &ar); if (strcmp(ar.namewhat, "method") == 0) { - narg--; /* do not count `self' */ - if (narg == 0) /* error is in the self argument itself? */ - return luaL_error(L, "calling " LUA_QS " on bad self (%s)", + arg--; /* do not count 'self' */ + if (arg == 0) /* error is in the self argument itself? */ + return luaL_error(L, "calling '%s' on bad self (%s)", ar.name, extramsg); } if (ar.name == NULL) ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; - return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)", - narg, ar.name, extramsg); + return luaL_error(L, "bad argument #%d to '%s' (%s)", + arg, ar.name, extramsg); } -static int typeerror (lua_State *L, int narg, const char *tname) { - const char *msg = lua_pushfstring(L, "%s expected, got %s", - tname, luaL_typename(L, narg)); - return luaL_argerror(L, narg, msg); +static int typeerror (lua_State *L, int arg, const char *tname) { + const char *msg; + const char *typearg; /* name for the type of the actual argument */ + if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING) + typearg = lua_tostring(L, -1); /* use the given type name */ + else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA) + typearg = "light userdata"; /* special name for messages */ + else + typearg = luaL_typename(L, arg); /* standard name */ + msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg); + return luaL_argerror(L, arg, msg); } -static void tag_error (lua_State *L, int narg, int tag) { - typeerror(L, narg, lua_typename(L, tag)); +static void tag_error (lua_State *L, int arg, int tag) { + typeerror(L, arg, lua_typename(L, tag)); } +/* +** The use of 'lua_pushfstring' ensures this function does not +** need reserved stack space when called. +*/ LUALIB_API void luaL_where (lua_State *L, int level) { lua_Debug ar; if (lua_getstack(L, level, &ar)) { /* check function at level */ @@ -189,10 +212,15 @@ LUALIB_API void luaL_where (lua_State *L, int level) { return; } } - lua_pushliteral(L, ""); /* else, no information available... */ + lua_pushfstring(L, ""); /* else, no information available... */ } +/* +** Again, the use of 'lua_pushvfstring' ensures this function does +** not need reserved stack space when called. (At worst, it generates +** an error with "stack overflow" instead of the given message.) +*/ LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); @@ -222,7 +250,7 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { } -#if !defined(inspectstat) /* { */ +#if !defined(l_inspectstat) /* { */ #if defined(LUA_USE_POSIX) @@ -231,13 +259,13 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { /* ** use appropriate macros to interpret 'pclose' return status */ -#define inspectstat(stat,what) \ +#define l_inspectstat(stat,what) \ if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \ else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = "signal"; } #else -#define inspectstat(stat,what) /* no op */ +#define l_inspectstat(stat,what) /* no op */ #endif @@ -249,7 +277,7 @@ LUALIB_API int luaL_execresult (lua_State *L, int stat) { if (stat == -1) /* error? */ return luaL_fileresult(L, 0, NULL); else { - inspectstat(stat, what); /* interpret result */ + l_inspectstat(stat, what); /* interpret result */ if (*what == 'e' && stat == 0) /* successful termination? */ lua_pushboolean(L, 1); else @@ -270,11 +298,12 @@ LUALIB_API int luaL_execresult (lua_State *L, int stat) { */ LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { - luaL_getmetatable(L, tname); /* try to get metatable */ - if (!lua_isnil(L, -1)) /* name already in use? */ + if (luaL_getmetatable(L, tname) != LUA_TNIL) /* name already in use? */ return 0; /* leave previous value on top, but return 0 */ lua_pop(L, 1); - lua_newtable(L); /* create metatable */ + lua_createtable(L, 0, 2); /* create metatable */ + lua_pushstring(L, tname); + lua_setfield(L, -2, "__name"); /* metatable.__name = tname */ lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ return 1; @@ -317,23 +346,28 @@ LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { ** ======================================================= */ -LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def, +LUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def, const char *const lst[]) { - const char *name = (def) ? luaL_optstring(L, narg, def) : - luaL_checkstring(L, narg); + const char *name = (def) ? luaL_optstring(L, arg, def) : + luaL_checkstring(L, arg); int i; for (i=0; lst[i]; i++) if (strcmp(lst[i], name) == 0) return i; - return luaL_argerror(L, narg, - lua_pushfstring(L, "invalid option " LUA_QS, name)); + return luaL_argerror(L, arg, + lua_pushfstring(L, "invalid option '%s'", name)); } +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { - /* keep some extra space to run error routines, if needed */ - const int extra = LUA_MINSTACK; - if (!lua_checkstack(L, space + extra)) { + if (!lua_checkstack(L, space)) { if (msg) luaL_error(L, "stack overflow (%s)", msg); else @@ -342,77 +376,71 @@ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { } -LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) { - if (lua_type(L, narg) != t) - tag_error(L, narg, t); +LUALIB_API void luaL_checktype (lua_State *L, int arg, int t) { + if (lua_type(L, arg) != t) + tag_error(L, arg, t); } -LUALIB_API void luaL_checkany (lua_State *L, int narg) { - if (lua_type(L, narg) == LUA_TNONE) - luaL_argerror(L, narg, "value expected"); +LUALIB_API void luaL_checkany (lua_State *L, int arg) { + if (lua_type(L, arg) == LUA_TNONE) + luaL_argerror(L, arg, "value expected"); } -LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) { - const char *s = lua_tolstring(L, narg, len); - if (!s) tag_error(L, narg, LUA_TSTRING); +LUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) { + const char *s = lua_tolstring(L, arg, len); + if (!s) tag_error(L, arg, LUA_TSTRING); return s; } -LUALIB_API const char *luaL_optlstring (lua_State *L, int narg, +LUALIB_API const char *luaL_optlstring (lua_State *L, int arg, const char *def, size_t *len) { - if (lua_isnoneornil(L, narg)) { + if (lua_isnoneornil(L, arg)) { if (len) *len = (def ? strlen(def) : 0); return def; } - else return luaL_checklstring(L, narg, len); + else return luaL_checklstring(L, arg, len); } -LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) { +LUALIB_API lua_Number luaL_checknumber (lua_State *L, int arg) { int isnum; - lua_Number d = lua_tonumberx(L, narg, &isnum); + lua_Number d = lua_tonumberx(L, arg, &isnum); if (!isnum) - tag_error(L, narg, LUA_TNUMBER); + tag_error(L, arg, LUA_TNUMBER); return d; } -LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) { - return luaL_opt(L, luaL_checknumber, narg, def); +LUALIB_API lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number def) { + return luaL_opt(L, luaL_checknumber, arg, def); } -LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) { - int isnum; - lua_Integer d = lua_tointegerx(L, narg, &isnum); - if (!isnum) - tag_error(L, narg, LUA_TNUMBER); - return d; +static void interror (lua_State *L, int arg) { + if (lua_isnumber(L, arg)) + luaL_argerror(L, arg, "number has no integer representation"); + else + tag_error(L, arg, LUA_TNUMBER); } -LUALIB_API lua_Unsigned luaL_checkunsigned (lua_State *L, int narg) { +LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int arg) { int isnum; - lua_Unsigned d = lua_tounsignedx(L, narg, &isnum); - if (!isnum) - tag_error(L, narg, LUA_TNUMBER); + lua_Integer d = lua_tointegerx(L, arg, &isnum); + if (!isnum) { + interror(L, arg); + } return d; } -LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg, +LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg, lua_Integer def) { - return luaL_opt(L, luaL_checkinteger, narg, def); -} - - -LUALIB_API lua_Unsigned luaL_optunsigned (lua_State *L, int narg, - lua_Unsigned def) { - return luaL_opt(L, luaL_checkunsigned, narg, def); + return luaL_opt(L, luaL_checkinteger, arg, def); } /* }====================================================== */ @@ -424,6 +452,47 @@ LUALIB_API lua_Unsigned luaL_optunsigned (lua_State *L, int narg, ** ======================================================= */ +/* userdata to box arbitrary data */ +typedef struct UBox { + void *box; + size_t bsize; +} UBox; + + +static void *resizebox (lua_State *L, int idx, size_t newsize) { + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + UBox *box = (UBox *)lua_touserdata(L, idx); + void *temp = allocf(ud, box->box, box->bsize, newsize); + if (temp == NULL && newsize > 0) { /* allocation error? */ + resizebox(L, idx, 0); /* free buffer */ + luaL_error(L, "not enough memory for buffer allocation"); + } + box->box = temp; + box->bsize = newsize; + return temp; +} + + +static int boxgc (lua_State *L) { + resizebox(L, 1, 0); + return 0; +} + + +static void *newbox (lua_State *L, size_t newsize) { + UBox *box = (UBox *)lua_newuserdata(L, sizeof(UBox)); + box->box = NULL; + box->bsize = 0; + if (luaL_newmetatable(L, "LUABOX")) { /* creating metatable? */ + lua_pushcfunction(L, boxgc); + lua_setfield(L, -2, "__gc"); /* metatable.__gc = boxgc */ + } + lua_setmetatable(L, -2); + return resizebox(L, -1, newsize); +} + + /* ** check whether buffer is using a userdata on the stack as a temporary ** buffer @@ -444,11 +513,12 @@ LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { if (newsize < B->n || newsize - B->n < sz) luaL_error(L, "buffer too large"); /* create larger buffer */ - newbuff = (char *)lua_newuserdata(L, newsize * sizeof(char)); - /* move content to new buffer */ - memcpy(newbuff, B->b, B->n * sizeof(char)); if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + newbuff = (char *)resizebox(L, -1, newsize); + else { /* no buffer yet */ + newbuff = (char *)newbox(L, newsize); + memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */ + } B->b = newbuff; B->size = newsize; } @@ -457,9 +527,11 @@ LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { - char *b = luaL_prepbuffsize(B, l); - memcpy(b, s, l * sizeof(char)); - luaL_addsize(B, l); + if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */ + char *b = luaL_prepbuffsize(B, l); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); + } } @@ -471,8 +543,10 @@ LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { LUALIB_API void luaL_pushresult (luaL_Buffer *B) { lua_State *L = B->L; lua_pushlstring(L, B->b, B->n); - if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + if (buffonstack(B)) { + resizebox(L, -2, 0); /* delete old buffer */ + lua_remove(L, -2); /* remove its header from the stack */ + } } @@ -523,7 +597,7 @@ LUALIB_API int luaL_ref (lua_State *L, int t) { int ref; if (lua_isnil(L, -1)) { lua_pop(L, 1); /* remove from stack */ - return LUA_REFNIL; /* `nil' has a unique fixed reference */ + return LUA_REFNIL; /* 'nil' has a unique fixed reference */ } t = lua_absindex(L, t); lua_rawgeti(L, t, freelist); /* get first free element */ @@ -562,7 +636,7 @@ LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { typedef struct LoadF { int n; /* number of pre-read characters */ FILE *f; /* file being read */ - char buff[LUAL_BUFFERSIZE]; /* area for reading file */ + char buff[BUFSIZ]; /* area for reading file */ } LoadF; @@ -594,7 +668,7 @@ static int errfile (lua_State *L, const char *what, int fnameindex) { static int skipBOM (LoadF *lf) { - const char *p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ + const char *p = "\xEF\xBB\xBF"; /* UTF-8 BOM mark */ int c; lf->n = 0; do { @@ -619,7 +693,7 @@ static int skipcomment (LoadF *lf, int *cp) { if (c == '#') { /* first line is a comment (Unix exec. file)? */ do { /* skip first line */ c = getc(lf->f); - } while (c != EOF && c != '\n') ; + } while (c != EOF && c != '\n'); *cp = getc(lf->f); /* skip end-of-line, if present */ return 1; /* there was a comment */ } @@ -655,7 +729,7 @@ LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, readstatus = ferror(lf.f); if (filename) fclose(lf.f); /* close file (even in case of errors) */ if (readstatus) { - lua_settop(L, fnameindex); /* ignore results from `lua_load' */ + lua_settop(L, fnameindex); /* ignore results from 'lua_load' */ return errfile(L, "read", fnameindex); } lua_remove(L, fnameindex); @@ -698,23 +772,23 @@ LUALIB_API int luaL_loadstring (lua_State *L, const char *s) { LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { if (!lua_getmetatable(L, obj)) /* no metatable? */ - return 0; - lua_pushstring(L, event); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) { - lua_pop(L, 2); /* remove metatable and metafield */ - return 0; - } + return LUA_TNIL; else { - lua_remove(L, -2); /* remove only metatable */ - return 1; + int tt; + lua_pushstring(L, event); + tt = lua_rawget(L, -2); + if (tt == LUA_TNIL) /* is metafield nil? */ + lua_pop(L, 2); /* remove metatable and metafield */ + else + lua_remove(L, -2); /* remove only metatable */ + return tt; /* return metafield type */ } } LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { obj = lua_absindex(L, obj); - if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ + if (luaL_getmetafield(L, obj, event) == LUA_TNIL) /* no metafield? */ return 0; lua_pushvalue(L, obj); lua_call(L, 1, 1); @@ -722,13 +796,13 @@ LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { } -LUALIB_API int luaL_len (lua_State *L, int idx) { - int l; +LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { + lua_Integer l; int isnum; lua_len(L, idx); - l = (int)lua_tointegerx(L, -1, &isnum); + l = lua_tointegerx(L, -1, &isnum); if (!isnum) - luaL_error(L, "object length is not a number"); + luaL_error(L, "object length is not an integer"); lua_pop(L, 1); /* remove object */ return l; } @@ -737,7 +811,13 @@ LUALIB_API int luaL_len (lua_State *L, int idx) { LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */ switch (lua_type(L, idx)) { - case LUA_TNUMBER: + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) + lua_pushfstring(L, "%I", lua_tointeger(L, idx)); + else + lua_pushfstring(L, "%f", lua_tonumber(L, idx)); + break; + } case LUA_TSTRING: lua_pushvalue(L, idx); break; @@ -772,8 +852,7 @@ static const char *luaL_findtable (lua_State *L, int idx, e = strchr(fname, '.'); if (e == NULL) e = fname + strlen(fname); lua_pushlstring(L, fname, e - fname); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) { /* no such field? */ + if (lua_rawget(L, -2) == LUA_TNIL) { /* no such field? */ lua_pop(L, 1); /* remove this nil */ lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ lua_pushlstring(L, fname, e - fname); @@ -810,13 +889,12 @@ static int libsize (const luaL_Reg *l) { LUALIB_API void luaL_pushmodule (lua_State *L, const char *modname, int sizehint) { luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); /* get _LOADED table */ - lua_getfield(L, -1, modname); /* get _LOADED[modname] */ - if (!lua_istable(L, -1)) { /* not found? */ + if (lua_getfield(L, -1, modname) != LUA_TTABLE) { /* no _LOADED[modname]? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ lua_pushglobaltable(L); if (luaL_findtable(L, 0, modname, sizehint) != NULL) - luaL_error(L, "name conflict for module " LUA_QS, modname); + luaL_error(L, "name conflict for module '%s'", modname); lua_pushvalue(L, -1); lua_setfield(L, -3, modname); /* _LOADED[modname] = new table */ } @@ -846,7 +924,6 @@ LUALIB_API void luaL_openlib (lua_State *L, const char *libname, ** Returns with only the table at the stack. */ LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { - luaL_checkversion(L); luaL_checkstack(L, nup, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; @@ -864,8 +941,8 @@ LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { ** into the stack */ LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { - lua_getfield(L, idx, fname); - if (lua_istable(L, -1)) return 1; /* table already there */ + if (lua_getfield(L, idx, fname) == LUA_TTABLE) + return 1; /* table already there */ else { lua_pop(L, 1); /* remove previous result */ idx = lua_absindex(L, idx); @@ -878,22 +955,26 @@ LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { /* -** stripped-down 'require'. Calls 'openf' to open a module, -** registers the result in 'package.loaded' table and, if 'glb' -** is true, also registers the result in the global table. +** Stripped-down 'require': After checking "loaded" table, calls 'openf' +** to open a module, registers the result in 'package.loaded' table and, +** if 'glb' is true, also registers the result in the global table. ** Leaves resulting module on the top. */ LUALIB_API void luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int glb) { - lua_pushcfunction(L, openf); - lua_pushstring(L, modname); /* argument to open function */ - lua_call(L, 1, 1); /* open module */ luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_pushvalue(L, -2); /* make copy of module (call result) */ - lua_setfield(L, -2, modname); /* _LOADED[modname] = module */ - lua_pop(L, 1); /* remove _LOADED table */ + lua_getfield(L, -1, modname); /* _LOADED[modname] */ + if (!lua_toboolean(L, -1)) { /* package not already loaded? */ + lua_pop(L, 1); /* remove field */ + lua_pushcfunction(L, openf); + lua_pushstring(L, modname); /* argument to open function */ + lua_call(L, 1, 1); /* call 'openf' to open module */ + lua_pushvalue(L, -1); /* make copy of module (call result) */ + lua_setfield(L, -3, modname); /* _LOADED[modname] = module */ + } + lua_remove(L, -2); /* remove _LOADED table */ if (glb) { - lua_pushvalue(L, -1); /* copy of 'mod' */ + lua_pushvalue(L, -1); /* copy of module */ lua_setglobal(L, modname); /* _G[modname] = module */ } } @@ -908,7 +989,7 @@ LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, while ((wild = strstr(s, p)) != NULL) { luaL_addlstring(&b, s, wild - s); /* push prefix */ luaL_addstring(&b, r); /* push replacement in place of pattern */ - s = wild + l; /* continue after `p' */ + s = wild + l; /* continue after 'p' */ } luaL_addstring(&b, s); /* push last suffix */ luaL_pushresult(&b); @@ -928,8 +1009,8 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { static int panic (lua_State *L) { - luai_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", - lua_tostring(L, -1)); + lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", + lua_tostring(L, -1)); return 0; /* return to Lua to abort */ } @@ -941,19 +1022,14 @@ LUALIB_API lua_State *luaL_newstate (void) { } -LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver) { +LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) { const lua_Number *v = lua_version(L); + if (sz != LUAL_NUMSIZES) /* check numeric types */ + luaL_error(L, "core and library have incompatible numeric types"); if (v != lua_version(NULL)) luaL_error(L, "multiple Lua VMs detected"); else if (*v != ver) luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", ver, *v); - /* check conversions number -> integer types */ - lua_pushnumber(L, -(lua_Number)0x1234); - if (lua_tointeger(L, -1) != -0x1234 || - lua_tounsigned(L, -1) != (lua_Unsigned)-0x1234) - luaL_error(L, "bad conversion number->int;" - " must recompile Lua with proper settings"); - lua_pop(L, 1); } diff --git a/depends/lua/src/lbaselib.c b/depends/lua/src/lbaselib.c index 5255b3cd9..d481c4e1e 100644 --- a/depends/lua/src/lbaselib.c +++ b/depends/lua/src/lbaselib.c @@ -1,9 +1,13 @@ /* -** $Id: lbaselib.c,v 1.276.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lbaselib.c,v 1.313 2016/04/11 19:18:40 roberto Exp $ ** Basic library ** See Copyright Notice in lua.h */ +#define lbaselib_c +#define LUA_LIB + +#include "lprefix.h" #include @@ -11,9 +15,6 @@ #include #include -#define lbaselib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -32,65 +33,77 @@ static int luaB_print (lua_State *L) { lua_call(L, 1, 1); s = lua_tolstring(L, -1, &l); /* get result */ if (s == NULL) - return luaL_error(L, - LUA_QL("tostring") " must return a string to " LUA_QL("print")); - if (i>1) luai_writestring("\t", 1); - luai_writestring(s, l); + return luaL_error(L, "'tostring' must return a string to 'print'"); + if (i>1) lua_writestring("\t", 1); + lua_writestring(s, l); lua_pop(L, 1); /* pop result */ } - luai_writeline(); + lua_writeline(); return 0; } #define SPACECHARS " \f\n\r\t\v" +static const char *b_str2int (const char *s, int base, lua_Integer *pn) { + lua_Unsigned n = 0; + int neg = 0; + s += strspn(s, SPACECHARS); /* skip initial spaces */ + if (*s == '-') { s++; neg = 1; } /* handle signal */ + else if (*s == '+') s++; + if (!isalnum((unsigned char)*s)) /* no digit? */ + return NULL; + do { + int digit = (isdigit((unsigned char)*s)) ? *s - '0' + : (toupper((unsigned char)*s) - 'A') + 10; + if (digit >= base) return NULL; /* invalid numeral */ + n = n * base + digit; + s++; + } while (isalnum((unsigned char)*s)); + s += strspn(s, SPACECHARS); /* skip trailing spaces */ + *pn = (lua_Integer)((neg) ? (0u - n) : n); + return s; +} + + static int luaB_tonumber (lua_State *L) { - if (lua_isnoneornil(L, 2)) { /* standard conversion */ - int isnum; - lua_Number n = lua_tonumberx(L, 1, &isnum); - if (isnum) { - lua_pushnumber(L, n); - return 1; - } /* else not a number; must be something */ + if (lua_isnoneornil(L, 2)) { /* standard conversion? */ luaL_checkany(L, 1); + if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */ + lua_settop(L, 1); /* yes; return it */ + return 1; + } + else { + size_t l; + const char *s = lua_tolstring(L, 1, &l); + if (s != NULL && lua_stringtonumber(L, s) == l + 1) + return 1; /* successful conversion to number */ + /* else not a number */ + } } else { size_t l; - const char *s = luaL_checklstring(L, 1, &l); - const char *e = s + l; /* end point for 's' */ - int base = luaL_checkint(L, 2); - int neg = 0; + const char *s; + lua_Integer n = 0; /* to avoid warnings */ + lua_Integer base = luaL_checkinteger(L, 2); + luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */ + s = lua_tolstring(L, 1, &l); luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); - s += strspn(s, SPACECHARS); /* skip initial spaces */ - if (*s == '-') { s++; neg = 1; } /* handle signal */ - else if (*s == '+') s++; - if (isalnum((unsigned char)*s)) { - lua_Number n = 0; - do { - int digit = (isdigit((unsigned char)*s)) ? *s - '0' - : toupper((unsigned char)*s) - 'A' + 10; - if (digit >= base) break; /* invalid numeral; force a fail */ - n = n * (lua_Number)base + (lua_Number)digit; - s++; - } while (isalnum((unsigned char)*s)); - s += strspn(s, SPACECHARS); /* skip trailing spaces */ - if (s == e) { /* no invalid trailing characters? */ - lua_pushnumber(L, (neg) ? -n : n); - return 1; - } /* else not a number */ + if (b_str2int(s, (int)base, &n) == s + l) { + lua_pushinteger(L, n); + return 1; } /* else not a number */ - } + } /* else not a number */ lua_pushnil(L); /* not a number */ return 1; } static int luaB_error (lua_State *L) { - int level = luaL_optint(L, 2, 1); + int level = (int)luaL_optinteger(L, 2, 1); lua_settop(L, 1); - if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ - luaL_where(L, level); + if (lua_type(L, 1) == LUA_TSTRING && level > 0) { + luaL_where(L, level); /* add extra information */ lua_pushvalue(L, 1); lua_concat(L, 2); } @@ -114,7 +127,7 @@ static int luaB_setmetatable (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table expected"); - if (luaL_getmetafield(L, 1, "__metatable")) + if (luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL) return luaL_error(L, "cannot change a protected metatable"); lua_settop(L, 2); lua_setmetatable(L, 1); @@ -160,19 +173,18 @@ static int luaB_rawset (lua_State *L) { static int luaB_collectgarbage (lua_State *L) { static const char *const opts[] = {"stop", "restart", "collect", "count", "step", "setpause", "setstepmul", - "setmajorinc", "isrunning", "generational", "incremental", NULL}; + "isrunning", NULL}; static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, - LUA_GCSETMAJORINC, LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC}; + LUA_GCISRUNNING}; int o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; - int ex = luaL_optint(L, 2, 0); + int ex = (int)luaL_optinteger(L, 2, 0); int res = lua_gc(L, o, ex); switch (o) { case LUA_GCCOUNT: { int b = lua_gc(L, LUA_GCCOUNTB, 0); - lua_pushnumber(L, res + ((lua_Number)b/1024)); - lua_pushinteger(L, b); - return 2; + lua_pushnumber(L, (lua_Number)res + ((lua_Number)b/1024)); + return 1; } case LUA_GCSTEP: case LUA_GCISRUNNING: { lua_pushboolean(L, res); @@ -187,15 +199,16 @@ static int luaB_collectgarbage (lua_State *L) { static int luaB_type (lua_State *L) { - luaL_checkany(L, 1); - lua_pushstring(L, luaL_typename(L, 1)); + int t = lua_type(L, 1); + luaL_argcheck(L, t != LUA_TNONE, 1, "value expected"); + lua_pushstring(L, lua_typename(L, t)); return 1; } static int pairsmeta (lua_State *L, const char *method, int iszero, lua_CFunction iter) { - if (!luaL_getmetafield(L, 1, method)) { /* no metamethod? */ + if (luaL_getmetafield(L, 1, method) == LUA_TNIL) { /* no metamethod? */ luaL_checktype(L, 1, LUA_TTABLE); /* argument must be a table */ lua_pushcfunction(L, iter); /* will return generator, */ lua_pushvalue(L, 1); /* state, */ @@ -227,18 +240,30 @@ static int luaB_pairs (lua_State *L) { } +/* +** Traversal function for 'ipairs' +*/ static int ipairsaux (lua_State *L) { - int i = luaL_checkint(L, 2); - luaL_checktype(L, 1, LUA_TTABLE); - i++; /* next value */ + lua_Integer i = luaL_checkinteger(L, 2) + 1; lua_pushinteger(L, i); - lua_rawgeti(L, 1, i); - return (lua_isnil(L, -1)) ? 1 : 2; + return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; } +/* +** 'ipairs' function. Returns 'ipairsaux', given "table", 0. +** (The given "table" may not be a table.) +*/ static int luaB_ipairs (lua_State *L) { +#if defined(LUA_COMPAT_IPAIRS) return pairsmeta(L, "__ipairs", 1, ipairsaux); +#else + luaL_checkany(L, 1); + lua_pushcfunction(L, ipairsaux); /* iteration function */ + lua_pushvalue(L, 1); /* state */ + lua_pushinteger(L, 0); /* initial value */ + return 3; +#endif } @@ -284,7 +309,7 @@ static int luaB_loadfile (lua_State *L) { /* -** Reader for generic `load' function: `lua_load' uses the +** Reader for generic 'load' function: 'lua_load' uses the ** stack for internal stuff, so the reader cannot change the ** stack top. Instead, it keeps its resulting string in a ** reserved slot inside the stack. @@ -328,7 +353,8 @@ static int luaB_load (lua_State *L) { /* }====================================================== */ -static int dofilecont (lua_State *L) { +static int dofilecont (lua_State *L, int d1, lua_KContext d2) { + (void)d1; (void)d2; /* only to match 'lua_Kfunction' prototype */ return lua_gettop(L) - 1; } @@ -339,14 +365,20 @@ static int luaB_dofile (lua_State *L) { if (luaL_loadfile(L, fname) != LUA_OK) return lua_error(L); lua_callk(L, 0, LUA_MULTRET, 0, dofilecont); - return dofilecont(L); + return dofilecont(L, 0, 0); } static int luaB_assert (lua_State *L) { - if (!lua_toboolean(L, 1)) - return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); - return lua_gettop(L); + if (lua_toboolean(L, 1)) /* condition is true? */ + return lua_gettop(L); /* return all arguments */ + else { /* error */ + luaL_checkany(L, 1); /* there must be a condition */ + lua_remove(L, 1); /* remove it */ + lua_pushliteral(L, "assertion failed!"); /* default message */ + lua_settop(L, 1); /* leave only message (default if no other one) */ + return luaB_error(L); /* call 'error' */ + } } @@ -357,53 +389,57 @@ static int luaB_select (lua_State *L) { return 1; } else { - int i = luaL_checkint(L, 1); + lua_Integer i = luaL_checkinteger(L, 1); if (i < 0) i = n + i; else if (i > n) i = n; luaL_argcheck(L, 1 <= i, 1, "index out of range"); - return n - i; + return n - (int)i; } } -static int finishpcall (lua_State *L, int status) { - if (!lua_checkstack(L, 1)) { /* no space for extra boolean? */ - lua_settop(L, 0); /* create space for return values */ - lua_pushboolean(L, 0); - lua_pushstring(L, "stack overflow"); +/* +** Continuation function for 'pcall' and 'xpcall'. Both functions +** already pushed a 'true' before doing the call, so in case of success +** 'finishpcall' only has to return everything in the stack minus +** 'extra' values (where 'extra' is exactly the number of items to be +** ignored). +*/ +static int finishpcall (lua_State *L, int status, lua_KContext extra) { + if (status != LUA_OK && status != LUA_YIELD) { /* error? */ + lua_pushboolean(L, 0); /* first result (false) */ + lua_pushvalue(L, -2); /* error message */ return 2; /* return false, msg */ } - lua_pushboolean(L, status); /* first result (status) */ - lua_replace(L, 1); /* put first result in first slot */ - return lua_gettop(L); -} - - -static int pcallcont (lua_State *L) { - int status = lua_getctx(L, NULL); - return finishpcall(L, (status == LUA_YIELD)); + else + return lua_gettop(L) - (int)extra; /* return all results */ } static int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); - lua_pushnil(L); - lua_insert(L, 1); /* create space for status result */ - status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, pcallcont); - return finishpcall(L, (status == LUA_OK)); + lua_pushboolean(L, 1); /* first result if no errors */ + lua_insert(L, 1); /* put it in place */ + status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); + return finishpcall(L, status, 0); } +/* +** Do a protected call with error handling. After 'lua_rotate', the +** stack will have ; so, the function passes +** 2 to 'finishpcall' to skip the 2 first values when returning results. +*/ static int luaB_xpcall (lua_State *L) { int status; int n = lua_gettop(L); - luaL_argcheck(L, n >= 2, 2, "value expected"); - lua_pushvalue(L, 1); /* exchange function... */ - lua_copy(L, 2, 1); /* ...and error handler */ - lua_replace(L, 2); - status = lua_pcallk(L, n - 2, LUA_MULTRET, 1, 0, pcallcont); - return finishpcall(L, (status == LUA_OK)); + luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ + lua_pushboolean(L, 1); /* first result */ + lua_pushvalue(L, 1); /* function */ + lua_rotate(L, 3, 2); /* move them below function's arguments */ + status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall); + return finishpcall(L, status, 2); } @@ -440,19 +476,23 @@ static const luaL_Reg base_funcs[] = { {"tostring", luaB_tostring}, {"type", luaB_type}, {"xpcall", luaB_xpcall}, + /* placeholders */ + {"_G", NULL}, + {"_VERSION", NULL}, {NULL, NULL} }; LUAMOD_API int luaopen_base (lua_State *L) { - /* set global _G */ - lua_pushglobaltable(L); - lua_pushglobaltable(L); - lua_setfield(L, -2, "_G"); /* open lib into global table */ + lua_pushglobaltable(L); luaL_setfuncs(L, base_funcs, 0); + /* set global _G */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "_G"); + /* set global _VERSION */ lua_pushliteral(L, LUA_VERSION); - lua_setfield(L, -2, "_VERSION"); /* set global _VERSION */ + lua_setfield(L, -2, "_VERSION"); return 1; } diff --git a/depends/lua/src/lbitlib.c b/depends/lua/src/lbitlib.c index 31c7b66f1..1cb1d5b93 100644 --- a/depends/lua/src/lbitlib.c +++ b/depends/lua/src/lbitlib.c @@ -1,5 +1,5 @@ /* -** $Id: lbitlib.c,v 1.18.1.2 2013/07/09 18:01:41 roberto Exp $ +** $Id: lbitlib.c,v 1.30 2015/11/11 19:08:09 roberto Exp $ ** Standard library for bitwise operations ** See Copyright Notice in lua.h */ @@ -7,20 +7,36 @@ #define lbitlib_c #define LUA_LIB +#include "lprefix.h" + + #include "lua.h" #include "lauxlib.h" #include "lualib.h" +#if defined(LUA_COMPAT_BITLIB) /* { */ + + +#define pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define checkunsigned(L,i) ((lua_Unsigned)luaL_checkinteger(L,i)) + + /* number of bits to consider in a number */ #if !defined(LUA_NBITS) #define LUA_NBITS 32 #endif +/* +** a lua_Unsigned with its first LUA_NBITS bits equal to 1. (Shift must +** be made in two parts to avoid problems when LUA_NBITS is equal to the +** number of bits in a lua_Unsigned.) +*/ #define ALLONES (~(((~(lua_Unsigned)0) << (LUA_NBITS - 1)) << 1)) + /* macro to trim extra bits */ #define trim(x) ((x) & ALLONES) @@ -29,28 +45,25 @@ #define mask(n) (~((ALLONES << 1) << ((n) - 1))) -typedef lua_Unsigned b_uint; - - -static b_uint andaux (lua_State *L) { +static lua_Unsigned andaux (lua_State *L) { int i, n = lua_gettop(L); - b_uint r = ~(b_uint)0; + lua_Unsigned r = ~(lua_Unsigned)0; for (i = 1; i <= n; i++) - r &= luaL_checkunsigned(L, i); + r &= checkunsigned(L, i); return trim(r); } static int b_and (lua_State *L) { - b_uint r = andaux(L); - lua_pushunsigned(L, r); + lua_Unsigned r = andaux(L); + pushunsigned(L, r); return 1; } static int b_test (lua_State *L) { - b_uint r = andaux(L); + lua_Unsigned r = andaux(L); lua_pushboolean(L, r != 0); return 1; } @@ -58,32 +71,32 @@ static int b_test (lua_State *L) { static int b_or (lua_State *L) { int i, n = lua_gettop(L); - b_uint r = 0; + lua_Unsigned r = 0; for (i = 1; i <= n; i++) - r |= luaL_checkunsigned(L, i); - lua_pushunsigned(L, trim(r)); + r |= checkunsigned(L, i); + pushunsigned(L, trim(r)); return 1; } static int b_xor (lua_State *L) { int i, n = lua_gettop(L); - b_uint r = 0; + lua_Unsigned r = 0; for (i = 1; i <= n; i++) - r ^= luaL_checkunsigned(L, i); - lua_pushunsigned(L, trim(r)); + r ^= checkunsigned(L, i); + pushunsigned(L, trim(r)); return 1; } static int b_not (lua_State *L) { - b_uint r = ~luaL_checkunsigned(L, 1); - lua_pushunsigned(L, trim(r)); + lua_Unsigned r = ~checkunsigned(L, 1); + pushunsigned(L, trim(r)); return 1; } -static int b_shift (lua_State *L, b_uint r, int i) { +static int b_shift (lua_State *L, lua_Unsigned r, lua_Integer i) { if (i < 0) { /* shift right? */ i = -i; r = trim(r); @@ -95,54 +108,54 @@ static int b_shift (lua_State *L, b_uint r, int i) { else r <<= i; r = trim(r); } - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } static int b_lshift (lua_State *L) { - return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkint(L, 2)); + return b_shift(L, checkunsigned(L, 1), luaL_checkinteger(L, 2)); } static int b_rshift (lua_State *L) { - return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkint(L, 2)); + return b_shift(L, checkunsigned(L, 1), -luaL_checkinteger(L, 2)); } static int b_arshift (lua_State *L) { - b_uint r = luaL_checkunsigned(L, 1); - int i = luaL_checkint(L, 2); - if (i < 0 || !(r & ((b_uint)1 << (LUA_NBITS - 1)))) + lua_Unsigned r = checkunsigned(L, 1); + lua_Integer i = luaL_checkinteger(L, 2); + if (i < 0 || !(r & ((lua_Unsigned)1 << (LUA_NBITS - 1)))) return b_shift(L, r, -i); else { /* arithmetic shift for 'negative' number */ if (i >= LUA_NBITS) r = ALLONES; else - r = trim((r >> i) | ~(~(b_uint)0 >> i)); /* add signal bit */ - lua_pushunsigned(L, r); + r = trim((r >> i) | ~(trim(~(lua_Unsigned)0) >> i)); /* add signal bit */ + pushunsigned(L, r); return 1; } } -static int b_rot (lua_State *L, int i) { - b_uint r = luaL_checkunsigned(L, 1); - i &= (LUA_NBITS - 1); /* i = i % NBITS */ +static int b_rot (lua_State *L, lua_Integer d) { + lua_Unsigned r = checkunsigned(L, 1); + int i = d & (LUA_NBITS - 1); /* i = d % NBITS */ r = trim(r); if (i != 0) /* avoid undefined shift of LUA_NBITS when i == 0 */ r = (r << i) | (r >> (LUA_NBITS - i)); - lua_pushunsigned(L, trim(r)); + pushunsigned(L, trim(r)); return 1; } static int b_lrot (lua_State *L) { - return b_rot(L, luaL_checkint(L, 2)); + return b_rot(L, luaL_checkinteger(L, 2)); } static int b_rrot (lua_State *L) { - return b_rot(L, -luaL_checkint(L, 2)); + return b_rot(L, -luaL_checkinteger(L, 2)); } @@ -153,36 +166,35 @@ static int b_rrot (lua_State *L) { ** 'width' being used uninitialized.) */ static int fieldargs (lua_State *L, int farg, int *width) { - int f = luaL_checkint(L, farg); - int w = luaL_optint(L, farg + 1, 1); + lua_Integer f = luaL_checkinteger(L, farg); + lua_Integer w = luaL_optinteger(L, farg + 1, 1); luaL_argcheck(L, 0 <= f, farg, "field cannot be negative"); luaL_argcheck(L, 0 < w, farg + 1, "width must be positive"); if (f + w > LUA_NBITS) luaL_error(L, "trying to access non-existent bits"); - *width = w; - return f; + *width = (int)w; + return (int)f; } static int b_extract (lua_State *L) { int w; - b_uint r = luaL_checkunsigned(L, 1); + lua_Unsigned r = trim(checkunsigned(L, 1)); int f = fieldargs(L, 2, &w); r = (r >> f) & mask(w); - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } static int b_replace (lua_State *L) { int w; - b_uint r = luaL_checkunsigned(L, 1); - b_uint v = luaL_checkunsigned(L, 2); + lua_Unsigned r = trim(checkunsigned(L, 1)); + lua_Unsigned v = trim(checkunsigned(L, 2)); int f = fieldargs(L, 3, &w); - int m = mask(w); - v &= m; /* erase bits outside given width */ - r = (r & ~(m << f)) | (v << f); - lua_pushunsigned(L, r); + lua_Unsigned m = mask(w); + r = (r & ~(m << f)) | ((v & m) << f); + pushunsigned(L, r); return 1; } @@ -210,3 +222,12 @@ LUAMOD_API int luaopen_bit32 (lua_State *L) { return 1; } + +#else /* }{ */ + + +LUAMOD_API int luaopen_bit32 (lua_State *L) { + return luaL_error(L, "library 'bit32' has been deprecated"); +} + +#endif /* } */ diff --git a/depends/lua/src/lcode.c b/depends/lua/src/lcode.c index 820b95c0e..2cd0dd2d5 100644 --- a/depends/lua/src/lcode.c +++ b/depends/lua/src/lcode.c @@ -1,15 +1,18 @@ /* -** $Id: lcode.c,v 2.62.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lcode.c,v 2.109 2016/05/13 19:09:21 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ - -#include - #define lcode_c #define LUA_CORE +#include "lprefix.h" + + +#include +#include + #include "lua.h" #include "lcode.h" @@ -26,21 +29,45 @@ #include "lvm.h" +/* Maximum number of registers in a Lua function (must fit in 8 bits) */ +#define MAXREGS 255 + + #define hasjumps(e) ((e)->t != (e)->f) -static int isnumeral(expdesc *e) { - return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +static int tonumeral(expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a numeral */ + switch (e->k) { + case VKINT: + if (v) setivalue(v, e->u.ival); + return 1; + case VKFLT: + if (v) setfltvalue(v, e->u.nval); + return 1; + default: return 0; + } } +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ void luaK_nil (FuncState *fs, int from, int n) { Instruction *previous; int l = from + n - 1; /* last register to set nil */ if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ previous = &fs->f->code[fs->pc-1]; - if (GET_OPCODE(*previous) == OP_LOADNIL) { - int pfrom = GETARG_A(*previous); + if (GET_OPCODE(*previous) == OP_LOADNIL) { /* previous is LOADNIL? */ + int pfrom = GETARG_A(*previous); /* get previous range */ int pl = pfrom + GETARG_B(*previous); if ((pfrom <= from && from <= pl + 1) || (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ @@ -56,39 +83,86 @@ void luaK_nil (FuncState *fs, int from, int n) { } +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sBx(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + lua_assert(dest != NO_JUMP); + if (abs(offset) > MAXARG_sBx) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_sBx(*jmp, offset); +} + + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; /* nothing to concatenate? */ + else if (*l1 == NO_JUMP) /* no original list? */ + *l1 = l2; /* 'l1' points to 'l2' */ + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); /* last element links to 'l2' */ + } +} + + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). If there are jumps to +** this position (kept in 'jpc'), link them all together so that +** 'patchlistaux' will fix all them directly to the final destination. +*/ int luaK_jump (FuncState *fs) { int jpc = fs->jpc; /* save list of jumps to here */ int j; - fs->jpc = NO_JUMP; + fs->jpc = NO_JUMP; /* no more jumps to here */ j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); luaK_concat(fs, &j, jpc); /* keep them on hold */ return j; } +/* +** Code a 'return' instruction +*/ void luaK_ret (FuncState *fs, int first, int nret) { luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); } +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { luaK_codeABC(fs, op, A, B, C); return luaK_jump(fs); } -static void fixjump (FuncState *fs, int pc, int dest) { - Instruction *jmp = &fs->f->code[pc]; - int offset = dest-(pc+1); - lua_assert(dest != NO_JUMP); - if (abs(offset) > MAXARG_sBx) - luaX_syntaxerror(fs->ls, "control structure too long"); - SETARG_sBx(*jmp, offset); -} - - /* -** returns current `pc' and marks it as a jump target (to avoid wrong +** returns current 'pc' and marks it as a jump target (to avoid wrong ** optimizations with consecutive instructions not in the same basic block). */ int luaK_getlabel (FuncState *fs) { @@ -97,15 +171,11 @@ int luaK_getlabel (FuncState *fs) { } -static int getjump (FuncState *fs, int pc) { - int offset = GETARG_sBx(fs->f->code[pc]); - if (offset == NO_JUMP) /* point to itself represents end of list */ - return NO_JUMP; /* end of list */ - else - return (pc+1)+offset; /* turn offset into absolute position */ -} - - +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ static Instruction *getjumpcontrol (FuncState *fs, int pc) { Instruction *pi = &fs->f->code[pc]; if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) @@ -116,37 +186,41 @@ static Instruction *getjumpcontrol (FuncState *fs, int pc) { /* -** check whether list has any jump that do not produce a value -** (or produce an inverted value) +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) */ -static int need_value (FuncState *fs, int list) { - for (; list != NO_JUMP; list = getjump(fs, list)) { - Instruction i = *getjumpcontrol(fs, list); - if (GET_OPCODE(i) != OP_TESTSET) return 1; - } - return 0; /* not found */ -} - - static int patchtestreg (FuncState *fs, int node, int reg) { Instruction *i = getjumpcontrol(fs, node); if (GET_OPCODE(*i) != OP_TESTSET) return 0; /* cannot patch other instructions */ if (reg != NO_REG && reg != GETARG_B(*i)) SETARG_A(*i, reg); - else /* no register to put value or register already has the value */ + else { + /* no register to put value or register already has the value; + change instruction to simple test */ *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); - + } return 1; } +/* +** Traverse a list of tests ensuring no one produces a value +*/ static void removevalues (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) patchtestreg(fs, list, NO_REG); } +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, int dtarget) { while (list != NO_JUMP) { @@ -160,15 +234,35 @@ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, } +/* +** Ensure all pending jumps to current position are fixed (jumping +** to current position with no values) and reset list of pending +** jumps +*/ static void dischargejpc (FuncState *fs) { patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); fs->jpc = NO_JUMP; } +/* +** Add elements in 'list' to list of pending jumps to "here" +** (current position) +*/ +void luaK_patchtohere (FuncState *fs, int list) { + luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_concat(fs, &fs->jpc, list); +} + + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ void luaK_patchlist (FuncState *fs, int list, int target) { - if (target == fs->pc) - luaK_patchtohere(fs, list); + if (target == fs->pc) /* 'target' is current position? */ + luaK_patchtohere(fs, list); /* add list to pending jumps */ else { lua_assert(target < fs->pc); patchlistaux(fs, list, target, NO_REG, target); @@ -176,42 +270,29 @@ void luaK_patchlist (FuncState *fs, int list, int target) { } -LUAI_FUNC void luaK_patchclose (FuncState *fs, int list, int level) { +/* +** Path all jumps in 'list' to close upvalues up to given 'level' +** (The assertion checks that jumps either were closing nothing +** or were closing higher levels, from inner blocks.) +*/ +void luaK_patchclose (FuncState *fs, int list, int level) { level++; /* argument is +1 to reserve 0 as non-op */ - while (list != NO_JUMP) { - int next = getjump(fs, list); + for (; list != NO_JUMP; list = getjump(fs, list)) { lua_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP && (GETARG_A(fs->f->code[list]) == 0 || GETARG_A(fs->f->code[list]) >= level)); SETARG_A(fs->f->code[list], level); - list = next; - } -} - - -void luaK_patchtohere (FuncState *fs, int list) { - luaK_getlabel(fs); - luaK_concat(fs, &fs->jpc, list); -} - - -void luaK_concat (FuncState *fs, int *l1, int l2) { - if (l2 == NO_JUMP) return; - else if (*l1 == NO_JUMP) - *l1 = l2; - else { - int list = *l1; - int next; - while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ - list = next; - fixjump(fs, list, l2); } } +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ static int luaK_code (FuncState *fs, Instruction i) { Proto *f = fs->f; - dischargejpc(fs); /* `pc' will change */ + dischargejpc(fs); /* 'pc' will change */ /* put new instruction in code array */ luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction, MAX_INT, "opcodes"); @@ -224,6 +305,10 @@ static int luaK_code (FuncState *fs, Instruction i) { } +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { lua_assert(getOpMode(o) == iABC); lua_assert(getBMode(o) != OpArgN || b == 0); @@ -233,6 +318,9 @@ int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { } +/* +** Format and emit an 'iABx' instruction. +*/ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); lua_assert(getCMode(o) == OpArgN); @@ -241,12 +329,20 @@ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { } +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ static int codeextraarg (FuncState *fs, int a) { lua_assert(a <= MAXARG_Ax); return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); } +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ int luaK_codek (FuncState *fs, int reg, int k) { if (k <= MAXARG_Bx) return luaK_codeABx(fs, OP_LOADK, reg, k); @@ -258,22 +354,35 @@ int luaK_codek (FuncState *fs, int reg, int k) { } +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ void luaK_checkstack (FuncState *fs, int n) { int newstack = fs->freereg + n; if (newstack > fs->f->maxstacksize) { - if (newstack >= MAXSTACK) - luaX_syntaxerror(fs->ls, "function or expression too complex"); + if (newstack >= MAXREGS) + luaX_syntaxerror(fs->ls, + "function or expression needs too many registers"); fs->f->maxstacksize = cast_byte(newstack); } } +/* +** Reserve 'n' registers in register stack +*/ void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +) +*/ static void freereg (FuncState *fs, int reg) { if (!ISK(reg) && reg >= fs->nactvar) { fs->freereg--; @@ -282,31 +391,58 @@ static void freereg (FuncState *fs, int reg) { } +/* +** Free register used by expression 'e' (if any) +*/ static void freeexp (FuncState *fs, expdesc *e) { if (e->k == VNONRELOC) freereg(fs, e->u.info); } +/* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { + int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1; + int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1; + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +} + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use scanner's table to cache position of constants in constant list +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +*/ static int addk (FuncState *fs, TValue *key, TValue *v) { lua_State *L = fs->ls->L; - TValue *idx = luaH_set(L, fs->h, key); Proto *f = fs->f; + TValue *idx = luaH_set(L, fs->ls->h, key); /* index scanner table */ int k, oldsize; - if (ttisnumber(idx)) { - lua_Number n = nvalue(idx); - lua_number2int(k, n); - if (luaV_rawequalobj(&f->k[k], v)) - return k; - /* else may be a collision (e.g., between 0.0 and "\0\0\0\0\0\0\0\0"); - go through and create a new entry for this value */ + if (ttisinteger(idx)) { /* is there an index there? */ + k = cast_int(ivalue(idx)); + /* correct value? (warning: must distinguish floats from integers!) */ + if (k < fs->nk && ttype(&f->k[k]) == ttype(v) && + luaV_rawequalobj(&f->k[k], v)) + return k; /* reuse index */ } /* constant not found; create a new entry */ oldsize = f->sizek; k = fs->nk; /* numerical value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ - setnvalue(idx, cast_num(k)); + setivalue(idx, k); luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); setobj(L, &f->k[k], v); @@ -316,94 +452,134 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { } +/* +** Add a string to list of constants and return its index. +*/ int luaK_stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->ls->L, &o, s); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use string itself as key */ } -int luaK_numberK (FuncState *fs, lua_Number r) { - int n; - lua_State *L = fs->ls->L; +/* +** Add an integer to list of constants and return its index. +** Integers use userdata as keys to avoid collision with floats with +** same value; conversion to 'void*' is used only for hashing, so there +** are no "precision" problems. +*/ +int luaK_intK (FuncState *fs, lua_Integer n) { + TValue k, o; + setpvalue(&k, cast(void*, cast(size_t, n))); + setivalue(&o, n); + return addk(fs, &k, &o); +} + +/* +** Add a float to list of constants and return its index. +*/ +static int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; - setnvalue(&o, r); - if (r == 0 || luai_numisnan(NULL, r)) { /* handle -0 and NaN */ - /* use raw representation as key to avoid numeric problems */ - setsvalue(L, L->top++, luaS_newlstr(L, (char *)&r, sizeof(r))); - n = addk(fs, L->top - 1, &o); - L->top--; - } - else - n = addk(fs, &o, &o); /* regular case */ - return n; + setfltvalue(&o, r); + return addk(fs, &o, &o); /* use number itself as key */ } +/* +** Add a boolean to list of constants and return its index. +*/ static int boolK (FuncState *fs, int b) { TValue o; setbvalue(&o, b); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use boolean itself as key */ } +/* +** Add nil to list of constants and return its index. +*/ static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); /* cannot use nil as key; instead use table itself to represent nil */ - sethvalue(fs->ls->L, &k, fs->h); + sethvalue(fs->ls->L, &k, fs->ls->h); return addk(fs, &k, &v); } +/* +** Fix an expression to return the number of results 'nresults'. +** Either 'e' is a multi-ret expression (function call or vararg) +** or 'nresults' is LUA_MULTRET (as any expression can satisfy that). +*/ void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { if (e->k == VCALL) { /* expression is an open function call? */ - SETARG_C(getcode(fs, e), nresults+1); + SETARG_C(getinstruction(fs, e), nresults + 1); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), nresults+1); - SETARG_A(getcode(fs, e), fs->freereg); + Instruction *pc = &getinstruction(fs, e); + SETARG_B(*pc, nresults + 1); + SETARG_A(*pc, fs->freereg); luaK_reserveregs(fs, 1); } + else lua_assert(nresults == LUA_MULTRET); } +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOCABLE (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ void luaK_setoneret (FuncState *fs, expdesc *e) { if (e->k == VCALL) { /* expression is an open function call? */ - e->k = VNONRELOC; - e->u.info = GETARG_A(getcode(fs, e)); + /* already returns 1 value */ + lua_assert(GETARG_C(getinstruction(fs, e)) == 2); + e->k = VNONRELOC; /* result has fixed position */ + e->u.info = GETARG_A(getinstruction(fs, e)); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), 2); + SETARG_B(getinstruction(fs, e), 2); e->k = VRELOCABLE; /* can relocate its simple result */ } } +/* +** Ensure that expression 'e' is not a variable. +*/ void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { - case VLOCAL: { - e->k = VNONRELOC; + case VLOCAL: { /* already in a register */ + e->k = VNONRELOC; /* becomes a non-relocatable value */ break; } - case VUPVAL: { + case VUPVAL: { /* move value to some (pending) register */ e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); e->k = VRELOCABLE; break; } case VINDEXED: { - OpCode op = OP_GETTABUP; /* assume 't' is in an upvalue */ + OpCode op; freereg(fs, e->u.ind.idx); - if (e->u.ind.vt == VLOCAL) { /* 't' is in a register? */ + if (e->u.ind.vt == VLOCAL) { /* is 't' in a register? */ freereg(fs, e->u.ind.t); op = OP_GETTABLE; } + else { + lua_assert(e->u.ind.vt == VUPVAL); + op = OP_GETTABUP; /* 't' is in an upvalue */ + } e->u.info = luaK_codeABC(fs, op, 0, e->u.ind.t, e->u.ind.idx); e->k = VRELOCABLE; break; } - case VVARARG: - case VCALL: { + case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; } @@ -412,12 +588,10 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { } -static int code_label (FuncState *fs, int A, int b, int jump) { - luaK_getlabel(fs); /* those instructions may be jump targets */ - return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); -} - - +/* +** Ensures expression value is in register 'reg' (and therefore +** 'e' will become a non-relocatable expression). +*/ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { @@ -433,13 +607,17 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_codek(fs, reg, e->u.info); break; } - case VKNUM: { + case VKFLT: { luaK_codek(fs, reg, luaK_numberK(fs, e->u.nval)); break; } + case VKINT: { + luaK_codek(fs, reg, luaK_intK(fs, e->u.ival)); + break; + } case VRELOCABLE: { - Instruction *pc = &getcode(fs, e); - SETARG_A(*pc, reg); + Instruction *pc = &getinstruction(fs, e); + SETARG_A(*pc, reg); /* instruction will put result in 'reg' */ break; } case VNONRELOC: { @@ -448,7 +626,7 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { break; } default: { - lua_assert(e->k == VVOID || e->k == VJMP); + lua_assert(e->k == VJMP); return; /* nothing to do... */ } } @@ -457,26 +635,55 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures expression value is in any register. +*/ static void discharge2anyreg (FuncState *fs, expdesc *e) { - if (e->k != VNONRELOC) { - luaK_reserveregs(fs, 1); - discharge2reg(fs, e, fs->freereg-1); + if (e->k != VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs->freereg-1); /* put value there */ } } +static int code_loadbool (FuncState *fs, int A, int b, int jump) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); +} + + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +/* +** Ensures final expression result (including results from its jump +** lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ static void exp2reg (FuncState *fs, expdesc *e, int reg) { discharge2reg(fs, e, reg); - if (e->k == VJMP) - luaK_concat(fs, &e->t, e->u.info); /* put this jump in `t' list */ + if (e->k == VJMP) /* expression itself is a test? */ + luaK_concat(fs, &e->t, e->u.info); /* put this jump in 't' list */ if (hasjumps(e)) { int final; /* position after whole expression */ int p_f = NO_JUMP; /* position of an eventual LOAD false */ int p_t = NO_JUMP; /* position of an eventual LOAD true */ if (need_value(fs, e->t) || need_value(fs, e->f)) { int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); - p_f = code_label(fs, reg, 0, 1); - p_t = code_label(fs, reg, 1, 0); + p_f = code_loadbool(fs, reg, 0, 1); + p_t = code_loadbool(fs, reg, 1, 0); luaK_patchtohere(fs, fj); } final = luaK_getlabel(fs); @@ -489,6 +696,10 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures final expression result (including results from its jump +** lists) is in next available register. +*/ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); freeexp(fs, e); @@ -497,26 +708,39 @@ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result (including results from its jump +** lists) is in some (any) register and return that register. +*/ int luaK_exp2anyreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); - if (e->k == VNONRELOC) { - if (!hasjumps(e)) return e->u.info; /* exp is already in a register */ + if (e->k == VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e->u.info; /* result is already in a register */ if (e->u.info >= fs->nactvar) { /* reg. is not a local? */ - exp2reg(fs, e, e->u.info); /* put value on it */ + exp2reg(fs, e, e->u.info); /* put final result in it */ return e->u.info; } } - luaK_exp2nextreg(fs, e); /* default */ + luaK_exp2nextreg(fs, e); /* otherwise, use next available register */ return e->u.info; } +/* +** Ensures final expression result is either in a register or in an +** upvalue. +*/ void luaK_exp2anyregup (FuncState *fs, expdesc *e) { if (e->k != VUPVAL || hasjumps(e)) luaK_exp2anyreg(fs, e); } +/* +** Ensures final expression result is either in a register or it is +** a constant. +*/ void luaK_exp2val (FuncState *fs, expdesc *e) { if (hasjumps(e)) luaK_exp2anyreg(fs, e); @@ -525,29 +749,26 @@ void luaK_exp2val (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns R/K index. +*/ int luaK_exp2RK (FuncState *fs, expdesc *e) { luaK_exp2val(fs, e); - switch (e->k) { - case VTRUE: - case VFALSE: - case VNIL: { - if (fs->nk <= MAXINDEXRK) { /* constant fits in RK operand? */ - e->u.info = (e->k == VNIL) ? nilK(fs) : boolK(fs, (e->k == VTRUE)); - e->k = VK; - return RKASK(e->u.info); - } - else break; - } - case VKNUM: { - e->u.info = luaK_numberK(fs, e->u.nval); + switch (e->k) { /* move constants to 'k' */ + case VTRUE: e->u.info = boolK(fs, 1); goto vk; + case VFALSE: e->u.info = boolK(fs, 0); goto vk; + case VNIL: e->u.info = nilK(fs); goto vk; + case VKINT: e->u.info = luaK_intK(fs, e->u.ival); goto vk; + case VKFLT: e->u.info = luaK_numberK(fs, e->u.nval); goto vk; + case VK: + vk: e->k = VK; - /* go through */ - } - case VK: { - if (e->u.info <= MAXINDEXRK) /* constant fits in argC? */ + if (e->u.info <= MAXINDEXRK) /* constant fits in 'argC'? */ return RKASK(e->u.info); else break; - } default: break; } /* not a constant in the right range: put it in a register */ @@ -555,11 +776,14 @@ int luaK_exp2RK (FuncState *fs, expdesc *e) { } +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); - exp2reg(fs, ex, var->u.info); + exp2reg(fs, ex, var->u.info); /* compute 'ex' into proper place */ return; } case VUPVAL: { @@ -573,29 +797,32 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { luaK_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); break; } - default: { - lua_assert(0); /* invalid var kind to store */ - break; - } + default: lua_assert(0); /* invalid var kind to store */ } freeexp(fs, ex); } +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int ereg; luaK_exp2anyreg(fs, e); ereg = e->u.info; /* register where 'e' was placed */ freeexp(fs, e); e->u.info = fs->freereg; /* base register for op_self */ - e->k = VNONRELOC; + e->k = VNONRELOC; /* self expression has a fixed register */ luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key)); freeexp(fs, key); } -static void invertjump (FuncState *fs, expdesc *e) { +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +static void negatecondition (FuncState *fs, expdesc *e) { Instruction *pc = getjumpcontrol(fs, e->u.info); lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && GET_OPCODE(*pc) != OP_TEST); @@ -603,9 +830,15 @@ static void invertjump (FuncState *fs, expdesc *e) { } +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ static int jumponcond (FuncState *fs, expdesc *e, int cond) { if (e->k == VRELOCABLE) { - Instruction ie = getcode(fs, e); + Instruction ie = getinstruction(fs, e); if (GET_OPCODE(ie) == OP_NOT) { fs->pc--; /* remove previous OP_NOT */ return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); @@ -618,36 +851,42 @@ static int jumponcond (FuncState *fs, expdesc *e, int cond) { } +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ void luaK_goiftrue (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { - case VJMP: { - invertjump(fs, e); - pc = e->u.info; + case VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e->u.info; /* save jump position */ break; } - case VK: case VKNUM: case VTRUE: { + case VK: case VKFLT: case VKINT: case VTRUE: { pc = NO_JUMP; /* always true; do nothing */ break; } default: { - pc = jumponcond(fs, e, 0); + pc = jumponcond(fs, e, 0); /* jump when false */ break; } } - luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */ - luaK_patchtohere(fs, e->t); + luaK_concat(fs, &e->f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e->t); /* true list jumps to here (to go through) */ e->t = NO_JUMP; } +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ void luaK_goiffalse (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { case VJMP: { - pc = e->u.info; + pc = e->u.info; /* already jump if true */ break; } case VNIL: case VFALSE: { @@ -655,29 +894,32 @@ void luaK_goiffalse (FuncState *fs, expdesc *e) { break; } default: { - pc = jumponcond(fs, e, 1); + pc = jumponcond(fs, e, 1); /* jump if true */ break; } } - luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */ - luaK_patchtohere(fs, e->f); + luaK_concat(fs, &e->t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e->f); /* false list jumps to here (to go through) */ e->f = NO_JUMP; } +/* +** Code 'not e', doing constant folding. +*/ static void codenot (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { - e->k = VTRUE; + e->k = VTRUE; /* true == not nil == not false */ break; } - case VK: case VKNUM: case VTRUE: { - e->k = VFALSE; + case VK: case VKFLT: case VKINT: case VTRUE: { + e->k = VFALSE; /* false == not "x" == not 0.5 == not 1 == not true */ break; } case VJMP: { - invertjump(fs, e); + negatecondition(fs, e); break; } case VRELOCABLE: @@ -688,118 +930,177 @@ static void codenot (FuncState *fs, expdesc *e) { e->k = VRELOCABLE; break; } - default: { - lua_assert(0); /* cannot happen */ - break; - } + default: lua_assert(0); /* cannot happen */ } /* interchange true and false lists */ { int temp = e->f; e->f = e->t; e->t = temp; } - removevalues(fs, e->f); + removevalues(fs, e->f); /* values are useless when negated */ removevalues(fs, e->t); } +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. +*/ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { - lua_assert(!hasjumps(t)); - t->u.ind.t = t->u.info; - t->u.ind.idx = luaK_exp2RK(fs, k); - t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL - : check_exp(vkisinreg(t->k), VLOCAL); + lua_assert(!hasjumps(t) && (vkisinreg(t->k) || t->k == VUPVAL)); + t->u.ind.t = t->u.info; /* register or upvalue index */ + t->u.ind.idx = luaK_exp2RK(fs, k); /* R/K index for key */ + t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL : VLOCAL; t->k = VINDEXED; } -static int constfolding (OpCode op, expdesc *e1, expdesc *e2) { - lua_Number r; - if (!isnumeral(e1) || !isnumeral(e2)) return 0; - if ((op == OP_DIV || op == OP_MOD) && e2->u.nval == 0) - return 0; /* do not attempt to divide by 0 */ - r = luaO_arith(op - OP_ADD + LUA_OPADD, e1->u.nval, e2->u.nval); - e1->u.nval = r; - return 1; +/* +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. +*/ +static int validop (int op, TValue *v1, TValue *v2) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* conversion errors */ + lua_Integer i; + return (tointeger(v1, &i) && tointeger(v2, &i)); + } + case LUA_OPDIV: case LUA_OPIDIV: case LUA_OPMOD: /* division by 0 */ + return (nvalue(v2) != 0); + default: return 1; /* everything else is valid */ + } } -static void codearith (FuncState *fs, OpCode op, - expdesc *e1, expdesc *e2, int line) { - if (constfolding(op, e1, e2)) - return; - else { - int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; - int o1 = luaK_exp2RK(fs, e1); - if (o1 > o2) { - freeexp(fs, e1); - freeexp(fs, e2); - } - else { - freeexp(fs, e2); - freeexp(fs, e1); - } - e1->u.info = luaK_codeABC(fs, op, 0, o1, o2); - e1->k = VRELOCABLE; - luaK_fixline(fs, line); +/* +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) +*/ +static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { + TValue v1, v2, res; + if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2)) + return 0; /* non-numeric operands or not safe to fold */ + luaO_arith(fs->ls->L, op, &v1, &v2, &res); /* does operation */ + if (ttisinteger(&res)) { + e1->k = VKINT; + e1->u.ival = ivalue(&res); } + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ + lua_Number n = fltvalue(&res); + if (luai_numisnan(n) || n == 0) + return 0; + e1->k = VKFLT; + e1->u.nval = n; + } + return 1; } -static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, - expdesc *e2) { - int o1 = luaK_exp2RK(fs, e1); - int o2 = luaK_exp2RK(fs, e2); - freeexp(fs, e2); - freeexp(fs, e1); - if (cond == 0 && op != OP_EQ) { - int temp; /* exchange args to replace by `<' or `<=' */ - temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ - cond = 1; +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) { + int r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e->k = VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +*/ +static void codebinexpval (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int line) { + int rk1 = luaK_exp2RK(fs, e1); /* both operands are "RK" */ + int rk2 = luaK_exp2RK(fs, e2); + freeexps(fs, e1, e2); + e1->u.info = luaK_codeABC(fs, op, 0, rk1, rk2); /* generate opcode */ + e1->k = VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for comparisons. +** 'e1' was already put in R/K form by 'luaK_infix'. +*/ +static void codecomp (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int rk1 = (e1->k == VK) ? RKASK(e1->u.info) + : check_exp(e1->k == VNONRELOC, e1->u.info); + int rk2 = luaK_exp2RK(fs, e2); + freeexps(fs, e1, e2); + switch (opr) { + case OPR_NE: { /* '(a ~= b)' ==> 'not (a == b)' */ + e1->u.info = condjump(fs, OP_EQ, 0, rk1, rk2); + break; + } + case OPR_GT: case OPR_GE: { + /* '(a > b)' ==> '(b < a)'; '(a >= b)' ==> '(b <= a)' */ + OpCode op = cast(OpCode, (opr - OPR_NE) + OP_EQ); + e1->u.info = condjump(fs, op, 1, rk2, rk1); /* invert operands */ + break; + } + default: { /* '==', '<', '<=' use their own opcodes */ + OpCode op = cast(OpCode, (opr - OPR_EQ) + OP_EQ); + e1->u.info = condjump(fs, op, 1, rk1, rk2); + break; + } } - e1->u.info = condjump(fs, op, cond, o1, o2); e1->k = VJMP; } +/* +** Aplly prefix operation 'op' to expression 'e'. +*/ void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e, int line) { - expdesc e2; - e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; + static expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP}; /* fake 2nd operand */ switch (op) { - case OPR_MINUS: { - if (isnumeral(e)) /* minus constant? */ - e->u.nval = luai_numunm(NULL, e->u.nval); /* fold it */ - else { - luaK_exp2anyreg(fs, e); - codearith(fs, OP_UNM, e, &e2, line); - } + case OPR_MINUS: case OPR_BNOT: + if (constfolding(fs, op + LUA_OPUNM, e, &ef)) + break; + /* FALLTHROUGH */ + case OPR_LEN: + codeunexpval(fs, cast(OpCode, op + OP_UNM), e, line); break; - } case OPR_NOT: codenot(fs, e); break; - case OPR_LEN: { - luaK_exp2anyreg(fs, e); /* cannot operate on constants */ - codearith(fs, OP_LEN, e, &e2, line); - break; - } default: lua_assert(0); } } +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { switch (op) { case OPR_AND: { - luaK_goiftrue(fs, v); + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ break; } case OPR_OR: { - luaK_goiffalse(fs, v); + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ break; } case OPR_CONCAT: { - luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ + luaK_exp2nextreg(fs, v); /* operand must be on the 'stack' */ break; } - case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: - case OPR_MOD: case OPR_POW: { - if (!isnumeral(v)) luaK_exp2RK(fs, v); + case OPR_ADD: case OPR_SUB: + case OPR_MUL: case OPR_DIV: case OPR_IDIV: + case OPR_MOD: case OPR_POW: + case OPR_BAND: case OPR_BOR: case OPR_BXOR: + case OPR_SHL: case OPR_SHR: { + if (!tonumeral(v, NULL)) + luaK_exp2RK(fs, v); + /* else keep numeral, which may be folded with 2nd operand */ break; } default: { @@ -810,18 +1111,24 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { } +/* +** Finalize code for binary operation, after reading 2nd operand. +** For '(a .. b .. c)' (which is '(a .. (b .. c))', because +** concatenation is right associative), merge second CONCAT into first +** one. +*/ void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2, int line) { switch (op) { case OPR_AND: { - lua_assert(e1->t == NO_JUMP); /* list must be closed */ + lua_assert(e1->t == NO_JUMP); /* list closed by 'luK_infix' */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->f, e1->f); *e1 = *e2; break; } case OPR_OR: { - lua_assert(e1->f == NO_JUMP); /* list must be closed */ + lua_assert(e1->f == NO_JUMP); /* list closed by 'luK_infix' */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->t, e1->t); *e1 = *e2; @@ -829,29 +1136,30 @@ void luaK_posfix (FuncState *fs, BinOpr op, } case OPR_CONCAT: { luaK_exp2val(fs, e2); - if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { - lua_assert(e1->u.info == GETARG_B(getcode(fs, e2))-1); + if (e2->k == VRELOCABLE && + GET_OPCODE(getinstruction(fs, e2)) == OP_CONCAT) { + lua_assert(e1->u.info == GETARG_B(getinstruction(fs, e2))-1); freeexp(fs, e1); - SETARG_B(getcode(fs, e2), e1->u.info); + SETARG_B(getinstruction(fs, e2), e1->u.info); e1->k = VRELOCABLE; e1->u.info = e2->u.info; } else { luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ - codearith(fs, OP_CONCAT, e1, e2, line); + codebinexpval(fs, OP_CONCAT, e1, e2, line); } break; } case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: - case OPR_MOD: case OPR_POW: { - codearith(fs, cast(OpCode, op - OPR_ADD + OP_ADD), e1, e2, line); - break; - } - case OPR_EQ: case OPR_LT: case OPR_LE: { - codecomp(fs, cast(OpCode, op - OPR_EQ + OP_EQ), 1, e1, e2); + case OPR_IDIV: case OPR_MOD: case OPR_POW: + case OPR_BAND: case OPR_BOR: case OPR_BXOR: + case OPR_SHL: case OPR_SHR: { + if (!constfolding(fs, op + LUA_OPADD, e1, e2)) + codebinexpval(fs, cast(OpCode, op + OP_ADD), e1, e2, line); break; } + case OPR_EQ: case OPR_LT: case OPR_LE: case OPR_NE: case OPR_GT: case OPR_GE: { - codecomp(fs, cast(OpCode, op - OPR_NE + OP_EQ), 0, e1, e2); + codecomp(fs, op, e1, e2); break; } default: lua_assert(0); @@ -859,15 +1167,25 @@ void luaK_posfix (FuncState *fs, BinOpr op, } +/* +** Change line information associated with current position. +*/ void luaK_fixline (FuncState *fs, int line) { fs->f->lineinfo[fs->pc - 1] = line; } +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; int b = (tostore == LUA_MULTRET) ? 0 : tostore; - lua_assert(tostore != 0); + lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH); if (c <= MAXARG_C) luaK_codeABC(fs, OP_SETLIST, base, b, c); else if (c <= MAXARG_Ax) { diff --git a/depends/lua/src/lcorolib.c b/depends/lua/src/lcorolib.c index ce4f6ad42..2303429e7 100644 --- a/depends/lua/src/lcorolib.c +++ b/depends/lua/src/lcorolib.c @@ -1,15 +1,16 @@ /* -** $Id: lcorolib.c,v 1.5.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lcorolib.c,v 1.10 2016/04/11 19:19:55 roberto Exp $ ** Coroutine Library ** See Copyright Notice in lua.h */ +#define lcorolib_c +#define LUA_LIB -#include +#include "lprefix.h" -#define lcorolib_c -#define LUA_LIB +#include #include "lua.h" @@ -17,6 +18,13 @@ #include "lualib.h" +static lua_State *getco (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, co, 1, "thread expected"); + return co; +} + + static int auxresume (lua_State *L, lua_State *co, int narg) { int status; if (!lua_checkstack(co, narg)) { @@ -47,9 +55,8 @@ static int auxresume (lua_State *L, lua_State *co, int narg) { static int luaB_coresume (lua_State *L) { - lua_State *co = lua_tothread(L, 1); + lua_State *co = getco(L); int r; - luaL_argcheck(L, co, 1, "coroutine expected"); r = auxresume(L, co, lua_gettop(L) - 1); if (r < 0) { lua_pushboolean(L, 0); @@ -59,7 +66,7 @@ static int luaB_coresume (lua_State *L) { else { lua_pushboolean(L, 1); lua_insert(L, -(r + 1)); - return r + 1; /* return true + `resume' returns */ + return r + 1; /* return true + 'resume' returns */ } } @@ -68,7 +75,7 @@ static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); if (r < 0) { - if (lua_isstring(L, -1)) { /* error object is a string? */ + if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */ luaL_where(L, 1); /* add extra info */ lua_insert(L, -2); lua_concat(L, 2); @@ -102,8 +109,7 @@ static int luaB_yield (lua_State *L) { static int luaB_costatus (lua_State *L) { - lua_State *co = lua_tothread(L, 1); - luaL_argcheck(L, co, 1, "coroutine expected"); + lua_State *co = getco(L); if (L == co) lua_pushliteral(L, "running"); else { switch (lua_status(co)) { @@ -129,6 +135,12 @@ static int luaB_costatus (lua_State *L) { } +static int luaB_yieldable (lua_State *L) { + lua_pushboolean(L, lua_isyieldable(L)); + return 1; +} + + static int luaB_corunning (lua_State *L) { int ismain = lua_pushthread(L); lua_pushboolean(L, ismain); @@ -143,6 +155,7 @@ static const luaL_Reg co_funcs[] = { {"status", luaB_costatus}, {"wrap", luaB_cowrap}, {"yield", luaB_yield}, + {"isyieldable", luaB_yieldable}, {NULL, NULL} }; diff --git a/depends/lua/src/lctype.c b/depends/lua/src/lctype.c index 93f8cadc3..ae9367e69 100644 --- a/depends/lua/src/lctype.c +++ b/depends/lua/src/lctype.c @@ -1,5 +1,5 @@ /* -** $Id: lctype.c,v 1.11.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lctype.c,v 1.12 2014/11/02 19:19:04 roberto Exp $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ @@ -7,6 +7,9 @@ #define lctype_c #define LUA_CORE +#include "lprefix.h" + + #include "lctype.h" #if !LUA_USE_CTYPE /* { */ diff --git a/depends/lua/src/ldblib.c b/depends/lua/src/ldblib.c index 84fe3c7d8..786f6cd95 100644 --- a/depends/lua/src/ldblib.c +++ b/depends/lua/src/ldblib.c @@ -1,26 +1,42 @@ /* -** $Id: ldblib.c,v 1.132.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldblib.c,v 1.151 2015/11/23 11:29:43 roberto Exp $ ** Interface from Lua to its debug API ** See Copyright Notice in lua.h */ +#define ldblib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include #include -#define ldblib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" #include "lualib.h" -#define HOOKKEY "_HKEY" +/* +** The hook table at registry[&HOOKKEY] maps threads to their current +** hook function. (We only need the unique address of 'HOOKKEY'.) +*/ +static const int HOOKKEY = 0; +/* +** If L1 != L, L1 can be in any state, and therefore there are no +** guarantees about its stack space; any push in L1 must be +** checked. +*/ +static void checkstack (lua_State *L, lua_State *L1, int n) { + if (L != L1 && !lua_checkstack(L1, n)) + luaL_error(L, "stack overflow"); +} + static int db_getregistry (lua_State *L) { lua_pushvalue(L, LUA_REGISTRYINDEX); @@ -57,35 +73,20 @@ static int db_getuservalue (lua_State *L) { static int db_setuservalue (lua_State *L) { - if (lua_type(L, 1) == LUA_TLIGHTUSERDATA) - luaL_argerror(L, 1, "full userdata expected, got light userdata"); luaL_checktype(L, 1, LUA_TUSERDATA); - if (!lua_isnoneornil(L, 2)) - luaL_checktype(L, 2, LUA_TTABLE); + luaL_checkany(L, 2); lua_settop(L, 2); lua_setuservalue(L, 1); return 1; } -static void settabss (lua_State *L, const char *i, const char *v) { - lua_pushstring(L, v); - lua_setfield(L, -2, i); -} - - -static void settabsi (lua_State *L, const char *i, int v) { - lua_pushinteger(L, v); - lua_setfield(L, -2, i); -} - - -static void settabsb (lua_State *L, const char *i, int v) { - lua_pushboolean(L, v); - lua_setfield(L, -2, i); -} - - +/* +** Auxiliary function used by several library functions: check for +** an optional thread as function's first argument and set 'arg' with +** 1 if this argument is present (so that functions can skip it to +** access their other arguments) +*/ static lua_State *getthread (lua_State *L, int *arg) { if (lua_isthread(L, 1)) { *arg = 1; @@ -93,44 +94,74 @@ static lua_State *getthread (lua_State *L, int *arg) { } else { *arg = 0; - return L; + return L; /* function will operate over current thread */ } } +/* +** Variations of 'lua_settable', used by 'db_getinfo' to put results +** from 'lua_getinfo' into result table. Key is always a string; +** value can be a string, an int, or a boolean. +*/ +static void settabss (lua_State *L, const char *k, const char *v) { + lua_pushstring(L, v); + lua_setfield(L, -2, k); +} + +static void settabsi (lua_State *L, const char *k, int v) { + lua_pushinteger(L, v); + lua_setfield(L, -2, k); +} + +static void settabsb (lua_State *L, const char *k, int v) { + lua_pushboolean(L, v); + lua_setfield(L, -2, k); +} + + +/* +** In function 'db_getinfo', the call to 'lua_getinfo' may push +** results on the stack; later it creates the result table to put +** these objects. Function 'treatstackoption' puts the result from +** 'lua_getinfo' on top of the result table so that it can call +** 'lua_setfield'. +*/ static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { - if (L == L1) { - lua_pushvalue(L, -2); - lua_remove(L, -3); - } + if (L == L1) + lua_rotate(L, -2, 1); /* exchange object and table */ else - lua_xmove(L1, L, 1); - lua_setfield(L, -2, fname); + lua_xmove(L1, L, 1); /* move object to the "main" stack */ + lua_setfield(L, -2, fname); /* put object into table */ } +/* +** Calls 'lua_getinfo' and collects all results in a new table. +** L1 needs stack space for an optional input (function) plus +** two optional outputs (function and line table) from function +** 'lua_getinfo'. +*/ static int db_getinfo (lua_State *L) { lua_Debug ar; int arg; lua_State *L1 = getthread(L, &arg); const char *options = luaL_optstring(L, arg+2, "flnStu"); - if (lua_isnumber(L, arg+1)) { - if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) { + checkstack(L, L1, 3); + if (lua_isfunction(L, arg + 1)) { /* info about a function? */ + options = lua_pushfstring(L, ">%s", options); /* add '>' to 'options' */ + lua_pushvalue(L, arg + 1); /* move function to 'L1' stack */ + lua_xmove(L, L1, 1); + } + else { /* stack level */ + if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) { lua_pushnil(L); /* level out of range */ return 1; } } - else if (lua_isfunction(L, arg+1)) { - lua_pushfstring(L, ">%s", options); - options = lua_tostring(L, -1); - lua_pushvalue(L, arg+1); - lua_xmove(L, L1, 1); - } - else - return luaL_argerror(L, arg+1, "function or level expected"); if (!lua_getinfo(L1, options, &ar)) return luaL_argerror(L, arg+2, "invalid option"); - lua_createtable(L, 0, 2); + lua_newtable(L); /* table to collect results */ if (strchr(options, 'S')) { settabss(L, "source", ar.source); settabss(L, "short_src", ar.short_src); @@ -164,20 +195,22 @@ static int db_getlocal (lua_State *L) { lua_State *L1 = getthread(L, &arg); lua_Debug ar; const char *name; - int nvar = luaL_checkint(L, arg+2); /* local-variable index */ + int nvar = (int)luaL_checkinteger(L, arg + 2); /* local-variable index */ if (lua_isfunction(L, arg + 1)) { /* function argument? */ lua_pushvalue(L, arg + 1); /* push function */ lua_pushstring(L, lua_getlocal(L, NULL, nvar)); /* push local name */ - return 1; + return 1; /* return only name (there is no value) */ } else { /* stack-level argument */ - if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + int level = (int)luaL_checkinteger(L, arg + 1); + if (!lua_getstack(L1, level, &ar)) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); + checkstack(L, L1, 1); name = lua_getlocal(L1, &ar, nvar); if (name) { - lua_xmove(L1, L, 1); /* push local value */ + lua_xmove(L1, L, 1); /* move local value */ lua_pushstring(L, name); /* push name */ - lua_pushvalue(L, -2); /* re-order */ + lua_rotate(L, -2, 1); /* re-order */ return 2; } else { @@ -190,26 +223,36 @@ static int db_getlocal (lua_State *L) { static int db_setlocal (lua_State *L) { int arg; + const char *name; lua_State *L1 = getthread(L, &arg); lua_Debug ar; - if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + int level = (int)luaL_checkinteger(L, arg + 1); + int nvar = (int)luaL_checkinteger(L, arg + 2); + if (!lua_getstack(L1, level, &ar)) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); luaL_checkany(L, arg+3); lua_settop(L, arg+3); + checkstack(L, L1, 1); lua_xmove(L, L1, 1); - lua_pushstring(L, lua_setlocal(L1, &ar, luaL_checkint(L, arg+2))); + name = lua_setlocal(L1, &ar, nvar); + if (name == NULL) + lua_pop(L1, 1); /* pop value (if not popped by 'lua_setlocal') */ + lua_pushstring(L, name); return 1; } +/* +** get (if 'get' is true) or set an upvalue from a closure +*/ static int auxupvalue (lua_State *L, int get) { const char *name; - int n = luaL_checkint(L, 2); - luaL_checktype(L, 1, LUA_TFUNCTION); + int n = (int)luaL_checkinteger(L, 2); /* upvalue index */ + luaL_checktype(L, 1, LUA_TFUNCTION); /* closure */ name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); if (name == NULL) return 0; lua_pushstring(L, name); - lua_insert(L, -(get+1)); + lua_insert(L, -(get+1)); /* no-op if get is false */ return get + 1; } @@ -225,13 +268,15 @@ static int db_setupvalue (lua_State *L) { } +/* +** Check whether a given upvalue from a given closure exists and +** returns its index +*/ static int checkupval (lua_State *L, int argf, int argnup) { - lua_Debug ar; - int nup = luaL_checkint(L, argnup); - luaL_checktype(L, argf, LUA_TFUNCTION); - lua_pushvalue(L, argf); - lua_getinfo(L, ">u", &ar); - luaL_argcheck(L, 1 <= nup && nup <= ar.nups, argnup, "invalid upvalue index"); + int nup = (int)luaL_checkinteger(L, argnup); /* upvalue index */ + luaL_checktype(L, argf, LUA_TFUNCTION); /* closure */ + luaL_argcheck(L, (lua_getupvalue(L, argf, nup) != NULL), argnup, + "invalid upvalue index"); return nup; } @@ -253,26 +298,29 @@ static int db_upvaluejoin (lua_State *L) { } -#define gethooktable(L) luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY) - - +/* +** Call hook function registered at hook table for the current +** thread (if there is one) +*/ static void hookf (lua_State *L, lua_Debug *ar) { static const char *const hooknames[] = {"call", "return", "line", "count", "tail call"}; - gethooktable(L); + lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY); lua_pushthread(L); - lua_rawget(L, -2); - if (lua_isfunction(L, -1)) { - lua_pushstring(L, hooknames[(int)ar->event]); + if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */ + lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */ if (ar->currentline >= 0) - lua_pushinteger(L, ar->currentline); + lua_pushinteger(L, ar->currentline); /* push current line */ else lua_pushnil(L); lua_assert(lua_getinfo(L, "lS", ar)); - lua_call(L, 2, 0); + lua_call(L, 2, 0); /* call hook function */ } } +/* +** Convert a string mask (for 'sethook') into a bit mask +*/ static int makemask (const char *smask, int count) { int mask = 0; if (strchr(smask, 'c')) mask |= LUA_MASKCALL; @@ -283,6 +331,9 @@ static int makemask (const char *smask, int count) { } +/* +** Convert a bit mask (for 'gethook') into a string mask +*/ static char *unmakemask (int mask, char *smask) { int i = 0; if (mask & LUA_MASKCALL) smask[i++] = 'c'; @@ -297,26 +348,30 @@ static int db_sethook (lua_State *L) { int arg, mask, count; lua_Hook func; lua_State *L1 = getthread(L, &arg); - if (lua_isnoneornil(L, arg+1)) { + if (lua_isnoneornil(L, arg+1)) { /* no hook? */ lua_settop(L, arg+1); func = NULL; mask = 0; count = 0; /* turn off hooks */ } else { const char *smask = luaL_checkstring(L, arg+2); luaL_checktype(L, arg+1, LUA_TFUNCTION); - count = luaL_optint(L, arg+3, 0); + count = (int)luaL_optinteger(L, arg + 3, 0); func = hookf; mask = makemask(smask, count); } - if (gethooktable(L) == 0) { /* creating hook table? */ + if (lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY) == LUA_TNIL) { + lua_createtable(L, 0, 2); /* create a hook table */ + lua_pushvalue(L, -1); + lua_rawsetp(L, LUA_REGISTRYINDEX, &HOOKKEY); /* set it in position */ lua_pushstring(L, "k"); lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ lua_pushvalue(L, -1); lua_setmetatable(L, -2); /* setmetatable(hooktable) = hooktable */ } - lua_pushthread(L1); lua_xmove(L1, L, 1); - lua_pushvalue(L, arg+1); - lua_rawset(L, -3); /* set new hook */ - lua_sethook(L1, func, mask, count); /* set hooks */ + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */ + lua_pushvalue(L, arg + 1); /* value (hook function) */ + lua_rawset(L, -3); /* hooktable[L1] = new Lua hook */ + lua_sethook(L1, func, mask, count); return 0; } @@ -327,16 +382,19 @@ static int db_gethook (lua_State *L) { char buff[5]; int mask = lua_gethookmask(L1); lua_Hook hook = lua_gethook(L1); - if (hook != NULL && hook != hookf) /* external hook? */ + if (hook == NULL) /* no hook? */ + lua_pushnil(L); + else if (hook != hookf) /* external hook? */ lua_pushliteral(L, "external hook"); - else { - gethooktable(L); + else { /* hook table must exist */ + lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY); + checkstack(L, L1, 1); lua_pushthread(L1); lua_xmove(L1, L, 1); - lua_rawget(L, -2); /* get hook */ + lua_rawget(L, -2); /* 1st result = hooktable[L1] */ lua_remove(L, -2); /* remove hook table */ } - lua_pushstring(L, unmakemask(mask, buff)); - lua_pushinteger(L, lua_gethookcount(L1)); + lua_pushstring(L, unmakemask(mask, buff)); /* 2nd result = mask */ + lua_pushinteger(L, lua_gethookcount(L1)); /* 3rd result = count */ return 3; } @@ -344,13 +402,13 @@ static int db_gethook (lua_State *L) { static int db_debug (lua_State *L) { for (;;) { char buffer[250]; - luai_writestringerror("%s", "lua_debug> "); + lua_writestringerror("%s", "lua_debug> "); if (fgets(buffer, sizeof(buffer), stdin) == 0 || strcmp(buffer, "cont\n") == 0) return 0; if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || lua_pcall(L, 0, 0, 0)) - luai_writestringerror("%s\n", lua_tostring(L, -1)); + lua_writestringerror("%s\n", lua_tostring(L, -1)); lua_settop(L, 0); /* remove eventual returns */ } } @@ -363,7 +421,7 @@ static int db_traceback (lua_State *L) { if (msg == NULL && !lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */ lua_pushvalue(L, arg + 1); /* return it untouched */ else { - int level = luaL_optint(L, arg + 2, (L == L1) ? 1 : 0); + int level = (int)luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0); luaL_traceback(L, L1, msg, level); } return 1; diff --git a/depends/lua/src/ldebug.c b/depends/lua/src/ldebug.c index 20d663eff..e499ee362 100644 --- a/depends/lua/src/ldebug.c +++ b/depends/lua/src/ldebug.c @@ -1,18 +1,19 @@ /* -** $Id: ldebug.c,v 2.90.1.3 2013/05/16 16:04:15 roberto Exp $ +** $Id: ldebug.c,v 2.120 2016/03/31 19:01:21 roberto Exp $ ** Debug Interface ** See Copyright Notice in lua.h */ +#define ldebug_c +#define LUA_CORE + +#include "lprefix.h" + #include #include #include - -#define ldebug_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -33,6 +34,10 @@ #define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_TCCL) +/* Active Lua function (given call info) */ +#define ci_func(ci) (clLvalue((ci)->func)) + + static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); @@ -48,9 +53,31 @@ static int currentline (CallInfo *ci) { /* -** this function can be called asynchronous (e.g. during a signal) +** If function yielded, its 'func' can be in the 'extra' field. The +** next function restores 'func' to its correct value for debugging +** purposes. (It exchanges 'func' and 'extra'; so, when called again, +** after debugging, it also "re-restores" ** 'func' to its altered value. +*/ +static void swapextra (lua_State *L) { + if (L->status == LUA_YIELD) { + CallInfo *ci = L->ci; /* get function that yielded */ + StkId temp = ci->func; /* exchange its 'func' and 'extra' values */ + ci->func = restorestack(L, ci->extra); + ci->extra = savestack(L, temp); + } +} + + +/* +** This function can be called asynchronously (e.g. during a signal). +** Fields 'oldpc', 'basehookcount', and 'hookcount' (set by +** 'resethookcount') are for debug only, and it is no problem if they +** get arbitrary values (causes at most one wrong hook call). 'hookmask' +** is an atomic value. We assume that pointers are atomic too (e.g., gcc +** ensures that for all platforms where it runs). Moreover, 'hook' is +** always checked before being called (see 'luaD_hook'). */ -LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { +LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { if (func == NULL || mask == 0) { /* turn off hooks? */ mask = 0; func = NULL; @@ -61,7 +88,6 @@ LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { L->basehookcount = count; resethookcount(L); L->hookmask = cast_byte(mask); - return 1; } @@ -106,7 +132,7 @@ static const char *upvalname (Proto *p, int uv) { static const char *findvararg (CallInfo *ci, int n, StkId *pos) { int nparams = clLvalue(ci->func)->p->numparams; - if (n >= ci->u.l.base - ci->func - nparams) + if (n >= cast_int(ci->u.l.base - ci->func) - nparams) return NULL; /* no such vararg */ else { *pos = ci->func + nparams + n; @@ -144,6 +170,7 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n, LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { const char *name; lua_lock(L); + swapextra(L); if (ar == NULL) { /* information about non-active function? */ if (!isLfunction(L->top - 1)) /* not a Lua function? */ name = NULL; @@ -151,25 +178,30 @@ LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { name = luaF_getlocalname(clLvalue(L->top - 1)->p, n, 0); } else { /* active function; get information through 'ar' */ - StkId pos = 0; /* to avoid warnings */ + StkId pos = NULL; /* to avoid warnings */ name = findlocal(L, ar->i_ci, n, &pos); if (name) { setobj2s(L, L->top, pos); api_incr_top(L); } } + swapextra(L); lua_unlock(L); return name; } LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { - StkId pos = 0; /* to avoid warnings */ - const char *name = findlocal(L, ar->i_ci, n, &pos); + StkId pos = NULL; /* to avoid warnings */ + const char *name; lua_lock(L); - if (name) + swapextra(L); + name = findlocal(L, ar->i_ci, n, &pos); + if (name) { setobjs2s(L, pos, L->top - 1); - L->top--; /* pop value */ + L->top--; /* pop value */ + } + swapextra(L); lua_unlock(L); return name; } @@ -269,6 +301,7 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { CallInfo *ci; StkId func; lua_lock(L); + swapextra(L); if (*what == '>') { ci = NULL; func = L->top - 1; @@ -287,6 +320,7 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { setobjs2s(L, L->top, func); api_incr_top(L); } + swapextra(L); /* correct before option 'L', which can raise a mem. error */ if (strchr(what, 'L')) collectvalidlines(L, cl); lua_unlock(L); @@ -366,18 +400,13 @@ static int findsetreg (Proto *p, int lastpc, int reg) { case OP_JMP: { int b = GETARG_sBx(i); int dest = pc + 1 + b; - /* jump is forward and do not skip `lastpc'? */ + /* jump is forward and do not skip 'lastpc'? */ if (pc < dest && dest <= lastpc) { if (dest > jmptarget) jmptarget = dest; /* update 'jmptarget' */ } break; } - case OP_TEST: { - if (reg == a) /* jumped code can change 'a' */ - setreg = filterpc(pc, jmptarget); - break; - } default: if (testAMode(op) && reg == a) /* any instruction that set A */ setreg = filterpc(pc, jmptarget); @@ -443,10 +472,14 @@ static const char *getobjname (Proto *p, int lastpc, int reg, static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { - TMS tm; + TMS tm = (TMS)0; /* to avoid warnings */ Proto *p = ci_func(ci)->p; /* calling function */ int pc = currentpc(ci); /* calling instruction index */ Instruction i = p->code[pc]; /* calling instruction */ + if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */ + *name = "?"; + return "hook"; + } switch (GET_OPCODE(i)) { case OP_CALL: case OP_TAILCALL: /* get function name */ @@ -456,25 +489,27 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { return "for iterator"; } /* all other instructions can call only through metamethods */ - case OP_SELF: - case OP_GETTABUP: - case OP_GETTABLE: tm = TM_INDEX; break; - case OP_SETTABUP: - case OP_SETTABLE: tm = TM_NEWINDEX; break; - case OP_EQ: tm = TM_EQ; break; - case OP_ADD: tm = TM_ADD; break; - case OP_SUB: tm = TM_SUB; break; - case OP_MUL: tm = TM_MUL; break; - case OP_DIV: tm = TM_DIV; break; - case OP_MOD: tm = TM_MOD; break; - case OP_POW: tm = TM_POW; break; + case OP_SELF: case OP_GETTABUP: case OP_GETTABLE: + tm = TM_INDEX; + break; + case OP_SETTABUP: case OP_SETTABLE: + tm = TM_NEWINDEX; + break; + case OP_ADD: case OP_SUB: case OP_MUL: case OP_MOD: + case OP_POW: case OP_DIV: case OP_IDIV: case OP_BAND: + case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: { + int offset = cast_int(GET_OPCODE(i)) - cast_int(OP_ADD); /* ORDER OP */ + tm = cast(TMS, offset + cast_int(TM_ADD)); /* ORDER TM */ + break; + } case OP_UNM: tm = TM_UNM; break; + case OP_BNOT: tm = TM_BNOT; break; case OP_LEN: tm = TM_LEN; break; + case OP_CONCAT: tm = TM_CONCAT; break; + case OP_EQ: tm = TM_EQ; break; case OP_LT: tm = TM_LT; break; case OP_LE: tm = TM_LE; break; - case OP_CONCAT: tm = TM_CONCAT; break; - default: - return NULL; /* else no useful name can be found */ + default: lua_assert(0); /* other instructions cannot call a function */ } *name = getstr(G(L)->tmname[tm]); return "metamethod"; @@ -485,17 +520,21 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { /* -** only ANSI way to check whether a pointer points to an array -** (used only for error messages, so efficiency is not a big concern) +** The subtraction of two potentially unrelated pointers is +** not ISO C, but it should not crash a program; the subsequent +** checks are ISO C and ensure a correct result. */ static int isinstack (CallInfo *ci, const TValue *o) { - StkId p; - for (p = ci->u.l.base; p < ci->top; p++) - if (o == p) return 1; - return 0; + ptrdiff_t i = o - ci->u.l.base; + return (0 <= i && i < (ci->top - ci->u.l.base) && ci->u.l.base + i == o); } +/* +** Checks whether value 'o' came from an upvalue. (That can only happen +** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on +** upvalues.) +*/ static const char *getupvalname (CallInfo *ci, const TValue *o, const char **name) { LClosure *c = ci_func(ci); @@ -510,10 +549,9 @@ static const char *getupvalname (CallInfo *ci, const TValue *o, } -l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { +static const char *varinfo (lua_State *L, const TValue *o) { + const char *name = NULL; /* to avoid warnings */ CallInfo *ci = L->ci; - const char *name = NULL; - const char *t = objtypename(o); const char *kind = NULL; if (isLua(ci)) { kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */ @@ -521,73 +559,121 @@ l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { kind = getobjname(ci_func(ci)->p, currentpc(ci), cast_int(o - ci->u.l.base), &name); } - if (kind) - luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)", - op, kind, name, t); - else - luaG_runerror(L, "attempt to %s a %s value", op, t); + return (kind) ? luaO_pushfstring(L, " (%s '%s')", kind, name) : ""; } -l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2) { - if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; - lua_assert(!ttisstring(p1) && !ttisnumber(p1)); +l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { + const char *t = luaT_objtypename(L, o); + luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); +} + + +l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) { + if (ttisstring(p1) || cvt2str(p1)) p1 = p2; luaG_typeerror(L, p1, "concatenate"); } -l_noret luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) { - TValue temp; - if (luaV_tonumber(p1, &temp) == NULL) - p2 = p1; /* first operand is wrong */ - luaG_typeerror(L, p2, "perform arithmetic on"); +l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, const char *msg) { + lua_Number temp; + if (!tonumber(p1, &temp)) /* first operand is wrong? */ + p2 = p1; /* now second is wrong */ + luaG_typeerror(L, p2, msg); +} + + +/* +** Error when both values are convertible to numbers, but not to integers +*/ +l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) { + lua_Integer temp; + if (!tointeger(p1, &temp)) + p2 = p1; + luaG_runerror(L, "number%s has no integer representation", varinfo(L, p2)); } l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { - const char *t1 = objtypename(p1); - const char *t2 = objtypename(p2); - if (t1 == t2) + const char *t1 = luaT_objtypename(L, p1); + const char *t2 = luaT_objtypename(L, p2); + if (strcmp(t1, t2) == 0) luaG_runerror(L, "attempt to compare two %s values", t1); else luaG_runerror(L, "attempt to compare %s with %s", t1, t2); } -static void addinfo (lua_State *L, const char *msg) { - CallInfo *ci = L->ci; - if (isLua(ci)) { /* is Lua code? */ - char buff[LUA_IDSIZE]; /* add file:line information */ - int line = currentline(ci); - TString *src = ci_func(ci)->p->source; - if (src) - luaO_chunkid(buff, getstr(src), LUA_IDSIZE); - else { /* no source available; use "?" instead */ - buff[0] = '?'; buff[1] = '\0'; - } - luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); +/* add src:line information to 'msg' */ +const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, + int line) { + char buff[LUA_IDSIZE]; + if (src) + luaO_chunkid(buff, getstr(src), LUA_IDSIZE); + else { /* no source available; use "?" instead */ + buff[0] = '?'; buff[1] = '\0'; } + return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } l_noret luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { /* is there an error handling function? */ StkId errfunc = restorestack(L, L->errfunc); - if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR); setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ - L->top++; - luaD_call(L, L->top - 2, 1, 0); /* call it */ + L->top++; /* assume EXTRA_STACK */ + luaD_callnoyield(L, L->top - 2, 1); /* call it */ } luaD_throw(L, LUA_ERRRUN); } l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { + CallInfo *ci = L->ci; + const char *msg; va_list argp; va_start(argp, fmt); - addinfo(L, luaO_pushvfstring(L, fmt, argp)); + msg = luaO_pushvfstring(L, fmt, argp); /* format message */ va_end(argp); + if (isLua(ci)) /* if Lua function, add source:line information */ + luaG_addinfo(L, msg, ci_func(ci)->p->source, currentline(ci)); luaG_errormsg(L); } + +void luaG_traceexec (lua_State *L) { + CallInfo *ci = L->ci; + lu_byte mask = L->hookmask; + int counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); + if (counthook) + resethookcount(L); /* reset count */ + else if (!(mask & LUA_MASKLINE)) + return; /* no line hook and count != 0; nothing to be done */ + if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ + return; /* do not call hook again (VM yielded, so it did not move) */ + } + if (counthook) + luaD_hook(L, LUA_HOOKCOUNT, -1); /* call count hook */ + if (mask & LUA_MASKLINE) { + Proto *p = ci_func(ci)->p; + int npc = pcRel(ci->u.l.savedpc, p); + int newline = getfuncline(p, npc); + if (npc == 0 || /* call linehook when enter a new function, */ + ci->u.l.savedpc <= L->oldpc || /* when jump back (loop), or when */ + newline != getfuncline(p, pcRel(L->oldpc, p))) /* enter a new line */ + luaD_hook(L, LUA_HOOKLINE, newline); /* call line hook */ + } + L->oldpc = ci->u.l.savedpc; + if (L->status == LUA_YIELD) { /* did hook yield? */ + if (counthook) + L->hookcount = 1; /* undo decrement to zero */ + ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ + ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ + ci->func = L->top - 1; /* protect stack below results */ + luaD_throw(L, LUA_YIELD); + } +} + diff --git a/depends/lua/src/ldo.c b/depends/lua/src/ldo.c index e9dd5fa95..8804c9977 100644 --- a/depends/lua/src/ldo.c +++ b/depends/lua/src/ldo.c @@ -1,17 +1,19 @@ /* -** $Id: ldo.c,v 2.108.1.3 2013/11/08 18:22:50 roberto Exp $ +** $Id: ldo.c,v 2.151 2015/12/16 16:40:07 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ +#define ldo_c +#define LUA_CORE + +#include "lprefix.h" + #include #include #include -#define ldo_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -33,6 +35,8 @@ +#define errorstatus(s) ((s) > LUA_YIELD) + /* ** {====================================================== @@ -46,30 +50,33 @@ ** C++ code, with _longjmp/_setjmp when asked to use them, and with ** longjmp/setjmp otherwise. */ -#if !defined(LUAI_THROW) +#if !defined(LUAI_THROW) /* { */ + +#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* { */ -#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* C++ exceptions */ #define LUAI_THROW(L,c) throw(c) #define LUAI_TRY(L,c,a) \ try { a } catch(...) { if ((c)->status == 0) (c)->status = -1; } #define luai_jmpbuf int /* dummy variable */ -#elif defined(LUA_USE_ULONGJMP) -/* in Unix, try _longjmp/_setjmp (more efficient) */ +#elif defined(LUA_USE_POSIX) /* }{ */ + +/* in POSIX, try _longjmp/_setjmp (more efficient) */ #define LUAI_THROW(L,c) _longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf -#else -/* default handling with long jumps */ +#else /* }{ */ + +/* ISO C handling with long jumps */ #define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf -#endif +#endif /* } */ -#endif +#endif /* } */ @@ -106,15 +113,19 @@ l_noret luaD_throw (lua_State *L, int errcode) { LUAI_THROW(L, L->errorJmp); /* jump to it */ } else { /* thread has no error handler */ + global_State *g = G(L); L->status = cast_byte(errcode); /* mark it as dead */ - if (G(L)->mainthread->errorJmp) { /* main thread has a handler? */ - setobjs2s(L, G(L)->mainthread->top++, L->top - 1); /* copy error obj. */ - luaD_throw(G(L)->mainthread, errcode); /* re-throw in main thread */ + if (g->mainthread->errorJmp) { /* main thread has a handler? */ + setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ + luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ } else { /* no handler at all; abort */ - if (G(L)->panic) { /* panic function? */ + if (g->panic) { /* panic function? */ + seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ + if (L->ci->top < L->top) + L->ci->top = L->top; /* pushing msg. can break this invariant */ lua_unlock(L); - G(L)->panic(L); /* call it (last chance to jump out) */ + g->panic(L); /* call panic function (last chance to jump out) */ } abort(); } @@ -139,12 +150,17 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { /* }====================================================== */ +/* +** {================================================================== +** Stack reallocation +** =================================================================== +*/ static void correctstack (lua_State *L, TValue *oldstack) { CallInfo *ci; - GCObject *up; + UpVal *up; L->top = (L->top - oldstack) + L->stack; - for (up = L->openupval; up != NULL; up = up->gch.next) - gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; + for (up = L->openupval; up != NULL; up = up->u.open.next) + up->v = (up->v - oldstack) + L->stack; for (ci = L->ci; ci != NULL; ci = ci->previous) { ci->top = (ci->top - oldstack) + L->stack; ci->func = (ci->func - oldstack) + L->stack; @@ -206,17 +222,34 @@ void luaD_shrinkstack (lua_State *L) { int inuse = stackinuse(L); int goodsize = inuse + (inuse / 8) + 2*EXTRA_STACK; if (goodsize > LUAI_MAXSTACK) goodsize = LUAI_MAXSTACK; - if (inuse > LUAI_MAXSTACK || /* handling stack overflow? */ - goodsize >= L->stacksize) /* would grow instead of shrink? */ - condmovestack(L); /* don't change stack (change only for debugging) */ + if (L->stacksize > LUAI_MAXSTACK) /* was handling stack overflow? */ + luaE_freeCI(L); /* free all CIs (list grew because of an error) */ else + luaE_shrinkCI(L); /* shrink list */ + if (inuse <= LUAI_MAXSTACK && /* not handling stack overflow? */ + goodsize < L->stacksize) /* trying to shrink? */ luaD_reallocstack(L, goodsize); /* shrink it */ + else + condmovestack(L,,); /* don't change stack (change only for debugging) */ } +void luaD_inctop (lua_State *L) { + luaD_checkstack(L, 1); + L->top++; +} + +/* }================================================================== */ + + +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which triggers this +** function, can be changed asynchronously by signals.) +*/ void luaD_hook (lua_State *L, int event, int line) { lua_Hook hook = L->hook; - if (hook && L->allowhook) { + if (hook && L->allowhook) { /* make sure there is a hook */ CallInfo *ci = L->ci; ptrdiff_t top = savestack(L, L->top); ptrdiff_t ci_top = savestack(L, ci->top); @@ -258,31 +291,34 @@ static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { int i; int nfixargs = p->numparams; StkId base, fixed; - lua_assert(actual >= nfixargs); /* move fixed parameters to final position */ - luaD_checkstack(L, p->maxstacksize); /* check again for new 'base' */ fixed = L->top - actual; /* first fixed argument */ base = L->top; /* final position of first argument */ - for (i=0; itop++, fixed + i); - setnilvalue(fixed + i); + setnilvalue(fixed + i); /* erase original copy (for GC) */ } + for (; i < nfixargs; i++) + setnilvalue(L->top++); /* complete missing arguments */ return base; } -static StkId tryfuncTM (lua_State *L, StkId func) { +/* +** Check whether __call metafield of 'func' is a function. If so, put +** it in stack below original 'func' so that 'luaD_precall' can call +** it. Raise an error if __call metafield is not a function. +*/ +static void tryfuncTM (lua_State *L, StkId func) { const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); StkId p; - ptrdiff_t funcr = savestack(L, func); if (!ttisfunction(tm)) luaG_typeerror(L, func, "call"); - /* Open a hole inside the stack at `func' */ - for (p = L->top; p > func; p--) setobjs2s(L, p, p-1); - incr_top(L); - func = restorestack(L, funcr); /* previous call may change stack */ + /* Open a hole inside the stack at 'func' */ + for (p = L->top; p > func; p--) + setobjs2s(L, p, p-1); + L->top++; /* slot ensured by caller */ setobj2s(L, func, tm); /* tag method is the new function to be called */ - return func; } @@ -290,79 +326,133 @@ static StkId tryfuncTM (lua_State *L, StkId func) { #define next_ci(L) (L->ci = (L->ci->next ? L->ci->next : luaE_extendCI(L))) +/* macro to check stack size, preserving 'p' */ +#define checkstackp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \ + luaC_checkGC(L), /* stack grow uses memory */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + + /* -** returns true if function has been executed (C function) +** Prepares a function call: checks the stack, creates a new CallInfo +** entry, fills in the relevant information, calls hook if needed. +** If function is a C function, does the call, too. (Otherwise, leave +** the execution ('luaV_execute') to the caller, to allow stackless +** calls.) Returns true iff function has been executed (C function). */ int luaD_precall (lua_State *L, StkId func, int nresults) { lua_CFunction f; CallInfo *ci; - int n; /* number of arguments (Lua) or returns (C) */ - ptrdiff_t funcr = savestack(L, func); switch (ttype(func)) { + case LUA_TCCL: /* C closure */ + f = clCvalue(func)->f; + goto Cfunc; case LUA_TLCF: /* light C function */ f = fvalue(func); - goto Cfunc; - case LUA_TCCL: { /* C closure */ - f = clCvalue(func)->f; - Cfunc: - luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + Cfunc: { + int n; /* number of returns */ + checkstackp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ ci = next_ci(L); /* now 'enter' new function */ ci->nresults = nresults; - ci->func = restorestack(L, funcr); + ci->func = func; ci->top = L->top + LUA_MINSTACK; lua_assert(ci->top <= L->stack_last); ci->callstatus = 0; - luaC_checkGC(L); /* stack grow uses memory */ if (L->hookmask & LUA_MASKCALL) luaD_hook(L, LUA_HOOKCALL, -1); lua_unlock(L); n = (*f)(L); /* do the actual call */ lua_lock(L); api_checknelems(L, n); - luaD_poscall(L, L->top - n); + luaD_poscall(L, ci, L->top - n, n); return 1; } case LUA_TLCL: { /* Lua function: prepare its call */ StkId base; Proto *p = clLvalue(func)->p; - n = cast_int(L->top - func) - 1; /* number of real arguments */ - luaD_checkstack(L, p->maxstacksize); - for (; n < p->numparams; n++) - setnilvalue(L->top++); /* complete missing arguments */ - if (!p->is_vararg) { - func = restorestack(L, funcr); + int n = cast_int(L->top - func) - 1; /* number of real arguments */ + int fsize = p->maxstacksize; /* frame size */ + checkstackp(L, fsize, func); + if (p->is_vararg != 1) { /* do not use vararg? */ + for (; n < p->numparams; n++) + setnilvalue(L->top++); /* complete missing arguments */ base = func + 1; } - else { + else base = adjust_varargs(L, p, n); - func = restorestack(L, funcr); /* previous call can change stack */ - } ci = next_ci(L); /* now 'enter' new function */ ci->nresults = nresults; ci->func = func; ci->u.l.base = base; - ci->top = base + p->maxstacksize; + L->top = ci->top = base + fsize; lua_assert(ci->top <= L->stack_last); ci->u.l.savedpc = p->code; /* starting point */ ci->callstatus = CIST_LUA; - L->top = ci->top; - luaC_checkGC(L); /* stack grow uses memory */ if (L->hookmask & LUA_MASKCALL) callhook(L, ci); return 0; } default: { /* not a function */ - func = tryfuncTM(L, func); /* retry with 'function' tag method */ + checkstackp(L, 1, func); /* ensure space for metamethod */ + tryfuncTM(L, func); /* try to get '__call' metamethod */ return luaD_precall(L, func, nresults); /* now it must be a function */ } } } -int luaD_poscall (lua_State *L, StkId firstResult) { +/* +** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'. +** Handle most typical cases (zero results for commands, one result for +** expressions, multiple results for tail calls/single parameters) +** separated. +*/ +static int moveresults (lua_State *L, const TValue *firstResult, StkId res, + int nres, int wanted) { + switch (wanted) { /* handle typical cases separately */ + case 0: break; /* nothing to move */ + case 1: { /* one result needed */ + if (nres == 0) /* no results? */ + firstResult = luaO_nilobject; /* adjust with nil */ + setobjs2s(L, res, firstResult); /* move it to proper place */ + break; + } + case LUA_MULTRET: { + int i; + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstResult + i); + L->top = res + nres; + return 0; /* wanted == LUA_MULTRET */ + } + default: { + int i; + if (wanted <= nres) { /* enough results? */ + for (i = 0; i < wanted; i++) /* move wanted results to correct place */ + setobjs2s(L, res + i, firstResult + i); + } + else { /* not enough results; use all of them plus nils */ + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstResult + i); + for (; i < wanted; i++) /* complete wanted number of results */ + setnilvalue(res + i); + } + break; + } + } + L->top = res + wanted; /* top points after the last result */ + return 1; +} + + +/* +** Finishes a function call: calls hook if necessary, removes CallInfo, +** moves current number of results to proper place; returns 0 iff call +** wanted multiple (variable number of) results. +*/ +int luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult, int nres) { StkId res; - int wanted, i; - CallInfo *ci = L->ci; + int wanted = ci->nresults; if (L->hookmask & (LUA_MASKRET | LUA_MASKLINE)) { if (L->hookmask & LUA_MASKRET) { ptrdiff_t fr = savestack(L, firstResult); /* hook may change stack */ @@ -372,15 +462,24 @@ int luaD_poscall (lua_State *L, StkId firstResult) { L->oldpc = ci->previous->u.l.savedpc; /* 'oldpc' for caller function */ } res = ci->func; /* res == final position of 1st result */ - wanted = ci->nresults; - L->ci = ci = ci->previous; /* back to caller */ - /* move results to correct place */ - for (i = wanted; i != 0 && firstResult < L->top; i--) - setobjs2s(L, res++, firstResult++); - while (i-- > 0) - setnilvalue(res++); - L->top = res; - return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ + L->ci = ci->previous; /* back to caller */ + /* move results to proper place */ + return moveresults(L, firstResult, res, nres, wanted); +} + + +/* +** Check appropriate error for stack overflow ("regular" overflow or +** overflow while handling stack overflow). If 'nCalls' is larger than +** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but +** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to +** allow overflow handling to work) +*/ +static void stackerror (lua_State *L) { + if (L->nCcalls == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } @@ -390,53 +489,67 @@ int luaD_poscall (lua_State *L, StkId firstResult) { ** When returns, all the results are on the stack, starting at the original ** function position. */ -void luaD_call (lua_State *L, StkId func, int nResults, int allowyield) { - if (++L->nCcalls >= LUAI_MAXCCALLS) { - if (L->nCcalls == LUAI_MAXCCALLS) - luaG_runerror(L, "C stack overflow"); - else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) - luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ - } - if (!allowyield) L->nny++; +void luaD_call (lua_State *L, StkId func, int nResults) { + if (++L->nCcalls >= LUAI_MAXCCALLS) + stackerror(L); if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ luaV_execute(L); /* call it */ - if (!allowyield) L->nny--; L->nCcalls--; } -static void finishCcall (lua_State *L) { +/* +** Similar to 'luaD_call', but does not allow yields during the call +*/ +void luaD_callnoyield (lua_State *L, StkId func, int nResults) { + L->nny++; + luaD_call(L, func, nResults); + L->nny--; +} + + +/* +** Completes the execution of an interrupted C function, calling its +** continuation function. +*/ +static void finishCcall (lua_State *L, int status) { CallInfo *ci = L->ci; int n; - lua_assert(ci->u.c.k != NULL); /* must have a continuation */ - lua_assert(L->nny == 0); + /* must have a continuation and must be able to call it */ + lua_assert(ci->u.c.k != NULL && L->nny == 0); + /* error status can only happen in a protected call */ + lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD); if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ ci->callstatus &= ~CIST_YPCALL; /* finish 'lua_pcall' */ L->errfunc = ci->u.c.old_errfunc; } - /* finish 'lua_callk'/'lua_pcall' */ + /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already + handled */ adjustresults(L, ci->nresults); /* call continuation function */ - if (!(ci->callstatus & CIST_STAT)) /* no call status? */ - ci->u.c.status = LUA_YIELD; /* 'default' status */ - lua_assert(ci->u.c.status != LUA_OK); - ci->callstatus = (ci->callstatus & ~(CIST_YPCALL | CIST_STAT)) | CIST_YIELDED; lua_unlock(L); - n = (*ci->u.c.k)(L); + n = (*ci->u.c.k)(L, status, ci->u.c.ctx); lua_lock(L); api_checknelems(L, n); /* finish 'luaD_precall' */ - luaD_poscall(L, L->top - n); + luaD_poscall(L, ci, L->top - n, n); } +/* +** Executes "full continuation" (everything in the stack) of a +** previously interrupted coroutine until the stack is empty (or another +** interruption long-jumps out of the loop). If the coroutine is +** recovering from an error, 'ud' points to the error status, which must +** be passed to the first continuation function (otherwise the default +** status is LUA_YIELD). +*/ static void unroll (lua_State *L, void *ud) { - UNUSED(ud); - for (;;) { - if (L->ci == &L->base_ci) /* stack is empty? */ - return; /* coroutine finished normally */ + if (ud != NULL) /* error status? */ + finishCcall(L, *(int *)ud); /* finish 'lua_pcallk' callee */ + while (L->ci != &L->base_ci) { /* something in the stack */ if (!isLua(L->ci)) /* C function? */ - finishCcall(L); + finishCcall(L, LUA_YIELD); /* complete its execution */ else { /* Lua function */ luaV_finishOp(L); /* finish interrupted instruction */ luaV_execute(L); /* execute down to higher C 'boundary' */ @@ -446,7 +559,8 @@ static void unroll (lua_State *L, void *ud) { /* -** check whether thread has a suspended protected call +** Try to find a suspended protected call (a "recover point") for the +** given thread. */ static CallInfo *findpcall (lua_State *L) { CallInfo *ci; @@ -458,6 +572,11 @@ static CallInfo *findpcall (lua_State *L) { } +/* +** Recovers from an error in a coroutine. Finds a recover point (if +** there is one) and completes the execution of the interrupted +** 'luaD_pcall'. If there is no recover point, returns zero. +*/ static int recover (lua_State *L, int status) { StkId oldtop; CallInfo *ci = findpcall(L); @@ -467,12 +586,10 @@ static int recover (lua_State *L, int status) { luaF_close(L, oldtop); seterrorobj(L, status, oldtop); L->ci = ci; - L->allowhook = ci->u.c.old_allowhook; + L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ L->nny = 0; /* should be zero to be yieldable */ luaD_shrinkstack(L); L->errfunc = ci->u.c.old_errfunc; - ci->callstatus |= CIST_STAT; /* call has error status */ - ci->u.c.status = status; /* (here it is) */ return 1; /* continue running the coroutine */ } @@ -491,11 +608,16 @@ static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { /* -** do the work for 'lua_resume' in protected mode +** Do the work for 'lua_resume' in protected mode. Most of the work +** depends on the status of the coroutine: initial state, suspended +** inside a hook, or regularly suspended (optionally with a continuation +** function), plus erroneous cases: non-suspended coroutine or dead +** coroutine. */ static void resume (lua_State *L, void *ud) { int nCcalls = L->nCcalls; - StkId firstArg = cast(StkId, ud); + int n = *(cast(int*, ud)); /* number of arguments */ + StkId firstArg = L->top - n; /* first argument */ CallInfo *ci = L->ci; if (nCcalls >= LUAI_MAXCCALLS) resume_error(L, "C stack overflow", firstArg); @@ -509,24 +631,21 @@ static void resume (lua_State *L, void *ud) { else if (L->status != LUA_YIELD) resume_error(L, "cannot resume dead coroutine", firstArg); else { /* resuming from previous yield */ - L->status = LUA_OK; + L->status = LUA_OK; /* mark that it is running (again) */ ci->func = restorestack(L, ci->extra); if (isLua(ci)) /* yielded inside a hook? */ luaV_execute(L); /* just continue running Lua code */ else { /* 'common' yield */ - if (ci->u.c.k != NULL) { /* does it have a continuation? */ - int n; - ci->u.c.status = LUA_YIELD; /* 'default' status */ - ci->callstatus |= CIST_YIELDED; + if (ci->u.c.k != NULL) { /* does it have a continuation function? */ lua_unlock(L); - n = (*ci->u.c.k)(L); /* call continuation */ + n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx); /* call continuation */ lua_lock(L); api_checknelems(L, n); firstArg = L->top - n; /* yield results come from continuation */ } - luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ + luaD_poscall(L, ci, firstArg, n); /* finish 'luaD_precall' */ } - unroll(L, NULL); + unroll(L, NULL); /* run continuation */ } lua_assert(nCcalls == L->nCcalls); } @@ -534,27 +653,26 @@ static void resume (lua_State *L, void *ud) { LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { int status; - int oldnny = L->nny; /* save 'nny' */ + unsigned short oldnny = L->nny; /* save "number of non-yieldable" calls */ lua_lock(L); luai_userstateresume(L, nargs); L->nCcalls = (from) ? from->nCcalls + 1 : 1; L->nny = 0; /* allow yields */ api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); - status = luaD_rawrunprotected(L, resume, L->top - nargs); + status = luaD_rawrunprotected(L, resume, &nargs); if (status == -1) /* error calling 'lua_resume'? */ status = LUA_ERRRUN; - else { /* yield or regular error */ - while (status != LUA_OK && status != LUA_YIELD) { /* error? */ - if (recover(L, status)) /* recover point? */ - status = luaD_rawrunprotected(L, unroll, NULL); /* run continuation */ - else { /* unrecoverable error */ - L->status = cast_byte(status); /* mark thread as `dead' */ - seterrorobj(L, status, L->top); - L->ci->top = L->top; - break; - } + else { /* continue running after recoverable errors */ + while (errorstatus(status) && recover(L, status)) { + /* unroll continuation */ + status = luaD_rawrunprotected(L, unroll, &status); } - lua_assert(status == L->status); + if (errorstatus(status)) { /* unrecoverable error? */ + L->status = cast_byte(status); /* mark thread as 'dead' */ + seterrorobj(L, status, L->top); /* push error message */ + L->ci->top = L->top; + } + else lua_assert(status == L->status); /* normal end or yield */ } L->nny = oldnny; /* restore 'nny' */ L->nCcalls--; @@ -564,7 +682,13 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { } -LUA_API int lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { +LUA_API int lua_isyieldable (lua_State *L) { + return (L->nny == 0); +} + + +LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k) { CallInfo *ci = L->ci; luai_userstateyield(L, nresults); lua_lock(L); @@ -619,7 +743,7 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u, /* ** Execute a protected parser. */ -struct SParser { /* data to `f_parser' */ +struct SParser { /* data to 'f_parser' */ ZIO *z; Mbuffer buff; /* dynamic structure used by the scanner */ Dyndata dyd; /* dynamic structures used by the parser */ @@ -631,31 +755,26 @@ struct SParser { /* data to `f_parser' */ static void checkmode (lua_State *L, const char *mode, const char *x) { if (mode && strchr(mode, x[0]) == NULL) { luaO_pushfstring(L, - "attempt to load a %s chunk (mode is " LUA_QS ")", x, mode); + "attempt to load a %s chunk (mode is '%s')", x, mode); luaD_throw(L, LUA_ERRSYNTAX); } } static void f_parser (lua_State *L, void *ud) { - int i; - Closure *cl; + LClosure *cl; struct SParser *p = cast(struct SParser *, ud); int c = zgetc(p->z); /* read first character */ if (c == LUA_SIGNATURE[0]) { checkmode(L, p->mode, "binary"); - cl = luaU_undump(L, p->z, &p->buff, p->name); + cl = luaU_undump(L, p->z, p->name); } else { checkmode(L, p->mode, "text"); cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); } - lua_assert(cl->l.nupvalues == cl->l.p->sizeupvalues); - for (i = 0; i < cl->l.nupvalues; i++) { /* initialize upvalues */ - UpVal *up = luaF_newupval(L); - cl->l.upvals[i] = up; - luaC_objbarrier(L, cl, up); - } + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luaF_initupvals(L, cl); } diff --git a/depends/lua/src/ldump.c b/depends/lua/src/ldump.c index 61fa2cd89..016e30082 100644 --- a/depends/lua/src/ldump.c +++ b/depends/lua/src/ldump.c @@ -1,173 +1,215 @@ /* -** $Id: ldump.c,v 2.17.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldump.c,v 2.37 2015/10/08 15:53:49 roberto Exp $ ** save precompiled Lua chunks ** See Copyright Notice in lua.h */ -#include - #define ldump_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "lobject.h" #include "lstate.h" #include "lundump.h" + typedef struct { - lua_State* L; - lua_Writer writer; - void* data; - int strip; - int status; + lua_State *L; + lua_Writer writer; + void *data; + int strip; + int status; } DumpState; -#define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D) -#define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D) - -static void DumpBlock(const void* b, size_t size, DumpState* D) -{ - if (D->status==0) - { - lua_unlock(D->L); - D->status=(*D->writer)(D->L,b,size,D->data); - lua_lock(D->L); - } + +/* +** All high-level dumps go through DumpVector; you can change it to +** change the endianness of the result +*/ +#define DumpVector(v,n,D) DumpBlock(v,(n)*sizeof((v)[0]),D) + +#define DumpLiteral(s,D) DumpBlock(s, sizeof(s) - sizeof(char), D) + + +static void DumpBlock (const void *b, size_t size, DumpState *D) { + if (D->status == 0 && size > 0) { + lua_unlock(D->L); + D->status = (*D->writer)(D->L, b, size, D->data); + lua_lock(D->L); + } } -static void DumpChar(int y, DumpState* D) -{ - char x=(char)y; - DumpVar(x,D); + +#define DumpVar(x,D) DumpVector(&x,1,D) + + +static void DumpByte (int y, DumpState *D) { + lu_byte x = (lu_byte)y; + DumpVar(x, D); } -static void DumpInt(int x, DumpState* D) -{ - DumpVar(x,D); + +static void DumpInt (int x, DumpState *D) { + DumpVar(x, D); } -static void DumpNumber(lua_Number x, DumpState* D) -{ - DumpVar(x,D); + +static void DumpNumber (lua_Number x, DumpState *D) { + DumpVar(x, D); } -static void DumpVector(const void* b, int n, size_t size, DumpState* D) -{ - DumpInt(n,D); - DumpMem(b,n,size,D); + +static void DumpInteger (lua_Integer x, DumpState *D) { + DumpVar(x, D); } -static void DumpString(const TString* s, DumpState* D) -{ - if (s==NULL) - { - size_t size=0; - DumpVar(size,D); - } - else - { - size_t size=s->tsv.len+1; /* include trailing '\0' */ - DumpVar(size,D); - DumpBlock(getstr(s),size*sizeof(char),D); - } + +static void DumpString (const TString *s, DumpState *D) { + if (s == NULL) + DumpByte(0, D); + else { + size_t size = tsslen(s) + 1; /* include trailing '\0' */ + const char *str = getstr(s); + if (size < 0xFF) + DumpByte(cast_int(size), D); + else { + DumpByte(0xFF, D); + DumpVar(size, D); + } + DumpVector(str, size - 1, D); /* no need to save '\0' */ + } } -#define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D) - -static void DumpFunction(const Proto* f, DumpState* D); - -static void DumpConstants(const Proto* f, DumpState* D) -{ - int i,n=f->sizek; - DumpInt(n,D); - for (i=0; ik[i]; - DumpChar(ttypenv(o),D); - switch (ttypenv(o)) - { - case LUA_TNIL: - break; - case LUA_TBOOLEAN: - DumpChar(bvalue(o),D); - break; - case LUA_TNUMBER: - DumpNumber(nvalue(o),D); - break; - case LUA_TSTRING: - DumpString(rawtsvalue(o),D); - break; - default: lua_assert(0); + +static void DumpCode (const Proto *f, DumpState *D) { + DumpInt(f->sizecode, D); + DumpVector(f->code, f->sizecode, D); +} + + +static void DumpFunction(const Proto *f, TString *psource, DumpState *D); + +static void DumpConstants (const Proto *f, DumpState *D) { + int i; + int n = f->sizek; + DumpInt(n, D); + for (i = 0; i < n; i++) { + const TValue *o = &f->k[i]; + DumpByte(ttype(o), D); + switch (ttype(o)) { + case LUA_TNIL: + break; + case LUA_TBOOLEAN: + DumpByte(bvalue(o), D); + break; + case LUA_TNUMFLT: + DumpNumber(fltvalue(o), D); + break; + case LUA_TNUMINT: + DumpInteger(ivalue(o), D); + break; + case LUA_TSHRSTR: + case LUA_TLNGSTR: + DumpString(tsvalue(o), D); + break; + default: + lua_assert(0); + } } - } - n=f->sizep; - DumpInt(n,D); - for (i=0; ip[i],D); } -static void DumpUpvalues(const Proto* f, DumpState* D) -{ - int i,n=f->sizeupvalues; - DumpInt(n,D); - for (i=0; iupvalues[i].instack,D); - DumpChar(f->upvalues[i].idx,D); - } + +static void DumpProtos (const Proto *f, DumpState *D) { + int i; + int n = f->sizep; + DumpInt(n, D); + for (i = 0; i < n; i++) + DumpFunction(f->p[i], f->source, D); +} + + +static void DumpUpvalues (const Proto *f, DumpState *D) { + int i, n = f->sizeupvalues; + DumpInt(n, D); + for (i = 0; i < n; i++) { + DumpByte(f->upvalues[i].instack, D); + DumpByte(f->upvalues[i].idx, D); + } } -static void DumpDebug(const Proto* f, DumpState* D) -{ - int i,n; - DumpString((D->strip) ? NULL : f->source,D); - n= (D->strip) ? 0 : f->sizelineinfo; - DumpVector(f->lineinfo,n,sizeof(int),D); - n= (D->strip) ? 0 : f->sizelocvars; - DumpInt(n,D); - for (i=0; ilocvars[i].varname,D); - DumpInt(f->locvars[i].startpc,D); - DumpInt(f->locvars[i].endpc,D); - } - n= (D->strip) ? 0 : f->sizeupvalues; - DumpInt(n,D); - for (i=0; iupvalues[i].name,D); + +static void DumpDebug (const Proto *f, DumpState *D) { + int i, n; + n = (D->strip) ? 0 : f->sizelineinfo; + DumpInt(n, D); + DumpVector(f->lineinfo, n, D); + n = (D->strip) ? 0 : f->sizelocvars; + DumpInt(n, D); + for (i = 0; i < n; i++) { + DumpString(f->locvars[i].varname, D); + DumpInt(f->locvars[i].startpc, D); + DumpInt(f->locvars[i].endpc, D); + } + n = (D->strip) ? 0 : f->sizeupvalues; + DumpInt(n, D); + for (i = 0; i < n; i++) + DumpString(f->upvalues[i].name, D); } -static void DumpFunction(const Proto* f, DumpState* D) -{ - DumpInt(f->linedefined,D); - DumpInt(f->lastlinedefined,D); - DumpChar(f->numparams,D); - DumpChar(f->is_vararg,D); - DumpChar(f->maxstacksize,D); - DumpCode(f,D); - DumpConstants(f,D); - DumpUpvalues(f,D); - DumpDebug(f,D); + +static void DumpFunction (const Proto *f, TString *psource, DumpState *D) { + if (D->strip || f->source == psource) + DumpString(NULL, D); /* no debug info or same source as its parent */ + else + DumpString(f->source, D); + DumpInt(f->linedefined, D); + DumpInt(f->lastlinedefined, D); + DumpByte(f->numparams, D); + DumpByte(f->is_vararg, D); + DumpByte(f->maxstacksize, D); + DumpCode(f, D); + DumpConstants(f, D); + DumpUpvalues(f, D); + DumpProtos(f, D); + DumpDebug(f, D); } -static void DumpHeader(DumpState* D) -{ - lu_byte h[LUAC_HEADERSIZE]; - luaU_header(h); - DumpBlock(h,LUAC_HEADERSIZE,D); + +static void DumpHeader (DumpState *D) { + DumpLiteral(LUA_SIGNATURE, D); + DumpByte(LUAC_VERSION, D); + DumpByte(LUAC_FORMAT, D); + DumpLiteral(LUAC_DATA, D); + DumpByte(sizeof(int), D); + DumpByte(sizeof(size_t), D); + DumpByte(sizeof(Instruction), D); + DumpByte(sizeof(lua_Integer), D); + DumpByte(sizeof(lua_Number), D); + DumpInteger(LUAC_INT, D); + DumpNumber(LUAC_NUM, D); } + /* ** dump Lua function as precompiled chunk */ -int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) -{ - DumpState D; - D.L=L; - D.writer=w; - D.data=data; - D.strip=strip; - D.status=0; - DumpHeader(&D); - DumpFunction(f,&D); - return D.status; +int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data, + int strip) { + DumpState D; + D.L = L; + D.writer = w; + D.data = data; + D.strip = strip; + D.status = 0; + DumpHeader(&D); + DumpByte(f->sizeupvalues, &D); + DumpFunction(f, NULL, &D); + return D.status; } + diff --git a/depends/lua/src/lfunc.c b/depends/lua/src/lfunc.c index e90e1520c..67967dab3 100644 --- a/depends/lua/src/lfunc.c +++ b/depends/lua/src/lfunc.c @@ -1,15 +1,17 @@ /* -** $Id: lfunc.c,v 2.30.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lfunc.c,v 2.45 2014/11/02 19:19:04 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ - -#include - #define lfunc_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "lfunc.h" @@ -20,95 +22,83 @@ -Closure *luaF_newCclosure (lua_State *L, int n) { - Closure *c = &luaC_newobj(L, LUA_TCCL, sizeCclosure(n), NULL, 0)->cl; - c->c.nupvalues = cast_byte(n); +CClosure *luaF_newCclosure (lua_State *L, int n) { + GCObject *o = luaC_newobj(L, LUA_TCCL, sizeCclosure(n)); + CClosure *c = gco2ccl(o); + c->nupvalues = cast_byte(n); return c; } -Closure *luaF_newLclosure (lua_State *L, int n) { - Closure *c = &luaC_newobj(L, LUA_TLCL, sizeLclosure(n), NULL, 0)->cl; - c->l.p = NULL; - c->l.nupvalues = cast_byte(n); - while (n--) c->l.upvals[n] = NULL; +LClosure *luaF_newLclosure (lua_State *L, int n) { + GCObject *o = luaC_newobj(L, LUA_TLCL, sizeLclosure(n)); + LClosure *c = gco2lcl(o); + c->p = NULL; + c->nupvalues = cast_byte(n); + while (n--) c->upvals[n] = NULL; return c; } - -UpVal *luaF_newupval (lua_State *L) { - UpVal *uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), NULL, 0)->uv; - uv->v = &uv->u.value; - setnilvalue(uv->v); - return uv; +/* +** fill a closure with new closed upvalues +*/ +void luaF_initupvals (lua_State *L, LClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) { + UpVal *uv = luaM_new(L, UpVal); + uv->refcount = 1; + uv->v = &uv->u.value; /* make it closed */ + setnilvalue(uv->v); + cl->upvals[i] = uv; + } } UpVal *luaF_findupval (lua_State *L, StkId level) { - global_State *g = G(L); - GCObject **pp = &L->openupval; + UpVal **pp = &L->openupval; UpVal *p; UpVal *uv; - while (*pp != NULL && (p = gco2uv(*pp))->v >= level) { - GCObject *o = obj2gco(p); - lua_assert(p->v != &p->u.value); - lua_assert(!isold(o) || isold(obj2gco(L))); - if (p->v == level) { /* found a corresponding upvalue? */ - if (isdead(g, o)) /* is it dead? */ - changewhite(o); /* resurrect it */ - return p; - } - pp = &p->next; + lua_assert(isintwups(L) || L->openupval == NULL); + while (*pp != NULL && (p = *pp)->v >= level) { + lua_assert(upisopen(p)); + if (p->v == level) /* found a corresponding upvalue? */ + return p; /* return it */ + pp = &p->u.open.next; } - /* not found: create a new one */ - uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), pp, 0)->uv; + /* not found: create a new upvalue */ + uv = luaM_new(L, UpVal); + uv->refcount = 0; + uv->u.open.next = *pp; /* link it to list of open upvalues */ + uv->u.open.touched = 1; + *pp = uv; uv->v = level; /* current value lives in the stack */ - uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ - uv->u.l.next = g->uvhead.u.l.next; - uv->u.l.next->u.l.prev = uv; - g->uvhead.u.l.next = uv; - lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ + L->twups = G(L)->twups; /* link it to the list */ + G(L)->twups = L; + } return uv; } -static void unlinkupval (UpVal *uv) { - lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); - uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */ - uv->u.l.prev->u.l.next = uv->u.l.next; -} - - -void luaF_freeupval (lua_State *L, UpVal *uv) { - if (uv->v != &uv->u.value) /* is it open? */ - unlinkupval(uv); /* remove from open list */ - luaM_free(L, uv); /* free upvalue */ -} - - void luaF_close (lua_State *L, StkId level) { UpVal *uv; - global_State *g = G(L); - while (L->openupval != NULL && (uv = gco2uv(L->openupval))->v >= level) { - GCObject *o = obj2gco(uv); - lua_assert(!isblack(o) && uv->v != &uv->u.value); - L->openupval = uv->next; /* remove from `open' list */ - if (isdead(g, o)) - luaF_freeupval(L, uv); /* free upvalue */ + while (L->openupval != NULL && (uv = L->openupval)->v >= level) { + lua_assert(upisopen(uv)); + L->openupval = uv->u.open.next; /* remove from 'open' list */ + if (uv->refcount == 0) /* no references? */ + luaM_free(L, uv); /* free upvalue */ else { - unlinkupval(uv); /* remove upvalue from 'uvhead' list */ setobj(L, &uv->u.value, uv->v); /* move value to upvalue slot */ uv->v = &uv->u.value; /* now current value lives here */ - gch(o)->next = g->allgc; /* link upvalue into 'allgc' list */ - g->allgc = o; - luaC_checkupvalcolor(g, uv); + luaC_upvalbarrier(L, uv); } } } Proto *luaF_newproto (lua_State *L) { - Proto *f = &luaC_newobj(L, LUA_TPROTO, sizeof(Proto), NULL, 0)->p; + GCObject *o = luaC_newobj(L, LUA_TPROTO, sizeof(Proto)); + Proto *f = gco2p(o); f->k = NULL; f->sizek = 0; f->p = NULL; @@ -144,7 +134,7 @@ void luaF_freeproto (lua_State *L, Proto *f) { /* -** Look for n-th local variable at line `line' in function `func'. +** Look for n-th local variable at line 'line' in function 'func'. ** Returns NULL if not found. */ const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { diff --git a/depends/lua/src/lgc.c b/depends/lua/src/lgc.c index 52460dcdd..7c29fb030 100644 --- a/depends/lua/src/lgc.c +++ b/depends/lua/src/lgc.c @@ -1,14 +1,17 @@ /* -** $Id: lgc.c,v 2.140.1.2 2013/04/26 18:22:05 roberto Exp $ +** $Id: lgc.c,v 2.212 2016/03/31 19:02:03 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ -#include - #define lgc_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "ldebug.h" @@ -23,6 +26,11 @@ #include "ltm.h" +/* +** internal state for collector while inside the atomic phase. The +** collector should never be in this state while running regular code. +*/ +#define GCSinsideatomic (GCSpause + 1) /* ** cost of sweeping one element (the size of a small object divided @@ -33,8 +41,8 @@ /* maximum number of elements to sweep in each single step */ #define GCSWEEPMAX (cast_int((GCSTEPSIZE / GCSWEEPCOST) / 4)) -/* maximum number of finalizers to call in each GC step */ -#define GCFINALIZENUM 4 +/* cost of calling one finalizer */ +#define GCFINALIZECOST GCSWEEPCOST /* @@ -52,18 +60,18 @@ /* -** 'makewhite' erases all color bits plus the old bit and then -** sets only the current white bit +** 'makewhite' erases all color bits then sets only the current white +** bit */ -#define maskcolors (~(bit2mask(BLACKBIT, OLDBIT) | WHITEBITS)) +#define maskcolors (~(bitmask(BLACKBIT) | WHITEBITS)) #define makewhite(g,x) \ - (gch(x)->marked = cast_byte((gch(x)->marked & maskcolors) | luaC_white(g))) + (x->marked = cast_byte((x->marked & maskcolors) | luaC_white(g))) -#define white2gray(x) resetbits(gch(x)->marked, WHITEBITS) -#define black2gray(x) resetbit(gch(x)->marked, BLACKBIT) +#define white2gray(x) resetbits(x->marked, WHITEBITS) +#define black2gray(x) resetbit(x->marked, BLACKBIT) -#define isfinalized(x) testbit(gch(x)->marked, FINALIZEDBIT) +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) #define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n))) @@ -75,8 +83,13 @@ #define markvalue(g,o) { checkconsistency(o); \ if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } -#define markobject(g,t) { if ((t) && iswhite(obj2gco(t))) \ - reallymarkobject(g, obj2gco(t)); } +#define markobject(g,t) { if (iswhite(t)) reallymarkobject(g, obj2gco(t)); } + +/* +** mark an object that can be NULL (either because it is really optional, +** or it was stripped as debug info, or inside an uncompleted structure) +*/ +#define markobjectN(g,t) { if (t) markobject(g,t); } static void reallymarkobject (global_State *g, GCObject *o); @@ -95,33 +108,38 @@ static void reallymarkobject (global_State *g, GCObject *o); /* -** link table 'h' into list pointed by 'p' +** link collectable object 'o' into list pointed by 'p' */ -#define linktable(h,p) ((h)->gclist = *(p), *(p) = obj2gco(h)) +#define linkgclist(o,p) ((o)->gclist = (p), (p) = obj2gco(o)) /* -** if key is not marked, mark its entry as dead (therefore removing it -** from the table) +** If key is not marked, mark its entry as dead. This allows key to be +** collected, but keeps its entry in the table. A dead node is needed +** when Lua looks up for a key (it may be part of a chain) and when +** traversing a weak table (key might be removed from the table during +** traversal). Other places never manipulate dead keys, because its +** associated nil value is enough to signal that the entry is logically +** empty. */ static void removeentry (Node *n) { lua_assert(ttisnil(gval(n))); if (valiswhite(gkey(n))) - setdeadvalue(gkey(n)); /* unused and unmarked key; remove it */ + setdeadvalue(wgkey(n)); /* unused and unmarked key; remove it */ } /* ** tells whether a key or value can be cleared from a weak ** table. Non-collectable objects are never removed from weak -** tables. Strings behave as `values', so are never removed too. for +** tables. Strings behave as 'values', so are never removed too. for ** other objects: if really collected, cannot keep them; for objects ** being finalized, keep them in keys, but not in values */ static int iscleared (global_State *g, const TValue *o) { if (!iscollectable(o)) return 0; else if (ttisstring(o)) { - markobject(g, rawtsvalue(o)); /* strings are `values', so are never weak */ + markobject(g, tsvalue(o)); /* strings are 'values', so are never weak */ return 0; } else return iswhite(gcvalue(o)); @@ -130,14 +148,14 @@ static int iscleared (global_State *g, const TValue *o) { /* ** barrier that moves collector forward, that is, mark the white object -** being pointed by a black object. +** being pointed by a black object. (If in sweep phase, clear the black +** object to white [sweep it] to avoid other barrier calls for this +** same object.) */ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { global_State *g = G(L); lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); - lua_assert(g->gcstate != GCSpause); - lua_assert(gch(o)->tt != LUA_TTABLE); - if (keepinvariantout(g)) /* must keep invariant? */ + if (keepinvariant(g)) /* must keep invariant? */ reallymarkobject(g, v); /* restore invariant */ else { /* sweep phase */ lua_assert(issweepphase(g)); @@ -148,78 +166,52 @@ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { /* ** barrier that moves collector backward, that is, mark the black object -** pointing to a white object as gray again. (Current implementation -** only works for tables; access to 'gclist' is not uniform across -** different types.) +** pointing to a white object as gray again. */ -void luaC_barrierback_ (lua_State *L, GCObject *o) { +void luaC_barrierback_ (lua_State *L, Table *t) { global_State *g = G(L); - lua_assert(isblack(o) && !isdead(g, o) && gch(o)->tt == LUA_TTABLE); - black2gray(o); /* make object gray (again) */ - gco2t(o)->gclist = g->grayagain; - g->grayagain = o; + lua_assert(isblack(t) && !isdead(g, t)); + black2gray(t); /* make table gray (again) */ + linkgclist(t, g->grayagain); } /* -** barrier for prototypes. When creating first closure (cache is -** NULL), use a forward barrier; this may be the only closure of the -** prototype (if it is a "regular" function, with a single instance) -** and the prototype may be big, so it is better to avoid traversing -** it again. Otherwise, use a backward barrier, to avoid marking all -** possible instances. +** barrier for assignments to closed upvalues. Because upvalues are +** shared among closures, it is impossible to know the color of all +** closures pointing to it. So, we assume that the object being assigned +** must be marked. */ -LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c) { +void luaC_upvalbarrier_ (lua_State *L, UpVal *uv) { global_State *g = G(L); - lua_assert(isblack(obj2gco(p))); - if (p->cache == NULL) { /* first time? */ - luaC_objbarrier(L, p, c); - } - else { /* use a backward barrier */ - black2gray(obj2gco(p)); /* make prototype gray (again) */ - p->gclist = g->grayagain; - g->grayagain = obj2gco(p); - } + GCObject *o = gcvalue(uv->v); + lua_assert(!upisopen(uv)); /* ensured by macro luaC_upvalbarrier */ + if (keepinvariant(g)) + markobject(g, o); } -/* -** check color (and invariants) for an upvalue that was closed, -** i.e., moved into the 'allgc' list -*/ -void luaC_checkupvalcolor (global_State *g, UpVal *uv) { - GCObject *o = obj2gco(uv); - lua_assert(!isblack(o)); /* open upvalues are never black */ - if (isgray(o)) { - if (keepinvariant(g)) { - resetoldbit(o); /* see MOVE OLD rule */ - gray2black(o); /* it is being visited now */ - markvalue(g, uv->v); - } - else { - lua_assert(issweepphase(g)); - makewhite(g, o); - } - } +void luaC_fix (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ + white2gray(o); /* they will be gray forever */ + g->allgc = o->next; /* remove object from 'allgc' list */ + o->next = g->fixedgc; /* link it to 'fixedgc' list */ + g->fixedgc = o; } /* ** create a new collectable object (with given type and size) and link -** it to '*list'. 'offset' tells how many bytes to allocate before the -** object itself (used only by states). +** it to 'allgc' list. */ -GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, GCObject **list, - int offset) { +GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { global_State *g = G(L); - char *raw = cast(char *, luaM_newobject(L, novariant(tt), sz)); - GCObject *o = obj2gco(raw + offset); - if (list == NULL) - list = &g->allgc; /* standard list for collectable objects */ - gch(o)->marked = luaC_white(g); - gch(o)->tt = tt; - gch(o)->next = *list; - *list = o; + GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz)); + o->marked = luaC_white(g); + o->tt = tt; + o->next = g->allgc; + g->allgc = o; return o; } @@ -241,57 +233,53 @@ GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, GCObject **list, ** upvalues are already linked in 'headuv' list.) */ static void reallymarkobject (global_State *g, GCObject *o) { - lu_mem size; + reentry: white2gray(o); - switch (gch(o)->tt) { - case LUA_TSHRSTR: - case LUA_TLNGSTR: { - size = sizestring(gco2ts(o)); - break; /* nothing else to mark; make it black */ + switch (o->tt) { + case LUA_TSHRSTR: { + gray2black(o); + g->GCmemtrav += sizelstring(gco2ts(o)->shrlen); + break; } - case LUA_TUSERDATA: { - Table *mt = gco2u(o)->metatable; - markobject(g, mt); - markobject(g, gco2u(o)->env); - size = sizeudata(gco2u(o)); + case LUA_TLNGSTR: { + gray2black(o); + g->GCmemtrav += sizelstring(gco2ts(o)->u.lnglen); break; } - case LUA_TUPVAL: { - UpVal *uv = gco2uv(o); - markvalue(g, uv->v); - if (uv->v != &uv->u.value) /* open? */ - return; /* open upvalues remain gray */ - size = sizeof(UpVal); + case LUA_TUSERDATA: { + TValue uvalue; + markobjectN(g, gco2u(o)->metatable); /* mark its metatable */ + gray2black(o); + g->GCmemtrav += sizeudata(gco2u(o)); + getuservalue(g->mainthread, gco2u(o), &uvalue); + if (valiswhite(&uvalue)) { /* markvalue(g, &uvalue); */ + o = gcvalue(&uvalue); + goto reentry; + } break; } case LUA_TLCL: { - gco2lcl(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2lcl(o), g->gray); + break; } case LUA_TCCL: { - gco2ccl(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2ccl(o), g->gray); + break; } case LUA_TTABLE: { - linktable(gco2t(o), &g->gray); - return; + linkgclist(gco2t(o), g->gray); + break; } case LUA_TTHREAD: { - gco2th(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2th(o), g->gray); + break; } case LUA_TPROTO: { - gco2p(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2p(o), g->gray); + break; } - default: lua_assert(0); return; + default: lua_assert(0); break; } - gray2black(o); - g->GCmemtrav += size; } @@ -301,7 +289,7 @@ static void reallymarkobject (global_State *g, GCObject *o) { static void markmt (global_State *g) { int i; for (i=0; i < LUA_NUMTAGS; i++) - markobject(g, g->mt[i]); + markobjectN(g, g->mt[i]); } @@ -310,29 +298,41 @@ static void markmt (global_State *g) { */ static void markbeingfnz (global_State *g) { GCObject *o; - for (o = g->tobefnz; o != NULL; o = gch(o)->next) { - makewhite(g, o); - reallymarkobject(g, o); - } + for (o = g->tobefnz; o != NULL; o = o->next) + markobject(g, o); } /* -** mark all values stored in marked open upvalues. (See comment in -** 'lstate.h'.) +** Mark all values stored in marked open upvalues from non-marked threads. +** (Values from marked threads were already marked when traversing the +** thread.) Remove from the list threads that no longer have upvalues and +** not-marked threads. */ static void remarkupvals (global_State *g) { - UpVal *uv; - for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { - if (isgray(obj2gco(uv))) - markvalue(g, uv->v); + lua_State *thread; + lua_State **p = &g->twups; + while ((thread = *p) != NULL) { + lua_assert(!isblack(thread)); /* threads are never black */ + if (isgray(thread) && thread->openupval != NULL) + p = &thread->twups; /* keep marked thread with upvalues in the list */ + else { /* thread is not marked or without upvalues */ + UpVal *uv; + *p = thread->twups; /* remove thread from the list */ + thread->twups = thread; /* mark that it is out of list */ + for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) { + if (uv->u.open.touched) { + markvalue(g, uv->v); /* remark upvalue's value */ + uv->u.open.touched = 0; + } + } + } } } /* -** mark root set and reset all gray lists, to start a new -** incremental (or full) collection +** mark root set and reset all gray lists, to start a new collection */ static void restartcollection (global_State *g) { g->gray = g->grayagain = NULL; @@ -352,12 +352,18 @@ static void restartcollection (global_State *g) { ** ======================================================= */ +/* +** Traverse a table with weak values and link it to proper list. During +** propagate phase, keep it in 'grayagain' list, to be revisited in the +** atomic phase. In the atomic phase, if table has any white value, +** put it in 'weak' list, to be cleared. +*/ static void traverseweakvalue (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); - /* if there is array part, assume it may have white values (do not - traverse it just to check) */ + /* if there is array part, assume it may have white values (it is not + worth traversing it now just to check) */ int hasclears = (h->sizearray > 0); - for (n = gnode(h, 0); n < limit; n++) { + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ checkdeadkey(n); if (ttisnil(gval(n))) /* entry is empty? */ removeentry(n); /* remove it */ @@ -368,20 +374,30 @@ static void traverseweakvalue (global_State *g, Table *h) { hasclears = 1; /* table will have to be cleared */ } } - if (hasclears) - linktable(h, &g->weak); /* has to be cleared later */ - else /* no white values */ - linktable(h, &g->grayagain); /* no need to clean */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasclears) + linkgclist(h, g->weak); /* has to be cleared later */ } +/* +** Traverse an ephemeron table and link it to proper list. Returns true +** iff any object was marked during this traversal (which implies that +** convergence has to continue). During propagation phase, keep table +** in 'grayagain' list, to be visited again in the atomic phase. In +** the atomic phase, if table has any white->white entry, it has to +** be revisited during ephemeron convergence (as that key may turn +** black). Otherwise, if it has any white key, table has to be cleared +** (in the atomic phase). +*/ static int traverseephemeron (global_State *g, Table *h) { int marked = 0; /* true if an object is marked in this traversal */ int hasclears = 0; /* true if table has white keys */ - int prop = 0; /* true if table has entry "white-key -> white-value" */ + int hasww = 0; /* true if table has entry "white-key -> white-value" */ Node *n, *limit = gnodelast(h); - int i; - /* traverse array part (numeric keys are 'strong') */ + unsigned int i; + /* traverse array part */ for (i = 0; i < h->sizearray; i++) { if (valiswhite(&h->array[i])) { marked = 1; @@ -396,26 +412,27 @@ static int traverseephemeron (global_State *g, Table *h) { else if (iscleared(g, gkey(n))) { /* key is not marked (yet)? */ hasclears = 1; /* table must be cleared */ if (valiswhite(gval(n))) /* value not marked yet? */ - prop = 1; /* must propagate again */ + hasww = 1; /* white-white entry */ } else if (valiswhite(gval(n))) { /* value not marked yet? */ marked = 1; reallymarkobject(g, gcvalue(gval(n))); /* mark it now */ } } - if (prop) - linktable(h, &g->ephemeron); /* have to propagate again */ - else if (hasclears) /* does table have white keys? */ - linktable(h, &g->allweak); /* may have to clean white keys */ - else /* no white keys */ - linktable(h, &g->grayagain); /* no need to clean */ + /* link table into proper list */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasww) /* table has white->white entries? */ + linkgclist(h, g->ephemeron); /* have to propagate again */ + else if (hasclears) /* table has white keys? */ + linkgclist(h, g->allweak); /* may have to clean white keys */ return marked; } static void traversestrongtable (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); - int i; + unsigned int i; for (i = 0; i < h->sizearray; i++) /* traverse array part */ markvalue(g, &h->array[i]); for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ @@ -434,18 +451,18 @@ static void traversestrongtable (global_State *g, Table *h) { static lu_mem traversetable (global_State *g, Table *h) { const char *weakkey, *weakvalue; const TValue *mode = gfasttm(g, h->metatable, TM_MODE); - markobject(g, h->metatable); + markobjectN(g, h->metatable); if (mode && ttisstring(mode) && /* is there a weak mode? */ ((weakkey = strchr(svalue(mode), 'k')), (weakvalue = strchr(svalue(mode), 'v')), (weakkey || weakvalue))) { /* is really weak? */ - black2gray(obj2gco(h)); /* keep table gray */ + black2gray(h); /* keep table gray */ if (!weakkey) /* strong keys? */ traverseweakvalue(g, h); else if (!weakvalue) /* strong values? */ traverseephemeron(g, h); else /* all weak */ - linktable(h, &g->allweak); /* nothing to traverse now */ + linkgclist(h, g->allweak); /* nothing to traverse now */ } else /* not weak */ traversestrongtable(g, h); @@ -454,19 +471,24 @@ static lu_mem traversetable (global_State *g, Table *h) { } +/* +** Traverse a prototype. (While a prototype is being build, its +** arrays can be larger than needed; the extra slots are filled with +** NULL, so the use of 'markobjectN') +*/ static int traverseproto (global_State *g, Proto *f) { int i; - if (f->cache && iswhite(obj2gco(f->cache))) + if (f->cache && iswhite(f->cache)) f->cache = NULL; /* allow cache to be collected */ - markobject(g, f->source); + markobjectN(g, f->source); for (i = 0; i < f->sizek; i++) /* mark literals */ markvalue(g, &f->k[i]); for (i = 0; i < f->sizeupvalues; i++) /* mark upvalue names */ - markobject(g, f->upvalues[i].name); + markobjectN(g, f->upvalues[i].name); for (i = 0; i < f->sizep; i++) /* mark nested protos */ - markobject(g, f->p[i]); + markobjectN(g, f->p[i]); for (i = 0; i < f->sizelocvars; i++) /* mark local-variable names */ - markobject(g, f->locvars[i].varname); + markobjectN(g, f->locvars[i].varname); return sizeof(Proto) + sizeof(Instruction) * f->sizecode + sizeof(Proto *) * f->sizep + sizeof(TValue) * f->sizek + @@ -483,34 +505,50 @@ static lu_mem traverseCclosure (global_State *g, CClosure *cl) { return sizeCclosure(cl->nupvalues); } +/* +** open upvalues point to values in a thread, so those values should +** be marked when the thread is traversed except in the atomic phase +** (because then the value cannot be changed by the thread and the +** thread may not be traversed again) +*/ static lu_mem traverseLclosure (global_State *g, LClosure *cl) { int i; - markobject(g, cl->p); /* mark its prototype */ - for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ - markobject(g, cl->upvals[i]); + markobjectN(g, cl->p); /* mark its prototype */ + for (i = 0; i < cl->nupvalues; i++) { /* mark its upvalues */ + UpVal *uv = cl->upvals[i]; + if (uv != NULL) { + if (upisopen(uv) && g->gcstate != GCSinsideatomic) + uv->u.open.touched = 1; /* can be marked in 'remarkupvals' */ + else + markvalue(g, uv->v); + } + } return sizeLclosure(cl->nupvalues); } -static lu_mem traversestack (global_State *g, lua_State *th) { - int n = 0; +static lu_mem traversethread (global_State *g, lua_State *th) { StkId o = th->stack; if (o == NULL) return 1; /* stack not completely built yet */ + lua_assert(g->gcstate == GCSinsideatomic || + th->openupval == NULL || isintwups(th)); for (; o < th->top; o++) /* mark live elements in the stack */ markvalue(g, o); - if (g->gcstate == GCSatomic) { /* final traversal? */ + if (g->gcstate == GCSinsideatomic) { /* final traversal? */ StkId lim = th->stack + th->stacksize; /* real end of stack */ for (; o < lim; o++) /* clear not-marked stack slice */ setnilvalue(o); + /* 'remarkupvals' may have removed thread from 'twups' list */ + if (!isintwups(th) && th->openupval != NULL) { + th->twups = g->twups; /* link it back to the list */ + g->twups = th; + } } - else { /* count call infos to compute size */ - CallInfo *ci; - for (ci = &th->base_ci; ci != th->ci; ci = ci->next) - n++; - } - return sizeof(lua_State) + sizeof(TValue) * th->stacksize + - sizeof(CallInfo) * n; + else if (g->gckind != KGC_EMERGENCY) + luaD_shrinkstack(th); /* do not change stack in emergency cycle */ + return (sizeof(lua_State) + sizeof(TValue) * th->stacksize + + sizeof(CallInfo) * th->nci); } @@ -523,7 +561,7 @@ static void propagatemark (global_State *g) { GCObject *o = g->gray; lua_assert(isgray(o)); gray2black(o); - switch (gch(o)->tt) { + switch (o->tt) { case LUA_TTABLE: { Table *h = gco2t(o); g->gray = h->gclist; /* remove from 'gray' list */ @@ -545,10 +583,9 @@ static void propagatemark (global_State *g) { case LUA_TTHREAD: { lua_State *th = gco2th(o); g->gray = th->gclist; /* remove from 'gray' list */ - th->gclist = g->grayagain; - g->grayagain = o; /* insert into 'grayagain' list */ + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ black2gray(o); - size = traversestack(g, th); + size = traversethread(g, th); break; } case LUA_TPROTO: { @@ -568,35 +605,12 @@ static void propagateall (global_State *g) { } -static void propagatelist (global_State *g, GCObject *l) { - lua_assert(g->gray == NULL); /* no grays left */ - g->gray = l; - propagateall(g); /* traverse all elements from 'l' */ -} - -/* -** retraverse all gray lists. Because tables may be reinserted in other -** lists when traversed, traverse the original lists to avoid traversing -** twice the same table (which is not wrong, but inefficient) -*/ -static void retraversegrays (global_State *g) { - GCObject *weak = g->weak; /* save original lists */ - GCObject *grayagain = g->grayagain; - GCObject *ephemeron = g->ephemeron; - g->weak = g->grayagain = g->ephemeron = NULL; - propagateall(g); /* traverse main gray list */ - propagatelist(g, grayagain); - propagatelist(g, weak); - propagatelist(g, ephemeron); -} - - static void convergeephemerons (global_State *g) { int changed; do { GCObject *w; GCObject *next = g->ephemeron; /* get ephemeron list */ - g->ephemeron = NULL; /* tables will return to this list when traversed */ + g->ephemeron = NULL; /* tables may return to this list when traversed */ changed = 0; while ((w = next) != NULL) { next = gco2t(w)->gclist; @@ -644,7 +658,7 @@ static void clearvalues (global_State *g, GCObject *l, GCObject *f) { for (; l != f; l = gco2t(l)->gclist) { Table *h = gco2t(l); Node *n, *limit = gnodelast(h); - int i; + unsigned int i; for (i = 0; i < h->sizearray; i++) { TValue *o = &h->array[i]; if (iscleared(g, o)) /* value was collected? */ @@ -660,26 +674,45 @@ static void clearvalues (global_State *g, GCObject *l, GCObject *f) { } +void luaC_upvdeccount (lua_State *L, UpVal *uv) { + lua_assert(uv->refcount > 0); + uv->refcount--; + if (uv->refcount == 0 && !upisopen(uv)) + luaM_free(L, uv); +} + + +static void freeLclosure (lua_State *L, LClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) { + UpVal *uv = cl->upvals[i]; + if (uv) + luaC_upvdeccount(L, uv); + } + luaM_freemem(L, cl, sizeLclosure(cl->nupvalues)); +} + + static void freeobj (lua_State *L, GCObject *o) { - switch (gch(o)->tt) { + switch (o->tt) { case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; case LUA_TLCL: { - luaM_freemem(L, o, sizeLclosure(gco2lcl(o)->nupvalues)); + freeLclosure(L, gco2lcl(o)); break; } case LUA_TCCL: { luaM_freemem(L, o, sizeCclosure(gco2ccl(o)->nupvalues)); break; } - case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break; case LUA_TTABLE: luaH_free(L, gco2t(o)); break; case LUA_TTHREAD: luaE_freethread(L, gco2th(o)); break; case LUA_TUSERDATA: luaM_freemem(L, o, sizeudata(gco2u(o))); break; case LUA_TSHRSTR: - G(L)->strt.nuse--; - /* go through */ + luaS_remove(L, gco2ts(o)); /* remove it from hash table */ + luaM_freemem(L, o, sizelstring(gco2ts(o)->shrlen)); + break; case LUA_TLNGSTR: { - luaM_freemem(L, o, sizestring(gco2ts(o))); + luaM_freemem(L, o, sizelstring(gco2ts(o)->u.lnglen)); break; } default: lua_assert(0); @@ -691,61 +724,27 @@ static void freeobj (lua_State *L, GCObject *o) { static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count); -/* -** sweep the (open) upvalues of a thread and resize its stack and -** list of call-info structures. -*/ -static void sweepthread (lua_State *L, lua_State *L1) { - if (L1->stack == NULL) return; /* stack not completely built yet */ - sweepwholelist(L, &L1->openupval); /* sweep open upvalues */ - luaE_freeCI(L1); /* free extra CallInfo slots */ - /* should not change the stack during an emergency gc cycle */ - if (G(L)->gckind != KGC_EMERGENCY) - luaD_shrinkstack(L1); -} - - /* ** sweep at most 'count' elements from a list of GCObjects erasing dead -** objects, where a dead (not alive) object is one marked with the "old" -** (non current) white and not fixed. -** In non-generational mode, change all non-dead objects back to white, -** preparing for next collection cycle. -** In generational mode, keep black objects black, and also mark them as -** old; stop when hitting an old object, as all objects after that -** one will be old too. -** When object is a thread, sweep its list of open upvalues too. +** objects, where a dead object is one marked with the old (non current) +** white; change all non-dead objects back to white, preparing for next +** collection cycle. Return where to continue the traversal or NULL if +** list is finished. */ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { global_State *g = G(L); int ow = otherwhite(g); - int toclear, toset; /* bits to clear and to set in all live objects */ - int tostop; /* stop sweep when this is true */ - if (isgenerational(g)) { /* generational mode? */ - toclear = ~0; /* clear nothing */ - toset = bitmask(OLDBIT); /* set the old bit of all surviving objects */ - tostop = bitmask(OLDBIT); /* do not sweep old generation */ - } - else { /* normal mode */ - toclear = maskcolors; /* clear all color bits + old bit */ - toset = luaC_white(g); /* make object white */ - tostop = 0; /* do not stop */ - } + int white = luaC_white(g); /* current white */ while (*p != NULL && count-- > 0) { GCObject *curr = *p; - int marked = gch(curr)->marked; + int marked = curr->marked; if (isdeadm(ow, marked)) { /* is 'curr' dead? */ - *p = gch(curr)->next; /* remove 'curr' from list */ + *p = curr->next; /* remove 'curr' from list */ freeobj(L, curr); /* erase 'curr' */ } - else { - if (testbits(marked, tostop)) - return NULL; /* stop sweeping this list */ - if (gch(curr)->tt == LUA_TTHREAD) - sweepthread(L, gco2th(curr)); /* sweep thread's upvalues */ - /* update marks */ - gch(curr)->marked = cast_byte((marked & toclear) | toset); - p = &gch(curr)->next; /* go to next element */ + else { /* change mark to 'white' */ + curr->marked = cast_byte((marked & maskcolors) | white); + p = &curr->next; /* go to next element */ } } return (*p == NULL) ? NULL : p; @@ -755,14 +754,11 @@ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { /* ** sweep a list until a live object (or end of list) */ -static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { - GCObject ** old = p; - int i = 0; +static GCObject **sweeptolive (lua_State *L, GCObject **p) { + GCObject **old = p; do { - i++; p = sweeplist(L, p, 1); } while (p == old); - if (n) *n += i; return p; } @@ -775,26 +771,27 @@ static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { ** ======================================================= */ -static void checkSizes (lua_State *L) { - global_State *g = G(L); - if (g->gckind != KGC_EMERGENCY) { /* do not change sizes in emergency */ - int hs = g->strt.size / 2; /* half the size of the string table */ - if (g->strt.nuse < cast(lu_int32, hs)) /* using less than that half? */ - luaS_resize(L, hs); /* halve its size */ - luaZ_freebuffer(L, &g->buff); /* free concatenation buffer */ +/* +** If possible, shrink string table +*/ +static void checkSizes (lua_State *L, global_State *g) { + if (g->gckind != KGC_EMERGENCY) { + l_mem olddebt = g->GCdebt; + if (g->strt.nuse < g->strt.size / 4) /* string table too big? */ + luaS_resize(L, g->strt.size / 2); /* shrink it a little */ + g->GCestimate += g->GCdebt - olddebt; /* update estimate */ } } static GCObject *udata2finalize (global_State *g) { GCObject *o = g->tobefnz; /* get first element */ - lua_assert(isfinalized(o)); - g->tobefnz = gch(o)->next; /* remove it from 'tobefnz' list */ - gch(o)->next = g->allgc; /* return it to 'allgc' list */ + lua_assert(tofinalize(o)); + g->tobefnz = o->next; /* remove it from 'tobefnz' list */ + o->next = g->allgc; /* return it to 'allgc' list */ g->allgc = o; - resetbit(gch(o)->marked, SEPARATED); /* mark that it is not in 'tobefnz' */ - lua_assert(!isold(o)); /* see MOVE OLD rule */ - if (!keepinvariantout(g)) /* not keeping invariant? */ + resetbit(o->marked, FINALIZEDBIT); /* object is "normal" again */ + if (issweepphase(g)) makewhite(g, o); /* "sweep" object */ return o; } @@ -802,7 +799,7 @@ static GCObject *udata2finalize (global_State *g) { static void dothecall (lua_State *L, void *ud) { UNUSED(ud); - luaD_call(L, L->top - 2, 0, 0); + luaD_callnoyield(L, L->top - 2, 0); } @@ -838,29 +835,58 @@ static void GCTM (lua_State *L, int propagateerrors) { } +/* +** call a few (up to 'g->gcfinnum') finalizers +*/ +static int runafewfinalizers (lua_State *L) { + global_State *g = G(L); + unsigned int i; + lua_assert(!g->tobefnz || g->gcfinnum > 0); + for (i = 0; g->tobefnz && i < g->gcfinnum; i++) + GCTM(L, 1); /* call one finalizer */ + g->gcfinnum = (!g->tobefnz) ? 0 /* nothing more to finalize? */ + : g->gcfinnum * 2; /* else call a few more next time */ + return i; +} + + +/* +** call all pending finalizers +*/ +static void callallpendingfinalizers (lua_State *L) { + global_State *g = G(L); + while (g->tobefnz) + GCTM(L, 0); +} + + +/* +** find last 'next' field in list 'p' list (to add elements in its end) +*/ +static GCObject **findlast (GCObject **p) { + while (*p != NULL) + p = &(*p)->next; + return p; +} + + /* ** move all unreachable objects (or 'all' objects) that need ** finalization from list 'finobj' to list 'tobefnz' (to be finalized) */ -static void separatetobefnz (lua_State *L, int all) { - global_State *g = G(L); - GCObject **p = &g->finobj; +static void separatetobefnz (global_State *g, int all) { GCObject *curr; - GCObject **lastnext = &g->tobefnz; - /* find last 'next' field in 'tobefnz' list (to add elements in its end) */ - while (*lastnext != NULL) - lastnext = &gch(*lastnext)->next; + GCObject **p = &g->finobj; + GCObject **lastnext = findlast(&g->tobefnz); while ((curr = *p) != NULL) { /* traverse all finalizable objects */ - lua_assert(!isfinalized(curr)); - lua_assert(testbit(gch(curr)->marked, SEPARATED)); + lua_assert(tofinalize(curr)); if (!(iswhite(curr) || all)) /* not being collected? */ - p = &gch(curr)->next; /* don't bother with it */ + p = &curr->next; /* don't bother with it */ else { - l_setbit(gch(curr)->marked, FINALIZEDBIT); /* won't be finalized again */ - *p = gch(curr)->next; /* remove 'curr' from 'finobj' list */ - gch(curr)->next = *lastnext; /* link at the end of 'tobefnz' list */ + *p = curr->next; /* remove 'curr' from 'finobj' list */ + curr->next = *lastnext; /* link at the end of 'tobefnz' list */ *lastnext = curr; - lastnext = &gch(curr)->next; + lastnext = &curr->next; } } } @@ -872,33 +898,29 @@ static void separatetobefnz (lua_State *L, int all) { */ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { global_State *g = G(L); - if (testbit(gch(o)->marked, SEPARATED) || /* obj. is already separated... */ - isfinalized(o) || /* ... or is finalized... */ - gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ + if (tofinalize(o) || /* obj. is already marked... */ + gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ return; /* nothing to be done */ else { /* move 'o' to 'finobj' list */ GCObject **p; - GCheader *ho = gch(o); - if (g->sweepgc == &ho->next) { /* avoid removing current sweep object */ - lua_assert(issweepphase(g)); - g->sweepgc = sweeptolive(L, g->sweepgc, NULL); + if (issweepphase(g)) { + makewhite(g, o); /* "sweep" object 'o' */ + if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */ + g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */ } /* search for pointer pointing to 'o' */ - for (p = &g->allgc; *p != o; p = &gch(*p)->next) { /* empty */ } - *p = ho->next; /* remove 'o' from root list */ - ho->next = g->finobj; /* link it in list 'finobj' */ + for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ } + *p = o->next; /* remove 'o' from 'allgc' list */ + o->next = g->finobj; /* link it in 'finobj' list */ g->finobj = o; - l_setbit(ho->marked, SEPARATED); /* mark it as such */ - if (!keepinvariantout(g)) /* not keeping invariant? */ - makewhite(g, o); /* "sweep" object */ - else - resetoldbit(o); /* see MOVE OLD rule */ + l_setbit(o->marked, FINALIZEDBIT); /* mark it as such */ } } /* }====================================================== */ + /* ** {====================================================== ** GC control @@ -907,195 +929,164 @@ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { /* -** set a reasonable "time" to wait before starting a new GC cycle; -** cycle will start when memory use hits threshold +** Set a reasonable "time" to wait before starting a new GC cycle; cycle +** will start when memory use hits threshold. (Division by 'estimate' +** should be OK: it cannot be zero (because Lua cannot even start with +** less than PAUSEADJ bytes). */ -static void setpause (global_State *g, l_mem estimate) { - l_mem debt, threshold; - estimate = estimate / PAUSEADJ; /* adjust 'estimate' */ +static void setpause (global_State *g) { + l_mem threshold, debt; + l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */ + lua_assert(estimate > 0); threshold = (g->gcpause < MAX_LMEM / estimate) /* overflow? */ ? estimate * g->gcpause /* no overflow */ : MAX_LMEM; /* overflow; truncate to maximum */ - debt = -cast(l_mem, threshold - gettotalbytes(g)); + debt = gettotalbytes(g) - threshold; luaE_setdebt(g, debt); } -#define sweepphases \ - (bitmask(GCSsweepstring) | bitmask(GCSsweepudata) | bitmask(GCSsweep)) - - -/* -** enter first sweep phase (strings) and prepare pointers for other -** sweep phases. The calls to 'sweeptolive' make pointers point to an -** object inside the list (instead of to the header), so that the real -** sweep do not need to skip objects created between "now" and the start -** of the real sweep. -** Returns how many objects it swept. -*/ -static int entersweep (lua_State *L) { - global_State *g = G(L); - int n = 0; - g->gcstate = GCSsweepstring; - lua_assert(g->sweepgc == NULL && g->sweepfin == NULL); - /* prepare to sweep strings, finalizable objects, and regular objects */ - g->sweepstrgc = 0; - g->sweepfin = sweeptolive(L, &g->finobj, &n); - g->sweepgc = sweeptolive(L, &g->allgc, &n); - return n; -} - - -/* -** change GC mode -*/ -void luaC_changemode (lua_State *L, int mode) { - global_State *g = G(L); - if (mode == g->gckind) return; /* nothing to change */ - if (mode == KGC_GEN) { /* change to generational mode */ - /* make sure gray lists are consistent */ - luaC_runtilstate(L, bitmask(GCSpropagate)); - g->GCestimate = gettotalbytes(g); - g->gckind = KGC_GEN; - } - else { /* change to incremental mode */ - /* sweep all objects to turn them back to white - (as white has not changed, nothing extra will be collected) */ - g->gckind = KGC_NORMAL; - entersweep(L); - luaC_runtilstate(L, ~sweepphases); - } -} - - /* -** call all pending finalizers +** Enter first sweep phase. +** The call to 'sweeplist' tries to make pointer point to an object +** inside the list (instead of to the header), so that the real sweep do +** not need to skip objects created between "now" and the start of the +** real sweep. */ -static void callallpendingfinalizers (lua_State *L, int propagateerrors) { +static void entersweep (lua_State *L) { global_State *g = G(L); - while (g->tobefnz) { - resetoldbit(g->tobefnz); - GCTM(L, propagateerrors); - } + g->gcstate = GCSswpallgc; + lua_assert(g->sweepgc == NULL); + g->sweepgc = sweeplist(L, &g->allgc, 1); } void luaC_freeallobjects (lua_State *L) { global_State *g = G(L); - int i; - separatetobefnz(L, 1); /* separate all objects with finalizers */ + separatetobefnz(g, 1); /* separate all objects with finalizers */ lua_assert(g->finobj == NULL); - callallpendingfinalizers(L, 0); + callallpendingfinalizers(L); + lua_assert(g->tobefnz == NULL); g->currentwhite = WHITEBITS; /* this "white" makes all objects look dead */ g->gckind = KGC_NORMAL; - sweepwholelist(L, &g->finobj); /* finalizers can create objs. in 'finobj' */ + sweepwholelist(L, &g->finobj); sweepwholelist(L, &g->allgc); - for (i = 0; i < g->strt.size; i++) /* free all string lists */ - sweepwholelist(L, &g->strt.hash[i]); + sweepwholelist(L, &g->fixedgc); /* collect fixed objects */ lua_assert(g->strt.nuse == 0); } static l_mem atomic (lua_State *L) { global_State *g = G(L); - l_mem work = -cast(l_mem, g->GCmemtrav); /* start counting work */ + l_mem work; GCObject *origweak, *origall; - lua_assert(!iswhite(obj2gco(g->mainthread))); + GCObject *grayagain = g->grayagain; /* save original list */ + lua_assert(g->ephemeron == NULL && g->weak == NULL); + lua_assert(!iswhite(g->mainthread)); + g->gcstate = GCSinsideatomic; + g->GCmemtrav = 0; /* start counting work */ markobject(g, L); /* mark running thread */ /* registry and global metatables may be changed by API */ markvalue(g, &g->l_registry); - markmt(g); /* mark basic metatables */ + markmt(g); /* mark global metatables */ /* remark occasional upvalues of (maybe) dead threads */ remarkupvals(g); propagateall(g); /* propagate changes */ - work += g->GCmemtrav; /* stop counting (do not (re)count grays) */ - /* traverse objects caught by write barrier and by 'remarkupvals' */ - retraversegrays(g); - work -= g->GCmemtrav; /* restart counting */ + work = g->GCmemtrav; /* stop counting (do not recount 'grayagain') */ + g->gray = grayagain; + propagateall(g); /* traverse 'grayagain' list */ + g->GCmemtrav = 0; /* restart counting */ convergeephemerons(g); /* at this point, all strongly accessible objects are marked. */ - /* clear values from weak tables, before checking finalizers */ + /* Clear values from weak tables, before checking finalizers */ clearvalues(g, g->weak, NULL); clearvalues(g, g->allweak, NULL); origweak = g->weak; origall = g->allweak; work += g->GCmemtrav; /* stop counting (objects being finalized) */ - separatetobefnz(L, 0); /* separate objects to be finalized */ + separatetobefnz(g, 0); /* separate objects to be finalized */ + g->gcfinnum = 1; /* there may be objects to be finalized */ markbeingfnz(g); /* mark objects that will be finalized */ - propagateall(g); /* remark, to propagate `preserveness' */ - work -= g->GCmemtrav; /* restart counting */ + propagateall(g); /* remark, to propagate 'resurrection' */ + g->GCmemtrav = 0; /* restart counting */ convergeephemerons(g); /* at this point, all resurrected objects are marked. */ /* remove dead objects from weak tables */ clearkeys(g, g->ephemeron, NULL); /* clear keys from all ephemeron tables */ - clearkeys(g, g->allweak, NULL); /* clear keys from all allweak tables */ + clearkeys(g, g->allweak, NULL); /* clear keys from all 'allweak' tables */ /* clear values from resurrected weak tables */ clearvalues(g, g->weak, origweak); clearvalues(g, g->allweak, origall); + luaS_clearcache(g); g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ work += g->GCmemtrav; /* complete counting */ return work; /* estimate of memory marked by 'atomic' */ } +static lu_mem sweepstep (lua_State *L, global_State *g, + int nextstate, GCObject **nextlist) { + if (g->sweepgc) { + l_mem olddebt = g->GCdebt; + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); + g->GCestimate += g->GCdebt - olddebt; /* update estimate */ + if (g->sweepgc) /* is there still something to sweep? */ + return (GCSWEEPMAX * GCSWEEPCOST); + } + /* else enter next state */ + g->gcstate = nextstate; + g->sweepgc = nextlist; + return 0; +} + + static lu_mem singlestep (lua_State *L) { global_State *g = G(L); switch (g->gcstate) { case GCSpause: { - /* start to count memory traversed */ g->GCmemtrav = g->strt.size * sizeof(GCObject*); - lua_assert(!isgenerational(g)); restartcollection(g); g->gcstate = GCSpropagate; return g->GCmemtrav; } case GCSpropagate: { - if (g->gray) { - lu_mem oldtrav = g->GCmemtrav; - propagatemark(g); - return g->GCmemtrav - oldtrav; /* memory traversed in this step */ - } - else { /* no more `gray' objects */ - lu_mem work; - int sw; - g->gcstate = GCSatomic; /* finish mark phase */ - g->GCestimate = g->GCmemtrav; /* save what was counted */; - work = atomic(L); /* add what was traversed by 'atomic' */ - g->GCestimate += work; /* estimate of total memory traversed */ - sw = entersweep(L); - return work + sw * GCSWEEPCOST; - } + g->GCmemtrav = 0; + lua_assert(g->gray); + propagatemark(g); + if (g->gray == NULL) /* no more gray objects? */ + g->gcstate = GCSatomic; /* finish propagate phase */ + return g->GCmemtrav; /* memory traversed in this step */ } - case GCSsweepstring: { - int i; - for (i = 0; i < GCSWEEPMAX && g->sweepstrgc + i < g->strt.size; i++) - sweepwholelist(L, &g->strt.hash[g->sweepstrgc + i]); - g->sweepstrgc += i; - if (g->sweepstrgc >= g->strt.size) /* no more strings to sweep? */ - g->gcstate = GCSsweepudata; - return i * GCSWEEPCOST; + case GCSatomic: { + lu_mem work; + propagateall(g); /* make sure gray list is empty */ + work = atomic(L); /* work is what was traversed by 'atomic' */ + entersweep(L); + g->GCestimate = gettotalbytes(g); /* first estimate */; + return work; } - case GCSsweepudata: { - if (g->sweepfin) { - g->sweepfin = sweeplist(L, g->sweepfin, GCSWEEPMAX); - return GCSWEEPMAX*GCSWEEPCOST; - } - else { - g->gcstate = GCSsweep; - return 0; - } + case GCSswpallgc: { /* sweep "regular" objects */ + return sweepstep(L, g, GCSswpfinobj, &g->finobj); + } + case GCSswpfinobj: { /* sweep objects with finalizers */ + return sweepstep(L, g, GCSswptobefnz, &g->tobefnz); } - case GCSsweep: { - if (g->sweepgc) { - g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); - return GCSWEEPMAX*GCSWEEPCOST; + case GCSswptobefnz: { /* sweep objects to be finalized */ + return sweepstep(L, g, GCSswpend, NULL); + } + case GCSswpend: { /* finish sweeps */ + makewhite(g, g->mainthread); /* sweep main thread */ + checkSizes(L, g); + g->gcstate = GCScallfin; + return 0; + } + case GCScallfin: { /* call remaining finalizers */ + if (g->tobefnz && g->gckind != KGC_EMERGENCY) { + int n = runafewfinalizers(L); + return (n * GCFINALIZECOST); } - else { - /* sweep main thread */ - GCObject *mt = obj2gco(g->mainthread); - sweeplist(L, &mt, 1); - checkSizes(L); + else { /* emergency mode or no more finalizers */ g->gcstate = GCSpause; /* finish collection */ - return GCSWEEPCOST; + return 0; } } default: lua_assert(0); return 0; @@ -1114,105 +1105,70 @@ void luaC_runtilstate (lua_State *L, int statesmask) { } -static void generationalcollection (lua_State *L) { - global_State *g = G(L); - lua_assert(g->gcstate == GCSpropagate); - if (g->GCestimate == 0) { /* signal for another major collection? */ - luaC_fullgc(L, 0); /* perform a full regular collection */ - g->GCestimate = gettotalbytes(g); /* update control */ - } +/* +** get GC debt and convert it from Kb to 'work units' (avoid zero debt +** and overflows) +*/ +static l_mem getdebt (global_State *g) { + l_mem debt = g->GCdebt; + int stepmul = g->gcstepmul; + if (debt <= 0) return 0; /* minimal debt */ else { - lu_mem estimate = g->GCestimate; - luaC_runtilstate(L, bitmask(GCSpause)); /* run complete (minor) cycle */ - g->gcstate = GCSpropagate; /* skip restart */ - if (gettotalbytes(g) > (estimate / 100) * g->gcmajorinc) - g->GCestimate = 0; /* signal for a major collection */ - else - g->GCestimate = estimate; /* keep estimate from last major coll. */ - + debt = (debt / STEPMULADJ) + 1; + debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; + return debt; } - setpause(g, gettotalbytes(g)); - lua_assert(g->gcstate == GCSpropagate); } - -static void incstep (lua_State *L) { +/* +** performs a basic GC step when collector is running +*/ +void luaC_step (lua_State *L) { global_State *g = G(L); - l_mem debt = g->GCdebt; - int stepmul = g->gcstepmul; - if (stepmul < 40) stepmul = 40; /* avoid ridiculous low values (and 0) */ - /* convert debt from Kb to 'work units' (avoid zero debt and overflows) */ - debt = (debt / STEPMULADJ) + 1; - debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; - do { /* always perform at least one single step */ - lu_mem work = singlestep(L); /* do some work */ + l_mem debt = getdebt(g); /* GC deficit (be paid now) */ + if (!g->gcrunning) { /* not running? */ + luaE_setdebt(g, -GCSTEPSIZE * 10); /* avoid being called too often */ + return; + } + do { /* repeat until pause or enough "credit" (negative debt) */ + lu_mem work = singlestep(L); /* perform one single step */ debt -= work; } while (debt > -GCSTEPSIZE && g->gcstate != GCSpause); if (g->gcstate == GCSpause) - setpause(g, g->GCestimate); /* pause until next cycle */ + setpause(g); /* pause until next cycle */ else { - debt = (debt / stepmul) * STEPMULADJ; /* convert 'work units' to Kb */ + debt = (debt / g->gcstepmul) * STEPMULADJ; /* convert 'work units' to Kb */ luaE_setdebt(g, debt); + runafewfinalizers(L); } } /* -** performs a basic GC step -*/ -void luaC_forcestep (lua_State *L) { - global_State *g = G(L); - int i; - if (isgenerational(g)) generationalcollection(L); - else incstep(L); - /* run a few finalizers (or all of them at the end of a collect cycle) */ - for (i = 0; g->tobefnz && (i < GCFINALIZENUM || g->gcstate == GCSpause); i++) - GCTM(L, 1); /* call one finalizer */ -} - - -/* -** performs a basic GC step only if collector is running -*/ -void luaC_step (lua_State *L) { - global_State *g = G(L); - if (g->gcrunning) luaC_forcestep(L); - else luaE_setdebt(g, -GCSTEPSIZE); /* avoid being called too often */ -} - - - -/* -** performs a full GC cycle; if "isemergency", does not call -** finalizers (which could change stack positions) +** Performs a full GC cycle; if 'isemergency', set a flag to avoid +** some operations which could change the interpreter state in some +** unexpected ways (running finalizers and shrinking some structures). +** Before running the collection, check 'keepinvariant'; if it is true, +** there may be some objects marked as black, so the collector has +** to sweep all objects to turn them back to white (as white has not +** changed, nothing will be collected). */ void luaC_fullgc (lua_State *L, int isemergency) { global_State *g = G(L); - int origkind = g->gckind; - lua_assert(origkind != KGC_EMERGENCY); - if (isemergency) /* do not run finalizers during emergency GC */ - g->gckind = KGC_EMERGENCY; - else { - g->gckind = KGC_NORMAL; - callallpendingfinalizers(L, 1); - } - if (keepinvariant(g)) { /* may there be some black objects? */ - /* must sweep all objects to turn them back to white - (as white has not changed, nothing will be collected) */ - entersweep(L); + lua_assert(g->gckind == KGC_NORMAL); + if (isemergency) g->gckind = KGC_EMERGENCY; /* set flag */ + if (keepinvariant(g)) { /* black objects? */ + entersweep(L); /* sweep everything to turn them back to white */ } /* finish any pending sweep phase to start a new cycle */ luaC_runtilstate(L, bitmask(GCSpause)); luaC_runtilstate(L, ~bitmask(GCSpause)); /* start new collection */ - luaC_runtilstate(L, bitmask(GCSpause)); /* run entire collection */ - if (origkind == KGC_GEN) { /* generational mode? */ - /* generational mode must be kept in propagate phase */ - luaC_runtilstate(L, bitmask(GCSpropagate)); - } - g->gckind = origkind; - setpause(g, gettotalbytes(g)); - if (!isemergency) /* do not run finalizers during emergency GC */ - callallpendingfinalizers(L, 1); + luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ + /* estimate must be correct after a full GC cycle */ + lua_assert(g->GCestimate == gettotalbytes(g)); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + g->gckind = KGC_NORMAL; + setpause(g); } /* }====================================================== */ diff --git a/depends/lua/src/linit.c b/depends/lua/src/linit.c index c1a383047..8ce94ccb3 100644 --- a/depends/lua/src/linit.c +++ b/depends/lua/src/linit.c @@ -1,20 +1,33 @@ /* -** $Id: linit.c,v 1.32.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: linit.c,v 1.38 2015/01/05 13:48:33 roberto Exp $ ** Initialization of libraries for lua.c and other clients ** See Copyright Notice in lua.h */ +#define linit_c +#define LUA_LIB + /* ** If you embed Lua in your program and need to open the standard ** libraries, call luaL_openlibs in your program. If you need a ** different set of libraries, copy this file to your project and edit ** it to suit your needs. +** +** You can also *preload* libraries, so that a later 'require' can +** open the library, which is already linked to the application. +** For that, do the following code: +** +** luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); +** lua_pushcfunction(L, luaopen_modname); +** lua_setfield(L, -2, modname); +** lua_pop(L, 1); // remove _PRELOAD table */ +#include "lprefix.h" -#define linit_c -#define LUA_LIB + +#include #include "lua.h" @@ -34,34 +47,22 @@ static const luaL_Reg loadedlibs[] = { {LUA_IOLIBNAME, luaopen_io}, {LUA_OSLIBNAME, luaopen_os}, {LUA_STRLIBNAME, luaopen_string}, - {LUA_BITLIBNAME, luaopen_bit32}, {LUA_MATHLIBNAME, luaopen_math}, + {LUA_UTF8LIBNAME, luaopen_utf8}, {LUA_DBLIBNAME, luaopen_debug}, - {NULL, NULL} -}; - - -/* -** these libs are preloaded and must be required before used -*/ -static const luaL_Reg preloadedlibs[] = { +#if defined(LUA_COMPAT_BITLIB) + {LUA_BITLIBNAME, luaopen_bit32}, +#endif {NULL, NULL} }; LUALIB_API void luaL_openlibs (lua_State *L) { const luaL_Reg *lib; - /* call open functions from 'loadedlibs' and set results to global table */ + /* "require" functions from 'loadedlibs' and set results to global table */ for (lib = loadedlibs; lib->func; lib++) { luaL_requiref(L, lib->name, lib->func, 1); lua_pop(L, 1); /* remove lib */ } - /* add open functions from 'preloadedlibs' into 'package.preload' table */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); - for (lib = preloadedlibs; lib->func; lib++) { - lua_pushcfunction(L, lib->func); - lua_setfield(L, -2, lib->name); - } - lua_pop(L, 1); /* remove _PRELOAD table */ } diff --git a/depends/lua/src/liolib.c b/depends/lua/src/liolib.c index 2a4ec4aa3..aa78e593f 100644 --- a/depends/lua/src/liolib.c +++ b/depends/lua/src/liolib.c @@ -1,120 +1,139 @@ /* -** $Id: liolib.c,v 2.112.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: liolib.c,v 2.149 2016/05/02 14:03:19 roberto Exp $ ** Standard I/O (and system) library ** See Copyright Notice in lua.h */ +#define liolib_c +#define LUA_LIB -/* -** This definition must come before the inclusion of 'stdio.h'; it -** should not affect non-POSIX systems -*/ -#if !defined(_FILE_OFFSET_BITS) -#define _LARGEFILE_SOURCE 1 -#define _FILE_OFFSET_BITS 64 -#endif +#include "lprefix.h" +#include #include +#include #include #include #include -#define liolib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" #include "lualib.h" -#if !defined(lua_checkmode) + /* -** Check whether 'mode' matches '[rwa]%+?b?'. ** Change this macro to accept other modes for 'fopen' besides ** the standard ones. */ -#define lua_checkmode(mode) \ +#if !defined(l_checkmode) + +/* accepted extensions to 'mode' in 'fopen' */ +#if !defined(L_MODEEXT) +#define L_MODEEXT "b" +#endif + +/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ +#define l_checkmode(mode) \ (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && \ - (*mode != '+' || ++mode) && /* skip if char is '+' */ \ - (*mode != 'b' || ++mode) && /* skip if char is 'b' */ \ - (*mode == '\0')) + (*mode != '+' || (++mode, 1)) && /* skip if char is '+' */ \ + (strspn(mode, L_MODEEXT) == strlen(mode))) #endif /* ** {====================================================== -** lua_popen spawns a new process connected to the current +** l_popen spawns a new process connected to the current ** one through the file streams. ** ======================================================= */ -#if !defined(lua_popen) /* { */ - -#if defined(LUA_USE_POPEN) /* { */ +#if !defined(l_popen) /* { */ -#define lua_popen(L,c,m) ((void)L, fflush(NULL), popen(c,m)) -#define lua_pclose(L,file) ((void)L, pclose(file)) +#if defined(LUA_USE_POSIX) /* { */ -#elif defined(LUA_WIN) /* }{ */ +#define l_popen(L,c,m) (fflush(NULL), popen(c,m)) +#define l_pclose(L,file) (pclose(file)) -#define lua_popen(L,c,m) ((void)L, _popen(c,m)) -#define lua_pclose(L,file) ((void)L, _pclose(file)) +#elif defined(LUA_USE_WINDOWS) /* }{ */ +#define l_popen(L,c,m) (_popen(c,m)) +#define l_pclose(L,file) (_pclose(file)) #else /* }{ */ -#define lua_popen(L,c,m) ((void)((void)c, m), \ - luaL_error(L, LUA_QL("popen") " not supported"), (FILE*)0) -#define lua_pclose(L,file) ((void)((void)L, file), -1) - +/* ISO C definitions */ +#define l_popen(L,c,m) \ + ((void)((void)c, m), \ + luaL_error(L, "'popen' not supported"), \ + (FILE*)0) +#define l_pclose(L,file) ((void)L, (void)file, -1) #endif /* } */ -#endif /* } */ +#endif /* } */ /* }====================================================== */ +#if !defined(l_getc) /* { */ + +#if defined(LUA_USE_POSIX) +#define l_getc(f) getc_unlocked(f) +#define l_lockfile(f) flockfile(f) +#define l_unlockfile(f) funlockfile(f) +#else +#define l_getc(f) getc(f) +#define l_lockfile(f) ((void)0) +#define l_unlockfile(f) ((void)0) +#endif + +#endif /* } */ + + /* ** {====================================================== -** lua_fseek: configuration for longer offsets +** l_fseek: configuration for longer offsets ** ======================================================= */ -#if !defined(lua_fseek) && !defined(LUA_ANSI) /* { */ +#if !defined(l_fseek) /* { */ #if defined(LUA_USE_POSIX) /* { */ +#include + #define l_fseek(f,o,w) fseeko(f,o,w) #define l_ftell(f) ftello(f) #define l_seeknum off_t -#elif defined(LUA_WIN) && !defined(_CRTIMP_TYPEINFO) \ +#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ -/* Windows (but not DDK) and Visual C++ 2005 or higher */ +/* Windows (but not DDK) and Visual C++ 2005 or higher */ #define l_fseek(f,o,w) _fseeki64(f,o,w) #define l_ftell(f) _ftelli64(f) #define l_seeknum __int64 -#endif /* } */ - -#endif /* } */ - +#else /* }{ */ -#if !defined(l_fseek) /* default definitions */ +/* ISO C definitions */ #define l_fseek(f,o,w) fseek(f,o,w) #define l_ftell(f) ftell(f) #define l_seeknum long -#endif + +#endif /* } */ + +#endif /* } */ /* }====================================================== */ #define IO_PREFIX "_IO_" +#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) #define IO_INPUT (IO_PREFIX "input") #define IO_OUTPUT (IO_PREFIX "output") @@ -161,9 +180,9 @@ static FILE *tofile (lua_State *L) { /* -** When creating file handles, always creates a `closed' file handle +** When creating file handles, always creates a 'closed' file handle ** before opening the actual file; so, if there is a memory error, the -** file is not left opened. +** handle is in a consistent state. */ static LStream *newprefile (lua_State *L) { LStream *p = (LStream *)lua_newuserdata(L, sizeof(LStream)); @@ -173,9 +192,14 @@ static LStream *newprefile (lua_State *L) { } +/* +** Calls the 'close' function from a file handle. The 'volatile' avoids +** a bug in some versions of the Clang compiler (e.g., clang 3.0 for +** 32 bits). +*/ static int aux_close (lua_State *L) { LStream *p = tolstream(L); - lua_CFunction cf = p->closef; + volatile lua_CFunction cf = p->closef; p->closef = NULL; /* mark stream as closed */ return (*cf)(L); /* close it */ } @@ -219,7 +243,7 @@ static void opencheck (lua_State *L, const char *fname, const char *mode) { LStream *p = newfile(L); p->f = fopen(fname, mode); if (p->f == NULL) - luaL_error(L, "cannot open file " LUA_QS " (%s)", fname, strerror(errno)); + luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno)); } @@ -228,7 +252,7 @@ static int io_open (lua_State *L) { const char *mode = luaL_optstring(L, 2, "r"); LStream *p = newfile(L); const char *md = mode; /* to traverse/check mode */ - luaL_argcheck(L, lua_checkmode(md), 2, "invalid mode"); + luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); p->f = fopen(filename, mode); return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; } @@ -239,7 +263,7 @@ static int io_open (lua_State *L) { */ static int io_pclose (lua_State *L) { LStream *p = tolstream(L); - return luaL_execresult(L, lua_pclose(L, p->f)); + return luaL_execresult(L, l_pclose(L, p->f)); } @@ -247,7 +271,7 @@ static int io_popen (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); LStream *p = newprefile(L); - p->f = lua_popen(L, filename, mode); + p->f = l_popen(L, filename, mode); p->closef = &io_pclose; return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; } @@ -265,7 +289,7 @@ static FILE *getiofile (lua_State *L, const char *findex) { lua_getfield(L, LUA_REGISTRYINDEX, findex); p = (LStream *)lua_touserdata(L, -1); if (isclosed(p)) - luaL_error(L, "standard %s file is closed", findex + strlen(IO_PREFIX)); + luaL_error(L, "standard %s file is closed", findex + IOPREF_LEN); return p->f; } @@ -300,15 +324,18 @@ static int io_output (lua_State *L) { static int io_readline (lua_State *L); +/* +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit +** in the limit for upvalues of a closure) +*/ +#define MAXARGLINE 250 + static void aux_lines (lua_State *L, int toclose) { - int i; int n = lua_gettop(L) - 1; /* number of arguments to read */ - /* ensure that arguments will fit here and into 'io_readline' stack */ - luaL_argcheck(L, n <= LUA_MINSTACK - 3, LUA_MINSTACK - 3, "too many options"); - lua_pushvalue(L, 1); /* file handle */ + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); lua_pushinteger(L, n); /* number of arguments to read */ lua_pushboolean(L, toclose); /* close/not close file when finished */ - for (i = 1; i <= n; i++) lua_pushvalue(L, i + 1); /* copy arguments */ + lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */ lua_pushcclosure(L, io_readline, 3 + n); } @@ -347,13 +374,91 @@ static int io_lines (lua_State *L) { */ -static int read_number (lua_State *L, FILE *f) { - lua_Number d; - if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) { - lua_pushnumber(L, d); - return 1; +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + + +/* auxiliary structure used by 'read_number' */ +typedef struct { + FILE *f; /* file being read */ + int c; /* current character (look ahead) */ + int n; /* number of elements in buffer 'buff' */ + char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ +} RN; + + +/* +** Add current char to buffer (if not out of space) and read next one +*/ +static int nextc (RN *rn) { + if (rn->n >= L_MAXLENNUM) { /* buffer overflow? */ + rn->buff[0] = '\0'; /* invalidate result */ + return 0; /* fail */ } else { + rn->buff[rn->n++] = rn->c; /* save current char */ + rn->c = l_getc(rn->f); /* read next one */ + return 1; + } +} + + +/* +** Accept current char if it is in 'set' (of size 2) +*/ +static int test2 (RN *rn, const char *set) { + if (rn->c == set[0] || rn->c == set[1]) + return nextc(rn); + else return 0; +} + + +/* +** Read a sequence of (hex)digits +*/ +static int readdigits (RN *rn, int hex) { + int count = 0; + while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) + count++; + return count; +} + + +/* +** Read a number: first reads a valid prefix of a numeral into a buffer. +** Then it calls 'lua_stringtonumber' to check whether the format is +** correct and to convert it to a Lua number +*/ +static int read_number (lua_State *L, FILE *f) { + RN rn; + int count = 0; + int hex = 0; + char decp[2]; + rn.f = f; rn.n = 0; + decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ + decp[1] = '.'; /* always accept a dot */ + l_lockfile(rn.f); + do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ + test2(&rn, "-+"); /* optional signal */ + if (test2(&rn, "00")) { + if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ + else count = 1; /* count initial '0' as a valid digit */ + } + count += readdigits(&rn, hex); /* integral part */ + if (test2(&rn, decp)) /* decimal point? */ + count += readdigits(&rn, hex); /* fractional part */ + if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ + test2(&rn, "-+"); /* exponent signal */ + readdigits(&rn, 0); /* exponent digits */ + } + ungetc(rn.c, rn.f); /* unread look-ahead char */ + l_unlockfile(rn.f); + rn.buff[rn.n] = '\0'; /* finish string */ + if (lua_stringtonumber(L, rn.buff)) /* is this a valid number? */ + return 1; /* ok */ + else { /* invalid format */ lua_pushnil(L); /* "result" to be removed */ return 0; /* read fails */ } @@ -362,48 +467,42 @@ static int read_number (lua_State *L, FILE *f) { static int test_eof (lua_State *L, FILE *f) { int c = getc(f); - ungetc(c, f); - lua_pushlstring(L, NULL, 0); + ungetc(c, f); /* no-op when c == EOF */ + lua_pushliteral(L, ""); return (c != EOF); } static int read_line (lua_State *L, FILE *f, int chop) { luaL_Buffer b; + int c = '\0'; luaL_buffinit(L, &b); - for (;;) { - size_t l; - char *p = luaL_prepbuffer(&b); - if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */ - luaL_pushresult(&b); /* close buffer */ - return (lua_rawlen(L, -1) > 0); /* check whether read something */ - } - l = strlen(p); - if (l == 0 || p[l-1] != '\n') - luaL_addsize(&b, l); - else { - luaL_addsize(&b, l - chop); /* chop 'eol' if needed */ - luaL_pushresult(&b); /* close buffer */ - return 1; /* read at least an `eol' */ - } + while (c != EOF && c != '\n') { /* repeat until end of line */ + char *buff = luaL_prepbuffer(&b); /* preallocate buffer */ + int i = 0; + l_lockfile(f); /* no memory errors can happen inside the lock */ + while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') + buff[i++] = c; + l_unlockfile(f); + luaL_addsize(&b, i); } + if (!chop && c == '\n') /* want a newline and have one? */ + luaL_addchar(&b, c); /* add ending newline to result */ + luaL_pushresult(&b); /* close buffer */ + /* return ok if read something (either a newline or something else) */ + return (c == '\n' || lua_rawlen(L, -1) > 0); } -#define MAX_SIZE_T (~(size_t)0) - static void read_all (lua_State *L, FILE *f) { - size_t rlen = LUAL_BUFFERSIZE; /* how much to read in each cycle */ + size_t nr; luaL_Buffer b; luaL_buffinit(L, &b); - for (;;) { - char *p = luaL_prepbuffsize(&b, rlen); - size_t nr = fread(p, sizeof(char), rlen, f); + do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ + char *p = luaL_prepbuffer(&b); + nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f); luaL_addsize(&b, nr); - if (nr < rlen) break; /* eof? */ - else if (rlen <= (MAX_SIZE_T / 4)) /* avoid buffers too large */ - rlen *= 2; /* double buffer size at each iteration */ - } + } while (nr == LUAL_BUFFERSIZE); luaL_pushresult(&b); /* close buffer */ } @@ -435,13 +534,13 @@ static int g_read (lua_State *L, FILE *f, int first) { success = 1; for (n = first; nargs-- && success; n++) { if (lua_type(L, n) == LUA_TNUMBER) { - size_t l = (size_t)lua_tointeger(L, n); + size_t l = (size_t)luaL_checkinteger(L, n); success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); } else { - const char *p = lua_tostring(L, n); - luaL_argcheck(L, p && p[0] == '*', n, "invalid option"); - switch (p[1]) { + const char *p = luaL_checkstring(L, n); + if (*p == '*') p++; /* skip optional '*' (for compatibility) */ + switch (*p) { case 'n': /* number */ success = read_number(L, f); break; @@ -488,11 +587,12 @@ static int io_readline (lua_State *L) { if (isclosed(p)) /* file is already closed? */ return luaL_error(L, "file is already closed"); lua_settop(L , 1); + luaL_checkstack(L, n, "too many arguments"); for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ lua_pushvalue(L, lua_upvalueindex(3 + i)); n = g_read(L, p->f, 2); /* 'n' is number of results */ lua_assert(n > 0); /* should return at least a nil */ - if (!lua_isnil(L, -n)) /* read at least one value? */ + if (lua_toboolean(L, -n)) /* read at least one value? */ return n; /* return them */ else { /* first result is nil: EOF or error */ if (n > 1) { /* is there error information? */ @@ -517,8 +617,10 @@ static int g_write (lua_State *L, FILE *f, int arg) { for (; nargs--; arg++) { if (lua_type(L, arg) == LUA_TNUMBER) { /* optimization: could be done exactly as for strings */ - status = status && - fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0; + int len = lua_isinteger(L, arg) + ? fprintf(f, LUA_INTEGER_FMT, lua_tointeger(L, arg)) + : fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)); + status = status && (len > 0); } else { size_t l; @@ -548,15 +650,15 @@ static int f_seek (lua_State *L) { static const char *const modenames[] = {"set", "cur", "end", NULL}; FILE *f = tofile(L); int op = luaL_checkoption(L, 2, "cur", modenames); - lua_Number p3 = luaL_optnumber(L, 3, 0); + lua_Integer p3 = luaL_optinteger(L, 3, 0); l_seeknum offset = (l_seeknum)p3; - luaL_argcheck(L, (lua_Number)offset == p3, 3, + luaL_argcheck(L, (lua_Integer)offset == p3, 3, "not an integer in proper range"); op = l_fseek(f, offset, mode[op]); if (op) return luaL_fileresult(L, 0, NULL); /* error */ else { - lua_pushnumber(L, (lua_Number)l_ftell(f)); + lua_pushinteger(L, (lua_Integer)l_ftell(f)); return 1; } } @@ -568,7 +670,7 @@ static int f_setvbuf (lua_State *L) { FILE *f = tofile(L); int op = luaL_checkoption(L, 2, NULL, modenames); lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); - int res = setvbuf(f, NULL, mode[op], sz); + int res = setvbuf(f, NULL, mode[op], (size_t)sz); return luaL_fileresult(L, res == 0, NULL); } diff --git a/depends/lua/src/llex.c b/depends/lua/src/llex.c index c4b820e83..70328273f 100644 --- a/depends/lua/src/llex.c +++ b/depends/lua/src/llex.c @@ -1,20 +1,24 @@ /* -** $Id: llex.c,v 2.63.1.2 2013/08/30 15:49:41 roberto Exp $ +** $Id: llex.c,v 2.96 2016/05/02 14:02:12 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ +#define llex_c +#define LUA_CORE + +#include "lprefix.h" + #include #include -#define llex_c -#define LUA_CORE - #include "lua.h" #include "lctype.h" +#include "ldebug.h" #include "ldo.h" +#include "lgc.h" #include "llex.h" #include "lobject.h" #include "lparser.h" @@ -38,8 +42,9 @@ static const char *const luaX_tokens [] = { "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", - "..", "...", "==", ">=", "<=", "~=", "::", "", - "", "", "" + "//", "..", "...", "==", ">=", "<=", "~=", + "<<", ">>", "::", "", + "", "", "", "" }; @@ -53,7 +58,7 @@ static void save (LexState *ls, int c) { Mbuffer *b = ls->buff; if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) { size_t newsize; - if (luaZ_sizebuffer(b) >= MAX_SIZET/2) + if (luaZ_sizebuffer(b) >= MAX_SIZE/2) lexerror(ls, "lexical element too long", 0); newsize = luaZ_sizebuffer(b) * 2; luaZ_resizebuffer(ls->L, b, newsize); @@ -64,24 +69,25 @@ static void save (LexState *ls, int c) { void luaX_init (lua_State *L) { int i; + TString *e = luaS_newliteral(L, LUA_ENV); /* create env name */ + luaC_fix(L, obj2gco(e)); /* never collect this name */ for (i=0; itsv.extra = cast_byte(i+1); /* reserved word */ + luaC_fix(L, obj2gco(ts)); /* reserved words are never collected */ + ts->extra = cast_byte(i+1); /* reserved word */ } } const char *luaX_token2str (LexState *ls, int token) { if (token < FIRST_RESERVED) { /* single-byte symbols? */ - lua_assert(token == cast(unsigned char, token)); - return (lisprint(token)) ? luaO_pushfstring(ls->L, LUA_QL("%c"), token) : - luaO_pushfstring(ls->L, "char(%d)", token); + lua_assert(token == cast_uchar(token)); + return luaO_pushfstring(ls->L, "'%c'", token); } else { const char *s = luaX_tokens[token - FIRST_RESERVED]; if (token < TK_EOS) /* fixed format (symbols and reserved words)? */ - return luaO_pushfstring(ls->L, LUA_QS, s); + return luaO_pushfstring(ls->L, "'%s'", s); else /* names, strings, and numerals */ return s; } @@ -90,11 +96,10 @@ const char *luaX_token2str (LexState *ls, int token) { static const char *txtToken (LexState *ls, int token) { switch (token) { - case TK_NAME: - case TK_STRING: - case TK_NUMBER: + case TK_NAME: case TK_STRING: + case TK_FLT: case TK_INT: save(ls, '\0'); - return luaO_pushfstring(ls->L, LUA_QS, luaZ_buffer(ls->buff)); + return luaO_pushfstring(ls->L, "'%s'", luaZ_buffer(ls->buff)); default: return luaX_token2str(ls, token); } @@ -102,9 +107,7 @@ static const char *txtToken (LexState *ls, int token) { static l_noret lexerror (LexState *ls, const char *msg, int token) { - char buff[LUA_IDSIZE]; - luaO_chunkid(buff, getstr(ls->source), LUA_IDSIZE); - msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg); + msg = luaG_addinfo(ls->L, msg, ls->source, ls->linenumber); if (token) luaO_pushfstring(ls->L, "%s near %s", msg, txtToken(ls, token)); luaD_throw(ls->L, LUA_ERRSYNTAX); @@ -117,24 +120,24 @@ l_noret luaX_syntaxerror (LexState *ls, const char *msg) { /* -** creates a new string and anchors it in function's table so that -** it will not be collected until the end of the function's compilation -** (by that time it should be anchored in function's prototype) +** creates a new string and anchors it in scanner's table so that +** it will not be collected until the end of the compilation +** (by that time it should be anchored somewhere) */ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->L; - TValue *o; /* entry for `str' */ + TValue *o; /* entry for 'str' */ TString *ts = luaS_newlstr(L, str, l); /* create new string */ setsvalue2s(L, L->top++, ts); /* temporarily anchor it in stack */ - o = luaH_set(L, ls->fs->h, L->top - 1); - if (ttisnil(o)) { /* not in use yet? (see 'addK') */ + o = luaH_set(L, ls->h, L->top - 1); + if (ttisnil(o)) { /* not in use yet? */ /* boolean value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ setbvalue(o, 1); /* t[string] = true */ luaC_checkGC(L); } else { /* string already present */ - ts = rawtsvalue(keyfromval(o)); /* re-use value previously stored */ + ts = tsvalue(keyfromval(o)); /* re-use value previously stored */ } L->top--; /* remove string from stack */ return ts; @@ -148,17 +151,17 @@ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { static void inclinenumber (LexState *ls) { int old = ls->current; lua_assert(currIsNewline(ls)); - next(ls); /* skip `\n' or `\r' */ + next(ls); /* skip '\n' or '\r' */ if (currIsNewline(ls) && ls->current != old) - next(ls); /* skip `\n\r' or `\r\n' */ + next(ls); /* skip '\n\r' or '\r\n' */ if (++ls->linenumber >= MAX_INT) - luaX_syntaxerror(ls, "chunk has too many lines"); + lexerror(ls, "chunk has too many lines", 0); } void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, int firstchar) { - ls->decpoint = '.'; + ls->t.token = 0; ls->L = L; ls->current = firstchar; ls->lookahead.token = TK_EOS; /* no look-ahead token */ @@ -167,8 +170,7 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, ls->linenumber = 1; ls->lastline = 1; ls->source = source; - ls->envn = luaS_new(L, LUA_ENV); /* create env name */ - luaS_fix(ls->envn); /* never collect this name */ + ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */ luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ } @@ -181,78 +183,70 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, */ - -static int check_next (LexState *ls, const char *set) { - if (ls->current == '\0' || !strchr(set, ls->current)) - return 0; - save_and_next(ls); - return 1; -} - - -/* -** change all characters 'from' in buffer to 'to' -*/ -static void buffreplace (LexState *ls, char from, char to) { - size_t n = luaZ_bufflen(ls->buff); - char *p = luaZ_buffer(ls->buff); - while (n--) - if (p[n] == from) p[n] = to; +static int check_next1 (LexState *ls, int c) { + if (ls->current == c) { + next(ls); + return 1; + } + else return 0; } -#if !defined(getlocaledecpoint) -#define getlocaledecpoint() (localeconv()->decimal_point[0]) -#endif - - -#define buff2d(b,e) luaO_str2d(luaZ_buffer(b), luaZ_bufflen(b) - 1, e) - /* -** in case of format error, try to change decimal point separator to -** the one defined in the current locale and check again +** Check whether current char is in set 'set' (with two chars) and +** saves it */ -static void trydecpoint (LexState *ls, SemInfo *seminfo) { - char old = ls->decpoint; - ls->decpoint = getlocaledecpoint(); - buffreplace(ls, old, ls->decpoint); /* try new decimal separator */ - if (!buff2d(ls->buff, &seminfo->r)) { - /* format error with correct decimal point: no more options */ - buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ - lexerror(ls, "malformed number", TK_NUMBER); +static int check_next2 (LexState *ls, const char *set) { + lua_assert(set[2] == '\0'); + if (ls->current == set[0] || ls->current == set[1]) { + save_and_next(ls); + return 1; } + else return 0; } /* LUA_NUMBER */ /* -** this function is quite liberal in what it accepts, as 'luaO_str2d' +** this function is quite liberal in what it accepts, as 'luaO_str2num' ** will reject ill-formed numerals. */ -static void read_numeral (LexState *ls, SemInfo *seminfo) { +static int read_numeral (LexState *ls, SemInfo *seminfo) { + TValue obj; const char *expo = "Ee"; int first = ls->current; lua_assert(lisdigit(ls->current)); save_and_next(ls); - if (first == '0' && check_next(ls, "Xx")) /* hexadecimal? */ + if (first == '0' && check_next2(ls, "xX")) /* hexadecimal? */ expo = "Pp"; for (;;) { - if (check_next(ls, expo)) /* exponent part? */ - check_next(ls, "+-"); /* optional exponent sign */ - if (lisxdigit(ls->current) || ls->current == '.') + if (check_next2(ls, expo)) /* exponent part? */ + check_next2(ls, "-+"); /* optional exponent sign */ + if (lisxdigit(ls->current)) + save_and_next(ls); + else if (ls->current == '.') save_and_next(ls); - else break; + else break; } save(ls, '\0'); - buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ - if (!buff2d(ls->buff, &seminfo->r)) /* format error? */ - trydecpoint(ls, seminfo); /* try to update decimal point separator */ + if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ + lexerror(ls, "malformed number", TK_FLT); + if (ttisinteger(&obj)) { + seminfo->i = ivalue(&obj); + return TK_INT; + } + else { + lua_assert(ttisfloat(&obj)); + seminfo->r = fltvalue(&obj); + return TK_FLT; + } } /* -** skip a sequence '[=*[' or ']=*]' and return its number of '='s or -** -1 if sequence is malformed +** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return +** its number of '='s; otherwise, return a negative number (-1 iff there +** are no '='s after initial bracket) */ static int skip_sep (LexState *ls) { int count = 0; @@ -268,18 +262,22 @@ static int skip_sep (LexState *ls) { static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { - save_and_next(ls); /* skip 2nd `[' */ + int line = ls->linenumber; /* initial line (for error message) */ + save_and_next(ls); /* skip 2nd '[' */ if (currIsNewline(ls)) /* string starts with a newline? */ inclinenumber(ls); /* skip it */ for (;;) { switch (ls->current) { - case EOZ: - lexerror(ls, (seminfo) ? "unfinished long string" : - "unfinished long comment", TK_EOS); + case EOZ: { /* error */ + const char *what = (seminfo ? "string" : "comment"); + const char *msg = luaO_pushfstring(ls->L, + "unfinished long %s (starting at line %d)", what, line); + lexerror(ls, msg, TK_EOS); break; /* to avoid warnings */ + } case ']': { if (skip_sep(ls) == sep) { - save_and_next(ls); /* skip 2nd `]' */ + save_and_next(ls); /* skip 2nd ']' */ goto endloop; } break; @@ -302,40 +300,65 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { } -static void escerror (LexState *ls, int *c, int n, const char *msg) { - int i; - luaZ_resetbuffer(ls->buff); /* prepare error message */ - save(ls, '\\'); - for (i = 0; i < n && c[i] != EOZ; i++) - save(ls, c[i]); - lexerror(ls, msg, TK_STRING); +static void esccheck (LexState *ls, int c, const char *msg) { + if (!c) { + if (ls->current != EOZ) + save_and_next(ls); /* add current to buffer for error message */ + lexerror(ls, msg, TK_STRING); + } +} + + +static int gethexa (LexState *ls) { + save_and_next(ls); + esccheck (ls, lisxdigit(ls->current), "hexadecimal digit expected"); + return luaO_hexavalue(ls->current); } static int readhexaesc (LexState *ls) { - int c[3], i; /* keep input for error message */ - int r = 0; /* result accumulator */ - c[0] = 'x'; /* for error message */ - for (i = 1; i < 3; i++) { /* read two hexadecimal digits */ - c[i] = next(ls); - if (!lisxdigit(c[i])) - escerror(ls, c, i + 1, "hexadecimal digit expected"); - r = (r << 4) + luaO_hexavalue(c[i]); + int r = gethexa(ls); + r = (r << 4) + gethexa(ls); + luaZ_buffremove(ls->buff, 2); /* remove saved chars from buffer */ + return r; +} + + +static unsigned long readutf8esc (LexState *ls) { + unsigned long r; + int i = 4; /* chars to be removed: '\', 'u', '{', and first digit */ + save_and_next(ls); /* skip 'u' */ + esccheck(ls, ls->current == '{', "missing '{'"); + r = gethexa(ls); /* must have at least one digit */ + while ((save_and_next(ls), lisxdigit(ls->current))) { + i++; + r = (r << 4) + luaO_hexavalue(ls->current); + esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large"); } + esccheck(ls, ls->current == '}', "missing '}'"); + next(ls); /* skip '}' */ + luaZ_buffremove(ls->buff, i); /* remove saved chars from buffer */ return r; } +static void utf8esc (LexState *ls) { + char buff[UTF8BUFFSZ]; + int n = luaO_utf8esc(buff, readutf8esc(ls)); + for (; n > 0; n--) /* add 'buff' to string */ + save(ls, buff[UTF8BUFFSZ - n]); +} + + static int readdecesc (LexState *ls) { - int c[3], i; + int i; int r = 0; /* result accumulator */ for (i = 0; i < 3 && lisdigit(ls->current); i++) { /* read up to 3 digits */ - c[i] = ls->current; - r = 10*r + c[i] - '0'; - next(ls); + r = 10*r + ls->current - '0'; + save_and_next(ls); } - if (r > UCHAR_MAX) - escerror(ls, c, i, "decimal escape too large"); + esccheck(ls, r <= UCHAR_MAX, "decimal escape too large"); + luaZ_buffremove(ls->buff, i); /* remove read digits from buffer */ return r; } @@ -353,7 +376,7 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { break; /* to avoid warnings */ case '\\': { /* escape sequences */ int c; /* final character to be saved */ - next(ls); /* do not save the `\' */ + save_and_next(ls); /* keep '\\' for error messages */ switch (ls->current) { case 'a': c = '\a'; goto read_save; case 'b': c = '\b'; goto read_save; @@ -363,12 +386,14 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { case 't': c = '\t'; goto read_save; case 'v': c = '\v'; goto read_save; case 'x': c = readhexaesc(ls); goto read_save; + case 'u': utf8esc(ls); goto no_save; case '\n': case '\r': inclinenumber(ls); c = '\n'; goto only_save; case '\\': case '\"': case '\'': c = ls->current; goto read_save; case EOZ: goto no_save; /* will raise an error next loop */ case 'z': { /* zap following span of spaces */ + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ next(ls); /* skip the 'z' */ while (lisspace(ls->current)) { if (currIsNewline(ls)) inclinenumber(ls); @@ -377,15 +402,18 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { goto no_save; } default: { - if (!lisdigit(ls->current)) - escerror(ls, &ls->current, 1, "invalid escape sequence"); - /* digital escape \ddd */ - c = readdecesc(ls); + esccheck(ls, lisdigit(ls->current), "invalid escape sequence"); + c = readdecesc(ls); /* digital escape '\ddd' */ goto only_save; } } - read_save: next(ls); /* read next character */ - only_save: save(ls, c); /* save 'c' */ + read_save: + next(ls); + /* go through */ + only_save: + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ + save(ls, c); + /* go through */ no_save: break; } default: @@ -417,7 +445,7 @@ static int llex (LexState *ls, SemInfo *seminfo) { next(ls); if (ls->current == '[') { /* long comment? */ int sep = skip_sep(ls); - luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ + luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */ if (sep >= 0) { read_long_string(ls, NULL, sep); /* skip long comment */ luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ @@ -435,33 +463,41 @@ static int llex (LexState *ls, SemInfo *seminfo) { read_long_string(ls, seminfo, sep); return TK_STRING; } - else if (sep == -1) return '['; - else lexerror(ls, "invalid long string delimiter", TK_STRING); + else if (sep != -1) /* '[=...' missing second bracket */ + lexerror(ls, "invalid long string delimiter", TK_STRING); + return '['; } case '=': { next(ls); - if (ls->current != '=') return '='; - else { next(ls); return TK_EQ; } + if (check_next1(ls, '=')) return TK_EQ; + else return '='; } case '<': { next(ls); - if (ls->current != '=') return '<'; - else { next(ls); return TK_LE; } + if (check_next1(ls, '=')) return TK_LE; + else if (check_next1(ls, '<')) return TK_SHL; + else return '<'; } case '>': { next(ls); - if (ls->current != '=') return '>'; - else { next(ls); return TK_GE; } + if (check_next1(ls, '=')) return TK_GE; + else if (check_next1(ls, '>')) return TK_SHR; + else return '>'; + } + case '/': { + next(ls); + if (check_next1(ls, '/')) return TK_IDIV; + else return '/'; } case '~': { next(ls); - if (ls->current != '=') return '~'; - else { next(ls); return TK_NE; } + if (check_next1(ls, '=')) return TK_NE; + else return '~'; } case ':': { next(ls); - if (ls->current != ':') return ':'; - else { next(ls); return TK_DBCOLON; } + if (check_next1(ls, ':')) return TK_DBCOLON; + else return ':'; } case '"': case '\'': { /* short literal strings */ read_string(ls, ls->current, seminfo); @@ -469,18 +505,17 @@ static int llex (LexState *ls, SemInfo *seminfo) { } case '.': { /* '.', '..', '...', or number */ save_and_next(ls); - if (check_next(ls, ".")) { - if (check_next(ls, ".")) + if (check_next1(ls, '.')) { + if (check_next1(ls, '.')) return TK_DOTS; /* '...' */ else return TK_CONCAT; /* '..' */ } else if (!lisdigit(ls->current)) return '.'; - /* else go through */ + else return read_numeral(ls, seminfo); } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { - read_numeral(ls, seminfo); - return TK_NUMBER; + return read_numeral(ls, seminfo); } case EOZ: { return TK_EOS; @@ -495,7 +530,7 @@ static int llex (LexState *ls, SemInfo *seminfo) { luaZ_bufflen(ls->buff)); seminfo->ts = ts; if (isreserved(ts)) /* reserved word? */ - return ts->tsv.extra - 1 + FIRST_RESERVED; + return ts->extra - 1 + FIRST_RESERVED; else { return TK_NAME; } diff --git a/depends/lua/src/lmathlib.c b/depends/lua/src/lmathlib.c index fe9fc5423..94815f129 100644 --- a/depends/lua/src/lmathlib.c +++ b/depends/lua/src/lmathlib.c @@ -1,16 +1,18 @@ /* -** $Id: lmathlib.c,v 1.83.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lmathlib.c,v 1.117 2015/10/02 15:39:23 roberto Exp $ ** Standard mathematical library ** See Copyright Notice in lua.h */ +#define lmathlib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include -#define lmathlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -18,13 +20,30 @@ #undef PI -#define PI ((lua_Number)(3.1415926535897932384626433832795)) -#define RADIANS_PER_DEGREE ((lua_Number)(PI/180.0)) - +#define PI (l_mathop(3.141592653589793238462643383279502884)) + + +#if !defined(l_rand) /* { */ +#if defined(LUA_USE_POSIX) +#define l_rand() random() +#define l_srand(x) srandom(x) +#define L_RANDMAX 2147483647 /* (2^31 - 1), following POSIX */ +#else +#define l_rand() rand() +#define l_srand(x) srand(x) +#define L_RANDMAX RAND_MAX +#endif +#endif /* } */ static int math_abs (lua_State *L) { - lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); + if (lua_isinteger(L, 1)) { + lua_Integer n = lua_tointeger(L, 1); + if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n); + lua_pushinteger(L, n); + } + else + lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); return 1; } @@ -33,31 +52,16 @@ static int math_sin (lua_State *L) { return 1; } -static int math_sinh (lua_State *L) { - lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); - return 1; -} - static int math_cos (lua_State *L) { lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1))); return 1; } -static int math_cosh (lua_State *L) { - lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); - return 1; -} - static int math_tan (lua_State *L) { lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1))); return 1; } -static int math_tanh (lua_State *L) { - lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); - return 1; -} - static int math_asin (lua_State *L) { lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1))); return 1; @@ -69,49 +73,106 @@ static int math_acos (lua_State *L) { } static int math_atan (lua_State *L) { - lua_pushnumber(L, l_mathop(atan)(luaL_checknumber(L, 1))); + lua_Number y = luaL_checknumber(L, 1); + lua_Number x = luaL_optnumber(L, 2, 1); + lua_pushnumber(L, l_mathop(atan2)(y, x)); return 1; } -static int math_atan2 (lua_State *L) { - lua_pushnumber(L, l_mathop(atan2)(luaL_checknumber(L, 1), - luaL_checknumber(L, 2))); + +static int math_toint (lua_State *L) { + int valid; + lua_Integer n = lua_tointegerx(L, 1, &valid); + if (valid) + lua_pushinteger(L, n); + else { + luaL_checkany(L, 1); + lua_pushnil(L); /* value is not convertible to integer */ + } return 1; } -static int math_ceil (lua_State *L) { - lua_pushnumber(L, l_mathop(ceil)(luaL_checknumber(L, 1))); - return 1; + +static void pushnumint (lua_State *L, lua_Number d) { + lua_Integer n; + if (lua_numbertointeger(d, &n)) /* does 'd' fit in an integer? */ + lua_pushinteger(L, n); /* result is integer */ + else + lua_pushnumber(L, d); /* result is float */ } + static int math_floor (lua_State *L) { - lua_pushnumber(L, l_mathop(floor)(luaL_checknumber(L, 1))); + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own floor */ + else { + lua_Number d = l_mathop(floor)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } + return 1; +} + + +static int math_ceil (lua_State *L) { + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own ceil */ + else { + lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } return 1; } + static int math_fmod (lua_State *L) { - lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), - luaL_checknumber(L, 2))); + if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) { + lua_Integer d = lua_tointeger(L, 2); + if ((lua_Unsigned)d + 1u <= 1u) { /* special cases: -1 or 0 */ + luaL_argcheck(L, d != 0, 2, "zero"); + lua_pushinteger(L, 0); /* avoid overflow with 0x80000... / -1 */ + } + else + lua_pushinteger(L, lua_tointeger(L, 1) % d); + } + else + lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), + luaL_checknumber(L, 2))); return 1; } + +/* +** next function does not use 'modf', avoiding problems with 'double*' +** (which is not compatible with 'float*') when lua_Number is not +** 'double'. +*/ static int math_modf (lua_State *L) { - lua_Number ip; - lua_Number fp = l_mathop(modf)(luaL_checknumber(L, 1), &ip); - lua_pushnumber(L, ip); - lua_pushnumber(L, fp); + if (lua_isinteger(L ,1)) { + lua_settop(L, 1); /* number is its own integer part */ + lua_pushnumber(L, 0); /* no fractional part */ + } + else { + lua_Number n = luaL_checknumber(L, 1); + /* integer part (rounds toward zero) */ + lua_Number ip = (n < 0) ? l_mathop(ceil)(n) : l_mathop(floor)(n); + pushnumint(L, ip); + /* fractional part (test needed for inf/-inf) */ + lua_pushnumber(L, (n == ip) ? l_mathop(0.0) : (n - ip)); + } return 2; } + static int math_sqrt (lua_State *L) { lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1))); return 1; } -static int math_pow (lua_State *L) { - lua_Number x = luaL_checknumber(L, 1); - lua_Number y = luaL_checknumber(L, 2); - lua_pushnumber(L, l_mathop(pow)(x, y)); + +static int math_ult (lua_State *L) { + lua_Integer a = luaL_checkinteger(L, 1); + lua_Integer b = luaL_checkinteger(L, 2); + lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b); return 1; } @@ -122,145 +183,208 @@ static int math_log (lua_State *L) { res = l_mathop(log)(x); else { lua_Number base = luaL_checknumber(L, 2); - if (base == (lua_Number)10.0) res = l_mathop(log10)(x); +#if !defined(LUA_USE_C89) + if (base == 2.0) res = l_mathop(log2)(x); else +#endif + if (base == 10.0) res = l_mathop(log10)(x); else res = l_mathop(log)(x)/l_mathop(log)(base); } lua_pushnumber(L, res); return 1; } -#if defined(LUA_COMPAT_LOG10) -static int math_log10 (lua_State *L) { - lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); - return 1; -} -#endif - static int math_exp (lua_State *L) { lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1))); return 1; } static int math_deg (lua_State *L) { - lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE); + lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI)); return 1; } static int math_rad (lua_State *L) { - lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE); - return 1; -} - -static int math_frexp (lua_State *L) { - int e; - lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); - lua_pushinteger(L, e); - return 2; -} - -static int math_ldexp (lua_State *L) { - lua_Number x = luaL_checknumber(L, 1); - int ep = luaL_checkint(L, 2); - lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0))); return 1; } - static int math_min (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ - lua_Number dmin = luaL_checknumber(L, 1); + int imin = 1; /* index of current minimum value */ int i; - for (i=2; i<=n; i++) { - lua_Number d = luaL_checknumber(L, i); - if (d < dmin) - dmin = d; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, i, imin, LUA_OPLT)) + imin = i; } - lua_pushnumber(L, dmin); + lua_pushvalue(L, imin); return 1; } static int math_max (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ - lua_Number dmax = luaL_checknumber(L, 1); + int imax = 1; /* index of current maximum value */ int i; - for (i=2; i<=n; i++) { - lua_Number d = luaL_checknumber(L, i); - if (d > dmax) - dmax = d; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, imax, i, LUA_OPLT)) + imax = i; } - lua_pushnumber(L, dmax); + lua_pushvalue(L, imax); return 1; } - +/* +** This function uses 'double' (instead of 'lua_Number') to ensure that +** all bits from 'l_rand' can be represented, and that 'RANDMAX + 1.0' +** will keep full precision (ensuring that 'r' is always less than 1.0.) +*/ static int math_random (lua_State *L) { - /* the `%' avoids the (rare) case of r==1, and is needed also because on - some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ - lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; + lua_Integer low, up; + double r = (double)l_rand() * (1.0 / ((double)L_RANDMAX + 1.0)); switch (lua_gettop(L)) { /* check number of arguments */ case 0: { /* no arguments */ - lua_pushnumber(L, r); /* Number between 0 and 1 */ - break; + lua_pushnumber(L, (lua_Number)r); /* Number between 0 and 1 */ + return 1; } case 1: { /* only upper limit */ - lua_Number u = luaL_checknumber(L, 1); - luaL_argcheck(L, (lua_Number)1.0 <= u, 1, "interval is empty"); - lua_pushnumber(L, l_mathop(floor)(r*u) + (lua_Number)(1.0)); /* [1, u] */ + low = 1; + up = luaL_checkinteger(L, 1); break; } case 2: { /* lower and upper limits */ - lua_Number l = luaL_checknumber(L, 1); - lua_Number u = luaL_checknumber(L, 2); - luaL_argcheck(L, l <= u, 2, "interval is empty"); - lua_pushnumber(L, l_mathop(floor)(r*(u-l+1)) + l); /* [l, u] */ + low = luaL_checkinteger(L, 1); + up = luaL_checkinteger(L, 2); break; } default: return luaL_error(L, "wrong number of arguments"); } + /* random integer in the interval [low, up] */ + luaL_argcheck(L, low <= up, 1, "interval is empty"); + luaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1, + "interval too large"); + r *= (double)(up - low) + 1.0; + lua_pushinteger(L, (lua_Integer)r + low); return 1; } static int math_randomseed (lua_State *L) { - srand(luaL_checkunsigned(L, 1)); - (void)rand(); /* discard first value to avoid undesirable correlations */ + l_srand((unsigned int)(lua_Integer)luaL_checknumber(L, 1)); + (void)l_rand(); /* discard first value to avoid undesirable correlations */ return 0; } +static int math_type (lua_State *L) { + if (lua_type(L, 1) == LUA_TNUMBER) { + if (lua_isinteger(L, 1)) + lua_pushliteral(L, "integer"); + else + lua_pushliteral(L, "float"); + } + else { + luaL_checkany(L, 1); + lua_pushnil(L); + } + return 1; +} + + +/* +** {================================================================== +** Deprecated functions (for compatibility only) +** =================================================================== +*/ +#if defined(LUA_COMPAT_MATHLIB) + +static int math_cosh (lua_State *L) { + lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sinh (lua_State *L) { + lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tanh (lua_State *L) { + lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_pow (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number y = luaL_checknumber(L, 2); + lua_pushnumber(L, l_mathop(pow)(x, y)); + return 1; +} + +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + int ep = (int)luaL_checkinteger(L, 2); + lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + return 1; +} + +static int math_log10 (lua_State *L) { + lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); + return 1; +} + +#endif +/* }================================================================== */ + + + static const luaL_Reg mathlib[] = { {"abs", math_abs}, {"acos", math_acos}, {"asin", math_asin}, - {"atan2", math_atan2}, {"atan", math_atan}, {"ceil", math_ceil}, - {"cosh", math_cosh}, {"cos", math_cos}, {"deg", math_deg}, {"exp", math_exp}, + {"tointeger", math_toint}, {"floor", math_floor}, {"fmod", math_fmod}, - {"frexp", math_frexp}, - {"ldexp", math_ldexp}, -#if defined(LUA_COMPAT_LOG10) - {"log10", math_log10}, -#endif + {"ult", math_ult}, {"log", math_log}, {"max", math_max}, {"min", math_min}, {"modf", math_modf}, - {"pow", math_pow}, {"rad", math_rad}, {"random", math_random}, {"randomseed", math_randomseed}, - {"sinh", math_sinh}, {"sin", math_sin}, {"sqrt", math_sqrt}, - {"tanh", math_tanh}, {"tan", math_tan}, + {"type", math_type}, +#if defined(LUA_COMPAT_MATHLIB) + {"atan2", math_atan}, + {"cosh", math_cosh}, + {"sinh", math_sinh}, + {"tanh", math_tanh}, + {"pow", math_pow}, + {"frexp", math_frexp}, + {"ldexp", math_ldexp}, + {"log10", math_log10}, +#endif + /* placeholders */ + {"pi", NULL}, + {"huge", NULL}, + {"maxinteger", NULL}, + {"mininteger", NULL}, {NULL, NULL} }; @@ -272,8 +396,12 @@ LUAMOD_API int luaopen_math (lua_State *L) { luaL_newlib(L, mathlib); lua_pushnumber(L, PI); lua_setfield(L, -2, "pi"); - lua_pushnumber(L, HUGE_VAL); + lua_pushnumber(L, (lua_Number)HUGE_VAL); lua_setfield(L, -2, "huge"); + lua_pushinteger(L, LUA_MAXINTEGER); + lua_setfield(L, -2, "maxinteger"); + lua_pushinteger(L, LUA_MININTEGER); + lua_setfield(L, -2, "mininteger"); return 1; } diff --git a/depends/lua/src/lmem.c b/depends/lua/src/lmem.c index ee343e3e0..0a0476cc7 100644 --- a/depends/lua/src/lmem.c +++ b/depends/lua/src/lmem.c @@ -1,15 +1,17 @@ /* -** $Id: lmem.c,v 1.84.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lmem.c,v 1.91 2015/03/06 19:45:54 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ - -#include - #define lmem_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "ldebug.h" @@ -24,15 +26,15 @@ /* ** About the realloc function: ** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); -** (`osize' is the old size, `nsize' is the new size) +** ('osize' is the old size, 'nsize' is the new size) ** -** * frealloc(ud, NULL, x, s) creates a new block of size `s' (no +** * frealloc(ud, NULL, x, s) creates a new block of size 's' (no ** matter 'x'). ** -** * frealloc(ud, p, x, 0) frees the block `p' +** * frealloc(ud, p, x, 0) frees the block 'p' ** (in this specific case, frealloc must return NULL); ** particularly, frealloc(ud, NULL, 0, 0) does nothing -** (which is equivalent to free(NULL) in ANSI C) +** (which is equivalent to free(NULL) in ISO C) ** ** frealloc returns NULL if it cannot create or reallocate the area ** (any reallocation to an equal or smaller size cannot fail!) @@ -83,9 +85,8 @@ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { #endif newblock = (*g->frealloc)(g->ud, block, osize, nsize); if (newblock == NULL && nsize > 0) { - api_check(L, nsize > realosize, - "realloc cannot fail when shrinking a block"); - if (g->gcrunning) { + lua_assert(nsize > realosize); /* cannot fail when shrinking a block */ + if (g->version) { /* is state fully built? */ luaC_fullgc(L, 1); /* try to free some memory... */ newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ } diff --git a/depends/lua/src/loadlib.c b/depends/lua/src/loadlib.c index bedbea3e9..79119287a 100644 --- a/depends/lua/src/loadlib.c +++ b/depends/lua/src/loadlib.c @@ -1,5 +1,5 @@ /* -** $Id: loadlib.c,v 1.111.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: loadlib.c,v 1.127 2015/11/23 11:30:45 roberto Exp $ ** Dynamic library loader for Lua ** See Copyright Notice in lua.h ** @@ -8,22 +8,16 @@ ** systems. */ +#define loadlib_c +#define LUA_LIB -/* -** if needed, includes windows header before everything else -*/ -#if defined(_WIN32) -#include -#endif +#include "lprefix.h" +#include #include #include - -#define loadlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -31,21 +25,21 @@ /* -** LUA_PATH and LUA_CPATH are the names of the environment +** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment ** variables that Lua check to set its paths. */ -#if !defined(LUA_PATH) -#define LUA_PATH "LUA_PATH" +#if !defined(LUA_PATH_VAR) +#define LUA_PATH_VAR "LUA_PATH" #endif -#if !defined(LUA_CPATH) -#define LUA_CPATH "LUA_CPATH" +#if !defined(LUA_CPATH_VAR) +#define LUA_CPATH_VAR "LUA_CPATH" #endif #define LUA_PATHSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR -#define LUA_PATHVERSION LUA_PATH LUA_PATHSUFFIX -#define LUA_CPATHVERSION LUA_CPATH LUA_PATHSUFFIX +#define LUA_PATHVARVERSION LUA_PATH_VAR LUA_PATHSUFFIX +#define LUA_CPATHVARVERSION LUA_CPATH_VAR LUA_PATHSUFFIX /* ** LUA_PATH_SEP is the character that separates templates in a path. @@ -92,29 +86,45 @@ #define LUA_OFSEP "_" -/* table (in the registry) that keeps handles for all loaded C libraries */ -#define CLIBS "_CLIBS" +/* +** unique key for table in the registry that keeps handles +** for all loaded C libraries +*/ +static const int CLIBS = 0; #define LIB_FAIL "open" - -/* error codes for ll_loadfunc */ -#define ERRLIB 1 -#define ERRFUNC 2 - #define setprogdir(L) ((void)0) /* ** system-dependent functions */ -static void ll_unloadlib (void *lib); -static void *ll_load (lua_State *L, const char *path, int seeglb); -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); +/* +** unload library 'lib' +*/ +static void lsys_unloadlib (void *lib); + +/* +** load C library in file 'path'. If 'seeglb', load with all names in +** the library global. +** Returns the library; in case of error, returns NULL plus an +** error string in the stack. +*/ +static void *lsys_load (lua_State *L, const char *path, int seeglb); + +/* +** Try to find a function named 'sym' in library 'lib'. +** Returns the function; in case of error, returns NULL plus an +** error string in the stack. +*/ +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym); -#if defined(LUA_USE_DLOPEN) + + +#if defined(LUA_USE_DLOPEN) /* { */ /* ** {======================================================================== ** This is an implementation of loadlib based on the dlfcn interface. @@ -126,20 +136,32 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); #include -static void ll_unloadlib (void *lib) { +/* +** Macro to convert pointer-to-void* to pointer-to-function. This cast +** is undefined according to ISO C, but POSIX assumes that it works. +** (The '__extension__' in gnu compilers is only to avoid warnings.) +*/ +#if defined(__GNUC__) +#define cast_func(p) (__extension__ (lua_CFunction)(p)) +#else +#define cast_func(p) ((lua_CFunction)(p)) +#endif + + +static void lsys_unloadlib (void *lib) { dlclose(lib); } -static void *ll_load (lua_State *L, const char *path, int seeglb) { +static void *lsys_load (lua_State *L, const char *path, int seeglb) { void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)); if (lib == NULL) lua_pushstring(L, dlerror()); return lib; } -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { - lua_CFunction f = (lua_CFunction)dlsym(lib, sym); +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = cast_func(dlsym(lib, sym)); if (f == NULL) lua_pushstring(L, dlerror()); return f; } @@ -148,13 +170,15 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { -#elif defined(LUA_DL_DLL) +#elif defined(LUA_DL_DLL) /* }{ */ /* ** {====================================================================== ** This is an implementation of loadlib for Windows using native functions. ** ======================================================================= */ +#include + #undef setprogdir /* @@ -190,12 +214,12 @@ static void pusherror (lua_State *L) { lua_pushfstring(L, "system error %d\n", error); } -static void ll_unloadlib (void *lib) { +static void lsys_unloadlib (void *lib) { FreeLibrary((HMODULE)lib); } -static void *ll_load (lua_State *L, const char *path, int seeglb) { +static void *lsys_load (lua_State *L, const char *path, int seeglb) { HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS); (void)(seeglb); /* not used: symbols are 'global' by default */ if (lib == NULL) pusherror(L); @@ -203,7 +227,7 @@ static void *ll_load (lua_State *L, const char *path, int seeglb) { } -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { lua_CFunction f = (lua_CFunction)GetProcAddress((HMODULE)lib, sym); if (f == NULL) pusherror(L); return f; @@ -212,7 +236,7 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { /* }====================================================== */ -#else +#else /* }{ */ /* ** {====================================================== ** Fallback for other systems @@ -226,31 +250,34 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { #define DLMSG "dynamic libraries not enabled; check your Lua installation" -static void ll_unloadlib (void *lib) { +static void lsys_unloadlib (void *lib) { (void)(lib); /* not used */ } -static void *ll_load (lua_State *L, const char *path, int seeglb) { +static void *lsys_load (lua_State *L, const char *path, int seeglb) { (void)(path); (void)(seeglb); /* not used */ lua_pushliteral(L, DLMSG); return NULL; } -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { (void)(lib); (void)(sym); /* not used */ lua_pushliteral(L, DLMSG); return NULL; } /* }====================================================== */ -#endif +#endif /* } */ -static void *ll_checkclib (lua_State *L, const char *path) { +/* +** return registry.CLIBS[path] +*/ +static void *checkclib (lua_State *L, const char *path) { void *plib; - lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_rawgetp(L, LUA_REGISTRYINDEX, &CLIBS); lua_getfield(L, -1, path); plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ lua_pop(L, 2); /* pop CLIBS table and 'plib' */ @@ -258,8 +285,12 @@ static void *ll_checkclib (lua_State *L, const char *path) { } -static void ll_addtoclib (lua_State *L, const char *path, void *plib) { - lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); +/* +** registry.CLIBS[path] = plib -- for queries +** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries +*/ +static void addtoclib (lua_State *L, const char *path, void *plib) { + lua_rawgetp(L, LUA_REGISTRYINDEX, &CLIBS); lua_pushlightuserdata(L, plib); lua_pushvalue(L, -1); lua_setfield(L, -3, path); /* CLIBS[path] = plib */ @@ -269,33 +300,49 @@ static void ll_addtoclib (lua_State *L, const char *path, void *plib) { /* -** __gc tag method for CLIBS table: calls 'll_unloadlib' for all lib +** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib ** handles in list CLIBS */ static int gctm (lua_State *L) { - int n = luaL_len(L, 1); + lua_Integer n = luaL_len(L, 1); for (; n >= 1; n--) { /* for each handle, in reverse order */ lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ - ll_unloadlib(lua_touserdata(L, -1)); + lsys_unloadlib(lua_touserdata(L, -1)); lua_pop(L, 1); /* pop handle */ } return 0; } -static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { - void *reg = ll_checkclib(L, path); /* check loaded C libraries */ + +/* error codes for 'lookforfunc' */ +#define ERRLIB 1 +#define ERRFUNC 2 + +/* +** Look for a C function named 'sym' in a dynamically loaded library +** 'path'. +** First, check whether the library is already loaded; if not, try +** to load it. +** Then, if 'sym' is '*', return true (as library has been loaded). +** Otherwise, look for symbol 'sym' in the library and push a +** C function with that symbol. +** Return 0 and 'true' or a function in the stack; in case of +** errors, return an error code and an error message in the stack. +*/ +static int lookforfunc (lua_State *L, const char *path, const char *sym) { + void *reg = checkclib(L, path); /* check loaded C libraries */ if (reg == NULL) { /* must load library? */ - reg = ll_load(L, path, *sym == '*'); + reg = lsys_load(L, path, *sym == '*'); /* global symbols if 'sym'=='*' */ if (reg == NULL) return ERRLIB; /* unable to load library */ - ll_addtoclib(L, path, reg); + addtoclib(L, path, reg); } if (*sym == '*') { /* loading only library (no function)? */ lua_pushboolean(L, 1); /* return 'true' */ return 0; /* no errors */ } else { - lua_CFunction f = ll_sym(L, reg, sym); + lua_CFunction f = lsys_sym(L, reg, sym); if (f == NULL) return ERRFUNC; /* unable to find function */ lua_pushcfunction(L, f); /* else create new function */ @@ -307,7 +354,7 @@ static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { static int ll_loadlib (lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *init = luaL_checkstring(L, 2); - int stat = ll_loadfunc(L, path, init); + int stat = lookforfunc(L, path, init); if (stat == 0) /* no errors? */ return 1; /* return the loaded function */ else { /* error; error message is on stack top */ @@ -360,7 +407,7 @@ static const char *searchpath (lua_State *L, const char *name, lua_remove(L, -2); /* remove path template */ if (readable(filename)) /* does file exist and is readable? */ return filename; /* return that file name */ - lua_pushfstring(L, "\n\tno file " LUA_QS, filename); + lua_pushfstring(L, "\n\tno file '%s'", filename); lua_remove(L, -2); /* remove file name */ luaL_addvalue(&msg); /* concatenate error msg. entry */ } @@ -390,7 +437,7 @@ static const char *findfile (lua_State *L, const char *name, lua_getfield(L, lua_upvalueindex(1), pname); path = lua_tostring(L, -1); if (path == NULL) - luaL_error(L, LUA_QL("package.%s") " must be a string", pname); + luaL_error(L, "'package.%s' must be a string", pname); return searchpath(L, name, path, ".", dirsep); } @@ -401,8 +448,7 @@ static int checkload (lua_State *L, int stat, const char *filename) { return 2; /* return open function and file name */ } else - return luaL_error(L, "error loading module " LUA_QS - " from file " LUA_QS ":\n\t%s", + return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s", lua_tostring(L, 1), filename, lua_tostring(L, -1)); } @@ -416,21 +462,29 @@ static int searcher_Lua (lua_State *L) { } +/* +** Try to find a load function for module 'modname' at file 'filename'. +** First, change '.' to '_' in 'modname'; then, if 'modname' has +** the form X-Y (that is, it has an "ignore mark"), build a function +** name "luaopen_X" and look for it. (For compatibility, if that +** fails, it also tries "luaopen_Y".) If there is no ignore mark, +** look for a function named "luaopen_modname". +*/ static int loadfunc (lua_State *L, const char *filename, const char *modname) { - const char *funcname; + const char *openfunc; const char *mark; modname = luaL_gsub(L, modname, ".", LUA_OFSEP); mark = strchr(modname, *LUA_IGMARK); if (mark) { int stat; - funcname = lua_pushlstring(L, modname, mark - modname); - funcname = lua_pushfstring(L, LUA_POF"%s", funcname); - stat = ll_loadfunc(L, filename, funcname); + openfunc = lua_pushlstring(L, modname, mark - modname); + openfunc = lua_pushfstring(L, LUA_POF"%s", openfunc); + stat = lookforfunc(L, filename, openfunc); if (stat != ERRFUNC) return stat; modname = mark + 1; /* else go ahead and try old-style name */ } - funcname = lua_pushfstring(L, LUA_POF"%s", modname); - return ll_loadfunc(L, filename, funcname); + openfunc = lua_pushfstring(L, LUA_POF"%s", modname); + return lookforfunc(L, filename, openfunc); } @@ -455,8 +509,7 @@ static int searcher_Croot (lua_State *L) { if (stat != ERRFUNC) return checkload(L, 0, filename); /* real error */ else { /* open function not found */ - lua_pushfstring(L, "\n\tno module " LUA_QS " in file " LUA_QS, - name, filename); + lua_pushfstring(L, "\n\tno module '%s' in file '%s'", name, filename); return 1; } } @@ -468,8 +521,7 @@ static int searcher_Croot (lua_State *L) { static int searcher_preload (lua_State *L) { const char *name = luaL_checkstring(L, 1); lua_getfield(L, LUA_REGISTRYINDEX, "_PRELOAD"); - lua_getfield(L, -1, name); - if (lua_isnil(L, -1)) /* not found? */ + if (lua_getfield(L, -1, name) == LUA_TNIL) /* not found? */ lua_pushfstring(L, "\n\tno field package.preload['%s']", name); return 1; } @@ -479,17 +531,15 @@ static void findloader (lua_State *L, const char *name) { int i; luaL_Buffer msg; /* to build error message */ luaL_buffinit(L, &msg); - lua_getfield(L, lua_upvalueindex(1), "searchers"); /* will be at index 3 */ - if (!lua_istable(L, 3)) - luaL_error(L, LUA_QL("package.searchers") " must be a table"); + /* push 'package.searchers' to index 3 in the stack */ + if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE) + luaL_error(L, "'package.searchers' must be a table"); /* iterate over available searchers to find a loader */ for (i = 1; ; i++) { - lua_rawgeti(L, 3, i); /* get a searcher */ - if (lua_isnil(L, -1)) { /* no more searchers? */ + if (lua_rawgeti(L, 3, i) == LUA_TNIL) { /* no more searchers? */ lua_pop(L, 1); /* remove nil */ luaL_pushresult(&msg); /* create error message */ - luaL_error(L, "module " LUA_QS " not found:%s", - name, lua_tostring(L, -1)); + luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); } lua_pushstring(L, name); lua_call(L, 1, 2); /* call it */ @@ -520,8 +570,7 @@ static int ll_require (lua_State *L) { lua_call(L, 2, 1); /* run loader to load module */ if (!lua_isnil(L, -1)) /* non-nil return? */ lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ - lua_getfield(L, 2, name); - if (lua_isnil(L, -1)) { /* module did not set a value? */ + if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */ lua_pushboolean(L, 1); /* use true as result */ lua_pushvalue(L, -1); /* extra copy to be returned */ lua_setfield(L, 2, name); /* _LOADED[name] = true */ @@ -548,7 +597,7 @@ static void set_env (lua_State *L) { if (lua_getstack(L, 1, &ar) == 0 || lua_getinfo(L, "f", &ar) == 0 || /* get calling function */ lua_iscfunction(L, -1)) - luaL_error(L, LUA_QL("module") " not called from a Lua function"); + luaL_error(L, "'module' not called from a Lua function"); lua_pushvalue(L, -2); /* copy new environment table to top */ lua_setupvalue(L, -2, 1); lua_pop(L, 1); /* remove function */ @@ -587,9 +636,8 @@ static int ll_module (lua_State *L) { int lastarg = lua_gettop(L); /* last parameter */ luaL_pushmodule(L, modname, 1); /* get/create module table */ /* check whether table already has a _NAME field */ - lua_getfield(L, -1, "_NAME"); - if (!lua_isnil(L, -1)) /* is table an initialized module? */ - lua_pop(L, 1); + if (lua_getfield(L, -1, "_NAME") != LUA_TNIL) + lua_pop(L, 1); /* table is an initialized module */ else { /* no; initialize it */ lua_pop(L, 1); modinit(L, modname); @@ -659,6 +707,12 @@ static const luaL_Reg pk_funcs[] = { #if defined(LUA_COMPAT_MODULE) {"seeall", ll_seeall}, #endif + /* placeholders */ + {"preload", NULL}, + {"cpath", NULL}, + {"path", NULL}, + {"searchers", NULL}, + {"loaded", NULL}, {NULL, NULL} }; @@ -678,42 +732,50 @@ static void createsearcherstable (lua_State *L) { int i; /* create 'searchers' table */ lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); - /* fill it with pre-defined searchers */ + /* fill it with predefined searchers */ for (i=0; searchers[i] != NULL; i++) { lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ lua_pushcclosure(L, searchers[i], 1); lua_rawseti(L, -2, i+1); } +#if defined(LUA_COMPAT_LOADERS) + lua_pushvalue(L, -1); /* make a copy of 'searchers' table */ + lua_setfield(L, -3, "loaders"); /* put it in field 'loaders' */ +#endif + lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ } -LUAMOD_API int luaopen_package (lua_State *L) { - /* create table CLIBS to keep track of loaded C libraries */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); - lua_createtable(L, 0, 1); /* metatable for CLIBS */ +/* +** create table CLIBS to keep track of loaded C libraries, +** setting a finalizer to close all libraries when closing state. +*/ +static void createclibstable (lua_State *L) { + lua_newtable(L); /* create CLIBS table */ + lua_createtable(L, 0, 1); /* create metatable for CLIBS */ lua_pushcfunction(L, gctm); lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ lua_setmetatable(L, -2); - /* create `package' table */ - luaL_newlib(L, pk_funcs); + lua_rawsetp(L, LUA_REGISTRYINDEX, &CLIBS); /* set CLIBS table in registry */ +} + + +LUAMOD_API int luaopen_package (lua_State *L) { + createclibstable(L); + luaL_newlib(L, pk_funcs); /* create 'package' table */ createsearcherstable(L); -#if defined(LUA_COMPAT_LOADERS) - lua_pushvalue(L, -1); /* make a copy of 'searchers' table */ - lua_setfield(L, -3, "loaders"); /* put it in field `loaders' */ -#endif - lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ /* set field 'path' */ - setpath(L, "path", LUA_PATHVERSION, LUA_PATH, LUA_PATH_DEFAULT); + setpath(L, "path", LUA_PATHVARVERSION, LUA_PATH_VAR, LUA_PATH_DEFAULT); /* set field 'cpath' */ - setpath(L, "cpath", LUA_CPATHVERSION, LUA_CPATH, LUA_CPATH_DEFAULT); + setpath(L, "cpath", LUA_CPATHVARVERSION, LUA_CPATH_VAR, LUA_CPATH_DEFAULT); /* store config information */ lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); lua_setfield(L, -2, "config"); - /* set field `loaded' */ + /* set field 'loaded' */ luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); lua_setfield(L, -2, "loaded"); - /* set field `preload' */ + /* set field 'preload' */ luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); lua_setfield(L, -2, "preload"); lua_pushglobaltable(L); diff --git a/depends/lua/src/lobject.c b/depends/lua/src/lobject.c index 882d994d4..a44b3850f 100644 --- a/depends/lua/src/lobject.c +++ b/depends/lua/src/lobject.c @@ -1,17 +1,22 @@ /* -** $Id: lobject.c,v 2.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lobject.c,v 2.111 2016/05/20 14:07:48 roberto Exp $ ** Some generic functions over Lua objects ** See Copyright Notice in lua.h */ +#define lobject_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include #include #include #include #include -#define lobject_c -#define LUA_CORE - #include "lua.h" #include "lctype.h" @@ -36,8 +41,12 @@ LUAI_DDEF const TValue luaO_nilobject_ = {NILCONSTANT}; int luaO_int2fb (unsigned int x) { int e = 0; /* exponent */ if (x < 8) return x; - while (x >= 0x10) { - x = (x+1) >> 1; + while (x >= (8 << 4)) { /* coarse steps */ + x = (x + 0xf) >> 4; /* x = ceil(x / 16) */ + e += 4; + } + while (x >= (8 << 1)) { /* fine steps */ + x = (x + 1) >> 1; /* x = ceil(x / 2) */ e++; } return ((e+1) << 3) | (cast_int(x) - 8); @@ -46,14 +55,15 @@ int luaO_int2fb (unsigned int x) { /* converts back */ int luaO_fb2int (int x) { - int e = (x >> 3) & 0x1f; - if (e == 0) return x; - else return ((x & 7) + 8) << (e - 1); + return (x < 8) ? x : ((x & 7) + 8) << ((x >> 3) - 1); } +/* +** Computes ceil(log2(x)) +*/ int luaO_ceillog2 (unsigned int x) { - static const lu_byte log_2[256] = { + static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */ 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, @@ -70,29 +80,90 @@ int luaO_ceillog2 (unsigned int x) { } -lua_Number luaO_arith (int op, lua_Number v1, lua_Number v2) { +static lua_Integer intarith (lua_State *L, int op, lua_Integer v1, + lua_Integer v2) { switch (op) { - case LUA_OPADD: return luai_numadd(NULL, v1, v2); - case LUA_OPSUB: return luai_numsub(NULL, v1, v2); - case LUA_OPMUL: return luai_nummul(NULL, v1, v2); - case LUA_OPDIV: return luai_numdiv(NULL, v1, v2); - case LUA_OPMOD: return luai_nummod(NULL, v1, v2); - case LUA_OPPOW: return luai_numpow(NULL, v1, v2); - case LUA_OPUNM: return luai_numunm(NULL, v1); + case LUA_OPADD: return intop(+, v1, v2); + case LUA_OPSUB:return intop(-, v1, v2); + case LUA_OPMUL:return intop(*, v1, v2); + case LUA_OPMOD: return luaV_mod(L, v1, v2); + case LUA_OPIDIV: return luaV_div(L, v1, v2); + case LUA_OPBAND: return intop(&, v1, v2); + case LUA_OPBOR: return intop(|, v1, v2); + case LUA_OPBXOR: return intop(^, v1, v2); + case LUA_OPSHL: return luaV_shiftl(v1, v2); + case LUA_OPSHR: return luaV_shiftl(v1, -v2); + case LUA_OPUNM: return intop(-, 0, v1); + case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1); default: lua_assert(0); return 0; } } -int luaO_hexavalue (int c) { - if (lisdigit(c)) return c - '0'; - else return ltolower(c) - 'a' + 10; +static lua_Number numarith (lua_State *L, int op, lua_Number v1, + lua_Number v2) { + switch (op) { + case LUA_OPADD: return luai_numadd(L, v1, v2); + case LUA_OPSUB: return luai_numsub(L, v1, v2); + case LUA_OPMUL: return luai_nummul(L, v1, v2); + case LUA_OPDIV: return luai_numdiv(L, v1, v2); + case LUA_OPPOW: return luai_numpow(L, v1, v2); + case LUA_OPIDIV: return luai_numidiv(L, v1, v2); + case LUA_OPUNM: return luai_numunm(L, v1); + case LUA_OPMOD: { + lua_Number m; + luai_nummod(L, v1, v2, m); + return m; + } + default: lua_assert(0); return 0; + } } -#if !defined(lua_strx2number) +void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, + TValue *res) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: + case LUA_OPBNOT: { /* operate only on integers */ + lua_Integer i1; lua_Integer i2; + if (tointeger(p1, &i1) && tointeger(p2, &i2)) { + setivalue(res, intarith(L, op, i1, i2)); + return; + } + else break; /* go to the end */ + } + case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */ + lua_Number n1; lua_Number n2; + if (tonumber(p1, &n1) && tonumber(p2, &n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return; + } + else break; /* go to the end */ + } + default: { /* other operations */ + lua_Number n1; lua_Number n2; + if (ttisinteger(p1) && ttisinteger(p2)) { + setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2))); + return; + } + else if (tonumber(p1, &n1) && tonumber(p2, &n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return; + } + else break; /* go to the end */ + } + } + /* could not perform raw operation; try metamethod */ + lua_assert(L != NULL); /* should not fail when folding (compile time) */ + luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD)); +} -#include + +int luaO_hexavalue (int c) { + if (lisdigit(c)) return c - '0'; + else return (ltolower(c) - 'a') + 10; +} static int isneg (const char **s) { @@ -102,122 +173,285 @@ static int isneg (const char **s) { } -static lua_Number readhexa (const char **s, lua_Number r, int *count) { - for (; lisxdigit(cast_uchar(**s)); (*s)++) { /* read integer part */ - r = (r * cast_num(16.0)) + cast_num(luaO_hexavalue(cast_uchar(**s))); - (*count)++; - } - return r; -} +/* +** {================================================================== +** Lua's implementation for 'lua_strx2number' +** =================================================================== +*/ + +#if !defined(lua_strx2number) + +/* maximum number of significant digits to read (to avoid overflows + even with single floats) */ +#define MAXSIGDIG 30 /* ** convert an hexadecimal numeric string to a number, following ** C99 specification for 'strtod' */ static lua_Number lua_strx2number (const char *s, char **endptr) { - lua_Number r = 0.0; - int e = 0, i = 0; - int neg = 0; /* 1 if number is negative */ + int dot = lua_getlocaledecpoint(); + lua_Number r = 0.0; /* result (accumulator) */ + int sigdig = 0; /* number of significant digits */ + int nosigdig = 0; /* number of non-significant digits */ + int e = 0; /* exponent correction */ + int neg; /* 1 if number is negative */ + int hasdot = 0; /* true after seen a dot */ *endptr = cast(char *, s); /* nothing is valid yet */ while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ neg = isneg(&s); /* check signal */ if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ return 0.0; /* invalid format (no '0x') */ - s += 2; /* skip '0x' */ - r = readhexa(&s, r, &i); /* read integer part */ - if (*s == '.') { - s++; /* skip dot */ - r = readhexa(&s, r, &e); /* read fractional part */ + for (s += 2; ; s++) { /* skip '0x' and read numeral */ + if (*s == dot) { + if (hasdot) break; /* second dot? stop loop */ + else hasdot = 1; + } + else if (lisxdigit(cast_uchar(*s))) { + if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */ + nosigdig++; + else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ + r = (r * cast_num(16.0)) + luaO_hexavalue(*s); + else e++; /* too many digits; ignore, but still count for exponent */ + if (hasdot) e--; /* decimal digit? correct exponent */ + } + else break; /* neither a dot nor a digit */ } - if (i == 0 && e == 0) - return 0.0; /* invalid format (no digit) */ - e *= -4; /* each fractional digit divides value by 2^-4 */ + if (nosigdig + sigdig == 0) /* no digits? */ + return 0.0; /* invalid format */ *endptr = cast(char *, s); /* valid up to here */ + e *= 4; /* each digit multiplies/divides value by 2^4 */ if (*s == 'p' || *s == 'P') { /* exponent part? */ - int exp1 = 0; - int neg1; + int exp1 = 0; /* exponent value */ + int neg1; /* exponent signal */ s++; /* skip 'p' */ neg1 = isneg(&s); /* signal */ if (!lisdigit(cast_uchar(*s))) - goto ret; /* must have at least one digit */ + return 0.0; /* invalid; must have at least one digit */ while (lisdigit(cast_uchar(*s))) /* read exponent */ exp1 = exp1 * 10 + *(s++) - '0'; if (neg1) exp1 = -exp1; e += exp1; + *endptr = cast(char *, s); /* valid up to here */ } - *endptr = cast(char *, s); /* valid up to here */ - ret: if (neg) r = -r; return l_mathop(ldexp)(r, e); } #endif +/* }====================================================== */ + +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif -int luaO_str2d (const char *s, size_t len, lua_Number *result) { +static const char *l_str2dloc (const char *s, lua_Number *result, int mode) { char *endptr; - if (strpbrk(s, "nN")) /* reject 'inf' and 'nan' */ - return 0; - else if (strpbrk(s, "xX")) /* hexa? */ - *result = lua_strx2number(s, &endptr); + *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */ + : lua_str2number(s, &endptr); + if (endptr == s) return NULL; /* nothing recognized? */ + while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */ + return (*endptr == '\0') ? endptr : NULL; /* OK if no trailing characters */ +} + + +/* +** Convert string 's' to a Lua number (put in 'result'). Return NULL +** on fail or the address of the ending '\0' on success. +** 'pmode' points to (and 'mode' contains) special things in the string: +** - 'x'/'X' means an hexadecimal numeral +** - 'n'/'N' means 'inf' or 'nan' (which should be rejected) +** - '.' just optimizes the search for the common case (nothing special) +** This function accepts both the current locale or a dot as the radix +** mark. If the convertion fails, it may mean number has a dot but +** locale accepts something else. In that case, the code copies 's' +** to a buffer (because 's' is read-only), changes the dot to the +** current locale radix mark, and tries to convert again. +*/ +static const char *l_str2d (const char *s, lua_Number *result) { + const char *endptr; + const char *pmode = strpbrk(s, ".xXnN"); + int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0; + if (mode == 'n') /* reject 'inf' and 'nan' */ + return NULL; + endptr = l_str2dloc(s, result, mode); /* try to convert */ + if (endptr == NULL) { /* failed? may be a different locale */ + char buff[L_MAXLENNUM + 1]; + char *pdot = strchr(s, '.'); + if (strlen(s) > L_MAXLENNUM || pdot == NULL) + return NULL; /* string too long or no dot; fail */ + strcpy(buff, s); /* copy string to buffer */ + buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */ + endptr = l_str2dloc(buff, result, mode); /* try again */ + if (endptr != NULL) + endptr = s + (endptr - buff); /* make relative to 's' */ + } + return endptr; +} + + +#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10) +#define MAXLASTD cast_int(LUA_MAXINTEGER % 10) + +static const char *l_str2int (const char *s, lua_Integer *result) { + lua_Unsigned a = 0; + int empty = 1; + int neg; + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); + if (s[0] == '0' && + (s[1] == 'x' || s[1] == 'X')) { /* hex? */ + s += 2; /* skip '0x' */ + for (; lisxdigit(cast_uchar(*s)); s++) { + a = a * 16 + luaO_hexavalue(*s); + empty = 0; + } + } + else { /* decimal */ + for (; lisdigit(cast_uchar(*s)); s++) { + int d = *s - '0'; + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return NULL; /* do not accept it (as integer) */ + a = a * 10 + d; + empty = 0; + } + } + while (lisspace(cast_uchar(*s))) s++; /* skip trailing spaces */ + if (empty || *s != '\0') return NULL; /* something wrong in the numeral */ + else { + *result = l_castU2S((neg) ? 0u - a : a); + return s; + } +} + + +size_t luaO_str2num (const char *s, TValue *o) { + lua_Integer i; lua_Number n; + const char *e; + if ((e = l_str2int(s, &i)) != NULL) { /* try as an integer */ + setivalue(o, i); + } + else if ((e = l_str2d(s, &n)) != NULL) { /* else try as a float */ + setfltvalue(o, n); + } else - *result = lua_str2number(s, &endptr); - if (endptr == s) return 0; /* nothing recognized */ - while (lisspace(cast_uchar(*endptr))) endptr++; - return (endptr == s + len); /* OK if no trailing characters */ + return 0; /* conversion failed */ + return (e - s) + 1; /* success; return string size */ +} + + +int luaO_utf8esc (char *buff, unsigned long x) { + int n = 1; /* number of bytes put in buffer (backwards) */ + lua_assert(x <= 0x10FFFF); + if (x < 0x80) /* ascii? */ + buff[UTF8BUFFSZ - 1] = cast(char, x); + else { /* need continuation bytes */ + unsigned int mfb = 0x3f; /* maximum that fits in first byte */ + do { /* add continuation bytes */ + buff[UTF8BUFFSZ - (n++)] = cast(char, 0x80 | (x & 0x3f)); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8BUFFSZ - n] = cast(char, (~mfb << 1) | x); /* add first byte */ + } + return n; } +/* maximum length of the conversion of a number to a string */ +#define MAXNUMBER2STR 50 + + +/* +** Convert a number object to a string +*/ +void luaO_tostring (lua_State *L, StkId obj) { + char buff[MAXNUMBER2STR]; + size_t len; + lua_assert(ttisnumber(obj)); + if (ttisinteger(obj)) + len = lua_integer2str(buff, sizeof(buff), ivalue(obj)); + else { + len = lua_number2str(buff, sizeof(buff), fltvalue(obj)); +#if !defined(LUA_COMPAT_FLOATSTRING) + if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ + buff[len++] = lua_getlocaledecpoint(); + buff[len++] = '0'; /* adds '.0' to result */ + } +#endif + } + setsvalue2s(L, obj, luaS_newlstr(L, buff, len)); +} + static void pushstr (lua_State *L, const char *str, size_t l) { - setsvalue2s(L, L->top++, luaS_newlstr(L, str, l)); + setsvalue2s(L, L->top, luaS_newlstr(L, str, l)); + luaD_inctop(L); } -/* this function handles only `%d', `%c', %f, %p, and `%s' formats */ +/* +** this function handles only '%d', '%c', '%f', '%p', and '%s' + conventional formats, plus Lua-specific '%I' and '%U' +*/ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { int n = 0; for (;;) { const char *e = strchr(fmt, '%'); if (e == NULL) break; - luaD_checkstack(L, 2); /* fmt + item */ pushstr(L, fmt, e - fmt); switch (*(e+1)) { - case 's': { + case 's': { /* zero-terminated string */ const char *s = va_arg(argp, char *); if (s == NULL) s = "(null)"; pushstr(L, s, strlen(s)); break; } - case 'c': { - char buff; - buff = cast(char, va_arg(argp, int)); - pushstr(L, &buff, 1); + case 'c': { /* an 'int' as a character */ + char buff = cast(char, va_arg(argp, int)); + if (lisprint(cast_uchar(buff))) + pushstr(L, &buff, 1); + else /* non-printable character; print its code */ + luaO_pushfstring(L, "<\\%d>", cast_uchar(buff)); break; } - case 'd': { - setnvalue(L->top++, cast_num(va_arg(argp, int))); - break; + case 'd': { /* an 'int' */ + setivalue(L->top, va_arg(argp, int)); + goto top2str; + } + case 'I': { /* a 'lua_Integer' */ + setivalue(L->top, cast(lua_Integer, va_arg(argp, l_uacInt))); + goto top2str; } - case 'f': { - setnvalue(L->top++, cast_num(va_arg(argp, l_uacNumber))); + case 'f': { /* a 'lua_Number' */ + setfltvalue(L->top, cast_num(va_arg(argp, l_uacNumber))); + top2str: /* convert the top element to a string */ + luaD_inctop(L); + luaO_tostring(L, L->top - 1); break; } - case 'p': { - char buff[4*sizeof(void *) + 8]; /* should be enough space for a `%p' */ - int l = sprintf(buff, "%p", va_arg(argp, void *)); + case 'p': { /* a pointer */ + char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */ + int l = l_sprintf(buff, sizeof(buff), "%p", va_arg(argp, void *)); pushstr(L, buff, l); break; } + case 'U': { /* an 'int' as a UTF-8 sequence */ + char buff[UTF8BUFFSZ]; + int l = luaO_utf8esc(buff, cast(long, va_arg(argp, long))); + pushstr(L, buff + UTF8BUFFSZ - l, l); + break; + } case '%': { pushstr(L, "%", 1); break; } default: { - luaG_runerror(L, - "invalid option " LUA_QL("%%%c") " to " LUA_QL("lua_pushfstring"), - *(e + 1)); + luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'", + *(e + 1)); } } n += 2; diff --git a/depends/lua/src/lopcodes.c b/depends/lua/src/lopcodes.c index 4190dc762..a1cbef857 100644 --- a/depends/lua/src/lopcodes.c +++ b/depends/lua/src/lopcodes.c @@ -1,13 +1,16 @@ /* -** $Id: lopcodes.c,v 1.49.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lopcodes.c,v 1.55 2015/01/05 13:48:33 roberto Exp $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ - #define lopcodes_c #define LUA_CORE +#include "lprefix.h" + + +#include #include "lopcodes.h" @@ -31,10 +34,17 @@ LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = { "ADD", "SUB", "MUL", - "DIV", "MOD", "POW", + "DIV", + "IDIV", + "BAND", + "BOR", + "BXOR", + "SHL", + "SHR", "UNM", + "BNOT", "NOT", "LEN", "CONCAT", @@ -79,10 +89,17 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_IDIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BAND */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BOR */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BXOR */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SHL */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SHR */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_BNOT */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ diff --git a/depends/lua/src/loslib.c b/depends/lua/src/loslib.c index 052ba1744..481065550 100644 --- a/depends/lua/src/loslib.c +++ b/depends/lua/src/loslib.c @@ -1,9 +1,14 @@ /* -** $Id: loslib.c,v 1.40.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: loslib.c,v 1.64 2016/04/18 13:06:55 roberto Exp $ ** Standard Operating System library ** See Copyright Notice in lua.h */ +#define loslib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include @@ -11,9 +16,6 @@ #include #include -#define loslib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -21,60 +23,119 @@ /* -** list of valid conversion specifiers for the 'strftime' function +** {================================================================== +** List of valid conversion specifiers for the 'strftime' function; +** options are grouped by length; group of length 2 start with '||'. +** =================================================================== */ -#if !defined(LUA_STRFTIMEOPTIONS) - -#if !defined(LUA_USE_POSIX) -#define LUA_STRFTIMEOPTIONS { "aAbBcdHIjmMpSUwWxXyYz%", "" } -#else -#define LUA_STRFTIMEOPTIONS \ - { "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%", "" \ - "", "E", "cCxXyY", \ - "O", "deHImMSuUVwWy" } -#endif +#if !defined(LUA_STRFTIMEOPTIONS) /* { */ +/* options for ANSI C 89 */ +#define L_STRFTIMEC89 "aAbBcdHIjmMpSUwWxXyYZ%" + +/* options for ISO C 99 and POSIX */ +#define L_STRFTIMEC99 "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ + "||" "EcECExEXEyEY" "OdOeOHOIOmOMOSOuOUOVOwOWOy" + +/* options for Windows */ +#define L_STRFTIMEWIN "aAbBcdHIjmMpSUwWxXyYzZ%" \ + "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" + +#if defined(LUA_USE_WINDOWS) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEWIN +#elif defined(LUA_USE_C89) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC89 +#else /* C99 specification */ +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC99 #endif +#endif /* } */ +/* }================================================================== */ /* -** By default, Lua uses tmpnam except when POSIX is available, where it -** uses mkstemp. +** {================================================================== +** Configuration for time-related stuff +** =================================================================== */ -#if defined(LUA_USE_MKSTEMP) -#include -#define LUA_TMPNAMBUFSIZE 32 -#define lua_tmpnam(b,e) { \ - strcpy(b, "/tmp/lua_XXXXXX"); \ - e = mkstemp(b); \ - if (e != -1) close(e); \ - e = (e == -1); } -#elif !defined(lua_tmpnam) +#if !defined(l_time_t) /* { */ +/* +** type to represent time_t in Lua +*/ +#define l_timet lua_Integer +#define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) -#define LUA_TMPNAMBUFSIZE L_tmpnam -#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } +static time_t l_checktime (lua_State *L, int arg) { + lua_Integer t = luaL_checkinteger(L, arg); + luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); + return (time_t)t; +} -#endif +#endif /* } */ +#if !defined(l_gmtime) /* { */ /* ** By default, Lua uses gmtime/localtime, except when POSIX is available, ** where it uses gmtime_r/localtime_r */ -#if defined(LUA_USE_GMTIME_R) + +#if defined(LUA_USE_POSIX) /* { */ #define l_gmtime(t,r) gmtime_r(t,r) #define l_localtime(t,r) localtime_r(t,r) -#elif !defined(l_gmtime) +#else /* }{ */ + +/* ISO C definitions */ +#define l_gmtime(t,r) ((void)(r)->tm_sec, gmtime(t)) +#define l_localtime(t,r) ((void)(r)->tm_sec, localtime(t)) + +#endif /* } */ + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Configuration for 'tmpnam': +** By default, Lua uses tmpnam except when POSIX is available, where +** it uses mkstemp. +** =================================================================== +*/ +#if !defined(lua_tmpnam) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#include -#define l_gmtime(t,r) ((void)r, gmtime(t)) -#define l_localtime(t,r) ((void)r, localtime(t)) +#define LUA_TMPNAMBUFSIZE 32 +#if !defined(LUA_TMPNAMTEMPLATE) +#define LUA_TMPNAMTEMPLATE "/tmp/lua_XXXXXX" #endif +#define lua_tmpnam(b,e) { \ + strcpy(b, LUA_TMPNAMTEMPLATE); \ + e = mkstemp(b); \ + if (e != -1) close(e); \ + e = (e == -1); } + +#else /* }{ */ + +/* ISO C definitions */ +#define LUA_TMPNAMBUFSIZE L_tmpnam +#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } + +#endif /* } */ + +#endif /* } */ +/* }================================================================== */ + + static int os_execute (lua_State *L) { @@ -145,45 +206,67 @@ static void setboolfield (lua_State *L, const char *key, int value) { lua_setfield(L, -2, key); } + +/* +** Set all fields from structure 'tm' in the table on top of the stack +*/ +static void setallfields (lua_State *L, struct tm *stm) { + setfield(L, "sec", stm->tm_sec); + setfield(L, "min", stm->tm_min); + setfield(L, "hour", stm->tm_hour); + setfield(L, "day", stm->tm_mday); + setfield(L, "month", stm->tm_mon + 1); + setfield(L, "year", stm->tm_year + 1900); + setfield(L, "wday", stm->tm_wday + 1); + setfield(L, "yday", stm->tm_yday + 1); + setboolfield(L, "isdst", stm->tm_isdst); +} + + static int getboolfield (lua_State *L, const char *key) { int res; - lua_getfield(L, -1, key); - res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1); + res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1); lua_pop(L, 1); return res; } -static int getfield (lua_State *L, const char *key, int d) { - int res, isnum; - lua_getfield(L, -1, key); - res = (int)lua_tointegerx(L, -1, &isnum); - if (!isnum) { - if (d < 0) - return luaL_error(L, "field " LUA_QS " missing in date table", key); +/* maximum value for date fields (to avoid arithmetic overflows with 'int') */ +#if !defined(L_MAXDATEFIELD) +#define L_MAXDATEFIELD (INT_MAX / 2) +#endif + +static int getfield (lua_State *L, const char *key, int d, int delta) { + int isnum; + int t = lua_getfield(L, -1, key); /* get field and its type */ + lua_Integer res = lua_tointegerx(L, -1, &isnum); + if (!isnum) { /* field is not an integer? */ + if (t != LUA_TNIL) /* some other value? */ + return luaL_error(L, "field '%s' is not an integer", key); + else if (d < 0) /* absent field; no default? */ + return luaL_error(L, "field '%s' missing in date table", key); res = d; } + else { + if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD)) + return luaL_error(L, "field '%s' is out-of-bound", key); + res -= delta; + } lua_pop(L, 1); - return res; + return (int)res; } static const char *checkoption (lua_State *L, const char *conv, char *buff) { - static const char *const options[] = LUA_STRFTIMEOPTIONS; - unsigned int i; - for (i = 0; i < sizeof(options)/sizeof(options[0]); i += 2) { - if (*conv != '\0' && strchr(options[i], *conv) != NULL) { - buff[1] = *conv; - if (*options[i + 1] == '\0') { /* one-char conversion specifier? */ - buff[2] = '\0'; /* end buffer */ - return conv + 1; - } - else if (*(conv + 1) != '\0' && - strchr(options[i + 1], *(conv + 1)) != NULL) { - buff[2] = *(conv + 1); /* valid two-char conversion specifier */ - buff[3] = '\0'; /* end buffer */ - return conv + 2; - } + const char *option; + int oplen = 1; + for (option = LUA_STRFTIMEOPTIONS; *option != '\0'; option += oplen) { + if (*option == '|') /* next block? */ + oplen++; /* next length */ + else if (memcmp(conv, option, oplen) == 0) { /* match? */ + memcpy(buff, conv, oplen); /* copy valid option to buffer */ + buff[oplen] = '\0'; + return conv + oplen; /* return next item */ } } luaL_argerror(L, 1, @@ -192,44 +275,40 @@ static const char *checkoption (lua_State *L, const char *conv, char *buff) { } +/* maximum size for an individual 'strftime' item */ +#define SIZETIMEFMT 250 + + static int os_date (lua_State *L) { const char *s = luaL_optstring(L, 1, "%c"); - time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); + time_t t = luaL_opt(L, l_checktime, 2, time(NULL)); struct tm tmr, *stm; if (*s == '!') { /* UTC? */ stm = l_gmtime(&t, &tmr); - s++; /* skip `!' */ + s++; /* skip '!' */ } else stm = l_localtime(&t, &tmr); if (stm == NULL) /* invalid date? */ - lua_pushnil(L); - else if (strcmp(s, "*t") == 0) { + luaL_error(L, "time result cannot be represented in this installation"); + if (strcmp(s, "*t") == 0) { lua_createtable(L, 0, 9); /* 9 = number of fields */ - setfield(L, "sec", stm->tm_sec); - setfield(L, "min", stm->tm_min); - setfield(L, "hour", stm->tm_hour); - setfield(L, "day", stm->tm_mday); - setfield(L, "month", stm->tm_mon+1); - setfield(L, "year", stm->tm_year+1900); - setfield(L, "wday", stm->tm_wday+1); - setfield(L, "yday", stm->tm_yday+1); - setboolfield(L, "isdst", stm->tm_isdst); + setallfields(L, stm); } else { - char cc[4]; + char cc[4]; /* buffer for individual conversion specifiers */ luaL_Buffer b; cc[0] = '%'; luaL_buffinit(L, &b); while (*s) { - if (*s != '%') /* no conversion specifier? */ + if (*s != '%') /* not a conversion specifier? */ luaL_addchar(&b, *s++); else { size_t reslen; - char buff[200]; /* should be big enough for any conversion result */ - s = checkoption(L, s + 1, cc); - reslen = strftime(buff, sizeof(buff), cc, stm); - luaL_addlstring(&b, buff, reslen); + char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); + s = checkoption(L, s + 1, cc + 1); /* copy specifier to 'cc' */ + reslen = strftime(buff, SIZETIMEFMT, cc, stm); + luaL_addsize(&b, reslen); } } luaL_pushresult(&b); @@ -246,26 +325,27 @@ static int os_time (lua_State *L) { struct tm ts; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); /* make sure table is at the top */ - ts.tm_sec = getfield(L, "sec", 0); - ts.tm_min = getfield(L, "min", 0); - ts.tm_hour = getfield(L, "hour", 12); - ts.tm_mday = getfield(L, "day", -1); - ts.tm_mon = getfield(L, "month", -1) - 1; - ts.tm_year = getfield(L, "year", -1) - 1900; + ts.tm_sec = getfield(L, "sec", 0, 0); + ts.tm_min = getfield(L, "min", 0, 0); + ts.tm_hour = getfield(L, "hour", 12, 0); + ts.tm_mday = getfield(L, "day", -1, 0); + ts.tm_mon = getfield(L, "month", -1, 1); + ts.tm_year = getfield(L, "year", -1, 1900); ts.tm_isdst = getboolfield(L, "isdst"); t = mktime(&ts); + setallfields(L, &ts); /* update fields with normalized values */ } - if (t == (time_t)(-1)) - lua_pushnil(L); - else - lua_pushnumber(L, (lua_Number)t); + if (t != (time_t)(l_timet)t || t == (time_t)(-1)) + luaL_error(L, "time result cannot be represented in this installation"); + l_pushtime(L, t); return 1; } static int os_difftime (lua_State *L) { - lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), - (time_t)(luaL_optnumber(L, 2, 0)))); + time_t t1 = l_checktime(L, 1); + time_t t2 = l_checktime(L, 2); + lua_pushnumber(L, (lua_Number)difftime(t1, t2)); return 1; } @@ -289,7 +369,7 @@ static int os_exit (lua_State *L) { if (lua_isboolean(L, 1)) status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE); else - status = luaL_optint(L, 1, EXIT_SUCCESS); + status = (int)luaL_optinteger(L, 1, EXIT_SUCCESS); if (lua_toboolean(L, 2)) lua_close(L); if (L) exit(status); /* 'if' to avoid warnings for unreachable 'return' */ diff --git a/depends/lua/src/lparser.c b/depends/lua/src/lparser.c index 9e1a9ca2c..22530a57b 100644 --- a/depends/lua/src/lparser.c +++ b/depends/lua/src/lparser.c @@ -1,15 +1,17 @@ /* -** $Id: lparser.c,v 2.130.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lparser.c,v 2.153 2016/05/13 19:10:16 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ - -#include - #define lparser_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "lcode.h" @@ -35,17 +37,21 @@ #define hasmultret(k) ((k) == VCALL || (k) == VVARARG) +/* because all strings are unified by the scanner, the parser + can use pointer equality for string equality */ +#define eqstr(a,b) ((a) == (b)) + /* ** nodes for block list (list of active blocks) */ typedef struct BlockCnt { struct BlockCnt *previous; /* chain */ - short firstlabel; /* index of first label in this block */ - short firstgoto; /* index of first pending goto in this block */ + int firstlabel; /* index of first label in this block */ + int firstgoto; /* index of first pending goto in this block */ lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ - lu_byte isloop; /* true if `block' is a loop */ + lu_byte isloop; /* true if 'block' is a loop */ } BlockCnt; @@ -57,19 +63,9 @@ static void statement (LexState *ls); static void expr (LexState *ls, expdesc *v); -static void anchor_token (LexState *ls) { - /* last token from outer function must be EOS */ - lua_assert(ls->fs != NULL || ls->t.token == TK_EOS); - if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { - TString *ts = ls->t.seminfo.ts; - luaX_newstring(ls, getstr(ts), ts->tsv.len); - } -} - - /* semantic error */ static l_noret semerror (LexState *ls, const char *msg) { - ls->t.token = 0; /* remove 'near to' from final message */ + ls->t.token = 0; /* remove "near " from final message */ luaX_syntaxerror(ls, msg); } @@ -168,7 +164,8 @@ static int registerlocalvar (LexState *ls, TString *varname) { int oldsize = f->sizelocvars; luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, LocVar, SHRT_MAX, "local variables"); - while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; f->locvars[fs->nlocvars].varname = varname; luaC_objbarrier(ls->L, f, varname); return fs->nlocvars++; @@ -222,7 +219,7 @@ static int searchupvalue (FuncState *fs, TString *name) { int i; Upvaldesc *up = fs->f->upvalues; for (i = 0; i < fs->nups; i++) { - if (luaS_eqstr(up[i].name, name)) return i; + if (eqstr(up[i].name, name)) return i; } return -1; /* not found */ } @@ -234,7 +231,8 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, Upvaldesc, MAXUPVAL, "upvalues"); - while (oldsize < f->sizeupvalues) f->upvalues[oldsize++].name = NULL; + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; f->upvalues[fs->nups].instack = (v->k == VLOCAL); f->upvalues[fs->nups].idx = cast_byte(v->u.info); f->upvalues[fs->nups].name = name; @@ -246,7 +244,7 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { static int searchvar (FuncState *fs, TString *n) { int i; for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { - if (luaS_eqstr(n, getlocvar(fs, i)->varname)) + if (eqstr(n, getlocvar(fs, i)->varname)) return i; } return -1; /* not found */ @@ -259,7 +257,8 @@ static int searchvar (FuncState *fs, TString *n) { */ static void markupval (FuncState *fs, int level) { BlockCnt *bl = fs->bl; - while (bl->nactvar > level) bl = bl->previous; + while (bl->nactvar > level) + bl = bl->previous; bl->upval = 1; } @@ -268,27 +267,26 @@ static void markupval (FuncState *fs, int level) { Find variable with given name 'n'. If it is an upvalue, add this upvalue into all intermediate functions. */ -static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { +static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) /* no more levels? */ - return VVOID; /* default is global */ + init_exp(var, VVOID, 0); /* default is global */ else { int v = searchvar(fs, n); /* look up locals at current level */ if (v >= 0) { /* found? */ init_exp(var, VLOCAL, v); /* variable is local */ if (!base) markupval(fs, v); /* local will be used as an upval */ - return VLOCAL; } else { /* not found as local at current level; try upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */ if (idx < 0) { /* not found? */ - if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */ - return VVOID; /* not found; is a global */ + singlevaraux(fs->prev, n, var, 0); /* try upper levels */ + if (var->k == VVOID) /* not found? */ + return; /* it is a global */ /* else was LOCAL or UPVAL */ idx = newupvalue(fs, n, var); /* will be a new upvalue */ } - init_exp(var, VUPVAL, idx); - return VUPVAL; + init_exp(var, VUPVAL, idx); /* new or old upvalue */ } } } @@ -297,10 +295,11 @@ static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; - if (singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ + singlevaraux(fs, varname, var, 1); + if (var->k == VVOID) { /* global name? */ expdesc key; singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ - lua_assert(var->k == VLOCAL || var->k == VUPVAL); + lua_assert(var->k != VVOID); /* this one must exist */ codestring(ls, &key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ } @@ -342,11 +341,11 @@ static void closegoto (LexState *ls, int g, Labeldesc *label) { FuncState *fs = ls->fs; Labellist *gl = &ls->dyd->gt; Labeldesc *gt = &gl->arr[g]; - lua_assert(luaS_eqstr(gt->name, label->name)); + lua_assert(eqstr(gt->name, label->name)); if (gt->nactvar < label->nactvar) { TString *vname = getlocvar(fs, gt->nactvar)->varname; const char *msg = luaO_pushfstring(ls->L, - " at line %d jumps into the scope of local " LUA_QS, + " at line %d jumps into the scope of local '%s'", getstr(gt->name), gt->line, getstr(vname)); semerror(ls, msg); } @@ -369,7 +368,7 @@ static int findlabel (LexState *ls, int g) { /* check labels in current block for a match */ for (i = bl->firstlabel; i < dyd->label.n; i++) { Labeldesc *lb = &dyd->label.arr[i]; - if (luaS_eqstr(lb->name, gt->name)) { /* correct label? */ + if (eqstr(lb->name, gt->name)) { /* correct label? */ if (gt->nactvar > lb->nactvar && (bl->upval || dyd->label.n > bl->firstlabel)) luaK_patchclose(ls->fs, gt->pc, lb->nactvar); @@ -390,7 +389,7 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, l->arr[n].line = line; l->arr[n].nactvar = ls->fs->nactvar; l->arr[n].pc = pc; - l->n++; + l->n = n + 1; return n; } @@ -403,7 +402,7 @@ static void findgotos (LexState *ls, Labeldesc *lb) { Labellist *gl = &ls->dyd->gt; int i = ls->fs->bl->firstgoto; while (i < gl->n) { - if (luaS_eqstr(gl->arr[i].name, lb->name)) + if (eqstr(gl->arr[i].name, lb->name)) closegoto(ls, i, lb); else i++; @@ -412,7 +411,7 @@ static void findgotos (LexState *ls, Labeldesc *lb) { /* -** "export" pending gotos to outer level, to check them against +** export pending gotos to outer level, to check them against ** outer labels; if the block being exited has upvalues, and ** the goto exits the scope of any variable (which can be the ** upvalue), close those variables being exited. @@ -448,7 +447,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { /* -** create a label named "break" to resolve break statements +** create a label named 'break' to resolve break statements */ static void breaklabel (LexState *ls) { TString *n = luaS_new(ls->L, "break"); @@ -463,7 +462,7 @@ static void breaklabel (LexState *ls) { static l_noret undefgoto (LexState *ls, Labeldesc *gt) { const char *msg = isreserved(gt->name) ? "<%s> at line %d not inside a loop" - : "no visible label " LUA_QS " for at line %d"; + : "no visible label '%s' for at line %d"; msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); semerror(ls, msg); } @@ -503,7 +502,8 @@ static Proto *addprototype (LexState *ls) { if (fs->np >= f->sizep) { int oldsize = f->sizep; luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); - while (oldsize < f->sizep) f->p[oldsize++] = NULL; + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; } f->p[fs->np++] = clp = luaF_newproto(L); luaC_objbarrier(L, f, clp); @@ -525,7 +525,6 @@ static void codeclosure (LexState *ls, expdesc *v) { static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { - lua_State *L = ls->L; Proto *f; fs->prev = ls->fs; /* linked list of funcstates */ fs->ls = ls; @@ -544,10 +543,6 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { f = fs->f; f->source = ls->source; f->maxstacksize = 2; /* registers 0/1 are always valid */ - fs->h = luaH_new(L); - /* anchor table of constants (to avoid being collected) */ - sethvalue2s(L, L->top, fs->h); - incr_top(L); enterblock(fs, bl, 0); } @@ -572,9 +567,6 @@ static void close_func (LexState *ls) { f->sizeupvalues = fs->nups; lua_assert(fs->bl == NULL); ls->fs = fs->prev; - /* last token read was anchored in defunct function; must re-anchor it */ - anchor_token(ls); - L->top--; /* pop table of constants */ luaC_checkGC(L); } @@ -588,7 +580,7 @@ static void close_func (LexState *ls) { /* ** check whether current token is in the follow set of a block. ** 'until' closes syntactical blocks, but do not close scope, -** so it handled in separate. +** so it is handled in separate. */ static int block_follow (LexState *ls, int withuntil) { switch (ls->t.token) { @@ -602,7 +594,7 @@ static int block_follow (LexState *ls, int withuntil) { static void statlist (LexState *ls) { - /* statlist -> { stat [`;'] } */ + /* statlist -> { stat [';'] } */ while (!block_follow(ls, 1)) { if (ls->t.token == TK_RETURN) { statement(ls); @@ -643,14 +635,14 @@ static void yindex (LexState *ls, expdesc *v) { struct ConsControl { expdesc v; /* last list item read */ expdesc *t; /* table descriptor */ - int nh; /* total number of `record' elements */ + int nh; /* total number of 'record' elements */ int na; /* total number of array elements */ int tostore; /* number of array elements pending to be stored */ }; static void recfield (LexState *ls, struct ConsControl *cc) { - /* recfield -> (NAME | `['exp1`]') = exp1 */ + /* recfield -> (NAME | '['exp1']') = exp1 */ FuncState *fs = ls->fs; int reg = ls->fs->freereg; expdesc key, val; @@ -757,12 +749,12 @@ static void constructor (LexState *ls, expdesc *t) { static void parlist (LexState *ls) { - /* parlist -> [ param { `,' param } ] */ + /* parlist -> [ param { ',' param } ] */ FuncState *fs = ls->fs; Proto *f = fs->f; int nparams = 0; f->is_vararg = 0; - if (ls->t.token != ')') { /* is `parlist' not empty? */ + if (ls->t.token != ')') { /* is 'parlist' not empty? */ do { switch (ls->t.token) { case TK_NAME: { /* param -> NAME */ @@ -770,12 +762,12 @@ static void parlist (LexState *ls) { nparams++; break; } - case TK_DOTS: { /* param -> `...' */ + case TK_DOTS: { /* param -> '...' */ luaX_next(ls); - f->is_vararg = 1; + f->is_vararg = 2; /* declared vararg */ break; } - default: luaX_syntaxerror(ls, " or " LUA_QL("...") " expected"); + default: luaX_syntaxerror(ls, " or '...' expected"); } } while (!f->is_vararg && testnext(ls, ',')); } @@ -786,7 +778,7 @@ static void parlist (LexState *ls) { static void body (LexState *ls, expdesc *e, int ismethod, int line) { - /* body -> `(' parlist `)' block END */ + /* body -> '(' parlist ')' block END */ FuncState new_fs; BlockCnt bl; new_fs.f = addprototype(ls); @@ -808,7 +800,7 @@ static void body (LexState *ls, expdesc *e, int ismethod, int line) { static int explist (LexState *ls, expdesc *v) { - /* explist -> expr { `,' expr } */ + /* explist -> expr { ',' expr } */ int n = 1; /* at least one expression */ expr(ls, v); while (testnext(ls, ',')) { @@ -825,7 +817,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { expdesc args; int base, nparams; switch (ls->t.token) { - case '(': { /* funcargs -> `(' [ explist ] `)' */ + case '(': { /* funcargs -> '(' [ explist ] ')' */ luaX_next(ls); if (ls->t.token == ')') /* arg list is empty? */ args.k = VVOID; @@ -842,7 +834,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { } case TK_STRING: { /* funcargs -> STRING */ codestring(ls, &args, ls->t.seminfo.ts); - luaX_next(ls); /* must use `seminfo' before `next' */ + luaX_next(ls); /* must use 'seminfo' before 'next' */ break; } default: { @@ -908,14 +900,14 @@ static void suffixedexp (LexState *ls, expdesc *v) { fieldsel(ls, v); break; } - case '[': { /* `[' exp1 `]' */ + case '[': { /* '[' exp1 ']' */ expdesc key; luaK_exp2anyregup(fs, v); yindex(ls, &key); luaK_indexed(fs, v, &key); break; } - case ':': { /* `:' NAME funcargs */ + case ':': { /* ':' NAME funcargs */ expdesc key; luaX_next(ls); checkname(ls, &key); @@ -935,14 +927,19 @@ static void suffixedexp (LexState *ls, expdesc *v) { static void simpleexp (LexState *ls, expdesc *v) { - /* simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... | + /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | constructor | FUNCTION body | suffixedexp */ switch (ls->t.token) { - case TK_NUMBER: { - init_exp(v, VKNUM, 0); + case TK_FLT: { + init_exp(v, VKFLT, 0); v->u.nval = ls->t.seminfo.r; break; } + case TK_INT: { + init_exp(v, VKINT, 0); + v->u.ival = ls->t.seminfo.i; + break; + } case TK_STRING: { codestring(ls, v, ls->t.seminfo.ts); break; @@ -962,7 +959,8 @@ static void simpleexp (LexState *ls, expdesc *v) { case TK_DOTS: { /* vararg */ FuncState *fs = ls->fs; check_condition(ls, fs->f->is_vararg, - "cannot use " LUA_QL("...") " outside a vararg function"); + "cannot use '...' outside a vararg function"); + fs->f->is_vararg = 1; /* function actually uses vararg */ init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); break; } @@ -988,6 +986,7 @@ static UnOpr getunopr (int op) { switch (op) { case TK_NOT: return OPR_NOT; case '-': return OPR_MINUS; + case '~': return OPR_BNOT; case '#': return OPR_LEN; default: return OPR_NOUNOPR; } @@ -999,9 +998,15 @@ static BinOpr getbinopr (int op) { case '+': return OPR_ADD; case '-': return OPR_SUB; case '*': return OPR_MUL; - case '/': return OPR_DIV; case '%': return OPR_MOD; case '^': return OPR_POW; + case '/': return OPR_DIV; + case TK_IDIV: return OPR_IDIV; + case '&': return OPR_BAND; + case '|': return OPR_BOR; + case '~': return OPR_BXOR; + case TK_SHL: return OPR_SHL; + case TK_SHR: return OPR_SHR; case TK_CONCAT: return OPR_CONCAT; case TK_NE: return OPR_NE; case TK_EQ: return OPR_EQ; @@ -1020,19 +1025,24 @@ static const struct { lu_byte left; /* left priority for each binary operator */ lu_byte right; /* right priority */ } priority[] = { /* ORDER OPR */ - {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `*' `/' `%' */ - {10, 9}, {5, 4}, /* ^, .. (right associative) */ - {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ - {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ - {2, 2}, {1, 1} /* and, or */ + {10, 10}, {10, 10}, /* '+' '-' */ + {11, 11}, {11, 11}, /* '*' '%' */ + {14, 13}, /* '^' (right associative) */ + {11, 11}, {11, 11}, /* '/' '//' */ + {6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */ + {7, 7}, {7, 7}, /* '<<' '>>' */ + {9, 8}, /* '..' (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ + {2, 2}, {1, 1} /* and, or */ }; -#define UNARY_PRIORITY 8 /* priority for unary operators */ +#define UNARY_PRIORITY 12 /* priority for unary operators */ /* ** subexpr -> (simpleexp | unop subexpr) { binop subexpr } -** where `binop' is any binary operator with a priority higher than `limit' +** where 'binop' is any binary operator with a priority higher than 'limit' */ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { BinOpr op; @@ -1046,7 +1056,7 @@ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { luaK_prefix(ls->fs, uop, v, line); } else simpleexp(ls, v); - /* expand while operators have priorities higher than `limit' */ + /* expand while operators have priorities higher than 'limit' */ op = getbinopr(ls->t.token); while (op != OPR_NOBINOPR && priority[op].left > limit) { expdesc v2; @@ -1146,7 +1156,7 @@ static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { "C levels"); assignment(ls, &nv, nvars+1); } - else { /* assignment -> `=' explist */ + else { /* assignment -> '=' explist */ int nexps; checknext(ls, '='); nexps = explist(ls, &e); @@ -1170,7 +1180,7 @@ static int cond (LexState *ls) { /* cond -> exp */ expdesc v; expr(ls, &v); /* read condition */ - if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */ + if (v.k == VNIL) v.k = VFALSE; /* 'falses' are all equal here */ luaK_goiftrue(ls->fs, &v); return v.f; } @@ -1195,9 +1205,9 @@ static void gotostat (LexState *ls, int pc) { static void checkrepeated (FuncState *fs, Labellist *ll, TString *label) { int i; for (i = fs->bl->firstlabel; i < ll->n; i++) { - if (luaS_eqstr(label, ll->arr[i].name)) { + if (eqstr(label, ll->arr[i].name)) { const char *msg = luaO_pushfstring(fs->ls->L, - "label " LUA_QS " already defined on line %d", + "label '%s' already defined on line %d", getstr(label), ll->arr[i].line); semerror(fs->ls, msg); } @@ -1220,7 +1230,7 @@ static void labelstat (LexState *ls, TString *label, int line) { checkrepeated(fs, ll, label); /* check for repeated labels */ checknext(ls, TK_DBCOLON); /* skip double colon */ /* create new entry for this label */ - l = newlabelentry(ls, ll, label, line, fs->pc); + l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs)); skipnoopstat(ls); /* skip other no-op statements */ if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ /* assume that locals are already out of scope */ @@ -1321,7 +1331,7 @@ static void fornum (LexState *ls, TString *varname, int line) { if (testnext(ls, ',')) exp1(ls); /* optional step */ else { /* default step = 1 */ - luaK_codek(fs, fs->freereg, luaK_numberK(fs, 1)); + luaK_codek(fs, fs->freereg, luaK_intK(fs, 1)); luaK_reserveregs(fs, 1); } forbody(ls, base, line, 1, 1); @@ -1359,15 +1369,15 @@ static void forstat (LexState *ls, int line) { TString *varname; BlockCnt bl; enterblock(fs, &bl, 1); /* scope for loop and control variables */ - luaX_next(ls); /* skip `for' */ + luaX_next(ls); /* skip 'for' */ varname = str_checkname(ls); /* first variable name */ switch (ls->t.token) { case '=': fornum(ls, varname, line); break; case ',': case TK_IN: forlist(ls, varname); break; - default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected"); + default: luaX_syntaxerror(ls, "'=' or 'in' expected"); } check_match(ls, TK_END, TK_FOR, line); - leaveblock(fs); /* loop scope (`break' jumps to this point) */ + leaveblock(fs); /* loop scope ('break' jumps to this point) */ } @@ -1397,7 +1407,7 @@ static void test_then_block (LexState *ls, int *escapelist) { enterblock(fs, &bl, 0); jf = v.f; } - statlist(ls); /* `then' part */ + statlist(ls); /* 'then' part */ leaveblock(fs); if (ls->t.token == TK_ELSE || ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ @@ -1414,7 +1424,7 @@ static void ifstat (LexState *ls, int line) { while (ls->t.token == TK_ELSEIF) test_then_block(ls, &escapelist); /* ELSEIF cond THEN block */ if (testnext(ls, TK_ELSE)) - block(ls); /* `else' part */ + block(ls); /* 'else' part */ check_match(ls, TK_END, TK_IF, line); luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ } @@ -1432,7 +1442,7 @@ static void localfunc (LexState *ls) { static void localstat (LexState *ls) { - /* stat -> LOCAL NAME {`,' NAME} [`=' explist] */ + /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ int nvars = 0; int nexps; expdesc e; @@ -1452,7 +1462,7 @@ static void localstat (LexState *ls) { static int funcname (LexState *ls, expdesc *v) { - /* funcname -> NAME {fieldsel} [`:' NAME] */ + /* funcname -> NAME {fieldsel} [':' NAME] */ int ismethod = 0; singlevar(ls, v); while (ls->t.token == '.') @@ -1473,7 +1483,7 @@ static void funcstat (LexState *ls, int line) { ismethod = funcname(ls, &v); body(ls, &b, ismethod, line); luaK_storevar(ls->fs, &v, &b); - luaK_fixline(ls->fs, line); /* definition `happens' in the first line */ + luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ } @@ -1488,7 +1498,7 @@ static void exprstat (LexState *ls) { } else { /* stat -> func */ check_condition(ls, v.v.k == VCALL, "syntax error"); - SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + SETARG_C(getinstruction(fs, &v.v), 1); /* call statement uses no results */ } } @@ -1505,8 +1515,8 @@ static void retstat (LexState *ls) { if (hasmultret(e.k)) { luaK_setmultret(fs, &e); if (e.k == VCALL && nret == 1) { /* tail call? */ - SET_OPCODE(getcode(fs,&e), OP_TAILCALL); - lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); } first = fs->nactvar; nret = LUA_MULTRET; /* return all values */ @@ -1515,8 +1525,8 @@ static void retstat (LexState *ls) { if (nret == 1) /* only one single value? */ first = luaK_exp2anyreg(fs, &e); else { - luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */ - first = fs->nactvar; /* return all `active' values */ + luaK_exp2nextreg(fs, &e); /* values must go to the stack */ + first = fs->nactvar; /* return all active values */ lua_assert(nret == fs->freereg - first); } } @@ -1605,7 +1615,7 @@ static void mainfunc (LexState *ls, FuncState *fs) { BlockCnt bl; expdesc v; open_func(ls, fs, &bl); - fs->f->is_vararg = 1; /* main function is always vararg */ + fs->f->is_vararg = 2; /* main function is always declared vararg */ init_exp(&v, VLOCAL, 0); /* create and... */ newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ luaX_next(ls); /* read first token */ @@ -1615,16 +1625,19 @@ static void mainfunc (LexState *ls, FuncState *fs) { } -Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, - Dyndata *dyd, const char *name, int firstchar) { +LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar) { LexState lexstate; FuncState funcstate; - Closure *cl = luaF_newLclosure(L, 1); /* create main closure */ - /* anchor closure (to avoid being collected) */ - setclLvalue(L, L->top, cl); - incr_top(L); - funcstate.f = cl->l.p = luaF_newproto(L); + LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */ + setclLvalue(L, L->top, cl); /* anchor it (to avoid being collected) */ + luaD_inctop(L); + lexstate.h = luaH_new(L); /* create table for scanner */ + sethvalue(L, L->top, lexstate.h); /* anchor it */ + luaD_inctop(L); + funcstate.f = cl->p = luaF_newproto(L); funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ + lua_assert(iswhite(funcstate.f)); /* do not need barrier here */ lexstate.buff = buff; lexstate.dyd = dyd; dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; @@ -1633,6 +1646,7 @@ Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); /* all scopes should be correctly finished */ lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); - return cl; /* it's on the stack too */ + L->top--; /* remove scanner's table */ + return cl; /* closure is on the stack, too */ } diff --git a/depends/lua/src/lstate.c b/depends/lua/src/lstate.c index c7f2672be..9194ac341 100644 --- a/depends/lua/src/lstate.c +++ b/depends/lua/src/lstate.c @@ -1,16 +1,18 @@ /* -** $Id: lstate.c,v 2.99.1.2 2013/11/08 17:45:31 roberto Exp $ +** $Id: lstate.c,v 2.133 2015/11/13 12:16:51 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ +#define lstate_c +#define LUA_CORE + +#include "lprefix.h" + #include #include -#define lstate_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -30,18 +32,11 @@ #define LUAI_GCPAUSE 200 /* 200% */ #endif -#if !defined(LUAI_GCMAJOR) -#define LUAI_GCMAJOR 200 /* 200% */ -#endif - #if !defined(LUAI_GCMUL) #define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ #endif -#define MEMERRMSG "not enough memory" - - /* ** a macro to help the creation of a unique random seed when a state is ** created; the seed is used to randomize hashes. @@ -57,9 +52,7 @@ ** thread state + extra space */ typedef struct LX { -#if defined(LUAI_EXTRASPACE) - char buff[LUAI_EXTRASPACE]; -#endif + lu_byte extra_[LUA_EXTRASPACE]; lua_State l; } LX; @@ -78,13 +71,12 @@ typedef struct LG { /* -** Compute an initial seed as random as possible. In ANSI, rely on -** Address Space Layout Randomization (if present) to increase -** randomness.. +** Compute an initial seed as random as possible. Rely on Address Space +** Layout Randomization (if present) to increase randomness.. */ #define addbuff(b,p,e) \ { size_t t = cast(size_t, e); \ - memcpy(buff + p, &t, sizeof(t)); p += sizeof(t); } + memcpy(b + p, &t, sizeof(t)); p += sizeof(t); } static unsigned int makeseed (lua_State *L) { char buff[4 * sizeof(size_t)]; @@ -101,10 +93,14 @@ static unsigned int makeseed (lua_State *L) { /* ** set GCdebt to a new value keeping the value (totalbytes + GCdebt) -** invariant +** invariant (and avoiding underflows in 'totalbytes') */ void luaE_setdebt (global_State *g, l_mem debt) { - g->totalbytes -= (debt - g->GCdebt); + l_mem tb = gettotalbytes(g); + lua_assert(tb > 0); + if (debt < tb - MAX_LMEM) + debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ + g->totalbytes = tb - debt; g->GCdebt = debt; } @@ -115,10 +111,14 @@ CallInfo *luaE_extendCI (lua_State *L) { L->ci->next = ci; ci->previous = L->ci; ci->next = NULL; + L->nci++; return ci; } +/* +** free all CallInfo structures not in use by a thread +*/ void luaE_freeCI (lua_State *L) { CallInfo *ci = L->ci; CallInfo *next = ci->next; @@ -126,6 +126,24 @@ void luaE_freeCI (lua_State *L) { while ((ci = next) != NULL) { next = ci->next; luaM_free(L, ci); + L->nci--; + } +} + + +/* +** free half of the CallInfo structures not in use by a thread +*/ +void luaE_shrinkCI (lua_State *L) { + CallInfo *ci = L->ci; + CallInfo *next2; /* next's next */ + /* while there are two nexts */ + while (ci->next != NULL && (next2 = ci->next->next) != NULL) { + luaM_free(L, ci->next); /* free next */ + L->nci--; + ci->next = next2; /* remove 'next' from the list */ + next2->previous = ci; + ci = next2; /* keep next's next */ } } @@ -155,6 +173,7 @@ static void freestack (lua_State *L) { return; /* stack not completely built yet */ L->ci = &L->base_ci; /* free the entire 'ci' list */ luaE_freeCI(L); + lua_assert(L->nci == 0); luaM_freearray(L, L->stack, L->stacksize); /* free stack array */ } @@ -163,34 +182,32 @@ static void freestack (lua_State *L) { ** Create registry table and its predefined values */ static void init_registry (lua_State *L, global_State *g) { - TValue mt; + TValue temp; /* create registry */ Table *registry = luaH_new(L); sethvalue(L, &g->l_registry, registry); luaH_resize(L, registry, LUA_RIDX_LAST, 0); /* registry[LUA_RIDX_MAINTHREAD] = L */ - setthvalue(L, &mt, L); - luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &mt); + setthvalue(L, &temp, L); /* temp = L */ + luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &temp); /* registry[LUA_RIDX_GLOBALS] = table of globals */ - sethvalue(L, &mt, luaH_new(L)); - luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt); + sethvalue(L, &temp, luaH_new(L)); /* temp = new table (global table) */ + luaH_setint(L, registry, LUA_RIDX_GLOBALS, &temp); } /* -** open parts of the state that may cause memory-allocation errors +** open parts of the state that may cause memory-allocation errors. +** ('g->version' != NULL flags that the state was completely build) */ static void f_luaopen (lua_State *L, void *ud) { global_State *g = G(L); UNUSED(ud); stack_init(L, L); /* init stack */ init_registry(L, g); - luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + luaS_init(L); luaT_init(L); luaX_init(L); - /* pre-create memory-error message */ - g->memerrmsg = luaS_newliteral(L, MEMERRMSG); - luaS_fix(g->memerrmsg); /* it should never be collected */ g->gcrunning = 1; /* allow gc */ g->version = lua_version(NULL); luai_userstateopen(L); @@ -198,14 +215,16 @@ static void f_luaopen (lua_State *L, void *ud) { /* -** preinitialize a state with consistent values without allocating +** preinitialize a thread with consistent values without allocating ** any memory (to avoid errors) */ -static void preinit_state (lua_State *L, global_State *g) { +static void preinit_thread (lua_State *L, global_State *g) { G(L) = g; L->stack = NULL; L->ci = NULL; + L->nci = 0; L->stacksize = 0; + L->twups = L; /* thread has no upvalues */ L->errorJmp = NULL; L->nCcalls = 0; L->hook = NULL; @@ -227,7 +246,6 @@ static void close_state (lua_State *L) { if (g->version) /* closing a fully built state? */ luai_userstateclose(L); luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); - luaZ_freebuffer(L, &g->buff); freestack(L); lua_assert(gettotalbytes(g) == sizeof(LG)); (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ @@ -235,17 +253,28 @@ static void close_state (lua_State *L) { LUA_API lua_State *lua_newthread (lua_State *L) { + global_State *g = G(L); lua_State *L1; lua_lock(L); luaC_checkGC(L); - L1 = &luaC_newobj(L, LUA_TTHREAD, sizeof(LX), NULL, offsetof(LX, l))->th; + /* create new thread */ + L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l; + L1->marked = luaC_white(g); + L1->tt = LUA_TTHREAD; + /* link it on list 'allgc' */ + L1->next = g->allgc; + g->allgc = obj2gco(L1); + /* anchor it on L stack */ setthvalue(L, L->top, L1); api_incr_top(L); - preinit_state(L1, G(L)); + preinit_thread(L1, g); L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; resethookcount(L1); + /* initialize L1 extra space */ + memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), + LUA_EXTRASPACE); luai_userstatethread(L, L1); stack_init(L1, L); /* init stack */ lua_unlock(L); @@ -273,36 +302,31 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { g = &l->g; L->next = NULL; L->tt = LUA_TTHREAD; - g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); + g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); - g->gckind = KGC_NORMAL; - preinit_state(L, g); + preinit_thread(L, g); g->frealloc = f; g->ud = ud; g->mainthread = L; g->seed = makeseed(L); - g->uvhead.u.l.prev = &g->uvhead; - g->uvhead.u.l.next = &g->uvhead; g->gcrunning = 0; /* no GC while building state */ g->GCestimate = 0; - g->strt.size = 0; - g->strt.nuse = 0; + g->strt.size = g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(&g->l_registry); - luaZ_initbuffer(L, &g->buff); g->panic = NULL; g->version = NULL; g->gcstate = GCSpause; - g->allgc = NULL; - g->finobj = NULL; - g->tobefnz = NULL; - g->sweepgc = g->sweepfin = NULL; + g->gckind = KGC_NORMAL; + g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL; + g->sweepgc = NULL; g->gray = g->grayagain = NULL; g->weak = g->ephemeron = g->allweak = NULL; + g->twups = NULL; g->totalbytes = sizeof(LG); g->GCdebt = 0; + g->gcfinnum = 0; g->gcpause = LUAI_GCPAUSE; - g->gcmajorinc = LUAI_GCMAJOR; g->gcstepmul = LUAI_GCMUL; for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { diff --git a/depends/lua/src/lstring.c b/depends/lua/src/lstring.c index af96c89c1..9351766fd 100644 --- a/depends/lua/src/lstring.c +++ b/depends/lua/src/lstring.c @@ -1,23 +1,30 @@ /* -** $Id: lstring.c,v 2.26.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstring.c,v 2.56 2015/11/23 11:32:51 roberto Exp $ ** String table (keeps all strings handled by Lua) ** See Copyright Notice in lua.h */ - -#include - #define lstring_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" +#include "ldebug.h" +#include "ldo.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" +#define MEMERRMSG "not enough memory" + + /* ** Lua will use at most ~(2^LUAI_HASHLIMIT) bytes from a string to ** compute its hash @@ -31,99 +38,126 @@ ** equality for long strings */ int luaS_eqlngstr (TString *a, TString *b) { - size_t len = a->tsv.len; - lua_assert(a->tsv.tt == LUA_TLNGSTR && b->tsv.tt == LUA_TLNGSTR); + size_t len = a->u.lnglen; + lua_assert(a->tt == LUA_TLNGSTR && b->tt == LUA_TLNGSTR); return (a == b) || /* same instance or... */ - ((len == b->tsv.len) && /* equal length and ... */ + ((len == b->u.lnglen) && /* equal length and ... */ (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ } -/* -** equality for strings -*/ -int luaS_eqstr (TString *a, TString *b) { - return (a->tsv.tt == b->tsv.tt) && - (a->tsv.tt == LUA_TSHRSTR ? eqshrstr(a, b) : luaS_eqlngstr(a, b)); -} - - unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { unsigned int h = seed ^ cast(unsigned int, l); - size_t l1; size_t step = (l >> LUAI_HASHLIMIT) + 1; - for (l1 = l; l1 >= step; l1 -= step) - h = h ^ ((h<<5) + (h>>2) + cast_byte(str[l1 - 1])); + for (; l >= step; l -= step) + h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); return h; } +unsigned int luaS_hashlongstr (TString *ts) { + lua_assert(ts->tt == LUA_TLNGSTR); + if (ts->extra == 0) { /* no hash? */ + ts->hash = luaS_hash(getstr(ts), ts->u.lnglen, ts->hash); + ts->extra = 1; /* now it has its hash */ + } + return ts->hash; +} + + /* ** resizes the string table */ void luaS_resize (lua_State *L, int newsize) { int i; stringtable *tb = &G(L)->strt; - /* cannot resize while GC is traversing strings */ - luaC_runtilstate(L, ~bitmask(GCSsweepstring)); - if (newsize > tb->size) { - luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *); - for (i = tb->size; i < newsize; i++) tb->hash[i] = NULL; + if (newsize > tb->size) { /* grow table if needed */ + luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); + for (i = tb->size; i < newsize; i++) + tb->hash[i] = NULL; } - /* rehash */ - for (i=0; isize; i++) { - GCObject *p = tb->hash[i]; + for (i = 0; i < tb->size; i++) { /* rehash */ + TString *p = tb->hash[i]; tb->hash[i] = NULL; while (p) { /* for each node in the list */ - GCObject *next = gch(p)->next; /* save next */ - unsigned int h = lmod(gco2ts(p)->hash, newsize); /* new position */ - gch(p)->next = tb->hash[h]; /* chain it */ + TString *hnext = p->u.hnext; /* save next */ + unsigned int h = lmod(p->hash, newsize); /* new position */ + p->u.hnext = tb->hash[h]; /* chain it */ tb->hash[h] = p; - resetoldbit(p); /* see MOVE OLD rule */ - p = next; + p = hnext; } } - if (newsize < tb->size) { - /* shrinking slice must be empty */ + if (newsize < tb->size) { /* shrink table if needed */ + /* vanishing slice should be empty */ lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL); - luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *); + luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); } tb->size = newsize; } +/* +** Clear API string cache. (Entries cannot be empty, so fill them with +** a non-collectable string.) +*/ +void luaS_clearcache (global_State *g) { + int i, j; + for (i = 0; i < STRCACHE_N; i++) + for (j = 0; j < STRCACHE_M; j++) { + if (iswhite(g->strcache[i][j])) /* will entry be collected? */ + g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */ + } +} + + +/* +** Initialize the string table and the string cache +*/ +void luaS_init (lua_State *L) { + global_State *g = G(L); + int i, j; + luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + /* pre-create memory-error message */ + g->memerrmsg = luaS_newliteral(L, MEMERRMSG); + luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */ + for (i = 0; i < STRCACHE_N; i++) /* fill cache with valid strings */ + for (j = 0; j < STRCACHE_M; j++) + g->strcache[i][j] = g->memerrmsg; +} + + + /* ** creates a new string object */ -static TString *createstrobj (lua_State *L, const char *str, size_t l, - int tag, unsigned int h, GCObject **list) { +static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { TString *ts; + GCObject *o; size_t totalsize; /* total size of TString object */ - totalsize = sizeof(TString) + ((l + 1) * sizeof(char)); - ts = &luaC_newobj(L, tag, totalsize, list, 0)->ts; - ts->tsv.len = l; - ts->tsv.hash = h; - ts->tsv.extra = 0; - memcpy(ts+1, str, l*sizeof(char)); - ((char *)(ts+1))[l] = '\0'; /* ending 0 */ + totalsize = sizelstring(l); + o = luaC_newobj(L, tag, totalsize); + ts = gco2ts(o); + ts->hash = h; + ts->extra = 0; + getstr(ts)[l] = '\0'; /* ending 0 */ return ts; } -/* -** creates a new short string, inserting it into string table -*/ -static TString *newshrstr (lua_State *L, const char *str, size_t l, - unsigned int h) { - GCObject **list; /* (pointer to) list where it will be inserted */ +TString *luaS_createlngstrobj (lua_State *L, size_t l) { + TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed); + ts->u.lnglen = l; + return ts; +} + + +void luaS_remove (lua_State *L, TString *ts) { stringtable *tb = &G(L)->strt; - TString *s; - if (tb->nuse >= cast(lu_int32, tb->size) && tb->size <= MAX_INT/2) - luaS_resize(L, tb->size*2); /* too crowded */ - list = &tb->hash[lmod(h, tb->size)]; - s = createstrobj(L, str, l, LUA_TSHRSTR, h, list); - tb->nuse++; - return s; + TString **p = &tb->hash[lmod(ts->hash, tb->size)]; + while (*p != ts) /* find previous element */ + p = &(*p)->u.hnext; + *p = (*p)->u.hnext; /* remove element from its list */ + tb->nuse--; } @@ -131,22 +165,31 @@ static TString *newshrstr (lua_State *L, const char *str, size_t l, ** checks whether short string exists and reuses it or creates a new one */ static TString *internshrstr (lua_State *L, const char *str, size_t l) { - GCObject *o; + TString *ts; global_State *g = G(L); unsigned int h = luaS_hash(str, l, g->seed); - for (o = g->strt.hash[lmod(h, g->strt.size)]; - o != NULL; - o = gch(o)->next) { - TString *ts = rawgco2ts(o); - if (h == ts->tsv.hash && - l == ts->tsv.len && + TString **list = &g->strt.hash[lmod(h, g->strt.size)]; + lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ + for (ts = *list; ts != NULL; ts = ts->u.hnext) { + if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { - if (isdead(G(L), o)) /* string is dead (but was not collected yet)? */ - changewhite(o); /* resurrect it */ + /* found! */ + if (isdead(g, ts)) /* dead (but not collected yet)? */ + changewhite(ts); /* resurrect it */ return ts; } } - return newshrstr(L, str, l, h); /* not found; create a new string */ + if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) { + luaS_resize(L, g->strt.size * 2); + list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */ + } + ts = createstrobj(L, l, LUA_TSHRSTR, h); + memcpy(getstr(ts), str, l * sizeof(char)); + ts->shrlen = cast_byte(l); + ts->u.hnext = *list; + *list = ts; + g->strt.nuse++; + return ts; } @@ -157,29 +200,49 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { if (l <= LUAI_MAXSHORTLEN) /* short string? */ return internshrstr(L, str, l); else { - if (l + 1 > (MAX_SIZET - sizeof(TString))/sizeof(char)) + TString *ts; + if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char)) luaM_toobig(L); - return createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed, NULL); + ts = luaS_createlngstrobj(L, l); + memcpy(getstr(ts), str, l * sizeof(char)); + return ts; } } /* -** new zero-terminated string +** Create or reuse a zero-terminated string, first checking in the +** cache (using the string address as a key). The cache can contain +** only zero-terminated strings, so it is safe to use 'strcmp' to +** check hits. */ TString *luaS_new (lua_State *L, const char *str) { - return luaS_newlstr(L, str, strlen(str)); + unsigned int i = point2uint(str) % STRCACHE_N; /* hash */ + int j; + TString **p = G(L)->strcache[i]; + for (j = 0; j < STRCACHE_M; j++) { + if (strcmp(str, getstr(p[j])) == 0) /* hit? */ + return p[j]; /* that is it */ + } + /* normal route */ + for (j = STRCACHE_M - 1; j > 0; j--) + p[j] = p[j - 1]; /* move out last element */ + /* new element is first in the list */ + p[0] = luaS_newlstr(L, str, strlen(str)); + return p[0]; } -Udata *luaS_newudata (lua_State *L, size_t s, Table *e) { +Udata *luaS_newudata (lua_State *L, size_t s) { Udata *u; - if (s > MAX_SIZET - sizeof(Udata)) + GCObject *o; + if (s > MAX_SIZE - sizeof(Udata)) luaM_toobig(L); - u = &luaC_newobj(L, LUA_TUSERDATA, sizeof(Udata) + s, NULL, 0)->u; - u->uv.len = s; - u->uv.metatable = NULL; - u->uv.env = e; + o = luaC_newobj(L, LUA_TUSERDATA, sizeludata(s)); + u = gco2u(o); + u->len = s; + u->metatable = NULL; + setuservalue(L, u, luaO_nilobject); return u; } diff --git a/depends/lua/src/lstrlib.c b/depends/lua/src/lstrlib.c index 9261fd220..12264f881 100644 --- a/depends/lua/src/lstrlib.c +++ b/depends/lua/src/lstrlib.c @@ -1,19 +1,24 @@ /* -** $Id: lstrlib.c,v 1.178.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstrlib.c,v 1.251 2016/05/20 14:13:21 roberto Exp $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ +#define lstrlib_c +#define LUA_LIB + +#include "lprefix.h" + #include +#include +#include +#include #include #include #include #include -#define lstrlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -22,17 +27,29 @@ /* ** maximum number of captures that a pattern can do during -** pattern-matching. This limit is arbitrary. +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. */ #if !defined(LUA_MAXCAPTURES) #define LUA_MAXCAPTURES 32 #endif -/* macro to `unsign' a character */ +/* macro to 'unsign' a character */ #define uchar(c) ((unsigned char)(c)) +/* +** Some sizes are better limited to fit in 'int', but must also fit in +** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.) +*/ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +#define MAXSIZE \ + (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) + + + static int str_len (lua_State *L) { size_t l; @@ -43,22 +60,22 @@ static int str_len (lua_State *L) { /* translate a relative string position: negative means back from end */ -static size_t posrelat (ptrdiff_t pos, size_t len) { - if (pos >= 0) return (size_t)pos; +static lua_Integer posrelat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; else if (0u - (size_t)pos > len) return 0; - else return len - ((size_t)-pos) + 1; + else return (lua_Integer)len + pos + 1; } static int str_sub (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); - size_t start = posrelat(luaL_checkinteger(L, 2), l); - size_t end = posrelat(luaL_optinteger(L, 3, -1), l); + lua_Integer start = posrelat(luaL_checkinteger(L, 2), l); + lua_Integer end = posrelat(luaL_optinteger(L, 3, -1), l); if (start < 1) start = 1; - if (end > l) end = l; + if (end > (lua_Integer)l) end = l; if (start <= end) - lua_pushlstring(L, s + start - 1, end - start + 1); + lua_pushlstring(L, s + start - 1, (size_t)(end - start) + 1); else lua_pushliteral(L, ""); return 1; } @@ -102,25 +119,23 @@ static int str_upper (lua_State *L) { } -/* reasonable limit to avoid arithmetic overflow */ -#define MAXSIZE ((~(size_t)0) >> 1) - static int str_rep (lua_State *L) { size_t l, lsep; const char *s = luaL_checklstring(L, 1, &l); - int n = luaL_checkint(L, 2); + lua_Integer n = luaL_checkinteger(L, 2); const char *sep = luaL_optlstring(L, 3, "", &lsep); if (n <= 0) lua_pushliteral(L, ""); - else if (l + lsep < l || l + lsep >= MAXSIZE / n) /* may overflow? */ + else if (l + lsep < l || l + lsep > MAXSIZE / n) /* may overflow? */ return luaL_error(L, "resulting string too large"); else { - size_t totallen = n * l + (n - 1) * lsep; + size_t totallen = (size_t)n * l + (size_t)(n - 1) * lsep; luaL_Buffer b; char *p = luaL_buffinitsize(L, &b, totallen); while (n-- > 1) { /* first n-1 copies (followed by separator) */ memcpy(p, s, l * sizeof(char)); p += l; - if (lsep > 0) { /* avoid empty 'memcpy' (may be expensive) */ - memcpy(p, sep, lsep * sizeof(char)); p += lsep; + if (lsep > 0) { /* empty 'memcpy' is not that cheap */ + memcpy(p, sep, lsep * sizeof(char)); + p += lsep; } } memcpy(p, s, l * sizeof(char)); /* last copy (not followed by separator) */ @@ -133,15 +148,15 @@ static int str_rep (lua_State *L) { static int str_byte (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); - size_t posi = posrelat(luaL_optinteger(L, 2, 1), l); - size_t pose = posrelat(luaL_optinteger(L, 3, posi), l); + lua_Integer posi = posrelat(luaL_optinteger(L, 2, 1), l); + lua_Integer pose = posrelat(luaL_optinteger(L, 3, posi), l); int n, i; if (posi < 1) posi = 1; - if (pose > l) pose = l; + if (pose > (lua_Integer)l) pose = l; if (posi > pose) return 0; /* empty interval; return no values */ - n = (int)(pose - posi + 1); - if (posi + n <= pose) /* (size_t -> int) overflow? */ + if (pose - posi >= INT_MAX) /* arithmetic overflow? */ return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; luaL_checkstack(L, n, "string slice too long"); for (i=0; ip_end) - luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); + luaL_error(ms->L, "malformed pattern (ends with '%%')"); return p+1; } case '[': { if (*p == '^') p++; - do { /* look for a `]' */ + do { /* look for a ']' */ if (p == ms->p_end) - luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); + luaL_error(ms->L, "malformed pattern (missing ']')"); if (*(p++) == L_ESC && p < ms->p_end) - p++; /* skip escapes (e.g. `%]') */ + p++; /* skip escapes (e.g. '%]') */ } while (*p != ']'); return p+1; } @@ -287,7 +303,7 @@ static int matchbracketclass (int c, const char *p, const char *ec) { int sig = 1; if (*(p+1) == '^') { sig = 0; - p++; /* skip the `^' */ + p++; /* skip the '^' */ } while (++p < ec) { if (*p == L_ESC) { @@ -325,8 +341,7 @@ static int singlematch (MatchState *ms, const char *s, const char *p, static const char *matchbalance (MatchState *ms, const char *s, const char *p) { if (p >= ms->p_end - 1) - luaL_error(ms->L, "malformed pattern " - "(missing arguments to " LUA_QL("%%b") ")"); + luaL_error(ms->L, "malformed pattern (missing arguments to '%%b')"); if (*s != *p) return NULL; else { int b = *p; @@ -425,7 +440,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { break; } case '$': { - if ((p + 1) != ms->p_end) /* is the `$' the last char in pattern? */ + if ((p + 1) != ms->p_end) /* is the '$' the last char in pattern? */ goto dflt; /* no; go to default */ s = (s == ms->src_end) ? s : NULL; /* check end of string */ break; @@ -443,8 +458,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { const char *ep; char previous; p += 2; if (*p != '[') - luaL_error(ms->L, "missing " LUA_QL("[") " after " - LUA_QL("%%f") " in pattern"); + luaL_error(ms->L, "missing '[' after '%%f' in pattern"); ep = classend(ms, p); /* points to what is next */ previous = (s == ms->src_init) ? '\0' : *(s - 1); if (!matchbracketclass(uchar(previous), p, ep - 1) && @@ -490,7 +504,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { } case '+': /* 1 or more repetitions */ s++; /* 1 match already done */ - /* go through */ + /* FALLTHROUGH */ case '*': /* 0 or more repetitions */ s = max_expand(ms, s, p, ep); break; @@ -514,16 +528,16 @@ static const char *match (MatchState *ms, const char *s, const char *p) { static const char *lmemfind (const char *s1, size_t l1, const char *s2, size_t l2) { if (l2 == 0) return s1; /* empty strings are everywhere */ - else if (l2 > l1) return NULL; /* avoids a negative `l1' */ + else if (l2 > l1) return NULL; /* avoids a negative 'l1' */ else { - const char *init; /* to search for a `*s2' inside `s1' */ - l2--; /* 1st char will be checked by `memchr' */ - l1 = l1-l2; /* `s2' cannot be found after that */ + const char *init; /* to search for a '*s2' inside 's1' */ + l2--; /* 1st char will be checked by 'memchr' */ + l1 = l1-l2; /* 's2' cannot be found after that */ while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { init++; /* 1st char is already checked */ if (memcmp(init, s2+1, l2) == 0) return init-1; - else { /* correct `l1' and `s1' to try again */ + else { /* correct 'l1' and 's1' to try again */ l1 -= init-s1; s1 = init; } @@ -539,13 +553,13 @@ static void push_onecapture (MatchState *ms, int i, const char *s, if (i == 0) /* ms->level == 0, too */ lua_pushlstring(ms->L, s, e - s); /* add whole match */ else - luaL_error(ms->L, "invalid capture index"); + luaL_error(ms->L, "invalid capture index %%%d", i + 1); } else { ptrdiff_t l = ms->capture[i].len; if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); if (l == CAP_POSITION) - lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1); + lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1); else lua_pushlstring(ms->L, ms->capture[i].init, l); } @@ -574,23 +588,39 @@ static int nospecials (const char *p, size_t l) { } +static void prepstate (MatchState *ms, lua_State *L, + const char *s, size_t ls, const char *p, size_t lp) { + ms->L = L; + ms->matchdepth = MAXCCALLS; + ms->src_init = s; + ms->src_end = s + ls; + ms->p_end = p + lp; +} + + +static void reprepstate (MatchState *ms) { + ms->level = 0; + lua_assert(ms->matchdepth == MAXCCALLS); +} + + static int str_find_aux (lua_State *L, int find) { size_t ls, lp; const char *s = luaL_checklstring(L, 1, &ls); const char *p = luaL_checklstring(L, 2, &lp); - size_t init = posrelat(luaL_optinteger(L, 3, 1), ls); + lua_Integer init = posrelat(luaL_optinteger(L, 3, 1), ls); if (init < 1) init = 1; - else if (init > ls + 1) { /* start after string's end? */ + else if (init > (lua_Integer)ls + 1) { /* start after string's end? */ lua_pushnil(L); /* cannot find anything */ return 1; } /* explicit request or no special characters? */ if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { /* do a plain search */ - const char *s2 = lmemfind(s + init - 1, ls - init + 1, p, lp); + const char *s2 = lmemfind(s + init - 1, ls - (size_t)init + 1, p, lp); if (s2) { - lua_pushinteger(L, s2 - s + 1); - lua_pushinteger(L, s2 - s + lp); + lua_pushinteger(L, (s2 - s) + 1); + lua_pushinteger(L, (s2 - s) + lp); return 2; } } @@ -601,18 +631,13 @@ static int str_find_aux (lua_State *L, int find) { if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s + ls; - ms.p_end = p + lp; + prepstate(&ms, L, s, ls, p, lp); do { const char *res; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); + reprepstate(&ms); if ((res=match(&ms, s1, p)) != NULL) { if (find) { - lua_pushinteger(L, s1 - s + 1); /* start */ + lua_pushinteger(L, (s1 - s) + 1); /* start */ lua_pushinteger(L, res - s); /* end */ return push_captures(&ms, NULL, 0) + 2; } @@ -636,29 +661,25 @@ static int str_match (lua_State *L) { } +/* state for 'gmatch' */ +typedef struct GMatchState { + const char *src; /* current position */ + const char *p; /* pattern */ + const char *lastmatch; /* end of last match */ + MatchState ms; /* match state */ +} GMatchState; + + static int gmatch_aux (lua_State *L) { - MatchState ms; - size_t ls, lp; - const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); - const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp); + GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); const char *src; - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s+ls; - ms.p_end = p + lp; - for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); - src <= ms.src_end; - src++) { + gm->ms.L = L; + for (src = gm->src; src <= gm->ms.src_end; src++) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - if ((e = match(&ms, src, p)) != NULL) { - lua_Integer newstart = e-s; - if (e == src) newstart++; /* empty match? go at least one position */ - lua_pushinteger(L, newstart); - lua_replace(L, lua_upvalueindex(3)); - return push_captures(&ms, src, e); + reprepstate(&gm->ms); + if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) { + gm->src = gm->lastmatch = e; + return push_captures(&gm->ms, src, e); } } return 0; /* not found */ @@ -666,10 +687,14 @@ static int gmatch_aux (lua_State *L) { static int gmatch (lua_State *L) { - luaL_checkstring(L, 1); - luaL_checkstring(L, 2); - lua_settop(L, 2); - lua_pushinteger(L, 0); + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + GMatchState *gm; + lua_settop(L, 2); /* keep them on closure to avoid being collected */ + gm = (GMatchState *)lua_newuserdata(L, sizeof(GMatchState)); + prepstate(&gm->ms, L, s, ls, p, lp); + gm->src = s; gm->p = p; gm->lastmatch = NULL; lua_pushcclosure(L, gmatch_aux, 3); return 1; } @@ -678,7 +703,8 @@ static int gmatch (lua_State *L) { static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { size_t l, i; - const char *news = lua_tolstring(ms->L, 3, &l); + lua_State *L = ms->L; + const char *news = lua_tolstring(L, 3, &l); for (i = 0; i < l; i++) { if (news[i] != L_ESC) luaL_addchar(b, news[i]); @@ -686,14 +712,15 @@ static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, i++; /* skip ESC */ if (!isdigit(uchar(news[i]))) { if (news[i] != L_ESC) - luaL_error(ms->L, "invalid use of " LUA_QL("%c") - " in replacement string", L_ESC); + luaL_error(L, "invalid use of '%c' in replacement string", L_ESC); luaL_addchar(b, news[i]); } else if (news[i] == '0') luaL_addlstring(b, s, e - s); else { push_onecapture(ms, news[i] - '1', s, e); + luaL_tolstring(L, -1, NULL); /* if number, convert it to string */ + lua_remove(L, -2); /* remove original value */ luaL_addvalue(b); /* add capture to accumulated result */ } } @@ -734,12 +761,13 @@ static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, static int str_gsub (lua_State *L) { size_t srcl, lp; - const char *src = luaL_checklstring(L, 1, &srcl); - const char *p = luaL_checklstring(L, 2, &lp); - int tr = lua_type(L, 3); - size_t max_s = luaL_optinteger(L, 4, srcl+1); + const char *src = luaL_checklstring(L, 1, &srcl); /* subject */ + const char *p = luaL_checklstring(L, 2, &lp); /* pattern */ + const char *lastmatch = NULL; /* end of last match */ + int tr = lua_type(L, 3); /* replacement type */ + lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */ int anchor = (*p == '^'); - size_t n = 0; + lua_Integer n = 0; /* replacement count */ MatchState ms; luaL_Buffer b; luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || @@ -749,25 +777,18 @@ static int str_gsub (lua_State *L) { if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = src; - ms.src_end = src+srcl; - ms.p_end = p + lp; + prepstate(&ms, L, src, srcl, p, lp); while (n < max_s) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - e = match(&ms, src, p); - if (e) { + reprepstate(&ms); /* (re)prepare state for new match */ + if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */ n++; - add_value(&ms, &b, src, e, tr); + add_value(&ms, &b, src, e, tr); /* add replacement to buffer */ + src = lastmatch = e; } - if (e && e>src) /* non empty match? */ - src = e; /* skip it */ - else if (src < ms.src_end) + else if (src < ms.src_end) /* otherwise, skip one character */ luaL_addchar(&b, *src++); - else break; + else break; /* end of subject */ if (anchor) break; } luaL_addlstring(&b, src, ms.src_end-src); @@ -786,65 +807,116 @@ static int str_gsub (lua_State *L) { ** ======================================================= */ +#if !defined(lua_number2strx) /* { */ + /* -** LUA_INTFRMLEN is the length modifier for integer conversions in -** 'string.format'; LUA_INTFRM_T is the integer type corresponding to -** the previous length +** Hexadecimal floating-point formatter */ -#if !defined(LUA_INTFRMLEN) /* { */ -#if defined(LUA_USE_LONGLONG) -#define LUA_INTFRMLEN "ll" -#define LUA_INTFRM_T long long +#include -#else +#define SIZELENMOD (sizeof(LUA_NUMBER_FRMLEN)/sizeof(char)) -#define LUA_INTFRMLEN "l" -#define LUA_INTFRM_T long -#endif -#endif /* } */ +/* +** Number of bits that goes into the first digit. It can be any value +** between 1 and 4; the following definition tries to align the number +** to nibble boundaries by making what is left after that first digit a +** multiple of 4. +*/ +#define L_NBFD ((l_mathlim(MANT_DIG) - 1)%4 + 1) /* -** LUA_FLTFRMLEN is the length modifier for float conversions in -** 'string.format'; LUA_FLTFRM_T is the float type corresponding to -** the previous length +** Add integer part of 'x' to buffer and return new 'x' */ -#if !defined(LUA_FLTFRMLEN) +static lua_Number adddigit (char *buff, int n, lua_Number x) { + lua_Number dd = l_mathop(floor)(x); /* get integer part from 'x' */ + int d = (int)dd; + buff[n] = (d < 10 ? d + '0' : d - 10 + 'a'); /* add to buffer */ + return x - dd; /* return what is left */ +} -#define LUA_FLTFRMLEN "" -#define LUA_FLTFRM_T double -#endif +static int num2straux (char *buff, int sz, lua_Number x) { + if (x != x || x == HUGE_VAL || x == -HUGE_VAL) /* inf or NaN? */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT, x); /* equal to '%g' */ + else if (x == 0) { /* can be -0... */ + /* create "0" or "-0" followed by exponent */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT "x0p+0", x); + } + else { + int e; + lua_Number m = l_mathop(frexp)(x, &e); /* 'x' fraction and exponent */ + int n = 0; /* character count */ + if (m < 0) { /* is number negative? */ + buff[n++] = '-'; /* add signal */ + m = -m; /* make it positive */ + } + buff[n++] = '0'; buff[n++] = 'x'; /* add "0x" */ + m = adddigit(buff, n++, m * (1 << L_NBFD)); /* add first digit */ + e -= L_NBFD; /* this digit goes before the radix point */ + if (m > 0) { /* more digits? */ + buff[n++] = lua_getlocaledecpoint(); /* add radix point */ + do { /* add as many digits as needed */ + m = adddigit(buff, n++, m * 16); + } while (m > 0); + } + n += l_sprintf(buff + n, sz - n, "p%+d", e); /* add exponent */ + lua_assert(n < sz); + return n; + } +} + + +static int lua_number2strx (lua_State *L, char *buff, int sz, + const char *fmt, lua_Number x) { + int n = num2straux(buff, sz, x); + if (fmt[SIZELENMOD] == 'A') { + int i; + for (i = 0; i < n; i++) + buff[i] = toupper(uchar(buff[i])); + } + else if (fmt[SIZELENMOD] != 'a') + luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); + return n; +} + +#endif /* } */ + + +/* +** Maximum size of each formatted item. This maximum size is produced +** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.', +** and '\0') + number of decimal digits to represent maxfloat (which +** is maximum exponent + 1). (99+3+1 then rounded to 120 for "extra +** expenses", such as locale-dependent stuff) +*/ +#define MAX_ITEM (120 + l_mathlim(MAX_10_EXP)) -/* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ -#define MAX_ITEM 512 /* valid flags in a format specification */ #define FLAGS "-+ #0" + /* -** maximum size of each format specification (such as '%-099.99d') -** (+10 accounts for %99.99x plus margin of error) +** maximum size of each format specification (such as "%-099.99d") */ -#define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) +#define MAX_FORMAT 32 -static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { - size_t l; - const char *s = luaL_checklstring(L, arg, &l); +static void addquoted (luaL_Buffer *b, const char *s, size_t len) { luaL_addchar(b, '"'); - while (l--) { + while (len--) { if (*s == '"' || *s == '\\' || *s == '\n') { luaL_addchar(b, '\\'); luaL_addchar(b, *s); } - else if (*s == '\0' || iscntrl(uchar(*s))) { + else if (iscntrl(uchar(*s))) { char buff[10]; if (!isdigit(uchar(*(s+1)))) - sprintf(buff, "\\%d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%d", (int)uchar(*s)); else - sprintf(buff, "\\%03d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%03d", (int)uchar(*s)); luaL_addstring(b, buff); } else @@ -854,6 +926,57 @@ static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { luaL_addchar(b, '"'); } + +/* +** Ensures the 'buff' string uses a dot as the radix character. +*/ +static void checkdp (char *buff, int nb) { + if (memchr(buff, '.', nb) == NULL) { /* no dot? */ + char point = lua_getlocaledecpoint(); /* try locale point */ + char *ppoint = memchr(buff, point, nb); + if (ppoint) *ppoint = '.'; /* change it to a dot */ + } +} + + +static void addliteral (lua_State *L, luaL_Buffer *b, int arg) { + switch (lua_type(L, arg)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(L, arg, &len); + addquoted(b, s, len); + break; + } + case LUA_TNUMBER: { + char *buff = luaL_prepbuffsize(b, MAX_ITEM); + int nb; + if (!lua_isinteger(L, arg)) { /* float? */ + lua_Number n = lua_tonumber(L, arg); /* write as hexa ('%a') */ + nb = lua_number2strx(L, buff, MAX_ITEM, "%" LUA_NUMBER_FRMLEN "a", n); + checkdp(buff, nb); /* ensure it uses a dot */ + } + else { /* integers */ + lua_Integer n = lua_tointeger(L, arg); + const char *format = (n == LUA_MININTEGER) /* corner case? */ + ? "0x%" LUA_INTEGER_FRMLEN "x" /* use hexa */ + : LUA_INTEGER_FMT; /* else use default format */ + nb = l_sprintf(buff, MAX_ITEM, format, n); + } + luaL_addsize(b, nb); + break; + } + case LUA_TNIL: case LUA_TBOOLEAN: { + luaL_tolstring(L, arg, NULL); + luaL_addvalue(b); + break; + } + default: { + luaL_argerror(L, arg, "value has no literal form"); + } + } +} + + static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ @@ -869,8 +992,8 @@ static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); *(form++) = '%'; - memcpy(form, strfrmt, (p - strfrmt + 1) * sizeof(char)); - form += p - strfrmt + 1; + memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); + form += (p - strfrmt) + 1; *form = '\0'; return p; } @@ -903,7 +1026,7 @@ static int str_format (lua_State *L) { else if (*++strfrmt == L_ESC) luaL_addchar(&b, *strfrmt++); /* %% */ else { /* format item */ - char form[MAX_FORMAT]; /* to store the format (`%...') */ + char form[MAX_FORMAT]; /* to store the format ('%...') */ char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */ int nb = 0; /* number of bytes in added item */ if (++arg > top) @@ -911,62 +1034,55 @@ static int str_format (lua_State *L) { strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { - nb = sprintf(buff, form, luaL_checkint(L, arg)); - break; - } - case 'd': case 'i': { - lua_Number n = luaL_checknumber(L, arg); - LUA_INTFRM_T ni = (LUA_INTFRM_T)n; - lua_Number diff = n - (lua_Number)ni; - luaL_argcheck(L, -1 < diff && diff < 1, arg, - "not a number in proper range"); - addlenmod(form, LUA_INTFRMLEN); - nb = sprintf(buff, form, ni); + nb = l_sprintf(buff, MAX_ITEM, form, (int)luaL_checkinteger(L, arg)); break; } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { - lua_Number n = luaL_checknumber(L, arg); - unsigned LUA_INTFRM_T ni = (unsigned LUA_INTFRM_T)n; - lua_Number diff = n - (lua_Number)ni; - luaL_argcheck(L, -1 < diff && diff < 1, arg, - "not a non-negative number in proper range"); - addlenmod(form, LUA_INTFRMLEN); - nb = sprintf(buff, form, ni); + lua_Integer n = luaL_checkinteger(L, arg); + addlenmod(form, LUA_INTEGER_FRMLEN); + nb = l_sprintf(buff, MAX_ITEM, form, n); break; } - case 'e': case 'E': case 'f': -#if defined(LUA_USE_AFORMAT) case 'a': case 'A': -#endif + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = lua_number2strx(L, buff, MAX_ITEM, form, + luaL_checknumber(L, arg)); + break; + case 'e': case 'E': case 'f': case 'g': case 'G': { - addlenmod(form, LUA_FLTFRMLEN); - nb = sprintf(buff, form, (LUA_FLTFRM_T)luaL_checknumber(L, arg)); + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = l_sprintf(buff, MAX_ITEM, form, luaL_checknumber(L, arg)); break; } case 'q': { - addquoted(L, &b, arg); + addliteral(L, &b, arg); break; } case 's': { size_t l; const char *s = luaL_tolstring(L, arg, &l); - if (!strchr(form, '.') && l >= 100) { - /* no precision and string is too long to be formatted; - keep original string */ - luaL_addvalue(&b); - break; - } + if (form[2] == '\0') /* no modifiers? */ + luaL_addvalue(&b); /* keep entire string */ else { - nb = sprintf(buff, form, s); - lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ - break; + luaL_argcheck(L, l == strlen(s), arg, "string contains zeros"); + if (!strchr(form, '.') && l >= 100) { + /* no precision and string is too long to be formatted */ + luaL_addvalue(&b); /* keep entire string */ + } + else { /* format the string into 'buff' */ + nb = l_sprintf(buff, MAX_ITEM, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + } } + break; } - default: { /* also treat cases `pnLlh' */ - return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " - LUA_QL("format"), *(strfrmt - 1)); + default: { /* also treat cases 'pnLlh' */ + return luaL_error(L, "invalid option '%%%c' to 'format'", + *(strfrmt - 1)); } } + lua_assert(nb < MAX_ITEM); luaL_addsize(&b, nb); } } @@ -977,6 +1093,450 @@ static int str_format (lua_State *L) { /* }====================================================== */ +/* +** {====================================================== +** PACK/UNPACK +** ======================================================= +*/ + + +/* value used for padding */ +#if !defined(LUAL_PACKPADBYTE) +#define LUAL_PACKPADBYTE 0x00 +#endif + +/* maximum size for the binary representation of an integer */ +#define MAXINTSIZE 16 + +/* number of bits in a character */ +#define NB CHAR_BIT + +/* mask for one character (NB 1's) */ +#define MC ((1 << NB) - 1) + +/* size of a lua_Integer */ +#define SZINT ((int)sizeof(lua_Integer)) + + +/* dummy union to get native endianness */ +static const union { + int dummy; + char little; /* true iff machine is little endian */ +} nativeendian = {1}; + + +/* dummy structure to get native alignment requirements */ +struct cD { + char c; + union { double d; void *p; lua_Integer i; lua_Number n; } u; +}; + +#define MAXALIGN (offsetof(struct cD, u)) + + +/* +** Union for serializing floats +*/ +typedef union Ftypes { + float f; + double d; + lua_Number n; + char buff[5 * sizeof(lua_Number)]; /* enough for any float type */ +} Ftypes; + + +/* +** information to pack/unpack stuff +*/ +typedef struct Header { + lua_State *L; + int islittle; + int maxalign; +} Header; + + +/* +** options for pack/unpack +*/ +typedef enum KOption { + Kint, /* signed integers */ + Kuint, /* unsigned integers */ + Kfloat, /* floating-point numbers */ + Kchar, /* fixed-length strings */ + Kstring, /* strings with prefixed length */ + Kzstr, /* zero-terminated strings */ + Kpadding, /* padding */ + Kpaddalign, /* padding for alignment */ + Knop /* no-op (configuration or spaces) */ +} KOption; + + +/* +** Read an integer numeral from string 'fmt' or return 'df' if +** there is no numeral +*/ +static int digit (int c) { return '0' <= c && c <= '9'; } + +static int getnum (const char **fmt, int df) { + if (!digit(**fmt)) /* no number? */ + return df; /* return default value */ + else { + int a = 0; + do { + a = a*10 + (*((*fmt)++) - '0'); + } while (digit(**fmt) && a <= ((int)MAXSIZE - 9)/10); + return a; + } +} + + +/* +** Read an integer numeral and raises an error if it is larger +** than the maximum size for integers. +*/ +static int getnumlimit (Header *h, const char **fmt, int df) { + int sz = getnum(fmt, df); + if (sz > MAXINTSIZE || sz <= 0) + luaL_error(h->L, "integral size (%d) out of limits [1,%d]", + sz, MAXINTSIZE); + return sz; +} + + +/* +** Initialize Header +*/ +static void initheader (lua_State *L, Header *h) { + h->L = L; + h->islittle = nativeendian.little; + h->maxalign = 1; +} + + +/* +** Read and classify next option. 'size' is filled with option's size. +*/ +static KOption getoption (Header *h, const char **fmt, int *size) { + int opt = *((*fmt)++); + *size = 0; /* default */ + switch (opt) { + case 'b': *size = sizeof(char); return Kint; + case 'B': *size = sizeof(char); return Kuint; + case 'h': *size = sizeof(short); return Kint; + case 'H': *size = sizeof(short); return Kuint; + case 'l': *size = sizeof(long); return Kint; + case 'L': *size = sizeof(long); return Kuint; + case 'j': *size = sizeof(lua_Integer); return Kint; + case 'J': *size = sizeof(lua_Integer); return Kuint; + case 'T': *size = sizeof(size_t); return Kuint; + case 'f': *size = sizeof(float); return Kfloat; + case 'd': *size = sizeof(double); return Kfloat; + case 'n': *size = sizeof(lua_Number); return Kfloat; + case 'i': *size = getnumlimit(h, fmt, sizeof(int)); return Kint; + case 'I': *size = getnumlimit(h, fmt, sizeof(int)); return Kuint; + case 's': *size = getnumlimit(h, fmt, sizeof(size_t)); return Kstring; + case 'c': + *size = getnum(fmt, -1); + if (*size == -1) + luaL_error(h->L, "missing size for format option 'c'"); + return Kchar; + case 'z': return Kzstr; + case 'x': *size = 1; return Kpadding; + case 'X': return Kpaddalign; + case ' ': break; + case '<': h->islittle = 1; break; + case '>': h->islittle = 0; break; + case '=': h->islittle = nativeendian.little; break; + case '!': h->maxalign = getnumlimit(h, fmt, MAXALIGN); break; + default: luaL_error(h->L, "invalid format option '%c'", opt); + } + return Knop; +} + + +/* +** Read, classify, and fill other details about the next option. +** 'psize' is filled with option's size, 'notoalign' with its +** alignment requirements. +** Local variable 'size' gets the size to be aligned. (Kpadal option +** always gets its full alignment, other options are limited by +** the maximum alignment ('maxalign'). Kchar option needs no alignment +** despite its size. +*/ +static KOption getdetails (Header *h, size_t totalsize, + const char **fmt, int *psize, int *ntoalign) { + KOption opt = getoption(h, fmt, psize); + int align = *psize; /* usually, alignment follows size */ + if (opt == Kpaddalign) { /* 'X' gets alignment from following option */ + if (**fmt == '\0' || getoption(h, fmt, &align) == Kchar || align == 0) + luaL_argerror(h->L, 1, "invalid next option for option 'X'"); + } + if (align <= 1 || opt == Kchar) /* need no alignment? */ + *ntoalign = 0; + else { + if (align > h->maxalign) /* enforce maximum alignment */ + align = h->maxalign; + if ((align & (align - 1)) != 0) /* is 'align' not a power of 2? */ + luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); + *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1); + } + return opt; +} + + +/* +** Pack integer 'n' with 'size' bytes and 'islittle' endianness. +** The final 'if' handles the case when 'size' is larger than +** the size of a Lua integer, correcting the extra sign-extension +** bytes if necessary (by default they would be zeros). +*/ +static void packint (luaL_Buffer *b, lua_Unsigned n, + int islittle, int size, int neg) { + char *buff = luaL_prepbuffsize(b, size); + int i; + buff[islittle ? 0 : size - 1] = (char)(n & MC); /* first byte */ + for (i = 1; i < size; i++) { + n >>= NB; + buff[islittle ? i : size - 1 - i] = (char)(n & MC); + } + if (neg && size > SZINT) { /* negative number need sign extension? */ + for (i = SZINT; i < size; i++) /* correct extra bytes */ + buff[islittle ? i : size - 1 - i] = (char)MC; + } + luaL_addsize(b, size); /* add result to buffer */ +} + + +/* +** Copy 'size' bytes from 'src' to 'dest', correcting endianness if +** given 'islittle' is different from native endianness. +*/ +static void copywithendian (volatile char *dest, volatile const char *src, + int size, int islittle) { + if (islittle == nativeendian.little) { + while (size-- != 0) + *(dest++) = *(src++); + } + else { + dest += size - 1; + while (size-- != 0) + *(dest--) = *(src++); + } +} + + +static int str_pack (lua_State *L) { + luaL_Buffer b; + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + int arg = 1; /* current argument to pack */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + lua_pushnil(L); /* mark to separate arguments from string buffer */ + luaL_buffinit(L, &b); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + totalsize += ntoalign + size; + while (ntoalign-- > 0) + luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ + arg++; + switch (opt) { + case Kint: { /* signed integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) { /* need overflow check? */ + lua_Integer lim = (lua_Integer)1 << ((size * NB) - 1); + luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow"); + } + packint(&b, (lua_Unsigned)n, h.islittle, size, (n < 0)); + break; + } + case Kuint: { /* unsigned integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) /* need overflow check? */ + luaL_argcheck(L, (lua_Unsigned)n < ((lua_Unsigned)1 << (size * NB)), + arg, "unsigned overflow"); + packint(&b, (lua_Unsigned)n, h.islittle, size, 0); + break; + } + case Kfloat: { /* floating-point options */ + volatile Ftypes u; + char *buff = luaL_prepbuffsize(&b, size); + lua_Number n = luaL_checknumber(L, arg); /* get argument */ + if (size == sizeof(u.f)) u.f = (float)n; /* copy it into 'u' */ + else if (size == sizeof(u.d)) u.d = (double)n; + else u.n = n; + /* move 'u' to final result, correcting endianness if needed */ + copywithendian(buff, u.buff, size, h.islittle); + luaL_addsize(&b, size); + break; + } + case Kchar: { /* fixed-size string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, len <= (size_t)size, arg, + "string longer than given size"); + luaL_addlstring(&b, s, len); /* add string */ + while (len++ < (size_t)size) /* pad extra space */ + luaL_addchar(&b, LUAL_PACKPADBYTE); + break; + } + case Kstring: { /* strings with length count */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, size >= (int)sizeof(size_t) || + len < ((size_t)1 << (size * NB)), + arg, "string length does not fit in given size"); + packint(&b, (lua_Unsigned)len, h.islittle, size, 0); /* pack length */ + luaL_addlstring(&b, s, len); + totalsize += len; + break; + } + case Kzstr: { /* zero-terminated string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros"); + luaL_addlstring(&b, s, len); + luaL_addchar(&b, '\0'); /* add zero at the end */ + totalsize += len + 1; + break; + } + case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ + case Kpaddalign: case Knop: + arg--; /* undo increment */ + break; + } + } + luaL_pushresult(&b); + return 1; +} + + +static int str_packsize (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + size += ntoalign; /* total space used by option */ + luaL_argcheck(L, totalsize <= MAXSIZE - size, 1, + "format result too large"); + totalsize += size; + switch (opt) { + case Kstring: /* strings with length count */ + case Kzstr: /* zero-terminated string */ + luaL_argerror(L, 1, "variable-length format"); + /* call never return, but to avoid warnings: *//* FALLTHROUGH */ + default: break; + } + } + lua_pushinteger(L, (lua_Integer)totalsize); + return 1; +} + + +/* +** Unpack an integer with 'size' bytes and 'islittle' endianness. +** If size is smaller than the size of a Lua integer and integer +** is signed, must do sign extension (propagating the sign to the +** higher bits); if size is larger than the size of a Lua integer, +** it must check the unread bytes to see whether they do not cause an +** overflow. +*/ +static lua_Integer unpackint (lua_State *L, const char *str, + int islittle, int size, int issigned) { + lua_Unsigned res = 0; + int i; + int limit = (size <= SZINT) ? size : SZINT; + for (i = limit - 1; i >= 0; i--) { + res <<= NB; + res |= (lua_Unsigned)(unsigned char)str[islittle ? i : size - 1 - i]; + } + if (size < SZINT) { /* real size smaller than lua_Integer? */ + if (issigned) { /* needs sign extension? */ + lua_Unsigned mask = (lua_Unsigned)1 << (size*NB - 1); + res = ((res ^ mask) - mask); /* do sign extension */ + } + } + else if (size > SZINT) { /* must check unread bytes */ + int mask = (!issigned || (lua_Integer)res >= 0) ? 0 : MC; + for (i = limit; i < size; i++) { + if ((unsigned char)str[islittle ? i : size - 1 - i] != mask) + luaL_error(L, "%d-byte integer does not fit into Lua Integer", size); + } + } + return (lua_Integer)res; +} + + +static int str_unpack (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); + size_t ld; + const char *data = luaL_checklstring(L, 2, &ld); + size_t pos = (size_t)posrelat(luaL_optinteger(L, 3, 1), ld) - 1; + int n = 0; /* number of results */ + luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign); + if ((size_t)ntoalign + size > ~pos || pos + ntoalign + size > ld) + luaL_argerror(L, 2, "data string too short"); + pos += ntoalign; /* skip alignment */ + /* stack space for item + next position */ + luaL_checkstack(L, 2, "too many results"); + n++; + switch (opt) { + case Kint: + case Kuint: { + lua_Integer res = unpackint(L, data + pos, h.islittle, size, + (opt == Kint)); + lua_pushinteger(L, res); + break; + } + case Kfloat: { + volatile Ftypes u; + lua_Number num; + copywithendian(u.buff, data + pos, size, h.islittle); + if (size == sizeof(u.f)) num = (lua_Number)u.f; + else if (size == sizeof(u.d)) num = (lua_Number)u.d; + else num = u.n; + lua_pushnumber(L, num); + break; + } + case Kchar: { + lua_pushlstring(L, data + pos, size); + break; + } + case Kstring: { + size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0); + luaL_argcheck(L, pos + len + size <= ld, 2, "data string too short"); + lua_pushlstring(L, data + pos + size, len); + pos += len; /* skip string */ + break; + } + case Kzstr: { + size_t len = (int)strlen(data + pos); + lua_pushlstring(L, data + pos, len); + pos += len + 1; /* skip string plus final '\0' */ + break; + } + case Kpaddalign: case Kpadding: case Knop: + n--; /* undo increment */ + break; + } + pos += size; + } + lua_pushinteger(L, pos + 1); /* next position */ + return n + 1; +} + +/* }====================================================== */ + + static const luaL_Reg strlib[] = { {"byte", str_byte}, {"char", str_char}, @@ -992,6 +1552,9 @@ static const luaL_Reg strlib[] = { {"reverse", str_reverse}, {"sub", str_sub}, {"upper", str_upper}, + {"pack", str_pack}, + {"packsize", str_packsize}, + {"unpack", str_unpack}, {NULL, NULL} }; diff --git a/depends/lua/src/ltable.c b/depends/lua/src/ltable.c index 5d76f97ec..7e15b71ba 100644 --- a/depends/lua/src/ltable.c +++ b/depends/lua/src/ltable.c @@ -1,27 +1,30 @@ /* -** $Id: ltable.c,v 2.72.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltable.c,v 2.117 2015/11/19 19:16:22 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ +#define ltable_c +#define LUA_CORE + +#include "lprefix.h" + /* ** Implementation of tables (aka arrays, objects, or hash tables). ** Tables keep its elements in two parts: an array part and a hash part. ** Non-negative integer keys are all candidates to be kept in the array -** part. The actual size of the array is the largest `n' such that at -** least half the slots between 0 and n are in use. +** part. The actual size of the array is the largest 'n' such that +** more than half the slots between 1 and n are in use. ** Hash uses a mix of chained scatter table with Brent's variation. ** A main invariant of these tables is that, if an element is not -** in its main position (i.e. the `original' position that its hash gives +** in its main position (i.e. the 'original' position that its hash gives ** to it), then the colliding element is in its own main position. ** Hence even when the load factor reaches 100%, performance remains good. */ -#include - -#define ltable_c -#define LUA_CORE +#include +#include #include "lua.h" @@ -37,21 +40,26 @@ /* -** max size of array part is 2^MAXBITS +** Maximum size of array part (MAXASIZE) is 2^MAXABITS. MAXABITS is +** the largest integer such that MAXASIZE fits in an unsigned int. */ -#if LUAI_BITSINT >= 32 -#define MAXBITS 30 -#else -#define MAXBITS (LUAI_BITSINT-2) -#endif +#define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1) +#define MAXASIZE (1u << MAXABITS) -#define MAXASIZE (1 << MAXBITS) +/* +** Maximum size of hash part is 2^MAXHBITS. MAXHBITS is the largest +** integer such that 2^MAXHBITS fits in a signed int. (Note that the +** maximum number of elements in a table, 2^MAXABITS + 2^MAXHBITS, still +** fits comfortably in an unsigned int.) +*/ +#define MAXHBITS (MAXABITS - 1) #define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) -#define hashstr(t,str) hashpow2(t, (str)->tsv.hash) +#define hashstr(t,str) hashpow2(t, (str)->hash) #define hashboolean(t,p) hashpow2(t, p) +#define hashint(t,i) hashpow2(t, i) /* @@ -61,7 +69,7 @@ #define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) -#define hashpointer(t,p) hashmod(t, IntPoint(p)) +#define hashpointer(t,p) hashmod(t, point2uint(p)) #define dummynode (&dummynode_) @@ -70,44 +78,54 @@ static const Node dummynode_ = { {NILCONSTANT}, /* value */ - {{NILCONSTANT, NULL}} /* key */ + {{NILCONSTANT, 0}} /* key */ }; /* -** hash for lua_Numbers +** Hash for floating-point numbers. +** The main computation should be just +** n = frexp(n, &i); return (n * INT_MAX) + i +** but there are some numerical subtleties. +** In a two-complement representation, INT_MAX does not has an exact +** representation as a float, but INT_MIN does; because the absolute +** value of 'frexp' is smaller than 1 (unless 'n' is inf/NaN), the +** absolute value of the product 'frexp * -INT_MIN' is smaller or equal +** to INT_MAX. Next, the use of 'unsigned int' avoids overflows when +** adding 'i'; the use of '~u' (instead of '-u') avoids problems with +** INT_MIN. */ -static Node *hashnum (const Table *t, lua_Number n) { +#if !defined(l_hashfloat) +static int l_hashfloat (lua_Number n) { int i; - luai_hashnum(i, n); - if (i < 0) { - if (cast(unsigned int, i) == 0u - i) /* use unsigned to avoid overflows */ - i = 0; /* handle INT_MIN */ - i = -i; /* must be a positive value */ + lua_Integer ni; + n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN); + if (!lua_numbertointeger(n, &ni)) { /* is 'n' inf/-inf/NaN? */ + lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL)); + return 0; + } + else { /* normal case */ + unsigned int u = cast(unsigned int, i) + cast(unsigned int, ni); + return cast_int(u <= cast(unsigned int, INT_MAX) ? u : ~u); } - return hashmod(t, i); } - +#endif /* -** returns the `main' position of an element in a table (that is, the index +** returns the 'main' position of an element in a table (that is, the index ** of its hash value) */ static Node *mainposition (const Table *t, const TValue *key) { switch (ttype(key)) { - case LUA_TNUMBER: - return hashnum(t, nvalue(key)); - case LUA_TLNGSTR: { - TString *s = rawtsvalue(key); - if (s->tsv.extra == 0) { /* no hash? */ - s->tsv.hash = luaS_hash(getstr(s), s->tsv.len, s->tsv.hash); - s->tsv.extra = 1; /* now it has its hash */ - } - return hashstr(t, rawtsvalue(key)); - } + case LUA_TNUMINT: + return hashint(t, ivalue(key)); + case LUA_TNUMFLT: + return hashmod(t, l_hashfloat(fltvalue(key))); case LUA_TSHRSTR: - return hashstr(t, rawtsvalue(key)); + return hashstr(t, tsvalue(key)); + case LUA_TLNGSTR: + return hashpow2(t, luaS_hashlongstr(tsvalue(key))); case LUA_TBOOLEAN: return hashboolean(t, bvalue(key)); case LUA_TLIGHTUSERDATA: @@ -115,67 +133,68 @@ static Node *mainposition (const Table *t, const TValue *key) { case LUA_TLCF: return hashpointer(t, fvalue(key)); default: + lua_assert(!ttisdeadkey(key)); return hashpointer(t, gcvalue(key)); } } /* -** returns the index for `key' if `key' is an appropriate key to live in -** the array part of the table, -1 otherwise. +** returns the index for 'key' if 'key' is an appropriate key to live in +** the array part of the table, 0 otherwise. */ -static int arrayindex (const TValue *key) { - if (ttisnumber(key)) { - lua_Number n = nvalue(key); - int k; - lua_number2int(k, n); - if (luai_numeq(cast_num(k), n)) - return k; +static unsigned int arrayindex (const TValue *key) { + if (ttisinteger(key)) { + lua_Integer k = ivalue(key); + if (0 < k && (lua_Unsigned)k <= MAXASIZE) + return cast(unsigned int, k); /* 'key' is an appropriate array index */ } - return -1; /* `key' did not match some condition */ + return 0; /* 'key' did not match some condition */ } /* -** returns the index of a `key' for table traversals. First goes all +** returns the index of a 'key' for table traversals. First goes all ** elements in the array part, then elements in the hash part. The -** beginning of a traversal is signaled by -1. +** beginning of a traversal is signaled by 0. */ -static int findindex (lua_State *L, Table *t, StkId key) { - int i; - if (ttisnil(key)) return -1; /* first iteration */ +static unsigned int findindex (lua_State *L, Table *t, StkId key) { + unsigned int i; + if (ttisnil(key)) return 0; /* first iteration */ i = arrayindex(key); - if (0 < i && i <= t->sizearray) /* is `key' inside array part? */ - return i-1; /* yes; that's the index (corrected to C) */ + if (i != 0 && i <= t->sizearray) /* is 'key' inside array part? */ + return i; /* yes; that's the index */ else { + int nx; Node *n = mainposition(t, key); - for (;;) { /* check whether `key' is somewhere in the chain */ - /* key may be dead already, but it is ok to use it in `next' */ + for (;;) { /* check whether 'key' is somewhere in the chain */ + /* key may be dead already, but it is ok to use it in 'next' */ if (luaV_rawequalobj(gkey(n), key) || (ttisdeadkey(gkey(n)) && iscollectable(key) && deadvalue(gkey(n)) == gcvalue(key))) { i = cast_int(n - gnode(t, 0)); /* key index in hash table */ /* hash elements are numbered after array ones */ - return i + t->sizearray; + return (i + 1) + t->sizearray; } - else n = gnext(n); - if (n == NULL) - luaG_runerror(L, "invalid key to " LUA_QL("next")); /* key not found */ + nx = gnext(n); + if (nx == 0) + luaG_runerror(L, "invalid key to 'next'"); /* key not found */ + else n += nx; } } } int luaH_next (lua_State *L, Table *t, StkId key) { - int i = findindex(L, t, key); /* find original element */ - for (i++; i < t->sizearray; i++) { /* try first array part */ + unsigned int i = findindex(L, t, key); /* find original element */ + for (; i < t->sizearray; i++) { /* try first array part */ if (!ttisnil(&t->array[i])) { /* a non-nil value? */ - setnvalue(key, cast_num(i+1)); + setivalue(key, i + 1); setobj2s(L, key+1, &t->array[i]); return 1; } } - for (i -= t->sizearray; i < sizenode(t); i++) { /* then hash part */ + for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) { /* hash part */ if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ setobj2s(L, key, gkey(gnode(t, i))); setobj2s(L, key+1, gval(gnode(t, i))); @@ -192,32 +211,38 @@ int luaH_next (lua_State *L, Table *t, StkId key) { ** ============================================================== */ - -static int computesizes (int nums[], int *narray) { +/* +** Compute the optimal size for the array part of table 't'. 'nums' is a +** "count array" where 'nums[i]' is the number of integers in the table +** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of +** integer keys in the table and leaves with the number of keys that +** will go to the array part; return the optimal size. +*/ +static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { int i; - int twotoi; /* 2^i */ - int a = 0; /* number of elements smaller than 2^i */ - int na = 0; /* number of elements to go to array part */ - int n = 0; /* optimal size for array part */ - for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { + unsigned int twotoi; /* 2^i (candidate for optimal size) */ + unsigned int a = 0; /* number of elements smaller than 2^i */ + unsigned int na = 0; /* number of elements to go to array part */ + unsigned int optimal = 0; /* optimal size for array part */ + /* loop while keys can fill more than half of total size */ + for (i = 0, twotoi = 1; *pna > twotoi / 2; i++, twotoi *= 2) { if (nums[i] > 0) { a += nums[i]; if (a > twotoi/2) { /* more than half elements present? */ - n = twotoi; /* optimal size (till now) */ - na = a; /* all elements smaller than n will go to array part */ + optimal = twotoi; /* optimal size (till now) */ + na = a; /* all elements up to 'optimal' will go to array part */ } } - if (a == *narray) break; /* all elements already counted */ } - *narray = n; - lua_assert(*narray/2 <= na && na <= *narray); - return na; + lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal); + *pna = na; + return optimal; } -static int countint (const TValue *key, int *nums) { - int k = arrayindex(key); - if (0 < k && k <= MAXASIZE) { /* is `key' an appropriate array index? */ +static int countint (const TValue *key, unsigned int *nums) { + unsigned int k = arrayindex(key); + if (k != 0) { /* is 'key' an appropriate array index? */ nums[luaO_ceillog2(k)]++; /* count as such */ return 1; } @@ -226,20 +251,26 @@ static int countint (const TValue *key, int *nums) { } -static int numusearray (const Table *t, int *nums) { +/* +** Count keys in array part of table 't': Fill 'nums[i]' with +** number of keys that will go into corresponding slice and return +** total number of non-nil keys. +*/ +static unsigned int numusearray (const Table *t, unsigned int *nums) { int lg; - int ttlg; /* 2^lg */ - int ause = 0; /* summation of `nums' */ - int i = 1; /* count to traverse all array keys */ - for (lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { /* for each slice */ - int lc = 0; /* counter */ - int lim = ttlg; + unsigned int ttlg; /* 2^lg */ + unsigned int ause = 0; /* summation of 'nums' */ + unsigned int i = 1; /* count to traverse all array keys */ + /* traverse each slice */ + for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) { + unsigned int lc = 0; /* counter */ + unsigned int lim = ttlg; if (lim > t->sizearray) { lim = t->sizearray; /* adjust upper limit */ if (i > lim) break; /* no more elements to count */ } - /* count elements in range (2^(lg-1), 2^lg] */ + /* count elements in range (2^(lg - 1), 2^lg] */ for (; i <= lim; i++) { if (!ttisnil(&t->array[i-1])) lc++; @@ -251,9 +282,9 @@ static int numusearray (const Table *t, int *nums) { } -static int numusehash (const Table *t, int *nums, int *pnasize) { +static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) { int totaluse = 0; /* total number of elements */ - int ause = 0; /* summation of `nums' */ + int ause = 0; /* elements added to 'nums' (can go to array part) */ int i = sizenode(t); while (i--) { Node *n = &t->node[i]; @@ -262,13 +293,13 @@ static int numusehash (const Table *t, int *nums, int *pnasize) { totaluse++; } } - *pnasize += ause; + *pna += ause; return totaluse; } -static void setarrayvector (lua_State *L, Table *t, int size) { - int i; +static void setarrayvector (lua_State *L, Table *t, unsigned int size) { + unsigned int i; luaM_reallocvector(L, t->array, t->sizearray, size, TValue); for (i=t->sizearray; iarray[i]); @@ -276,23 +307,23 @@ static void setarrayvector (lua_State *L, Table *t, int size) { } -static void setnodevector (lua_State *L, Table *t, int size) { +static void setnodevector (lua_State *L, Table *t, unsigned int size) { int lsize; if (size == 0) { /* no elements to hash part? */ - t->node = cast(Node *, dummynode); /* use common `dummynode' */ + t->node = cast(Node *, dummynode); /* use common 'dummynode' */ lsize = 0; } else { int i; lsize = luaO_ceillog2(size); - if (lsize > MAXBITS) + if (lsize > MAXHBITS) luaG_runerror(L, "table overflow"); size = twoto(lsize); t->node = luaM_newvector(L, size, Node); - for (i=0; isizearray; +void luaH_resize (lua_State *L, Table *t, unsigned int nasize, + unsigned int nhsize) { + unsigned int i; + int j; + unsigned int oldasize = t->sizearray; int oldhsize = t->lsizenode; Node *nold = t->node; /* save old hash ... */ if (nasize > oldasize) /* array part must grow? */ @@ -321,8 +354,8 @@ void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize) { luaM_reallocvector(L, t->array, oldasize, nasize, TValue); } /* re-insert elements from hash part */ - for (i = twoto(oldhsize) - 1; i >= 0; i--) { - Node *old = nold+i; + for (j = twoto(oldhsize) - 1; j >= 0; j--) { + Node *old = nold + j; if (!ttisnil(gval(old))) { /* doesn't need barrier/invalidate cache, as entry was already present in the table */ @@ -330,32 +363,35 @@ void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize) { } } if (!isdummy(nold)) - luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old array */ + luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old hash */ } -void luaH_resizearray (lua_State *L, Table *t, int nasize) { +void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) { int nsize = isdummy(t->node) ? 0 : sizenode(t); luaH_resize(L, t, nasize, nsize); } - +/* +** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i +*/ static void rehash (lua_State *L, Table *t, const TValue *ek) { - int nasize, na; - int nums[MAXBITS+1]; /* nums[i] = number of keys with 2^(i-1) < k <= 2^i */ + unsigned int asize; /* optimal size for array part */ + unsigned int na; /* number of keys in the array part */ + unsigned int nums[MAXABITS + 1]; int i; int totaluse; - for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */ - nasize = numusearray(t, nums); /* count keys in array part */ - totaluse = nasize; /* all those keys are integer keys */ - totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ + for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */ + na = numusearray(t, nums); /* count keys in array part */ + totaluse = na; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &na); /* count keys in hash part */ /* count extra key */ - nasize += countint(ek, nums); + na += countint(ek, nums); totaluse++; /* compute new size for array part */ - na = computesizes(nums, &nasize); + asize = computesizes(nums, &na); /* resize the table to new computed sizes */ - luaH_resize(L, t, nasize, totaluse - na); + luaH_resize(L, t, asize, totaluse - na); } @@ -366,7 +402,8 @@ static void rehash (lua_State *L, Table *t, const TValue *ek) { Table *luaH_new (lua_State *L) { - Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h; + GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table)); + Table *t = gco2t(o); t->metatable = NULL; t->flags = cast_byte(~0); t->array = NULL; @@ -404,37 +441,51 @@ static Node *getfreepos (Table *t) { */ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { Node *mp; + TValue aux; if (ttisnil(key)) luaG_runerror(L, "table index is nil"); - else if (ttisnumber(key) && luai_numisnan(L, nvalue(key))) - luaG_runerror(L, "table index is NaN"); + else if (ttisfloat(key)) { + lua_Integer k; + if (luaV_tointeger(key, &k, 0)) { /* index is int? */ + setivalue(&aux, k); + key = &aux; /* insert it as an integer */ + } + else if (luai_numisnan(fltvalue(key))) + luaG_runerror(L, "table index is NaN"); + } mp = mainposition(t, key); if (!ttisnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */ Node *othern; - Node *n = getfreepos(t); /* get a free place */ - if (n == NULL) { /* cannot find a free place? */ + Node *f = getfreepos(t); /* get a free place */ + if (f == NULL) { /* cannot find a free place? */ rehash(L, t, key); /* grow table */ - /* whatever called 'newkey' take care of TM cache and GC barrier */ + /* whatever called 'newkey' takes care of TM cache */ return luaH_set(L, t, key); /* insert key into grown table */ } - lua_assert(!isdummy(n)); + lua_assert(!isdummy(f)); othern = mainposition(t, gkey(mp)); if (othern != mp) { /* is colliding node out of its main position? */ /* yes; move colliding node into free position */ - while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ - gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ - *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ - gnext(mp) = NULL; /* now `mp' is free */ + while (othern + gnext(othern) != mp) /* find previous */ + othern += gnext(othern); + gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */ + *f = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + if (gnext(mp) != 0) { + gnext(f) += cast_int(mp - f); /* correct 'next' */ + gnext(mp) = 0; /* now 'mp' is free */ + } setnilvalue(gval(mp)); } else { /* colliding node is in its own main position */ /* new node will go into free position */ - gnext(n) = gnext(mp); /* chain new position */ - gnext(mp) = n; - mp = n; + if (gnext(mp) != 0) + gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */ + else lua_assert(gnext(f) == 0); + gnext(mp) = cast_int(f - mp); + mp = f; } } - setobj2t(L, gkey(mp), key); - luaC_barrierback(L, obj2gco(t), key); + setnodekey(L, &mp->i_key, key); + luaC_barrierback(L, t, key); lua_assert(ttisnil(gval(mp))); return gval(mp); } @@ -443,18 +494,21 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { /* ** search function for integers */ -const TValue *luaH_getint (Table *t, int key) { +const TValue *luaH_getint (Table *t, lua_Integer key) { /* (1 <= key && key <= t->sizearray) */ - if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) - return &t->array[key-1]; + if (l_castS2U(key) - 1 < t->sizearray) + return &t->array[key - 1]; else { - lua_Number nk = cast_num(key); - Node *n = hashnum(t, nk); - do { /* check whether `key' is somewhere in the chain */ - if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) + Node *n = hashint(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (ttisinteger(gkey(n)) && ivalue(gkey(n)) == key) return gval(n); /* that's it */ - else n = gnext(n); - } while (n); + else { + int nx = gnext(n); + if (nx == 0) break; + n += nx; + } + } return luaO_nilobject; } } @@ -463,15 +517,50 @@ const TValue *luaH_getint (Table *t, int key) { /* ** search function for short strings */ -const TValue *luaH_getstr (Table *t, TString *key) { +const TValue *luaH_getshortstr (Table *t, TString *key) { Node *n = hashstr(t, key); - lua_assert(key->tsv.tt == LUA_TSHRSTR); - do { /* check whether `key' is somewhere in the chain */ - if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), key)) + lua_assert(key->tt == LUA_TSHRSTR); + for (;;) { /* check whether 'key' is somewhere in the chain */ + const TValue *k = gkey(n); + if (ttisshrstring(k) && eqshrstr(tsvalue(k), key)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return luaO_nilobject; /* not found */ + n += nx; + } + } +} + + +/* +** "Generic" get version. (Not that generic: not valid for integers, +** which may be in array part, nor for floats with integral values.) +*/ +static const TValue *getgeneric (Table *t, const TValue *key) { + Node *n = mainposition(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (luaV_rawequalobj(gkey(n), key)) return gval(n); /* that's it */ - else n = gnext(n); - } while (n); - return luaO_nilobject; + else { + int nx = gnext(n); + if (nx == 0) + return luaO_nilobject; /* not found */ + n += nx; + } + } +} + + +const TValue *luaH_getstr (Table *t, TString *key) { + if (key->tt == LUA_TSHRSTR) + return luaH_getshortstr(t, key); + else { /* for long strings, use generic case */ + TValue ko; + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko); + } } @@ -480,25 +569,17 @@ const TValue *luaH_getstr (Table *t, TString *key) { */ const TValue *luaH_get (Table *t, const TValue *key) { switch (ttype(key)) { - case LUA_TSHRSTR: return luaH_getstr(t, rawtsvalue(key)); + case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key)); + case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); case LUA_TNIL: return luaO_nilobject; - case LUA_TNUMBER: { - int k; - lua_Number n = nvalue(key); - lua_number2int(k, n); - if (luai_numeq(cast_num(k), n)) /* index is int? */ + case LUA_TNUMFLT: { + lua_Integer k; + if (luaV_tointeger(key, &k, 0)) /* index is int? */ return luaH_getint(t, k); /* use specialized version */ - /* else go through */ - } - default: { - Node *n = mainposition(t, key); - do { /* check whether `key' is somewhere in the chain */ - if (luaV_rawequalobj(gkey(n), key)) - return gval(n); /* that's it */ - else n = gnext(n); - } while (n); - return luaO_nilobject; - } + /* else... */ + } /* FALLTHROUGH */ + default: + return getgeneric(t, key); } } @@ -515,14 +596,14 @@ TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { } -void luaH_setint (lua_State *L, Table *t, int key, TValue *value) { +void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { const TValue *p = luaH_getint(t, key); TValue *cell; if (p != luaO_nilobject) cell = cast(TValue *, p); else { TValue k; - setnvalue(&k, cast_num(key)); + setivalue(&k, key); cell = luaH_newkey(L, t, &k); } setobj2t(L, cell, value); @@ -532,16 +613,16 @@ void luaH_setint (lua_State *L, Table *t, int key, TValue *value) { static int unbound_search (Table *t, unsigned int j) { unsigned int i = j; /* i is zero or a present index */ j++; - /* find `i' and `j' such that i is present and j is not */ + /* find 'i' and 'j' such that i is present and j is not */ while (!ttisnil(luaH_getint(t, j))) { i = j; - j *= 2; - if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ + if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */ /* table was built with bad purposes: resort to linear search */ i = 1; while (!ttisnil(luaH_getint(t, i))) i++; return i - 1; } + j *= 2; } /* now do a binary search between them */ while (j - i > 1) { @@ -554,7 +635,7 @@ static int unbound_search (Table *t, unsigned int j) { /* -** Try to find a boundary in table `t'. A `boundary' is an integer index +** Try to find a boundary in table 't'. A 'boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ int luaH_getn (Table *t) { diff --git a/depends/lua/src/ltablib.c b/depends/lua/src/ltablib.c index 6001224e3..98b2f8713 100644 --- a/depends/lua/src/ltablib.c +++ b/depends/lua/src/ltablib.c @@ -1,23 +1,61 @@ /* -** $Id: ltablib.c,v 1.65.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltablib.c,v 1.93 2016/02/25 19:41:54 roberto Exp $ ** Library for Table Manipulation ** See Copyright Notice in lua.h */ - -#include - #define ltablib_c #define LUA_LIB +#include "lprefix.h" + + +#include +#include +#include + #include "lua.h" #include "lauxlib.h" #include "lualib.h" -#define aux_getn(L,n) (luaL_checktype(L, n, LUA_TTABLE), luaL_len(L, n)) +/* +** Operations that an object must define to mimic a table +** (some functions only need some of them) +*/ +#define TAB_R 1 /* read */ +#define TAB_W 2 /* write */ +#define TAB_L 4 /* length */ +#define TAB_RW (TAB_R | TAB_W) /* read/write */ + + +#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) + +static int checkfield (lua_State *L, const char *key, int n) { + lua_pushstring(L, key); + return (lua_rawget(L, -n) != LUA_TNIL); +} + + +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +static void checktab (lua_State *L, int arg, int what) { + if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int n = 1; /* number of elements to pop */ + if (lua_getmetatable(L, arg) && /* must have metatable */ + (!(what & TAB_R) || checkfield(L, "__index", ++n)) && + (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && + (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + lua_pop(L, n); /* pop metatable and tested metamethods */ + } + else + luaL_checktype(L, arg, LUA_TTABLE); /* force an error */ + } +} #if defined(LUA_COMPAT_MAXN) @@ -39,65 +77,102 @@ static int maxn (lua_State *L) { static int tinsert (lua_State *L) { - int e = aux_getn(L, 1) + 1; /* first empty element */ - int pos; /* where to insert new element */ + lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */ + lua_Integer pos; /* where to insert new element */ switch (lua_gettop(L)) { case 2: { /* called with only 2 arguments */ pos = e; /* insert new element at the end */ break; } case 3: { - int i; - pos = luaL_checkint(L, 2); /* 2nd argument is the position */ + lua_Integer i; + pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); for (i = e; i > pos; i--) { /* move up elements */ - lua_rawgeti(L, 1, i-1); - lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ + lua_geti(L, 1, i - 1); + lua_seti(L, 1, i); /* t[i] = t[i - 1] */ } break; } default: { - return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); + return luaL_error(L, "wrong number of arguments to 'insert'"); } } - lua_rawseti(L, 1, pos); /* t[pos] = v */ + lua_seti(L, 1, pos); /* t[pos] = v */ return 0; } static int tremove (lua_State *L) { - int size = aux_getn(L, 1); - int pos = luaL_optint(L, 2, size); + lua_Integer size = aux_getn(L, 1, TAB_RW); + lua_Integer pos = luaL_optinteger(L, 2, size); if (pos != size) /* validate 'pos' if given */ luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); - lua_rawgeti(L, 1, pos); /* result = t[pos] */ + lua_geti(L, 1, pos); /* result = t[pos] */ for ( ; pos < size; pos++) { - lua_rawgeti(L, 1, pos+1); - lua_rawseti(L, 1, pos); /* t[pos] = t[pos+1] */ + lua_geti(L, 1, pos + 1); + lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ } lua_pushnil(L); - lua_rawseti(L, 1, pos); /* t[pos] = nil */ + lua_seti(L, 1, pos); /* t[pos] = nil */ return 1; } -static void addfield (lua_State *L, luaL_Buffer *b, int i) { - lua_rawgeti(L, 1, i); +/* +** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever +** possible, copy in increasing order, which is better for rehashing. +** "possible" means destination after original range, or smaller +** than origin, or copying to another table. +*/ +static int tmove (lua_State *L) { + lua_Integer f = luaL_checkinteger(L, 2); + lua_Integer e = luaL_checkinteger(L, 3); + lua_Integer t = luaL_checkinteger(L, 4); + int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + checktab(L, 1, TAB_R); + checktab(L, tt, TAB_W); + if (e >= f) { /* otherwise, nothing to move */ + lua_Integer n, i; + luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, + "too many elements to move"); + n = e - f + 1; /* number of elements to move */ + luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, + "destination wrap around"); + if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) { + for (i = 0; i < n; i++) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + else { + for (i = n - 1; i >= 0; i--) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + } + lua_pushvalue(L, tt); /* return destination table */ + return 1; +} + + +static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) { + lua_geti(L, 1, i); if (!lua_isstring(L, -1)) - luaL_error(L, "invalid value (%s) at index %d in table for " - LUA_QL("concat"), luaL_typename(L, -1), i); + luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", + luaL_typename(L, -1), i); luaL_addvalue(b); } static int tconcat (lua_State *L) { luaL_Buffer b; + lua_Integer last = aux_getn(L, 1, TAB_R); size_t lsep; - int i, last; const char *sep = luaL_optlstring(L, 2, "", &lsep); - luaL_checktype(L, 1, LUA_TTABLE); - i = luaL_optint(L, 3, 1); - last = luaL_opt(L, luaL_checkint, 4, luaL_len(L, 1)); + lua_Integer i = luaL_optinteger(L, 3, 1); + last = luaL_optinteger(L, 4, last); luaL_buffinit(L, &b); for (; i < last; i++) { addfield(L, &b, i); @@ -117,35 +192,31 @@ static int tconcat (lua_State *L) { */ static int pack (lua_State *L) { + int i; int n = lua_gettop(L); /* number of elements to pack */ lua_createtable(L, n, 1); /* create result table */ + lua_insert(L, 1); /* put it at index 1 */ + for (i = n; i >= 1; i--) /* assign elements */ + lua_seti(L, 1, i); lua_pushinteger(L, n); - lua_setfield(L, -2, "n"); /* t.n = number of elements */ - if (n > 0) { /* at least one element? */ - int i; - lua_pushvalue(L, 1); - lua_rawseti(L, -2, 1); /* insert first element */ - lua_replace(L, 1); /* move table into index 1 */ - for (i = n; i >= 2; i--) /* assign other elements */ - lua_rawseti(L, 1, i); - } + lua_setfield(L, 1, "n"); /* t.n = number of elements */ return 1; /* return table */ } static int unpack (lua_State *L) { - int i, e, n; - luaL_checktype(L, 1, LUA_TTABLE); - i = luaL_optint(L, 2, 1); - e = luaL_opt(L, luaL_checkint, 3, luaL_len(L, 1)); + lua_Unsigned n; + lua_Integer i = luaL_optinteger(L, 2, 1); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); if (i > e) return 0; /* empty range */ - n = e - i + 1; /* number of elements */ - if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ + n = (lua_Unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ + if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n))) return luaL_error(L, "too many results to unpack"); - lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ - while (i++ < e) /* push arg[i + 1...e] */ - lua_rawgeti(L, 1, i); - return n; + for (; i < e; i++) { /* push arg[i..e - 1] (to avoid overflows) */ + lua_geti(L, 1, i); + } + lua_geti(L, 1, e); /* push last element */ + return (int)n; } /* }====================================================== */ @@ -155,102 +226,197 @@ static int unpack (lua_State *L) { /* ** {====================================================== ** Quicksort -** (based on `Algorithms in MODULA-3', Robert Sedgewick; +** (based on 'Algorithms in MODULA-3', Robert Sedgewick; ** Addison-Wesley, 1993.) ** ======================================================= */ -static void set2 (lua_State *L, int i, int j) { - lua_rawseti(L, 1, i); - lua_rawseti(L, 1, j); +/* type for array indices */ +typedef unsigned int IdxT; + + +/* +** Produce a "random" 'unsigned int' to randomize pivot choice. This +** macro is used only when 'sort' detects a big imbalance in the result +** of a partition. (If you don't want/need this "randomness", ~0 is a +** good choice.) +*/ +#if !defined(l_randomizePivot) /* { */ + +#include + +/* size of 'e' measured in number of 'unsigned int's */ +#define sof(e) (sizeof(e) / sizeof(unsigned int)) + +/* +** Use 'time' and 'clock' as sources of "randomness". Because we don't +** know the types 'clock_t' and 'time_t', we cannot cast them to +** anything without risking overflows. A safe way to use their values +** is to copy them to an array of a known type and use the array values. +*/ +static unsigned int l_randomizePivot (void) { + clock_t c = clock(); + time_t t = time(NULL); + unsigned int buff[sof(c) + sof(t)]; + unsigned int i, rnd = 0; + memcpy(buff, &c, sof(c) * sizeof(unsigned int)); + memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int)); + for (i = 0; i < sof(buff); i++) + rnd += buff[i]; + return rnd; +} + +#endif /* } */ + + +/* arrays larger than 'RANLIMIT' may use randomized pivots */ +#define RANLIMIT 100u + + +static void set2 (lua_State *L, IdxT i, IdxT j) { + lua_seti(L, 1, i); + lua_seti(L, 1, j); } + +/* +** Return true iff value at stack index 'a' is less than the value at +** index 'b' (according to the order of the sort). +*/ static int sort_comp (lua_State *L, int a, int b) { - if (!lua_isnil(L, 2)) { /* function? */ + if (lua_isnil(L, 2)) /* no function? */ + return lua_compare(L, a, b, LUA_OPLT); /* a < b */ + else { /* function */ int res; - lua_pushvalue(L, 2); + lua_pushvalue(L, 2); /* push function */ lua_pushvalue(L, a-1); /* -1 to compensate function */ - lua_pushvalue(L, b-2); /* -2 to compensate function and `a' */ - lua_call(L, 2, 1); - res = lua_toboolean(L, -1); - lua_pop(L, 1); + lua_pushvalue(L, b-2); /* -2 to compensate function and 'a' */ + lua_call(L, 2, 1); /* call function */ + res = lua_toboolean(L, -1); /* get result */ + lua_pop(L, 1); /* pop result */ return res; } - else /* a < b? */ - return lua_compare(L, a, b, LUA_OPLT); } -static void auxsort (lua_State *L, int l, int u) { - while (l < u) { /* for tail recursion */ - int i, j; - /* sort elements a[l], a[(l+u)/2] and a[u] */ - lua_rawgeti(L, 1, l); - lua_rawgeti(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ - set2(L, l, u); /* swap a[l] - a[u] */ + +/* +** Does the partition: Pivot P is at the top of the stack. +** precondition: a[lo] <= P == a[up-1] <= a[up], +** so it only needs to do the partition from lo + 1 to up - 2. +** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up] +** returns 'i'. +*/ +static IdxT partition (lua_State *L, IdxT lo, IdxT up) { + IdxT i = lo; /* will be incremented before first use */ + IdxT j = up - 1; /* will be decremented before first use */ + /* loop invariant: a[lo .. i] <= P <= a[j .. up] */ + for (;;) { + /* next loop: repeat ++i while a[i] < P */ + while (lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (i == up - 1) /* a[i] < P but a[up - 1] == P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* after the loop, a[i] >= P and a[lo .. i - 1] < P */ + /* next loop: repeat --j while P < a[j] */ + while (lua_geti(L, 1, --j), sort_comp(L, -3, -1)) { + if (j < i) /* j < i but a[j] > P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */ + if (j < i) { /* no elements out of place? */ + /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */ + lua_pop(L, 1); /* pop a[j] */ + /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */ + set2(L, up - 1, i); + return i; + } + /* otherwise, swap a[i] - a[j] to restore invariant and repeat */ + set2(L, i, j); + } +} + + +/* +** Choose an element in the middle (2nd-3th quarters) of [lo,up] +** "randomized" by 'rnd' +*/ +static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) { + IdxT r4 = (up - lo) / 4; /* range/4 */ + IdxT p = rnd % (r4 * 2) + (lo + r4); + lua_assert(lo + r4 <= p && p <= up - r4); + return p; +} + + +/* +** QuickSort algorithm (recursive function) +*/ +static void auxsort (lua_State *L, IdxT lo, IdxT up, + unsigned int rnd) { + while (lo < up) { /* loop for tail recursion */ + IdxT p; /* Pivot index */ + IdxT n; /* to be used later */ + /* sort elements 'lo', 'p', and 'up' */ + lua_geti(L, 1, lo); + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[lo]? */ + set2(L, lo, up); /* swap a[lo] - a[up] */ else - lua_pop(L, 2); - if (u-l == 1) break; /* only 2 elements */ - i = (l+u)/2; - lua_rawgeti(L, 1, i); - lua_rawgeti(L, 1, l); - if (sort_comp(L, -2, -1)) /* a[i]= P */ - while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { - if (i>=u) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[i] */ - } - /* repeat --j until a[j] <= P */ - while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { - if (j<=l) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[j] */ - } - if (j n) /* partition too imbalanced? */ + rnd = l_randomizePivot(); /* try a new randomization */ + } /* tail call auxsort(L, lo, up, rnd) */ } + static int sort (lua_State *L) { - int n = aux_getn(L, 1); - luaL_checkstack(L, 40, ""); /* assume array is smaller than 2^40 */ - if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ - luaL_checktype(L, 2, LUA_TFUNCTION); - lua_settop(L, 2); /* make sure there is two arguments */ - auxsort(L, 1, n); + lua_Integer n = aux_getn(L, 1, TAB_RW); + if (n > 1) { /* non-trivial interval? */ + luaL_argcheck(L, n < INT_MAX, 1, "array too big"); + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); /* must be a function */ + lua_settop(L, 2); /* make sure there are two arguments */ + auxsort(L, 1, (IdxT)n, 0); + } return 0; } @@ -266,6 +432,7 @@ static const luaL_Reg tab_funcs[] = { {"pack", pack}, {"unpack", unpack}, {"remove", tremove}, + {"move", tmove}, {"sort", sort}, {NULL, NULL} }; diff --git a/depends/lua/src/ltm.c b/depends/lua/src/ltm.c index 69b4ed772..4650cc293 100644 --- a/depends/lua/src/ltm.c +++ b/depends/lua/src/ltm.c @@ -1,22 +1,27 @@ /* -** $Id: ltm.c,v 2.14.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltm.c,v 2.37 2016/02/26 19:20:15 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ - -#include - #define ltm_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" +#include "ldebug.h" +#include "ldo.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" +#include "lvm.h" static const char udatatypename[] = "userdata"; @@ -25,7 +30,7 @@ LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTAGS] = { "no value", "nil", "boolean", udatatypename, "number", "string", "table", "function", udatatypename, "thread", - "proto", "upval" /* these last two cases are used for tests only */ + "proto" /* this last case is used for tests only */ }; @@ -33,14 +38,16 @@ void luaT_init (lua_State *L) { static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__gc", "__mode", "__len", "__eq", - "__add", "__sub", "__mul", "__div", "__mod", - "__pow", "__unm", "__lt", "__le", + "__add", "__sub", "__mul", "__mod", "__pow", + "__div", "__idiv", + "__band", "__bor", "__bxor", "__shl", "__shr", + "__unm", "__bnot", "__lt", "__le", "__concat", "__call" }; int i; for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); - luaS_fix(G(L)->tmname[i]); /* never collect these names */ + luaC_fix(L, obj2gco(G(L)->tmname[i])); /* never collect these names */ } } @@ -50,7 +57,7 @@ void luaT_init (lua_State *L) { ** tag methods */ const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { - const TValue *tm = luaH_getstr(events, ename); + const TValue *tm = luaH_getshortstr(events, ename); lua_assert(event <= TM_EQ); if (ttisnil(tm)) { /* no tag method? */ events->flags |= cast_byte(1u<metatable; break; @@ -70,8 +77,89 @@ const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) { mt = uvalue(o)->metatable; break; default: - mt = G(L)->mt[ttypenv(o)]; + mt = G(L)->mt[ttnov(o)]; + } + return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : luaO_nilobject); +} + + +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const char *luaT_objtypename (lua_State *L, const TValue *o) { + Table *mt; + if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) || + (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) { + const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name")); + if (ttisstring(name)) /* is '__name' a string? */ + return getstr(tsvalue(name)); /* use it as type name */ } - return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); + return ttypename(ttnov(o)); /* else use standard type name */ +} + + +void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, TValue *p3, int hasres) { + ptrdiff_t result = savestack(L, p3); + StkId func = L->top; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + L->top += 3; + if (!hasres) /* no result? 'p3' is third argument */ + setobj2s(L, L->top++, p3); /* 3rd argument */ + /* metamethod may yield only when called from Lua code */ + if (isLua(L->ci)) + luaD_call(L, func, hasres); + else + luaD_callnoyield(L, func, hasres); + if (hasres) { /* if has result, move it to its place */ + p3 = restorestack(L, result); + setobjs2s(L, p3, --L->top); + } +} + + +int luaT_callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + if (ttisnil(tm)) + tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + if (ttisnil(tm)) return 0; + luaT_callTM(L, tm, p1, p2, res, 1); + return 1; +} + + +void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + if (!luaT_callbinTM(L, p1, p2, res, event)) { + switch (event) { + case TM_CONCAT: + luaG_concaterror(L, p1, p2); + /* call never returns, but to avoid warnings: *//* FALLTHROUGH */ + case TM_BAND: case TM_BOR: case TM_BXOR: + case TM_SHL: case TM_SHR: case TM_BNOT: { + lua_Number dummy; + if (tonumber(p1, &dummy) && tonumber(p2, &dummy)) + luaG_tointerror(L, p1, p2); + else + luaG_opinterror(L, p1, p2, "perform bitwise operation on"); + } + /* calls never return, but to avoid warnings: *//* FALLTHROUGH */ + default: + luaG_opinterror(L, p1, p2, "perform arithmetic on"); + } + } +} + + +int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2, + TMS event) { + if (!luaT_callbinTM(L, p1, p2, L->top, event)) + return -1; /* no metamethod */ + else + return !l_isfalse(L->top); } diff --git a/depends/lua/src/lua.c b/depends/lua/src/lua.c index 4345e554e..545d23d49 100644 --- a/depends/lua/src/lua.c +++ b/depends/lua/src/lua.c @@ -1,17 +1,19 @@ /* -** $Id: lua.c,v 1.206.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lua.c,v 1.226 2015/08/14 19:11:20 roberto Exp $ ** Lua stand-alone interpreter ** See Copyright Notice in lua.h */ +#define lua_c + +#include "lprefix.h" + #include #include #include #include -#define lua_c - #include "lua.h" #include "lauxlib.h" @@ -31,28 +33,38 @@ #define LUA_MAXINPUT 512 #endif -#if !defined(LUA_INIT) -#define LUA_INIT "LUA_INIT" +#if !defined(LUA_INIT_VAR) +#define LUA_INIT_VAR "LUA_INIT" #endif -#define LUA_INITVERSION \ - LUA_INIT "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR +#define LUA_INITVARVERSION \ + LUA_INIT_VAR "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR /* ** lua_stdin_is_tty detects whether the standard input is a 'tty' (that ** is, whether we're running lua interactively). */ -#if defined(LUA_USE_ISATTY) +#if !defined(lua_stdin_is_tty) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + #include #define lua_stdin_is_tty() isatty(0) -#elif defined(LUA_WIN) + +#elif defined(LUA_USE_WINDOWS) /* }{ */ + #include -#include #define lua_stdin_is_tty() _isatty(_fileno(stdin)) -#else + +#else /* }{ */ + +/* ISO C definition */ #define lua_stdin_is_tty() 1 /* assume stdin is a tty */ -#endif + +#endif /* } */ + +#endif /* } */ /* @@ -61,26 +73,27 @@ ** lua_saveline defines how to "save" a read line in a "history". ** lua_freeline defines how to free a line read by lua_readline. */ -#if defined(LUA_USE_READLINE) +#if !defined(lua_readline) /* { */ + +#if defined(LUA_USE_READLINE) /* { */ -#include #include #include #define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) -#define lua_saveline(L,idx) \ - if (lua_rawlen(L,idx) > 0) /* non-empty line? */ \ - add_history(lua_tostring(L, idx)); /* add it to history */ +#define lua_saveline(L,line) ((void)L, add_history(line)) #define lua_freeline(L,b) ((void)L, free(b)) -#elif !defined(lua_readline) +#else /* }{ */ #define lua_readline(L,b,p) \ ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ -#define lua_saveline(L,idx) { (void)L; (void)idx; } +#define lua_saveline(L,line) { (void)L; (void)line; } #define lua_freeline(L,b) { (void)L; (void)b; } -#endif +#endif /* } */ + +#endif /* } */ @@ -90,33 +103,40 @@ static lua_State *globalL = NULL; static const char *progname = LUA_PROGNAME; - +/* +** Hook set by signal function to stop the interpreter. +*/ static void lstop (lua_State *L, lua_Debug *ar) { (void)ar; /* unused arg. */ - lua_sethook(L, NULL, 0, 0); + lua_sethook(L, NULL, 0, 0); /* reset hook */ luaL_error(L, "interrupted!"); } +/* +** Function to be called at a C signal. Because a C signal cannot +** just change a Lua state (as there is no proper synchronization), +** this function only sets a hook that, when called, will stop the +** interpreter. +*/ static void laction (int i) { - signal(i, SIG_DFL); /* if another SIGINT happens before lstop, - terminate process (default action) */ + signal(i, SIG_DFL); /* if another SIGINT happens, terminate process */ lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } static void print_usage (const char *badoption) { - luai_writestringerror("%s: ", progname); + lua_writestringerror("%s: ", progname); if (badoption[1] == 'e' || badoption[1] == 'l') - luai_writestringerror("'%s' needs argument\n", badoption); + lua_writestringerror("'%s' needs argument\n", badoption); else - luai_writestringerror("unrecognized option '%s'\n", badoption); - luai_writestringerror( + lua_writestringerror("unrecognized option '%s'\n", badoption); + lua_writestringerror( "usage: %s [options] [script [args]]\n" "Available options are:\n" - " -e stat execute string " LUA_QL("stat") "\n" - " -i enter interactive mode after executing " LUA_QL("script") "\n" - " -l name require library " LUA_QL("name") "\n" + " -e stat execute string 'stat'\n" + " -i enter interactive mode after executing 'script'\n" + " -l name require library 'name'\n" " -v show version information\n" " -E ignore environment variables\n" " -- stop handling options\n" @@ -126,101 +146,114 @@ static void print_usage (const char *badoption) { } +/* +** Prints an error message, adding the program name in front of it +** (if present) +*/ static void l_message (const char *pname, const char *msg) { - if (pname) luai_writestringerror("%s: ", pname); - luai_writestringerror("%s\n", msg); + if (pname) lua_writestringerror("%s: ", pname); + lua_writestringerror("%s\n", msg); } +/* +** Check whether 'status' is not OK and, if so, prints the error +** message on the top of the stack. It assumes that the error object +** is a string, as it was either generated by Lua or by 'msghandler'. +*/ static int report (lua_State *L, int status) { - if (status != LUA_OK && !lua_isnil(L, -1)) { + if (status != LUA_OK) { const char *msg = lua_tostring(L, -1); - if (msg == NULL) msg = "(error object is not a string)"; l_message(progname, msg); - lua_pop(L, 1); - /* force a complete garbage collection in case of errors */ - lua_gc(L, LUA_GCCOLLECT, 0); + lua_pop(L, 1); /* remove message */ } return status; } -/* the next function is called unprotected, so it must avoid errors */ -static void finalreport (lua_State *L, int status) { - if (status != LUA_OK) { - const char *msg = (lua_type(L, -1) == LUA_TSTRING) ? lua_tostring(L, -1) - : NULL; - if (msg == NULL) msg = "(error object is not a string)"; - l_message(progname, msg); - lua_pop(L, 1); - } -} - - -static int traceback (lua_State *L) { +/* +** Message handler used to run all chunks +*/ +static int msghandler (lua_State *L) { const char *msg = lua_tostring(L, 1); - if (msg) - luaL_traceback(L, L, msg, 1); - else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ - if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ - lua_pushliteral(L, "(no error message)"); + if (msg == NULL) { /* is error object not a string? */ + if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */ + lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */ + return 1; /* that is the message */ + else + msg = lua_pushfstring(L, "(error object is a %s value)", + luaL_typename(L, 1)); } - return 1; + luaL_traceback(L, L, msg, 1); /* append a standard traceback */ + return 1; /* return the traceback */ } +/* +** Interface to 'lua_pcall', which sets appropriate message function +** and C-signal handler. Used to run all chunks. +*/ static int docall (lua_State *L, int narg, int nres) { int status; int base = lua_gettop(L) - narg; /* function index */ - lua_pushcfunction(L, traceback); /* push traceback function */ - lua_insert(L, base); /* put it under chunk and args */ + lua_pushcfunction(L, msghandler); /* push message handler */ + lua_insert(L, base); /* put it under function and args */ globalL = L; /* to be available to 'laction' */ - signal(SIGINT, laction); + signal(SIGINT, laction); /* set C-signal handler */ status = lua_pcall(L, narg, nres, base); - signal(SIGINT, SIG_DFL); - lua_remove(L, base); /* remove traceback function */ + signal(SIGINT, SIG_DFL); /* reset C-signal handler */ + lua_remove(L, base); /* remove message handler from the stack */ return status; } static void print_version (void) { - luai_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); - luai_writeline(); + lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); + lua_writeline(); } -static int getargs (lua_State *L, char **argv, int n) { - int narg; - int i; - int argc = 0; - while (argv[argc]) argc++; /* count total number of arguments */ - narg = argc - (n + 1); /* number of arguments to the script */ - luaL_checkstack(L, narg + 3, "too many arguments to script"); - for (i=n+1; i < argc; i++) - lua_pushstring(L, argv[i]); - lua_createtable(L, narg, n + 1); - for (i=0; i < argc; i++) { +/* +** Create the 'arg' table, which stores all arguments from the +** command line ('argv'). It should be aligned so that, at index 0, +** it has 'argv[script]', which is the script name. The arguments +** to the script (everything after 'script') go to positive indices; +** other arguments (before the script name) go to negative indices. +** If there is no script name, assume interpreter's name as base. +*/ +static void createargtable (lua_State *L, char **argv, int argc, int script) { + int i, narg; + if (script == argc) script = 0; /* no script name? */ + narg = argc - (script + 1); /* number of positive indices */ + lua_createtable(L, narg, script + 1); + for (i = 0; i < argc; i++) { lua_pushstring(L, argv[i]); - lua_rawseti(L, -2, i - n); + lua_rawseti(L, -2, i - script); } - return narg; + lua_setglobal(L, "arg"); } -static int dofile (lua_State *L, const char *name) { - int status = luaL_loadfile(L, name); +static int dochunk (lua_State *L, int status) { if (status == LUA_OK) status = docall(L, 0, 0); return report(L, status); } +static int dofile (lua_State *L, const char *name) { + return dochunk(L, luaL_loadfile(L, name)); +} + + static int dostring (lua_State *L, const char *s, const char *name) { - int status = luaL_loadbuffer(L, s, strlen(s), name); - if (status == LUA_OK) status = docall(L, 0, 0); - return report(L, status); + return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name)); } +/* +** Calls 'require(name)' and stores the result in a global variable +** with the given name. +*/ static int dolibrary (lua_State *L, const char *name) { int status; lua_getglobal(L, "require"); @@ -232,6 +265,9 @@ static int dolibrary (lua_State *L, const char *name) { } +/* +** Returns the string to be used as a prompt by the interpreter. +*/ static const char *get_prompt (lua_State *L, int firstline) { const char *p; lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2"); @@ -244,6 +280,12 @@ static const char *get_prompt (lua_State *L, int firstline) { #define EOFMARK "" #define marklen (sizeof(EOFMARK)/sizeof(char) - 1) + +/* +** Check whether 'status' signals a syntax error and the error +** message at the top of the stack ends with the above mark for +** incomplete statements. +*/ static int incomplete (lua_State *L, int status) { if (status == LUA_ERRSYNTAX) { size_t lmsg; @@ -257,163 +299,230 @@ static int incomplete (lua_State *L, int status) { } +/* +** Prompt the user, read a line, and push it into the Lua stack. +*/ static int pushline (lua_State *L, int firstline) { char buffer[LUA_MAXINPUT]; char *b = buffer; size_t l; const char *prmt = get_prompt(L, firstline); int readstatus = lua_readline(L, b, prmt); - lua_pop(L, 1); /* remove result from 'get_prompt' */ if (readstatus == 0) - return 0; /* no input */ + return 0; /* no input (prompt will be popped by caller) */ + lua_pop(L, 1); /* remove prompt */ l = strlen(b); if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ - b[l-1] = '\0'; /* remove it */ - if (firstline && b[0] == '=') /* first line starts with `=' ? */ - lua_pushfstring(L, "return %s", b+1); /* change it to `return' */ + b[--l] = '\0'; /* remove it */ + if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */ + lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */ else - lua_pushstring(L, b); + lua_pushlstring(L, b, l); lua_freeline(L, b); return 1; } +/* +** Try to compile line on the stack as 'return ;'; on return, stack +** has either compiled chunk or original line (if compilation failed). +*/ +static int addreturn (lua_State *L) { + const char *line = lua_tostring(L, -1); /* original line */ + const char *retline = lua_pushfstring(L, "return %s;", line); + int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin"); + if (status == LUA_OK) { + lua_remove(L, -2); /* remove modified line */ + if (line[0] != '\0') /* non empty? */ + lua_saveline(L, line); /* keep history */ + } + else + lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */ + return status; +} + + +/* +** Read multiple lines until a complete Lua statement +*/ +static int multiline (lua_State *L) { + for (;;) { /* repeat until gets a complete statement */ + size_t len; + const char *line = lua_tolstring(L, 1, &len); /* get what it has */ + int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */ + if (!incomplete(L, status) || !pushline(L, 0)) { + lua_saveline(L, line); /* keep history */ + return status; /* cannot or should not try to add continuation line */ + } + lua_pushliteral(L, "\n"); /* add newline... */ + lua_insert(L, -2); /* ...between the two lines */ + lua_concat(L, 3); /* join them */ + } +} + + +/* +** Read a line and try to load (compile) it first as an expression (by +** adding "return " in front of it) and second as a statement. Return +** the final status of load/call with the resulting function (if any) +** in the top of the stack. +*/ static int loadline (lua_State *L) { int status; lua_settop(L, 0); if (!pushline(L, 1)) return -1; /* no input */ - for (;;) { /* repeat until gets a complete line */ - size_t l; - const char *line = lua_tolstring(L, 1, &l); - status = luaL_loadbuffer(L, line, l, "=stdin"); - if (!incomplete(L, status)) break; /* cannot try to add lines? */ - if (!pushline(L, 0)) /* no more input? */ - return -1; - lua_pushliteral(L, "\n"); /* add a new line... */ - lua_insert(L, -2); /* ...between the two lines */ - lua_concat(L, 3); /* join them */ - } - lua_saveline(L, 1); - lua_remove(L, 1); /* remove line */ + if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */ + status = multiline(L); /* try as command, maybe with continuation lines */ + lua_remove(L, 1); /* remove line from the stack */ + lua_assert(lua_gettop(L) == 1); return status; } -static void dotty (lua_State *L) { +/* +** Prints (calling the Lua 'print' function) any values on the stack +*/ +static void l_print (lua_State *L) { + int n = lua_gettop(L); + if (n > 0) { /* any result to be printed? */ + luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, n, 0, 0) != LUA_OK) + l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)", + lua_tostring(L, -1))); + } +} + + +/* +** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and +** print any results. +*/ +static void doREPL (lua_State *L) { int status; const char *oldprogname = progname; - progname = NULL; + progname = NULL; /* no 'progname' on errors in interactive mode */ while ((status = loadline(L)) != -1) { - if (status == LUA_OK) status = docall(L, 0, LUA_MULTRET); - report(L, status); - if (status == LUA_OK && lua_gettop(L) > 0) { /* any result to print? */ - luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); - lua_getglobal(L, "print"); - lua_insert(L, 1); - if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != LUA_OK) - l_message(progname, lua_pushfstring(L, - "error calling " LUA_QL("print") " (%s)", - lua_tostring(L, -1))); - } + if (status == LUA_OK) + status = docall(L, 0, LUA_MULTRET); + if (status == LUA_OK) l_print(L); + else report(L, status); } lua_settop(L, 0); /* clear stack */ - luai_writeline(); + lua_writeline(); progname = oldprogname; } -static int handle_script (lua_State *L, char **argv, int n) { +/* +** Push on the stack the contents of table 'arg' from 1 to #arg +*/ +static int pushargs (lua_State *L) { + int i, n; + if (lua_getglobal(L, "arg") != LUA_TTABLE) + luaL_error(L, "'arg' is not a table"); + n = (int)luaL_len(L, -1); + luaL_checkstack(L, n + 3, "too many arguments to script"); + for (i = 1; i <= n; i++) + lua_rawgeti(L, -i, i); + lua_remove(L, -i); /* remove table from the stack */ + return n; +} + + +static int handle_script (lua_State *L, char **argv) { int status; - const char *fname; - int narg = getargs(L, argv, n); /* collect arguments */ - lua_setglobal(L, "arg"); - fname = argv[n]; - if (strcmp(fname, "-") == 0 && strcmp(argv[n-1], "--") != 0) + const char *fname = argv[0]; + if (strcmp(fname, "-") == 0 && strcmp(argv[-1], "--") != 0) fname = NULL; /* stdin */ status = luaL_loadfile(L, fname); - lua_insert(L, -(narg+1)); - if (status == LUA_OK) - status = docall(L, narg, LUA_MULTRET); - else - lua_pop(L, narg); + if (status == LUA_OK) { + int n = pushargs(L); /* push arguments to script */ + status = docall(L, n, LUA_MULTRET); + } return report(L, status); } -/* check that argument has no extra characters at the end */ -#define noextrachars(x) {if ((x)[2] != '\0') return -1;} - - -/* indices of various argument indicators in array args */ -#define has_i 0 /* -i */ -#define has_v 1 /* -v */ -#define has_e 2 /* -e */ -#define has_E 3 /* -E */ - -#define num_has 4 /* number of 'has_*' */ +/* bits of various argument indicators in 'args' */ +#define has_error 1 /* bad option */ +#define has_i 2 /* -i */ +#define has_v 4 /* -v */ +#define has_e 8 /* -e */ +#define has_E 16 /* -E */ -static int collectargs (char **argv, int *args) { +/* +** Traverses all arguments from 'argv', returning a mask with those +** needed before running any Lua code (or an error code if it finds +** any invalid argument). 'first' returns the first not-handled argument +** (either the script name or a bad argument in case of error). +*/ +static int collectargs (char **argv, int *first) { + int args = 0; int i; for (i = 1; argv[i] != NULL; i++) { + *first = i; if (argv[i][0] != '-') /* not an option? */ - return i; - switch (argv[i][1]) { /* option */ - case '-': - noextrachars(argv[i]); - return (argv[i+1] != NULL ? i+1 : 0); - case '\0': - return i; + return args; /* stop handling options */ + switch (argv[i][1]) { /* else check option */ + case '-': /* '--' */ + if (argv[i][2] != '\0') /* extra characters after '--'? */ + return has_error; /* invalid option */ + *first = i + 1; + return args; + case '\0': /* '-' */ + return args; /* script "name" is '-' */ case 'E': - args[has_E] = 1; + if (argv[i][2] != '\0') /* extra characters after 1st? */ + return has_error; /* invalid option */ + args |= has_E; break; case 'i': - noextrachars(argv[i]); - args[has_i] = 1; /* go through */ + args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */ case 'v': - noextrachars(argv[i]); - args[has_v] = 1; + if (argv[i][2] != '\0') /* extra characters after 1st? */ + return has_error; /* invalid option */ + args |= has_v; break; case 'e': - args[has_e] = 1; /* go through */ + args |= has_e; /* FALLTHROUGH */ case 'l': /* both options need an argument */ if (argv[i][2] == '\0') { /* no concatenated argument? */ i++; /* try next 'argv' */ if (argv[i] == NULL || argv[i][0] == '-') - return -(i - 1); /* no next argument or it is another option */ + return has_error; /* no next argument or it is another option */ } break; - default: /* invalid option; return its index... */ - return -i; /* ...as a negative value */ + default: /* invalid option */ + return has_error; } } - return 0; + *first = i; /* no script name */ + return args; } +/* +** Processes options 'e' and 'l', which involve running Lua code. +** Returns 0 if some code raises an error. +*/ static int runargs (lua_State *L, char **argv, int n) { int i; for (i = 1; i < n; i++) { - lua_assert(argv[i][0] == '-'); - switch (argv[i][1]) { /* option */ - case 'e': { - const char *chunk = argv[i] + 2; - if (*chunk == '\0') chunk = argv[++i]; - lua_assert(chunk != NULL); - if (dostring(L, chunk, "=(command line)") != LUA_OK) - return 0; - break; - } - case 'l': { - const char *filename = argv[i] + 2; - if (*filename == '\0') filename = argv[++i]; - lua_assert(filename != NULL); - if (dolibrary(L, filename) != LUA_OK) - return 0; /* stop if file fails */ - break; - } - default: break; + int option = argv[i][1]; + lua_assert(argv[i][0] == '-'); /* already checked */ + if (option == 'e' || option == 'l') { + int status; + const char *extra = argv[i] + 2; /* both options need an argument */ + if (*extra == '\0') extra = argv[++i]; + lua_assert(extra != NULL); + status = (option == 'e') + ? dostring(L, extra, "=(command line)") + : dolibrary(L, extra); + if (status != LUA_OK) return 0; } } return 1; @@ -421,10 +530,10 @@ static int runargs (lua_State *L, char **argv, int n) { static int handle_luainit (lua_State *L) { - const char *name = "=" LUA_INITVERSION; + const char *name = "=" LUA_INITVARVERSION; const char *init = getenv(name + 1); if (init == NULL) { - name = "=" LUA_INIT; + name = "=" LUA_INIT_VAR; init = getenv(name + 1); /* try alternative name */ } if (init == NULL) return LUA_OK; @@ -435,40 +544,44 @@ static int handle_luainit (lua_State *L) { } +/* +** Main body of stand-alone interpreter (to be called in protected mode). +** Reads the options and handles them all. +*/ static int pmain (lua_State *L) { int argc = (int)lua_tointeger(L, 1); char **argv = (char **)lua_touserdata(L, 2); int script; - int args[num_has]; - args[has_i] = args[has_v] = args[has_e] = args[has_E] = 0; + int args = collectargs(argv, &script); + luaL_checkversion(L); /* check that interpreter has correct version */ if (argv[0] && argv[0][0]) progname = argv[0]; - script = collectargs(argv, args); - if (script < 0) { /* invalid arg? */ - print_usage(argv[-script]); + if (args == has_error) { /* bad arg? */ + print_usage(argv[script]); /* 'script' has index of bad arg. */ return 0; } - if (args[has_v]) print_version(); - if (args[has_E]) { /* option '-E'? */ + if (args & has_v) /* option '-v'? */ + print_version(); + if (args & has_E) { /* option '-E'? */ lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); } - /* open standard libraries */ - luaL_checkversion(L); - lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ - luaL_openlibs(L); /* open libraries */ - lua_gc(L, LUA_GCRESTART, 0); - if (!args[has_E] && handle_luainit(L) != LUA_OK) - return 0; /* error running LUA_INIT */ - /* execute arguments -e and -l */ - if (!runargs(L, argv, (script > 0) ? script : argc)) return 0; - /* execute main script (if there is one) */ - if (script && handle_script(L, argv, script) != LUA_OK) return 0; - if (args[has_i]) /* -i option? */ - dotty(L); - else if (script == 0 && !args[has_e] && !args[has_v]) { /* no arguments? */ - if (lua_stdin_is_tty()) { + luaL_openlibs(L); /* open standard libraries */ + createargtable(L, argv, argc, script); /* create table 'arg' */ + if (!(args & has_E)) { /* no option '-E'? */ + if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */ + return 0; /* error running LUA_INIT */ + } + if (!runargs(L, argv, script)) /* execute arguments -e and -l */ + return 0; /* something failed */ + if (script < argc && /* execute main script (if there is one) */ + handle_script(L, argv + script) != LUA_OK) + return 0; + if (args & has_i) /* -i option? */ + doREPL(L); /* do read-eval-print loop */ + else if (script == argc && !(args & (has_e | has_v))) { /* no arguments? */ + if (lua_stdin_is_tty()) { /* running in interactive mode? */ print_version(); - dotty(L); + doREPL(L); /* do read-eval-print loop */ } else dofile(L, NULL); /* executes stdin as a file */ } @@ -484,13 +597,12 @@ int main (int argc, char **argv) { l_message(argv[0], "cannot create state: not enough memory"); return EXIT_FAILURE; } - /* call 'pmain' in protected mode */ - lua_pushcfunction(L, &pmain); + lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */ lua_pushinteger(L, argc); /* 1st argument */ lua_pushlightuserdata(L, argv); /* 2nd argument */ - status = lua_pcall(L, 2, 1, 0); + status = lua_pcall(L, 2, 1, 0); /* do the call */ result = lua_toboolean(L, -1); /* get result */ - finalreport(L, status); + report(L, status); lua_close(L); return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/depends/lua/src/luac.c b/depends/lua/src/luac.c index 7409706ec..c0c91d017 100644 --- a/depends/lua/src/luac.c +++ b/depends/lua/src/luac.c @@ -1,17 +1,20 @@ /* -** $Id: luac.c,v 1.69 2011/11/29 17:46:33 lhf Exp $ -** Lua compiler (saves bytecodes to files; also list bytecodes) +** $Id: luac.c,v 1.75 2015/03/12 01:58:27 lhf Exp $ +** Lua compiler (saves bytecodes to files; also lists bytecodes) ** See Copyright Notice in lua.h */ +#define luac_c +#define LUA_CORE + +#include "lprefix.h" + +#include #include #include #include #include -#define luac_c -#define LUA_CORE - #include "lua.h" #include "lauxlib.h" @@ -47,14 +50,14 @@ static void cannot(const char* what) static void usage(const char* message) { if (*message=='-') - fprintf(stderr,"%s: unrecognized option " LUA_QS "\n",progname,message); + fprintf(stderr,"%s: unrecognized option '%s'\n",progname,message); else fprintf(stderr,"%s: %s\n",progname,message); fprintf(stderr, "usage: %s [options] [filenames]\n" "Available options are:\n" " -l list (use -l -l for full listing)\n" - " -o name output to file " LUA_QL("name") " (default is \"%s\")\n" + " -o name output to file 'name' (default is \"%s\")\n" " -p parse only\n" " -s strip debug information\n" " -v show version information\n" @@ -89,7 +92,7 @@ static int doargs(int argc, char* argv[]) { output=argv[++i]; if (output==NULL || *output==0 || (*output=='-' && output[1]!=0)) - usage(LUA_QL("-o") " needs argument"); + usage("'-o' needs argument"); if (IS("-")) output=NULL; } else if (IS("-p")) /* parse only */ @@ -203,7 +206,7 @@ int main(int argc, char* argv[]) } /* -** $Id: print.c,v 1.69 2013/07/04 01:03:46 lhf Exp $ +** $Id: luac.c,v 1.75 2015/03/12 01:58:27 lhf Exp $ ** print bytecodes ** See Copyright Notice in lua.h */ @@ -223,7 +226,7 @@ int main(int argc, char* argv[]) static void PrintString(const TString* ts) { const char* s=getstr(ts); - size_t i,n=ts->tsv.len; + size_t i,n=tsslen(ts); printf("%c",'"'); for (i=0; ik[i]; - switch (ttypenv(o)) + switch (ttype(o)) { case LUA_TNIL: printf("nil"); @@ -259,11 +262,19 @@ static void PrintConstant(const Proto* f, int i) case LUA_TBOOLEAN: printf(bvalue(o) ? "true" : "false"); break; - case LUA_TNUMBER: - printf(LUA_NUMBER_FMT,nvalue(o)); + case LUA_TNUMFLT: + { + char buff[100]; + sprintf(buff,LUA_NUMBER_FMT,fltvalue(o)); + printf("%s",buff); + if (buff[strspn(buff,"-0123456789")]=='\0') printf(".0"); + break; + } + case LUA_TNUMINT: + printf(LUA_INTEGER_FMT,ivalue(o)); break; - case LUA_TSTRING: - PrintString(rawtsvalue(o)); + case LUA_TSHRSTR: case LUA_TLNGSTR: + PrintString(tsvalue(o)); break; default: /* cannot happen */ printf("? type=%d",ttype(o)); @@ -337,8 +348,14 @@ static void PrintCode(const Proto* f) case OP_ADD: case OP_SUB: case OP_MUL: - case OP_DIV: case OP_POW: + case OP_DIV: + case OP_IDIV: + case OP_BAND: + case OP_BOR: + case OP_BXOR: + case OP_SHL: + case OP_SHR: case OP_EQ: case OP_LT: case OP_LE: diff --git a/depends/lua/src/lundump.c b/depends/lua/src/lundump.c index 4163cb5d3..4080af9c0 100644 --- a/depends/lua/src/lundump.c +++ b/depends/lua/src/lundump.c @@ -1,14 +1,17 @@ /* -** $Id: lundump.c,v 2.22.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lundump.c,v 2.44 2015/11/02 16:09:30 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ -#include - #define lundump_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "ldebug.h" @@ -20,239 +23,257 @@ #include "lundump.h" #include "lzio.h" + +#if !defined(luai_verifycode) +#define luai_verifycode(L,b,f) /* empty */ +#endif + + typedef struct { - lua_State* L; - ZIO* Z; - Mbuffer* b; - const char* name; + lua_State *L; + ZIO *Z; + const char *name; } LoadState; -static l_noret error(LoadState* S, const char* why) -{ - luaO_pushfstring(S->L,"%s: %s precompiled chunk",S->name,why); - luaD_throw(S->L,LUA_ERRSYNTAX); + +static l_noret error(LoadState *S, const char *why) { + luaO_pushfstring(S->L, "%s: %s precompiled chunk", S->name, why); + luaD_throw(S->L, LUA_ERRSYNTAX); } -#define LoadMem(S,b,n,size) LoadBlock(S,b,(n)*(size)) -#define LoadByte(S) (lu_byte)LoadChar(S) -#define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x)) -#define LoadVector(S,b,n,size) LoadMem(S,b,n,size) -#if !defined(luai_verifycode) -#define luai_verifycode(L,b,f) /* empty */ -#endif +/* +** All high-level loads go through LoadVector; you can change it to +** adapt to the endianness of the input +*/ +#define LoadVector(S,b,n) LoadBlock(S,b,(n)*sizeof((b)[0])) -static void LoadBlock(LoadState* S, void* b, size_t size) -{ - if (luaZ_read(S->Z,b,size)!=0) error(S,"truncated"); +static void LoadBlock (LoadState *S, void *b, size_t size) { + if (luaZ_read(S->Z, b, size) != 0) + error(S, "truncated"); } -static int LoadChar(LoadState* S) -{ - char x; - LoadVar(S,x); - return x; + +#define LoadVar(S,x) LoadVector(S,&x,1) + + +static lu_byte LoadByte (LoadState *S) { + lu_byte x; + LoadVar(S, x); + return x; } -static int LoadInt(LoadState* S) -{ - int x; - LoadVar(S,x); - if (x<0) error(S,"corrupted"); - return x; + +static int LoadInt (LoadState *S) { + int x; + LoadVar(S, x); + return x; } -static lua_Number LoadNumber(LoadState* S) -{ - lua_Number x; - LoadVar(S,x); - return x; + +static lua_Number LoadNumber (LoadState *S) { + lua_Number x; + LoadVar(S, x); + return x; } -static TString* LoadString(LoadState* S) -{ - size_t size; - LoadVar(S,size); - if (size==0) - return NULL; - else - { - char* s=luaZ_openspace(S->L,S->b,size); - LoadBlock(S,s,size*sizeof(char)); - return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */ - } + +static lua_Integer LoadInteger (LoadState *S) { + lua_Integer x; + LoadVar(S, x); + return x; } -static void LoadCode(LoadState* S, Proto* f) -{ - int n=LoadInt(S); - f->code=luaM_newvector(S->L,n,Instruction); - f->sizecode=n; - LoadVector(S,f->code,n,sizeof(Instruction)); + +static TString *LoadString (LoadState *S) { + size_t size = LoadByte(S); + if (size == 0xFF) + LoadVar(S, size); + if (size == 0) + return NULL; + else if (--size <= LUAI_MAXSHORTLEN) { /* short string? */ + char buff[LUAI_MAXSHORTLEN]; + LoadVector(S, buff, size); + return luaS_newlstr(S->L, buff, size); + } + else { /* long string */ + TString *ts = luaS_createlngstrobj(S->L, size); + LoadVector(S, getstr(ts), size); /* load directly in final place */ + return ts; + } } -static void LoadFunction(LoadState* S, Proto* f); - -static void LoadConstants(LoadState* S, Proto* f) -{ - int i,n; - n=LoadInt(S); - f->k=luaM_newvector(S->L,n,TValue); - f->sizek=n; - for (i=0; ik[i]); - for (i=0; ik[i]; - int t=LoadChar(S); - switch (t) - { - case LUA_TNIL: - setnilvalue(o); - break; - case LUA_TBOOLEAN: - setbvalue(o,LoadChar(S)); - break; - case LUA_TNUMBER: - setnvalue(o,LoadNumber(S)); - break; - case LUA_TSTRING: - setsvalue2n(S->L,o,LoadString(S)); - break; - default: lua_assert(0); + +static void LoadCode (LoadState *S, Proto *f) { + int n = LoadInt(S); + f->code = luaM_newvector(S->L, n, Instruction); + f->sizecode = n; + LoadVector(S, f->code, n); +} + + +static void LoadFunction(LoadState *S, Proto *f, TString *psource); + + +static void LoadConstants (LoadState *S, Proto *f) { + int i; + int n = LoadInt(S); + f->k = luaM_newvector(S->L, n, TValue); + f->sizek = n; + for (i = 0; i < n; i++) + setnilvalue(&f->k[i]); + for (i = 0; i < n; i++) { + TValue *o = &f->k[i]; + int t = LoadByte(S); + switch (t) { + case LUA_TNIL: + setnilvalue(o); + break; + case LUA_TBOOLEAN: + setbvalue(o, LoadByte(S)); + break; + case LUA_TNUMFLT: + setfltvalue(o, LoadNumber(S)); + break; + case LUA_TNUMINT: + setivalue(o, LoadInteger(S)); + break; + case LUA_TSHRSTR: + case LUA_TLNGSTR: + setsvalue2n(S->L, o, LoadString(S)); + break; + default: + lua_assert(0); + } } - } - n=LoadInt(S); - f->p=luaM_newvector(S->L,n,Proto*); - f->sizep=n; - for (i=0; ip[i]=NULL; - for (i=0; ip[i]=luaF_newproto(S->L); - LoadFunction(S,f->p[i]); - } } -static void LoadUpvalues(LoadState* S, Proto* f) -{ - int i,n; - n=LoadInt(S); - f->upvalues=luaM_newvector(S->L,n,Upvaldesc); - f->sizeupvalues=n; - for (i=0; iupvalues[i].name=NULL; - for (i=0; iupvalues[i].instack=LoadByte(S); - f->upvalues[i].idx=LoadByte(S); - } + +static void LoadProtos (LoadState *S, Proto *f) { + int i; + int n = LoadInt(S); + f->p = luaM_newvector(S->L, n, Proto *); + f->sizep = n; + for (i = 0; i < n; i++) + f->p[i] = NULL; + for (i = 0; i < n; i++) { + f->p[i] = luaF_newproto(S->L); + LoadFunction(S, f->p[i], f->source); + } } -static void LoadDebug(LoadState* S, Proto* f) -{ - int i,n; - f->source=LoadString(S); - n=LoadInt(S); - f->lineinfo=luaM_newvector(S->L,n,int); - f->sizelineinfo=n; - LoadVector(S,f->lineinfo,n,sizeof(int)); - n=LoadInt(S); - f->locvars=luaM_newvector(S->L,n,LocVar); - f->sizelocvars=n; - for (i=0; ilocvars[i].varname=NULL; - for (i=0; ilocvars[i].varname=LoadString(S); - f->locvars[i].startpc=LoadInt(S); - f->locvars[i].endpc=LoadInt(S); - } - n=LoadInt(S); - for (i=0; iupvalues[i].name=LoadString(S); + +static void LoadUpvalues (LoadState *S, Proto *f) { + int i, n; + n = LoadInt(S); + f->upvalues = luaM_newvector(S->L, n, Upvaldesc); + f->sizeupvalues = n; + for (i = 0; i < n; i++) + f->upvalues[i].name = NULL; + for (i = 0; i < n; i++) { + f->upvalues[i].instack = LoadByte(S); + f->upvalues[i].idx = LoadByte(S); + } } -static void LoadFunction(LoadState* S, Proto* f) -{ - f->linedefined=LoadInt(S); - f->lastlinedefined=LoadInt(S); - f->numparams=LoadByte(S); - f->is_vararg=LoadByte(S); - f->maxstacksize=LoadByte(S); - LoadCode(S,f); - LoadConstants(S,f); - LoadUpvalues(S,f); - LoadDebug(S,f); + +static void LoadDebug (LoadState *S, Proto *f) { + int i, n; + n = LoadInt(S); + f->lineinfo = luaM_newvector(S->L, n, int); + f->sizelineinfo = n; + LoadVector(S, f->lineinfo, n); + n = LoadInt(S); + f->locvars = luaM_newvector(S->L, n, LocVar); + f->sizelocvars = n; + for (i = 0; i < n; i++) + f->locvars[i].varname = NULL; + for (i = 0; i < n; i++) { + f->locvars[i].varname = LoadString(S); + f->locvars[i].startpc = LoadInt(S); + f->locvars[i].endpc = LoadInt(S); + } + n = LoadInt(S); + for (i = 0; i < n; i++) + f->upvalues[i].name = LoadString(S); } -/* the code below must be consistent with the code in luaU_header */ -#define N0 LUAC_HEADERSIZE -#define N1 (sizeof(LUA_SIGNATURE)-sizeof(char)) -#define N2 N1+2 -#define N3 N2+6 - -static void LoadHeader(LoadState* S) -{ - lu_byte h[LUAC_HEADERSIZE]; - lu_byte s[LUAC_HEADERSIZE]; - luaU_header(h); - memcpy(s,h,sizeof(char)); /* first char already read */ - LoadBlock(S,s+sizeof(char),LUAC_HEADERSIZE-sizeof(char)); - if (memcmp(h,s,N0)==0) return; - if (memcmp(h,s,N1)!=0) error(S,"not a"); - if (memcmp(h,s,N2)!=0) error(S,"version mismatch in"); - if (memcmp(h,s,N3)!=0) error(S,"incompatible"); else error(S,"corrupted"); + +static void LoadFunction (LoadState *S, Proto *f, TString *psource) { + f->source = LoadString(S); + if (f->source == NULL) /* no source in dump? */ + f->source = psource; /* reuse parent's source */ + f->linedefined = LoadInt(S); + f->lastlinedefined = LoadInt(S); + f->numparams = LoadByte(S); + f->is_vararg = LoadByte(S); + f->maxstacksize = LoadByte(S); + LoadCode(S, f); + LoadConstants(S, f); + LoadUpvalues(S, f); + LoadProtos(S, f); + LoadDebug(S, f); } -/* -** load precompiled chunk -*/ -Closure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name) -{ - LoadState S; - Closure* cl; - if (*name=='@' || *name=='=') - S.name=name+1; - else if (*name==LUA_SIGNATURE[0]) - S.name="binary string"; - else - S.name=name; - S.L=L; - S.Z=Z; - S.b=buff; - LoadHeader(&S); - cl=luaF_newLclosure(L,1); - setclLvalue(L,L->top,cl); incr_top(L); - cl->l.p=luaF_newproto(L); - LoadFunction(&S,cl->l.p); - if (cl->l.p->sizeupvalues != 1) - { - Proto* p=cl->l.p; - cl=luaF_newLclosure(L,cl->l.p->sizeupvalues); - cl->l.p=p; - setclLvalue(L,L->top-1,cl); - } - luai_verifycode(L,buff,cl->l.p); - return cl; + +static void checkliteral (LoadState *S, const char *s, const char *msg) { + char buff[sizeof(LUA_SIGNATURE) + sizeof(LUAC_DATA)]; /* larger than both */ + size_t len = strlen(s); + LoadVector(S, buff, len); + if (memcmp(s, buff, len) != 0) + error(S, msg); +} + + +static void fchecksize (LoadState *S, size_t size, const char *tname) { + if (LoadByte(S) != size) + error(S, luaO_pushfstring(S->L, "%s size mismatch in", tname)); +} + + +#define checksize(S,t) fchecksize(S,sizeof(t),#t) + +static void checkHeader (LoadState *S) { + checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */ + if (LoadByte(S) != LUAC_VERSION) + error(S, "version mismatch in"); + if (LoadByte(S) != LUAC_FORMAT) + error(S, "format mismatch in"); + checkliteral(S, LUAC_DATA, "corrupted"); + checksize(S, int); + checksize(S, size_t); + checksize(S, Instruction); + checksize(S, lua_Integer); + checksize(S, lua_Number); + if (LoadInteger(S) != LUAC_INT) + error(S, "endianness mismatch in"); + if (LoadNumber(S) != LUAC_NUM) + error(S, "float format mismatch in"); } -#define MYINT(s) (s[0]-'0') -#define VERSION MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR) -#define FORMAT 0 /* this is the official format */ /* -* make header for precompiled chunks -* if you change the code below be sure to update LoadHeader and FORMAT above -* and LUAC_HEADERSIZE in lundump.h +** load precompiled chunk */ -void luaU_header (lu_byte* h) -{ - int x=1; - memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-sizeof(char)); - h+=sizeof(LUA_SIGNATURE)-sizeof(char); - *h++=cast_byte(VERSION); - *h++=cast_byte(FORMAT); - *h++=cast_byte(*(char*)&x); /* endianness */ - *h++=cast_byte(sizeof(int)); - *h++=cast_byte(sizeof(size_t)); - *h++=cast_byte(sizeof(Instruction)); - *h++=cast_byte(sizeof(lua_Number)); - *h++=cast_byte(((lua_Number)0.5)==0); /* is lua_Number integral? */ - memcpy(h,LUAC_TAIL,sizeof(LUAC_TAIL)-sizeof(char)); +LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) { + LoadState S; + LClosure *cl; + if (*name == '@' || *name == '=') + S.name = name + 1; + else if (*name == LUA_SIGNATURE[0]) + S.name = "binary string"; + else + S.name = name; + S.L = L; + S.Z = Z; + checkHeader(&S); + cl = luaF_newLclosure(L, LoadByte(&S)); + setclLvalue(L, L->top, cl); + luaD_inctop(L); + cl->p = luaF_newproto(L); + LoadFunction(&S, cl->p, NULL); + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luai_verifycode(L, buff, cl->p); + return cl; } + diff --git a/depends/lua/src/lutf8lib.c b/depends/lua/src/lutf8lib.c new file mode 100644 index 000000000..9042582d1 --- /dev/null +++ b/depends/lua/src/lutf8lib.c @@ -0,0 +1,256 @@ +/* +** $Id: lutf8lib.c,v 1.15 2015/03/28 19:16:55 roberto Exp $ +** Standard library for UTF-8 manipulation +** See Copyright Notice in lua.h +*/ + +#define lutf8lib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + +#define MAXUNICODE 0x10FFFF + +#define iscont(p) ((*(p) & 0xC0) == 0x80) + + +/* from strlib */ +/* translate a relative string position: negative means back from end */ +static lua_Integer u_posrelat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + + +/* +** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid. +*/ +static const char *utf8_decode (const char *o, int *val) { + static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF}; + const unsigned char *s = (const unsigned char *)o; + unsigned int c = s[0]; + unsigned int res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + while (c & 0x40) { /* still have continuation bytes? */ + int cc = s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + c <<= 1; /* to test next bit */ + } + res |= ((c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 3 || res > MAXUNICODE || res <= limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (val) *val = res; + return (const char *)s + 1; /* +1 to include first byte */ +} + + +/* +** utf8len(s [, i [, j]]) --> number of characters that start in the +** range [i,j], or nil + current position if 's' is not well formed in +** that interval +*/ +static int utflen (lua_State *L) { + int n = 0; + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2, + "initial position out of string"); + luaL_argcheck(L, --posj < (lua_Integer)len, 3, + "final position out of string"); + while (posi <= posj) { + const char *s1 = utf8_decode(s + posi, NULL); + if (s1 == NULL) { /* conversion error? */ + lua_pushnil(L); /* return nil ... */ + lua_pushinteger(L, posi + 1); /* ... and current position */ + return 2; + } + posi = s1 - s; + n++; + } + lua_pushinteger(L, n); + return 1; +} + + +/* +** codepoint(s, [i, [j]]) -> returns codepoints for all characters +** that start in the range [i,j] +*/ +static int codepoint (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len); + int n; + const char *se; + luaL_argcheck(L, posi >= 1, 2, "out of range"); + luaL_argcheck(L, pose <= (lua_Integer)len, 3, "out of range"); + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; + luaL_checkstack(L, n, "string slice too long"); + n = 0; + se = s + pose; + for (s += posi - 1; s < se;) { + int code; + s = utf8_decode(s, &code); + if (s == NULL) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, code); + n++; + } + return n; +} + + +static void pushutfchar (lua_State *L, int arg) { + lua_Integer code = luaL_checkinteger(L, arg); + luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, "value out of range"); + lua_pushfstring(L, "%U", (long)code); +} + + +/* +** utfchar(n1, n2, ...) -> char(n1)..char(n2)... +*/ +static int utfchar (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + if (n == 1) /* optimize common case of single char */ + pushutfchar(L, 1); + else { + int i; + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i = 1; i <= n; i++) { + pushutfchar(L, i); + luaL_addvalue(&b); + } + luaL_pushresult(&b); + } + return 1; +} + + +/* +** offset(s, n, [i]) -> index where n-th character counting from +** position 'i' starts; 0 means character at 'i'. +*/ +static int byteoffset (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = luaL_checkinteger(L, 2); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = u_posrelat(luaL_optinteger(L, 3, posi), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3, + "position out of range"); + if (n == 0) { + /* find beginning of current byte sequence */ + while (posi > 0 && iscont(s + posi)) posi--; + } + else { + if (iscont(s + posi)) + luaL_error(L, "initial position is a continuation byte"); + if (n < 0) { + while (n < 0 && posi > 0) { /* move back */ + do { /* find beginning of previous character */ + posi--; + } while (posi > 0 && iscont(s + posi)); + n++; + } + } + else { + n--; /* do not move for 1st character */ + while (n > 0 && posi < (lua_Integer)len) { + do { /* find beginning of next character */ + posi++; + } while (iscont(s + posi)); /* (cannot pass final '\0') */ + n--; + } + } + } + if (n == 0) /* did it find given character? */ + lua_pushinteger(L, posi + 1); + else /* no such character */ + lua_pushnil(L); + return 1; +} + + +static int iter_aux (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = lua_tointeger(L, 2) - 1; + if (n < 0) /* first iteration? */ + n = 0; /* start from here */ + else if (n < (lua_Integer)len) { + n++; /* skip current byte */ + while (iscont(s + n)) n++; /* and its continuations */ + } + if (n >= (lua_Integer)len) + return 0; /* no more codepoints */ + else { + int code; + const char *next = utf8_decode(s + n, &code); + if (next == NULL || iscont(next)) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, n + 1); + lua_pushinteger(L, code); + return 2; + } +} + + +static int iter_codes (lua_State *L) { + luaL_checkstring(L, 1); + lua_pushcfunction(L, iter_aux); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + + +/* pattern to match a single UTF-8 character */ +#define UTF8PATT "[\0-\x7F\xC2-\xF4][\x80-\xBF]*" + + +static const luaL_Reg funcs[] = { + {"offset", byteoffset}, + {"codepoint", codepoint}, + {"char", utfchar}, + {"len", utflen}, + {"codes", iter_codes}, + /* placeholders */ + {"charpattern", NULL}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_utf8 (lua_State *L) { + luaL_newlib(L, funcs); + lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)/sizeof(char) - 1); + lua_setfield(L, -2, "charpattern"); + return 1; +} + diff --git a/depends/lua/src/lvm.c b/depends/lua/src/lvm.c index 141b9fd19..84ade6b2f 100644 --- a/depends/lua/src/lvm.c +++ b/depends/lua/src/lvm.c @@ -1,17 +1,21 @@ /* -** $Id: lvm.c,v 2.155.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lvm.c,v 2.268 2016/02/05 19:59:14 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ +#define lvm_c +#define LUA_CORE + +#include "lprefix.h" +#include +#include +#include #include #include #include -#define lvm_c -#define LUA_CORE - #include "lua.h" #include "ldebug.h" @@ -27,304 +31,479 @@ #include "lvm.h" - /* limit for table tag-method chains (to avoid loops) */ -#define MAXTAGLOOP 100 +#define MAXTAGLOOP 2000 -const TValue *luaV_tonumber (const TValue *obj, TValue *n) { - lua_Number num; - if (ttisnumber(obj)) return obj; - if (ttisstring(obj) && luaO_str2d(svalue(obj), tsvalue(obj)->len, &num)) { - setnvalue(n, num); - return n; - } - else - return NULL; -} +/* +** 'l_intfitsf' checks whether a given integer can be converted to a +** float without rounding. Used in comparisons. Left undefined if +** all integers fit in a float precisely. +*/ +#if !defined(l_intfitsf) -int luaV_tostring (lua_State *L, StkId obj) { - if (!ttisnumber(obj)) - return 0; - else { - char s[LUAI_MAXNUMBER2STR]; - lua_Number n = nvalue(obj); - int l = lua_number2str(s, n); - setsvalue2s(L, obj, luaS_newlstr(L, s, l)); +/* number of bits in the mantissa of a float */ +#define NBM (l_mathlim(MANT_DIG)) + +/* +** Check whether some integers may not fit in a float, that is, whether +** (maxinteger >> NBM) > 0 (that implies (1 << NBM) <= maxinteger). +** (The shifts are done in parts to avoid shifting by more than the size +** of an integer. In a worst case, NBM == 113 for long double and +** sizeof(integer) == 32.) +*/ +#if ((((LUA_MAXINTEGER >> (NBM / 4)) >> (NBM / 4)) >> (NBM / 4)) \ + >> (NBM - (3 * (NBM / 4)))) > 0 + +#define l_intfitsf(i) \ + (-((lua_Integer)1 << NBM) <= (i) && (i) <= ((lua_Integer)1 << NBM)) + +#endif + +#endif + + + +/* +** Try to convert a value to a float. The float case is already handled +** by the macro 'tonumber'. +*/ +int luaV_tonumber_ (const TValue *obj, lua_Number *n) { + TValue v; + if (ttisinteger(obj)) { + *n = cast_num(ivalue(obj)); + return 1; + } + else if (cvt2num(obj) && /* string convertible to number? */ + luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { + *n = nvalue(&v); /* convert result of 'luaO_str2num' to a float */ return 1; } + else + return 0; /* conversion failed */ } -static void traceexec (lua_State *L) { - CallInfo *ci = L->ci; - lu_byte mask = L->hookmask; - int counthook = ((mask & LUA_MASKCOUNT) && L->hookcount == 0); - if (counthook) - resethookcount(L); /* reset count */ - if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ - ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ - return; /* do not call hook again (VM yielded, so it did not move) */ +/* +** try to convert a value to an integer, rounding according to 'mode': +** mode == 0: accepts only integral values +** mode == 1: takes the floor of the number +** mode == 2: takes the ceil of the number +*/ +int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) { + TValue v; + again: + if (ttisfloat(obj)) { + lua_Number n = fltvalue(obj); + lua_Number f = l_floor(n); + if (n != f) { /* not an integral value? */ + if (mode == 0) return 0; /* fails if mode demands integral value */ + else if (mode > 1) /* needs ceil? */ + f += 1; /* convert floor to ceil (remember: n != f) */ + } + return lua_numbertointeger(f, p); } - if (counthook) - luaD_hook(L, LUA_HOOKCOUNT, -1); /* call count hook */ - if (mask & LUA_MASKLINE) { - Proto *p = ci_func(ci)->p; - int npc = pcRel(ci->u.l.savedpc, p); - int newline = getfuncline(p, npc); - if (npc == 0 || /* call linehook when enter a new function, */ - ci->u.l.savedpc <= L->oldpc || /* when jump back (loop), or when */ - newline != getfuncline(p, pcRel(L->oldpc, p))) /* enter a new line */ - luaD_hook(L, LUA_HOOKLINE, newline); /* call line hook */ + else if (ttisinteger(obj)) { + *p = ivalue(obj); + return 1; } - L->oldpc = ci->u.l.savedpc; - if (L->status == LUA_YIELD) { /* did hook yield? */ - if (counthook) - L->hookcount = 1; /* undo decrement to zero */ - ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ - ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ - ci->func = L->top - 1; /* protect stack below results */ - luaD_throw(L, LUA_YIELD); + else if (cvt2num(obj) && + luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { + obj = &v; + goto again; /* convert result from 'luaO_str2num' to an integer */ } + return 0; /* conversion failed */ } -static void callTM (lua_State *L, const TValue *f, const TValue *p1, - const TValue *p2, TValue *p3, int hasres) { - ptrdiff_t result = savestack(L, p3); - setobj2s(L, L->top++, f); /* push function */ - setobj2s(L, L->top++, p1); /* 1st argument */ - setobj2s(L, L->top++, p2); /* 2nd argument */ - if (!hasres) /* no result? 'p3' is third argument */ - setobj2s(L, L->top++, p3); /* 3rd argument */ - /* metamethod may yield only when called from Lua code */ - luaD_call(L, L->top - (4 - hasres), hasres, isLua(L->ci)); - if (hasres) { /* if has result, move it to its place */ - p3 = restorestack(L, result); - setobjs2s(L, p3, --L->top); +/* +** Try to convert a 'for' limit to an integer, preserving the +** semantics of the loop. +** (The following explanation assumes a non-negative step; it is valid +** for negative steps mutatis mutandis.) +** If the limit can be converted to an integer, rounding down, that is +** it. +** Otherwise, check whether the limit can be converted to a number. If +** the number is too large, it is OK to set the limit as LUA_MAXINTEGER, +** which means no limit. If the number is too negative, the loop +** should not run, because any initial integer value is larger than the +** limit. So, it sets the limit to LUA_MININTEGER. 'stopnow' corrects +** the extreme case when the initial value is LUA_MININTEGER, in which +** case the LUA_MININTEGER limit would still run the loop once. +*/ +static int forlimit (const TValue *obj, lua_Integer *p, lua_Integer step, + int *stopnow) { + *stopnow = 0; /* usually, let loops run */ + if (!luaV_tointeger(obj, p, (step < 0 ? 2 : 1))) { /* not fit in integer? */ + lua_Number n; /* try to convert to float */ + if (!tonumber(obj, &n)) /* cannot convert to float? */ + return 0; /* not a number */ + if (luai_numlt(0, n)) { /* if true, float is larger than max integer */ + *p = LUA_MAXINTEGER; + if (step < 0) *stopnow = 1; + } + else { /* float is smaller than min integer */ + *p = LUA_MININTEGER; + if (step >= 0) *stopnow = 1; + } } + return 1; } -void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { - int loop; +/* +** Finish the table access 'val = t[key]'. +** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to +** t[k] entry (which must be nil). +*/ +void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, + const TValue *slot) { + int loop; /* counter to avoid infinite loops */ + const TValue *tm; /* metamethod */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* `t' is a table? */ - Table *h = hvalue(t); - const TValue *res = luaH_get(h, key); /* do a primitive get */ - if (!ttisnil(res) || /* result is not nil? */ - (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ - setobj2s(L, val, res); + if (slot == NULL) { /* 't' is not a table? */ + lua_assert(!ttistable(t)); + tm = luaT_gettmbyobj(L, t, TM_INDEX); + if (ttisnil(tm)) + luaG_typeerror(L, t, "index"); /* no metamethod */ + /* else will try the metamethod */ + } + else { /* 't' is a table */ + lua_assert(ttisnil(slot)); + tm = fasttm(L, hvalue(t)->metatable, TM_INDEX); /* table's metamethod */ + if (tm == NULL) { /* no metamethod? */ + setnilvalue(val); /* result is nil */ return; } - /* else will try the tag method */ + /* else will try the metamethod */ } - else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) - luaG_typeerror(L, t, "index"); - if (ttisfunction(tm)) { - callTM(L, tm, t, key, val, 1); + if (ttisfunction(tm)) { /* is metamethod a function? */ + luaT_callTM(L, tm, t, key, val, 1); /* call it */ + return; + } + t = tm; /* else try to access 'tm[key]' */ + if (luaV_fastget(L,t,key,slot,luaH_get)) { /* fast track? */ + setobj2s(L, val, slot); /* done */ return; } - t = tm; /* else repeat with 'tm' */ + /* else repeat (tail call 'luaV_finishget') */ } - luaG_runerror(L, "loop in gettable"); + luaG_runerror(L, "'__index' chain too long; possible loop"); } -void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { - int loop; +/* +** Finish a table assignment 't[key] = val'. +** If 'slot' is NULL, 't' is not a table. Otherwise, 'slot' points +** to the entry 't[key]', or to 'luaO_nilobject' if there is no such +** entry. (The value at 'slot' must be nil, otherwise 'luaV_fastset' +** would have done the job.) +*/ +void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot) { + int loop; /* counter to avoid infinite loops */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* `t' is a table? */ - Table *h = hvalue(t); - TValue *oldval = cast(TValue *, luaH_get(h, key)); - /* if previous value is not nil, there must be a previous entry - in the table; moreover, a metamethod has no relevance */ - if (!ttisnil(oldval) || - /* previous value is nil; must check the metamethod */ - ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL && - /* no metamethod; is there a previous entry in the table? */ - (oldval != luaO_nilobject || - /* no previous entry; must create one. (The next test is - always true; we only need the assignment.) */ - (oldval = luaH_newkey(L, h, key), 1)))) { + const TValue *tm; /* '__newindex' metamethod */ + if (slot != NULL) { /* is 't' a table? */ + Table *h = hvalue(t); /* save 't' table */ + lua_assert(ttisnil(slot)); /* old value must be nil */ + tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ + if (tm == NULL) { /* no metamethod? */ + if (slot == luaO_nilobject) /* no previous entry? */ + slot = luaH_newkey(L, h, key); /* create one */ /* no metamethod and (now) there is an entry with given key */ - setobj2t(L, oldval, val); /* assign new value to that entry */ + setobj2t(L, cast(TValue *, slot), val); /* set its new value */ invalidateTMcache(h); - luaC_barrierback(L, obj2gco(h), val); + luaC_barrierback(L, h, val); return; } /* else will try the metamethod */ } - else /* not a table; check metamethod */ + else { /* not a table; check metamethod */ if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_typeerror(L, t, "index"); - /* there is a metamethod */ + } + /* try the metamethod */ if (ttisfunction(tm)) { - callTM(L, tm, t, key, val, 0); + luaT_callTM(L, tm, t, key, val, 0); return; } - t = tm; /* else repeat with 'tm' */ + t = tm; /* else repeat assignment over 'tm' */ + if (luaV_fastset(L, t, key, slot, luaH_get, val)) + return; /* done */ + /* else loop */ } - luaG_runerror(L, "loop in settable"); + luaG_runerror(L, "'__newindex' chain too long; possible loop"); } -static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, - StkId res, TMS event) { - const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ - if (ttisnil(tm)) - tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ - if (ttisnil(tm)) return 0; - callTM(L, tm, p1, p2, res, 1); - return 1; +/* +** Compare two strings 'ls' x 'rs', returning an integer smaller-equal- +** -larger than zero if 'ls' is smaller-equal-larger than 'rs'. +** The code is a little tricky because it allows '\0' in the strings +** and it uses 'strcoll' (to respect locales) for each segments +** of the strings. +*/ +static int l_strcmp (const TString *ls, const TString *rs) { + const char *l = getstr(ls); + size_t ll = tsslen(ls); + const char *r = getstr(rs); + size_t lr = tsslen(rs); + for (;;) { /* for each segment */ + int temp = strcoll(l, r); + if (temp != 0) /* not equal? */ + return temp; /* done */ + else { /* strings are equal up to a '\0' */ + size_t len = strlen(l); /* index of first '\0' in both strings */ + if (len == lr) /* 'rs' is finished? */ + return (len == ll) ? 0 : 1; /* check 'ls' */ + else if (len == ll) /* 'ls' is finished? */ + return -1; /* 'ls' is smaller than 'rs' ('rs' is not finished) */ + /* both strings longer than 'len'; go on comparing after the '\0' */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } } -static const TValue *get_equalTM (lua_State *L, Table *mt1, Table *mt2, - TMS event) { - const TValue *tm1 = fasttm(L, mt1, event); - const TValue *tm2; - if (tm1 == NULL) return NULL; /* no metamethod */ - if (mt1 == mt2) return tm1; /* same metatables => same metamethods */ - tm2 = fasttm(L, mt2, event); - if (tm2 == NULL) return NULL; /* no metamethod */ - if (luaV_rawequalobj(tm1, tm2)) /* same metamethods? */ - return tm1; - return NULL; +/* +** Check whether integer 'i' is less than float 'f'. If 'i' has an +** exact representation as a float ('l_intfitsf'), compare numbers as +** floats. Otherwise, if 'f' is outside the range for integers, result +** is trivial. Otherwise, compare them as integers. (When 'i' has no +** float representation, either 'f' is "far away" from 'i' or 'f' has +** no precision left for a fractional part; either way, how 'f' is +** truncated is irrelevant.) When 'f' is NaN, comparisons must result +** in false. +*/ +static int LTintfloat (lua_Integer i, lua_Number f) { +#if defined(l_intfitsf) + if (!l_intfitsf(i)) { + if (f >= -cast_num(LUA_MININTEGER)) /* -minint == maxint + 1 */ + return 1; /* f >= maxint + 1 > i */ + else if (f > cast_num(LUA_MININTEGER)) /* minint < f <= maxint ? */ + return (i < cast(lua_Integer, f)); /* compare them as integers */ + else /* f <= minint <= i (or 'f' is NaN) --> not(i < f) */ + return 0; + } +#endif + return luai_numlt(cast_num(i), f); /* compare them as floats */ } -static int call_orderTM (lua_State *L, const TValue *p1, const TValue *p2, - TMS event) { - if (!call_binTM(L, p1, p2, L->top, event)) - return -1; /* no metamethod */ - else - return !l_isfalse(L->top); +/* +** Check whether integer 'i' is less than or equal to float 'f'. +** See comments on previous function. +*/ +static int LEintfloat (lua_Integer i, lua_Number f) { +#if defined(l_intfitsf) + if (!l_intfitsf(i)) { + if (f >= -cast_num(LUA_MININTEGER)) /* -minint == maxint + 1 */ + return 1; /* f >= maxint + 1 > i */ + else if (f >= cast_num(LUA_MININTEGER)) /* minint <= f <= maxint ? */ + return (i <= cast(lua_Integer, f)); /* compare them as integers */ + else /* f < minint <= i (or 'f' is NaN) --> not(i <= f) */ + return 0; + } +#endif + return luai_numle(cast_num(i), f); /* compare them as floats */ } -static int l_strcmp (const TString *ls, const TString *rs) { - const char *l = getstr(ls); - size_t ll = ls->tsv.len; - const char *r = getstr(rs); - size_t lr = rs->tsv.len; - for (;;) { - int temp = strcoll(l, r); - if (temp != 0) return temp; - else { /* strings are equal up to a `\0' */ - size_t len = strlen(l); /* index of first `\0' in both strings */ - if (len == lr) /* r is finished? */ - return (len == ll) ? 0 : 1; - else if (len == ll) /* l is finished? */ - return -1; /* l is smaller than r (because r is not finished) */ - /* both strings longer than `len'; go on comparing (after the `\0') */ - len++; - l += len; ll -= len; r += len; lr -= len; - } +/* +** Return 'l < r', for numbers. +*/ +static int LTnum (const TValue *l, const TValue *r) { + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li < ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LTintfloat(li, fltvalue(r)); /* l < r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numlt(lf, fltvalue(r)); /* both are float */ + else if (luai_numisnan(lf)) /* 'r' is int and 'l' is float */ + return 0; /* NaN < i is always false */ + else /* without NaN, (l < r) <--> not(r <= l) */ + return !LEintfloat(ivalue(r), lf); /* not (r <= l) ? */ } } +/* +** Return 'l <= r', for numbers. +*/ +static int LEnum (const TValue *l, const TValue *r) { + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li <= ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LEintfloat(li, fltvalue(r)); /* l <= r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numle(lf, fltvalue(r)); /* both are float */ + else if (luai_numisnan(lf)) /* 'r' is int and 'l' is float */ + return 0; /* NaN <= i is always false */ + else /* without NaN, (l <= r) <--> not(r < l) */ + return !LTintfloat(ivalue(r), lf); /* not (r < l) ? */ + } +} + + +/* +** Main operation less than; return 'l < r'. +*/ int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { int res; - if (ttisnumber(l) && ttisnumber(r)) - return luai_numlt(L, nvalue(l), nvalue(r)); - else if (ttisstring(l) && ttisstring(r)) - return l_strcmp(rawtsvalue(l), rawtsvalue(r)) < 0; - else if ((res = call_orderTM(L, l, r, TM_LT)) < 0) - luaG_ordererror(L, l, r); + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LTnum(l, r); + else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) < 0; + else if ((res = luaT_callorderTM(L, l, r, TM_LT)) < 0) /* no metamethod? */ + luaG_ordererror(L, l, r); /* error */ return res; } +/* +** Main operation less than or equal to; return 'l <= r'. If it needs +** a metamethod and there is no '__le', try '__lt', based on +** l <= r iff !(r < l) (assuming a total order). If the metamethod +** yields during this substitution, the continuation has to know +** about it (to negate the result of r= 0) /* first try `le' */ + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LEnum(l, r); + else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; + else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* try 'le' */ return res; - else if ((res = call_orderTM(L, r, l, TM_LT)) < 0) /* else try `lt' */ - luaG_ordererror(L, l, r); - return !res; + else { /* try 'lt': */ + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ + res = luaT_callorderTM(L, r, l, TM_LT); + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ + if (res < 0) + luaG_ordererror(L, l, r); + return !res; /* result is negated */ + } } /* -** equality of Lua values. L == NULL means raw equality (no metamethods) +** Main operation for equality of Lua values; return 't1 == t2'. +** L == NULL means raw equality (no metamethods) */ -int luaV_equalobj_ (lua_State *L, const TValue *t1, const TValue *t2) { +int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; - lua_assert(ttisequal(t1, t2)); + if (ttype(t1) != ttype(t2)) { /* not the same variant? */ + if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER) + return 0; /* only numbers can be equal with different variants */ + else { /* two numbers with different variants */ + lua_Integer i1, i2; /* compare them as integers */ + return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); + } + } + /* values have same type and same variant */ switch (ttype(t1)) { case LUA_TNIL: return 1; - case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); + case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2)); + case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); case LUA_TLCF: return fvalue(t1) == fvalue(t2); - case LUA_TSHRSTR: return eqshrstr(rawtsvalue(t1), rawtsvalue(t2)); - case LUA_TLNGSTR: return luaS_eqlngstr(rawtsvalue(t1), rawtsvalue(t2)); + case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); + case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); case LUA_TUSERDATA: { if (uvalue(t1) == uvalue(t2)) return 1; else if (L == NULL) return 0; - tm = get_equalTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ); + tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } case LUA_TTABLE: { if (hvalue(t1) == hvalue(t2)) return 1; else if (L == NULL) return 0; - tm = get_equalTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); + tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } default: - lua_assert(iscollectable(t1)); return gcvalue(t1) == gcvalue(t2); } - if (tm == NULL) return 0; /* no TM? */ - callTM(L, tm, t1, t2, L->top, 1); /* call TM */ + if (tm == NULL) /* no TM? */ + return 0; /* objects are different */ + luaT_callTM(L, tm, t1, t2, L->top, 1); /* call TM */ return !l_isfalse(L->top); } +/* macro used by 'luaV_concat' to ensure that element at 'o' is a string */ +#define tostring(L,o) \ + (ttisstring(o) || (cvt2str(o) && (luaO_tostring(L, o), 1))) + +#define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) + +/* copy strings in stack from top - n up to top - 1 to buffer */ +static void copy2buff (StkId top, int n, char *buff) { + size_t tl = 0; /* size already copied */ + do { + size_t l = vslen(top - n); /* length of string being copied */ + memcpy(buff + tl, svalue(top - n), l * sizeof(char)); + tl += l; + } while (--n > 0); +} + + +/* +** Main operation for concatenation: concat 'total' values in the stack, +** from 'L->top - total' up to 'L->top - 1'. +*/ void luaV_concat (lua_State *L, int total) { lua_assert(total >= 2); do { StkId top = L->top; int n = 2; /* number of elements handled in this pass (at least 2) */ - if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { - if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) - luaG_concaterror(L, top-2, top-1); - } - else if (tsvalue(top-1)->len == 0) /* second operand is empty? */ - (void)tostring(L, top - 2); /* result is first operand */ - else if (ttisstring(top-2) && tsvalue(top-2)->len == 0) { + if (!(ttisstring(top-2) || cvt2str(top-2)) || !tostring(L, top-1)) + luaT_trybinTM(L, top-2, top-1, top-2, TM_CONCAT); + else if (isemptystr(top - 1)) /* second operand is empty? */ + cast_void(tostring(L, top - 2)); /* result is first operand */ + else if (isemptystr(top - 2)) { /* first operand is an empty string? */ setobjs2s(L, top - 2, top - 1); /* result is second op. */ } else { /* at least two non-empty string values; get as many as possible */ - size_t tl = tsvalue(top-1)->len; - char *buffer; - int i; - /* collect total length */ - for (i = 1; i < total && tostring(L, top-i-1); i++) { - size_t l = tsvalue(top-i-1)->len; - if (l >= (MAX_SIZET/sizeof(char)) - tl) + size_t tl = vslen(top - 1); + TString *ts; + /* collect total length and number of strings */ + for (n = 1; n < total && tostring(L, top - n - 1); n++) { + size_t l = vslen(top - n - 1); + if (l >= (MAX_SIZE/sizeof(char)) - tl) luaG_runerror(L, "string length overflow"); tl += l; } - buffer = luaZ_openspace(L, &G(L)->buff, tl); - tl = 0; - n = i; - do { /* concat all strings */ - size_t l = tsvalue(top-i)->len; - memcpy(buffer+tl, svalue(top-i), l * sizeof(char)); - tl += l; - } while (--i > 0); - setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); + if (tl <= LUAI_MAXSHORTLEN) { /* is result a short string? */ + char buff[LUAI_MAXSHORTLEN]; + copy2buff(top, n, buff); /* copy strings to buffer */ + ts = luaS_newlstr(L, buff, tl); + } + else { /* long string; copy strings directly to final result */ + ts = luaS_createlngstrobj(L, tl); + copy2buff(top, n, getstr(ts)); + } + setsvalue2s(L, top - n, ts); /* create result */ } total -= n-1; /* got 'n' strings to create 1 new */ L->top -= n-1; /* popped 'n' strings and pushed one */ @@ -332,18 +511,25 @@ void luaV_concat (lua_State *L, int total) { } +/* +** Main operation 'ra' = #rb'. +*/ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { const TValue *tm; - switch (ttypenv(rb)) { + switch (ttype(rb)) { case LUA_TTABLE: { Table *h = hvalue(rb); tm = fasttm(L, h->metatable, TM_LEN); if (tm) break; /* metamethod? break switch to call it */ - setnvalue(ra, cast_num(luaH_getn(h))); /* else primitive len */ + setivalue(ra, luaH_getn(h)); /* else primitive len */ return; } - case LUA_TSTRING: { - setnvalue(ra, cast_num(tsvalue(rb)->len)); + case LUA_TSHRSTR: { + setivalue(ra, tsvalue(rb)->shrlen); + return; + } + case LUA_TLNGSTR: { + setivalue(ra, tsvalue(rb)->u.lnglen); return; } default: { /* try metamethod */ @@ -353,21 +539,66 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { break; } } - callTM(L, tm, rb, rb, ra, 1); + luaT_callTM(L, tm, rb, rb, ra, 1); +} + + +/* +** Integer division; return 'm // n', that is, floor(m/n). +** C division truncates its result (rounds towards zero). +** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer, +** otherwise 'floor(q) == trunc(q) - 1'. +*/ +lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to divide by zero"); + return intop(-, 0, m); /* n==-1; avoid overflow with 0x80000...//-1 */ + } + else { + lua_Integer q = m / n; /* perform C division */ + if ((m ^ n) < 0 && m % n != 0) /* 'm/n' would be negative non-integer? */ + q -= 1; /* correct result for different rounding */ + return q; + } +} + + +/* +** Integer modulus; return 'm % n'. (Assume that C '%' with +** negative operands follows C99 behavior. See previous comment +** about luaV_div.) +*/ +lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to perform 'n%%0'"); + return 0; /* m % -1 == 0; avoid overflow with 0x80000...%-1 */ + } + else { + lua_Integer r = m % n; + if (r != 0 && (m ^ n) < 0) /* 'm/n' would be non-integer negative? */ + r += n; /* correct result for different rounding */ + return r; + } } -void luaV_arith (lua_State *L, StkId ra, const TValue *rb, - const TValue *rc, TMS op) { - TValue tempb, tempc; - const TValue *b, *c; - if ((b = luaV_tonumber(rb, &tempb)) != NULL && - (c = luaV_tonumber(rc, &tempc)) != NULL) { - lua_Number res = luaO_arith(op - TM_ADD + LUA_OPADD, nvalue(b), nvalue(c)); - setnvalue(ra, res); +/* number of bits in an integer */ +#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT) + +/* +** Shift left operation. (Shift right just negates 'y'.) +*/ +lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) { + if (y < 0) { /* shift right? */ + if (y <= -NBITS) return 0; + else return intop(>>, x, -y); + } + else { /* shift left */ + if (y >= NBITS) return 0; + else return intop(<<, x, y); } - else if (!call_binTM(L, rb, rc, ra, op)) - luaG_aritherror(L, rb, rc); } @@ -376,15 +607,15 @@ void luaV_arith (lua_State *L, StkId ra, const TValue *rb, ** whether there is a cached closure with the same upvalues needed by ** new closure to be created. */ -static Closure *getcached (Proto *p, UpVal **encup, StkId base) { - Closure *c = p->cache; +static LClosure *getcached (Proto *p, UpVal **encup, StkId base) { + LClosure *c = p->cache; if (c != NULL) { /* is there a cached closure? */ int nup = p->sizeupvalues; Upvaldesc *uv = p->upvalues; int i; for (i = 0; i < nup; i++) { /* check whether it has right upvalues */ TValue *v = uv[i].instack ? base + uv[i].idx : encup[uv[i].idx]->v; - if (c->l.upvals[i]->v != v) + if (c->upvals[i]->v != v) return NULL; /* wrong upvalue; cannot reuse closure */ } } @@ -394,26 +625,28 @@ static Closure *getcached (Proto *p, UpVal **encup, StkId base) { /* ** create a new Lua closure, push it in the stack, and initialize -** its upvalues. Note that the call to 'luaC_barrierproto' must come -** before the assignment to 'p->cache', as the function needs the -** original value of that field. +** its upvalues. Note that the closure is not cached if prototype is +** already black (which means that 'cache' was already cleared by the +** GC). */ static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, StkId ra) { int nup = p->sizeupvalues; Upvaldesc *uv = p->upvalues; int i; - Closure *ncl = luaF_newLclosure(L, nup); - ncl->l.p = p; + LClosure *ncl = luaF_newLclosure(L, nup); + ncl->p = p; setclLvalue(L, ra, ncl); /* anchor new closure in stack */ for (i = 0; i < nup; i++) { /* fill in its upvalues */ if (uv[i].instack) /* upvalue refers to local variable? */ - ncl->l.upvals[i] = luaF_findupval(L, base + uv[i].idx); + ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx); else /* get upvalue from enclosing function */ - ncl->l.upvals[i] = encup[uv[i].idx]; + ncl->upvals[i] = encup[uv[i].idx]; + ncl->upvals[i]->refcount++; + /* new closure is white, so we do not need a barrier here */ } - luaC_barrierproto(L, p, ncl); - p->cache = ncl; /* save it on cache for reuse */ + if (!isblack(p)) /* cache will not break GC invariant? */ + p->cache = ncl; /* save it on cache for reuse */ } @@ -426,8 +659,10 @@ void luaV_finishOp (lua_State *L) { Instruction inst = *(ci->u.l.savedpc - 1); /* interrupted instruction */ OpCode op = GET_OPCODE(inst); switch (op) { /* finish its execution */ - case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: - case OP_MOD: case OP_POW: case OP_UNM: case OP_LEN: + case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_IDIV: + case OP_BAND: case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: + case OP_MOD: case OP_POW: + case OP_UNM: case OP_BNOT: case OP_LEN: case OP_GETTABUP: case OP_GETTABLE: case OP_SELF: { setobjs2s(L, base + GETARG_A(inst), --L->top); break; @@ -435,18 +670,18 @@ void luaV_finishOp (lua_State *L) { case OP_LE: case OP_LT: case OP_EQ: { int res = !l_isfalse(L->top - 1); L->top--; - /* metamethod should not be called when operand is K */ - lua_assert(!ISK(GETARG_B(inst))); - if (op == OP_LE && /* "<=" using "<" instead? */ - ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE))) - res = !res; /* invert result */ + if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ + lua_assert(op == OP_LE); + ci->callstatus ^= CIST_LEQ; /* clear mark */ + res = !res; /* negate result */ + } lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); if (res != GETARG_A(inst)) /* condition failed? */ ci->u.l.savedpc++; /* skip jump instruction */ break; } case OP_CONCAT: { - StkId top = L->top - 1; /* top when 'call_binTM' was called */ + StkId top = L->top - 1; /* top when 'luaT_trybinTM' was called */ int b = GETARG_B(inst); /* first element to concatenate */ int total = cast_int(top - 1 - (base + b)); /* yet to concatenate */ setobj2s(L, top - 2, top); /* put TM result in proper position */ @@ -477,31 +712,32 @@ void luaV_finishOp (lua_State *L) { + /* -** some macros for common tasks in `luaV_execute' +** {================================================================== +** Function 'luaV_execute': main interpreter loop +** =================================================================== */ -#if !defined luai_runtimecheck -#define luai_runtimecheck(L, c) /* void */ -#endif + +/* +** some macros for common tasks in 'luaV_execute' +*/ #define RA(i) (base+GETARG_A(i)) -/* to be used after possible stack reallocation */ #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) -#define KBx(i) \ - (k + (GETARG_Bx(i) != 0 ? GETARG_Bx(i) - 1 : GETARG_Ax(*ci->u.l.savedpc++))) /* execute a jump instruction */ #define dojump(ci,i,e) \ { int a = GETARG_A(i); \ - if (a > 0) luaF_close(L, ci->u.l.base + a - 1); \ + if (a != 0) luaF_close(L, ci->u.l.base + a - 1); \ ci->u.l.savedpc += GETARG_sBx(i) + e; } /* for test instructions, execute the jump instruction that follows it */ @@ -511,96 +747,124 @@ void luaV_finishOp (lua_State *L) { #define Protect(x) { {x;}; base = ci->u.l.base; } #define checkGC(L,c) \ - Protect( luaC_condGC(L,{L->top = (c); /* limit of live values */ \ - luaC_step(L); \ - L->top = ci->top;}) /* restore top */ \ - luai_threadyield(L); ) + { luaC_condGC(L, L->top = (c), /* limit of live values */ \ + Protect(L->top = ci->top)); /* restore top */ \ + luai_threadyield(L); } + + +/* fetch an instruction and prepare its execution */ +#define vmfetch() { \ + i = *(ci->u.l.savedpc++); \ + if (L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) \ + Protect(luaG_traceexec(L)); \ + ra = RA(i); /* WARNING: any stack reallocation invalidates 'ra' */ \ + lua_assert(base == ci->u.l.base); \ + lua_assert(base <= L->top && L->top < L->stack + L->stacksize); \ +} +#define vmdispatch(o) switch(o) +#define vmcase(l) case l: +#define vmbreak break -#define arith_op(op,tm) { \ - TValue *rb = RKB(i); \ - TValue *rc = RKC(i); \ - if (ttisnumber(rb) && ttisnumber(rc)) { \ - lua_Number nb = nvalue(rb), nc = nvalue(rc); \ - setnvalue(ra, op(L, nb, nc)); \ - } \ - else { Protect(luaV_arith(L, ra, rb, rc, tm)); } } + +/* +** copy of 'luaV_gettable', but protecting the call to potential +** metamethod (which can reallocate the stack) +*/ +#define gettableProtected(L,t,k,v) { const TValue *slot; \ + if (luaV_fastget(L,t,k,slot,luaH_get)) { setobj2s(L, v, slot); } \ + else Protect(luaV_finishget(L,t,k,v,slot)); } + + +/* same for 'luaV_settable' */ +#define settableProtected(L,t,k,v) { const TValue *slot; \ + if (!luaV_fastset(L,t,k,slot,luaH_get,v)) \ + Protect(luaV_finishset(L,t,k,v,slot)); } -#define vmdispatch(o) switch(o) -#define vmcase(l,b) case l: {b} break; -#define vmcasenb(l,b) case l: {b} /* nb = no break */ void luaV_execute (lua_State *L) { CallInfo *ci = L->ci; LClosure *cl; TValue *k; StkId base; + ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */ newframe: /* reentry point when frame changes (call/return) */ lua_assert(ci == L->ci); - cl = clLvalue(ci->func); - k = cl->p->k; - base = ci->u.l.base; + cl = clLvalue(ci->func); /* local reference to function's closure */ + k = cl->p->k; /* local reference to function's constant table */ + base = ci->u.l.base; /* local copy of function's base */ /* main loop of interpreter */ for (;;) { - Instruction i = *(ci->u.l.savedpc++); + Instruction i; StkId ra; - if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && - (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { - Protect(traceexec(L)); - } - /* WARNING: several calls may realloc the stack and invalidate `ra' */ - ra = RA(i); - lua_assert(base == ci->u.l.base); - lua_assert(base <= L->top && L->top < L->stack + L->stacksize); + vmfetch(); vmdispatch (GET_OPCODE(i)) { - vmcase(OP_MOVE, + vmcase(OP_MOVE) { setobjs2s(L, ra, RB(i)); - ) - vmcase(OP_LOADK, + vmbreak; + } + vmcase(OP_LOADK) { TValue *rb = k + GETARG_Bx(i); setobj2s(L, ra, rb); - ) - vmcase(OP_LOADKX, + vmbreak; + } + vmcase(OP_LOADKX) { TValue *rb; lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); rb = k + GETARG_Ax(*ci->u.l.savedpc++); setobj2s(L, ra, rb); - ) - vmcase(OP_LOADBOOL, + vmbreak; + } + vmcase(OP_LOADBOOL) { setbvalue(ra, GETARG_B(i)); if (GETARG_C(i)) ci->u.l.savedpc++; /* skip next instruction (if C) */ - ) - vmcase(OP_LOADNIL, + vmbreak; + } + vmcase(OP_LOADNIL) { int b = GETARG_B(i); do { setnilvalue(ra++); } while (b--); - ) - vmcase(OP_GETUPVAL, + vmbreak; + } + vmcase(OP_GETUPVAL) { int b = GETARG_B(i); setobj2s(L, ra, cl->upvals[b]->v); - ) - vmcase(OP_GETTABUP, - int b = GETARG_B(i); - Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra)); - ) - vmcase(OP_GETTABLE, - Protect(luaV_gettable(L, RB(i), RKC(i), ra)); - ) - vmcase(OP_SETTABUP, - int a = GETARG_A(i); - Protect(luaV_settable(L, cl->upvals[a]->v, RKB(i), RKC(i))); - ) - vmcase(OP_SETUPVAL, + vmbreak; + } + vmcase(OP_GETTABUP) { + TValue *upval = cl->upvals[GETARG_B(i)]->v; + TValue *rc = RKC(i); + gettableProtected(L, upval, rc, ra); + vmbreak; + } + vmcase(OP_GETTABLE) { + StkId rb = RB(i); + TValue *rc = RKC(i); + gettableProtected(L, rb, rc, ra); + vmbreak; + } + vmcase(OP_SETTABUP) { + TValue *upval = cl->upvals[GETARG_A(i)]->v; + TValue *rb = RKB(i); + TValue *rc = RKC(i); + settableProtected(L, upval, rb, rc); + vmbreak; + } + vmcase(OP_SETUPVAL) { UpVal *uv = cl->upvals[GETARG_B(i)]; setobj(L, uv->v, ra); - luaC_barrier(L, uv, ra); - ) - vmcase(OP_SETTABLE, - Protect(luaV_settable(L, ra, RKB(i), RKC(i))); - ) - vmcase(OP_NEWTABLE, + luaC_upvalbarrier(L, uv); + vmbreak; + } + vmcase(OP_SETTABLE) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + settableProtected(L, ra, rb, rc); + vmbreak; + } + vmcase(OP_NEWTABLE) { int b = GETARG_B(i); int c = GETARG_C(i); Table *t = luaH_new(L); @@ -608,96 +872,252 @@ void luaV_execute (lua_State *L) { if (b != 0 || c != 0) luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c)); checkGC(L, ra + 1); - ) - vmcase(OP_SELF, + vmbreak; + } + vmcase(OP_SELF) { + const TValue *aux; StkId rb = RB(i); - setobjs2s(L, ra+1, rb); - Protect(luaV_gettable(L, rb, RKC(i), ra)); - ) - vmcase(OP_ADD, - arith_op(luai_numadd, TM_ADD); - ) - vmcase(OP_SUB, - arith_op(luai_numsub, TM_SUB); - ) - vmcase(OP_MUL, - arith_op(luai_nummul, TM_MUL); - ) - vmcase(OP_DIV, - arith_op(luai_numdiv, TM_DIV); - ) - vmcase(OP_MOD, - arith_op(luai_nummod, TM_MOD); - ) - vmcase(OP_POW, - arith_op(luai_numpow, TM_POW); - ) - vmcase(OP_UNM, + TValue *rc = RKC(i); + TString *key = tsvalue(rc); /* key must be a string */ + setobjs2s(L, ra + 1, rb); + if (luaV_fastget(L, rb, key, aux, luaH_getstr)) { + setobj2s(L, ra, aux); + } + else Protect(luaV_finishget(L, rb, rc, ra, aux)); + vmbreak; + } + vmcase(OP_ADD) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, intop(+, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numadd(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD)); } + vmbreak; + } + vmcase(OP_SUB) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, intop(-, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numsub(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SUB)); } + vmbreak; + } + vmcase(OP_MUL) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, intop(*, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_nummul(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MUL)); } + vmbreak; + } + vmcase(OP_DIV) { /* float division (always with floats) */ + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numdiv(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_DIV)); } + vmbreak; + } + vmcase(OP_BAND) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, intop(&, ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BAND)); } + vmbreak; + } + vmcase(OP_BOR) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, intop(|, ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BOR)); } + vmbreak; + } + vmcase(OP_BXOR) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, intop(^, ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BXOR)); } + vmbreak; + } + vmcase(OP_SHL) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, luaV_shiftl(ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHL)); } + vmbreak; + } + vmcase(OP_SHR) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, luaV_shiftl(ib, -ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHR)); } + vmbreak; + } + vmcase(OP_MOD) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, luaV_mod(L, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + lua_Number m; + luai_nummod(L, nb, nc, m); + setfltvalue(ra, m); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MOD)); } + vmbreak; + } + vmcase(OP_IDIV) { /* floor division */ + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, luaV_div(L, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numidiv(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_IDIV)); } + vmbreak; + } + vmcase(OP_POW) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numpow(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_POW)); } + vmbreak; + } + vmcase(OP_UNM) { TValue *rb = RB(i); - if (ttisnumber(rb)) { - lua_Number nb = nvalue(rb); - setnvalue(ra, luai_numunm(L, nb)); + lua_Number nb; + if (ttisinteger(rb)) { + lua_Integer ib = ivalue(rb); + setivalue(ra, intop(-, 0, ib)); + } + else if (tonumber(rb, &nb)) { + setfltvalue(ra, luai_numunm(L, nb)); } else { - Protect(luaV_arith(L, ra, rb, rb, TM_UNM)); + Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM)); } - ) - vmcase(OP_NOT, + vmbreak; + } + vmcase(OP_BNOT) { + TValue *rb = RB(i); + lua_Integer ib; + if (tointeger(rb, &ib)) { + setivalue(ra, intop(^, ~l_castS2U(0), ib)); + } + else { + Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT)); + } + vmbreak; + } + vmcase(OP_NOT) { TValue *rb = RB(i); int res = l_isfalse(rb); /* next assignment may change this value */ setbvalue(ra, res); - ) - vmcase(OP_LEN, + vmbreak; + } + vmcase(OP_LEN) { Protect(luaV_objlen(L, ra, RB(i))); - ) - vmcase(OP_CONCAT, + vmbreak; + } + vmcase(OP_CONCAT) { int b = GETARG_B(i); int c = GETARG_C(i); StkId rb; L->top = base + c + 1; /* mark the end of concat operands */ Protect(luaV_concat(L, c - b + 1)); - ra = RA(i); /* 'luav_concat' may invoke TMs and move the stack */ - rb = b + base; + ra = RA(i); /* 'luaV_concat' may invoke TMs and move the stack */ + rb = base + b; setobjs2s(L, ra, rb); checkGC(L, (ra >= rb ? ra + 1 : rb)); L->top = ci->top; /* restore top */ - ) - vmcase(OP_JMP, + vmbreak; + } + vmcase(OP_JMP) { dojump(ci, i, 0); - ) - vmcase(OP_EQ, + vmbreak; + } + vmcase(OP_EQ) { TValue *rb = RKB(i); TValue *rc = RKC(i); Protect( - if (cast_int(equalobj(L, rb, rc)) != GETARG_A(i)) + if (luaV_equalobj(L, rb, rc) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); ) - ) - vmcase(OP_LT, + vmbreak; + } + vmcase(OP_LT) { Protect( if (luaV_lessthan(L, RKB(i), RKC(i)) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); ) - ) - vmcase(OP_LE, + vmbreak; + } + vmcase(OP_LE) { Protect( if (luaV_lessequal(L, RKB(i), RKC(i)) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); ) - ) - vmcase(OP_TEST, + vmbreak; + } + vmcase(OP_TEST) { if (GETARG_C(i) ? l_isfalse(ra) : !l_isfalse(ra)) ci->u.l.savedpc++; else donextjump(ci); - ) - vmcase(OP_TESTSET, + vmbreak; + } + vmcase(OP_TESTSET) { TValue *rb = RB(i); if (GETARG_C(i) ? l_isfalse(rb) : !l_isfalse(rb)) ci->u.l.savedpc++; @@ -705,27 +1125,30 @@ void luaV_execute (lua_State *L) { setobjs2s(L, ra, rb); donextjump(ci); } - ) - vmcase(OP_CALL, + vmbreak; + } + vmcase(OP_CALL) { int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra+b; /* else previous instruction set top */ if (luaD_precall(L, ra, nresults)) { /* C function? */ - if (nresults >= 0) L->top = ci->top; /* adjust results */ - base = ci->u.l.base; + if (nresults >= 0) + L->top = ci->top; /* adjust results */ + Protect((void)0); /* update 'base' */ } else { /* Lua function */ ci = L->ci; - ci->callstatus |= CIST_REENTRY; goto newframe; /* restart luaV_execute over new Lua function */ } - ) - vmcase(OP_TAILCALL, + vmbreak; + } + vmcase(OP_TAILCALL) { int b = GETARG_B(i); if (b != 0) L->top = ra+b; /* else previous instruction set top */ lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); - if (luaD_precall(L, ra, LUA_MULTRET)) /* C function? */ - base = ci->u.l.base; + if (luaD_precall(L, ra, LUA_MULTRET)) { /* C function? */ + Protect((void)0); /* update 'base' */ + } else { /* tail call: put called frame (n) in place of caller one (o) */ CallInfo *nci = L->ci; /* called frame */ @@ -748,13 +1171,13 @@ void luaV_execute (lua_State *L) { lua_assert(L->top == oci->u.l.base + getproto(ofunc)->maxstacksize); goto newframe; /* restart luaV_execute over new Lua function */ } - ) - vmcasenb(OP_RETURN, + vmbreak; + } + vmcase(OP_RETURN) { int b = GETARG_B(i); - if (b != 0) L->top = ra+b-1; if (cl->p->sizep > 0) luaF_close(L, base); - b = luaD_poscall(L, ra); - if (!(ci->callstatus & CIST_REENTRY)) /* 'ci' still the called one */ + b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra))); + if (ci->callstatus & CIST_FRESH) /* local 'ci' still from callee */ return; /* external invocation: return */ else { /* invocation via reentry: continue execution */ ci = L->ci; @@ -763,105 +1186,137 @@ void luaV_execute (lua_State *L) { lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL); goto newframe; /* restart luaV_execute over new Lua function */ } - ) - vmcase(OP_FORLOOP, - lua_Number step = nvalue(ra+2); - lua_Number idx = luai_numadd(L, nvalue(ra), step); /* increment index */ - lua_Number limit = nvalue(ra+1); - if (luai_numlt(L, 0, step) ? luai_numle(L, idx, limit) - : luai_numle(L, limit, idx)) { - ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ - setnvalue(ra, idx); /* update internal index... */ - setnvalue(ra+3, idx); /* ...and external index */ - } - ) - vmcase(OP_FORPREP, - const TValue *init = ra; - const TValue *plimit = ra+1; - const TValue *pstep = ra+2; - if (!tonumber(init, ra)) - luaG_runerror(L, LUA_QL("for") " initial value must be a number"); - else if (!tonumber(plimit, ra+1)) - luaG_runerror(L, LUA_QL("for") " limit must be a number"); - else if (!tonumber(pstep, ra+2)) - luaG_runerror(L, LUA_QL("for") " step must be a number"); - setnvalue(ra, luai_numsub(L, nvalue(ra), nvalue(pstep))); + } + vmcase(OP_FORLOOP) { + if (ttisinteger(ra)) { /* integer loop? */ + lua_Integer step = ivalue(ra + 2); + lua_Integer idx = intop(+, ivalue(ra), step); /* increment index */ + lua_Integer limit = ivalue(ra + 1); + if ((0 < step) ? (idx <= limit) : (limit <= idx)) { + ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + chgivalue(ra, idx); /* update internal index... */ + setivalue(ra + 3, idx); /* ...and external index */ + } + } + else { /* floating loop */ + lua_Number step = fltvalue(ra + 2); + lua_Number idx = luai_numadd(L, fltvalue(ra), step); /* inc. index */ + lua_Number limit = fltvalue(ra + 1); + if (luai_numlt(0, step) ? luai_numle(idx, limit) + : luai_numle(limit, idx)) { + ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + chgfltvalue(ra, idx); /* update internal index... */ + setfltvalue(ra + 3, idx); /* ...and external index */ + } + } + vmbreak; + } + vmcase(OP_FORPREP) { + TValue *init = ra; + TValue *plimit = ra + 1; + TValue *pstep = ra + 2; + lua_Integer ilimit; + int stopnow; + if (ttisinteger(init) && ttisinteger(pstep) && + forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) { + /* all values are integer */ + lua_Integer initv = (stopnow ? 0 : ivalue(init)); + setivalue(plimit, ilimit); + setivalue(init, intop(-, initv, ivalue(pstep))); + } + else { /* try making all values floats */ + lua_Number ninit; lua_Number nlimit; lua_Number nstep; + if (!tonumber(plimit, &nlimit)) + luaG_runerror(L, "'for' limit must be a number"); + setfltvalue(plimit, nlimit); + if (!tonumber(pstep, &nstep)) + luaG_runerror(L, "'for' step must be a number"); + setfltvalue(pstep, nstep); + if (!tonumber(init, &ninit)) + luaG_runerror(L, "'for' initial value must be a number"); + setfltvalue(init, luai_numsub(L, ninit, nstep)); + } ci->u.l.savedpc += GETARG_sBx(i); - ) - vmcasenb(OP_TFORCALL, + vmbreak; + } + vmcase(OP_TFORCALL) { StkId cb = ra + 3; /* call base */ setobjs2s(L, cb+2, ra+2); setobjs2s(L, cb+1, ra+1); setobjs2s(L, cb, ra); L->top = cb + 3; /* func. + 2 args (state and index) */ - Protect(luaD_call(L, cb, GETARG_C(i), 1)); + Protect(luaD_call(L, cb, GETARG_C(i))); L->top = ci->top; i = *(ci->u.l.savedpc++); /* go to next instruction */ ra = RA(i); lua_assert(GET_OPCODE(i) == OP_TFORLOOP); goto l_tforloop; - ) - vmcase(OP_TFORLOOP, + } + vmcase(OP_TFORLOOP) { l_tforloop: if (!ttisnil(ra + 1)) { /* continue loop? */ setobjs2s(L, ra, ra + 1); /* save control variable */ ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ } - ) - vmcase(OP_SETLIST, + vmbreak; + } + vmcase(OP_SETLIST) { int n = GETARG_B(i); int c = GETARG_C(i); - int last; + unsigned int last; Table *h; if (n == 0) n = cast_int(L->top - ra) - 1; if (c == 0) { lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); c = GETARG_Ax(*ci->u.l.savedpc++); } - luai_runtimecheck(L, ttistable(ra)); h = hvalue(ra); last = ((c-1)*LFIELDS_PER_FLUSH) + n; if (last > h->sizearray) /* needs more space? */ - luaH_resizearray(L, h, last); /* pre-allocate it at once */ + luaH_resizearray(L, h, last); /* preallocate it at once */ for (; n > 0; n--) { TValue *val = ra+n; luaH_setint(L, h, last--, val); - luaC_barrierback(L, obj2gco(h), val); + luaC_barrierback(L, h, val); } L->top = ci->top; /* correct top (in case of previous open call) */ - ) - vmcase(OP_CLOSURE, + vmbreak; + } + vmcase(OP_CLOSURE) { Proto *p = cl->p->p[GETARG_Bx(i)]; - Closure *ncl = getcached(p, cl->upvals, base); /* cached closure */ + LClosure *ncl = getcached(p, cl->upvals, base); /* cached closure */ if (ncl == NULL) /* no match? */ pushclosure(L, p, cl->upvals, base, ra); /* create a new one */ else setclLvalue(L, ra, ncl); /* push cashed closure */ checkGC(L, ra + 1); - ) - vmcase(OP_VARARG, - int b = GETARG_B(i) - 1; + vmbreak; + } + vmcase(OP_VARARG) { + int b = GETARG_B(i) - 1; /* required results */ int j; int n = cast_int(base - ci->func) - cl->p->numparams - 1; + if (n < 0) /* less arguments than parameters? */ + n = 0; /* no vararg arguments */ if (b < 0) { /* B == 0? */ b = n; /* get all var. arguments */ Protect(luaD_checkstack(L, n)); ra = RA(i); /* previous call may change the stack */ L->top = ra + n; } - for (j = 0; j < b; j++) { - if (j < n) { - setobjs2s(L, ra + j, base - n + j); - } - else { - setnilvalue(ra + j); - } - } - ) - vmcase(OP_EXTRAARG, + for (j = 0; j < b && j < n; j++) + setobjs2s(L, ra + j, base - n + j); + for (; j < b; j++) /* complete required results with nil */ + setnilvalue(ra + j); + vmbreak; + } + vmcase(OP_EXTRAARG) { lua_assert(0); - ) + vmbreak; + } } } } +/* }================================================================== */ + diff --git a/depends/lua/src/lzio.c b/depends/lua/src/lzio.c index 20efea983..c9e1f491f 100644 --- a/depends/lua/src/lzio.c +++ b/depends/lua/src/lzio.c @@ -1,15 +1,17 @@ /* -** $Id: lzio.c,v 1.35.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lzio.c,v 1.37 2015/09/08 15:41:05 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ - -#include - #define lzio_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "llimits.h" @@ -64,13 +66,3 @@ size_t luaZ_read (ZIO *z, void *b, size_t n) { return 0; } -/* ------------------------------------------------------------------------ */ -char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { - if (n > buff->buffsize) { - if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; - luaZ_resizebuffer(L, buff, n); - } - return buff->buffer; -} - - From 4dd411e86227216ff0b2e534a625bb1b47a3b9ca Mon Sep 17 00:00:00 2001 From: Vitaly Pronkin Date: Tue, 26 Jul 2016 23:29:26 -0400 Subject: [PATCH 0123/1012] Update Lua config and patch checkdp --- depends/lua/CMakeLists.txt | 1 + depends/lua/include/luaconf.h | 35 +++++++++++++++++------------------ depends/lua/src/lstrlib.c | 4 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index 782bb5e96..bd244a126 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -80,6 +80,7 @@ src/ltable.c src/ltablib.c src/ltm.c src/lundump.c +src/lutf8lib.c src/lvm.c src/lzio.c ) diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index fd447ccb9..6578a6d07 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -11,6 +11,9 @@ #include #include +#define LUA_USE_LONGJMP //TODO: this is bad +#define LUA_COMPAT_APIINTCASTS +#define LUA_COMPAT_IPAIRS /* ** =================================================================== @@ -167,38 +170,30 @@ ** hierarchy or if you want to install your libraries in ** non-conventional directories. */ -#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #if defined(_WIN32) /* { */ /* ** In Windows, any exclamation mark ('!') in the path is replaced by the ** path of the directory of the executable file of the current process. */ -#define LUA_LDIR "!\\lua\\" +#define LUA_LDIR "!\\hack\\lua\\" #define LUA_CDIR "!\\" -#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" #define LUA_PATH_DEFAULT \ - LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ - LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ - LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ - ".\\?.lua;" ".\\?\\init.lua" + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" ".\\?.lua" #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.dll;" \ - LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ - LUA_CDIR"loadall.dll;" ".\\?.dll" + LUA_CDIR"?.dll;" ".\\?.dll" #else /* }{ */ -#define LUA_ROOT "/usr/local/" -#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" -#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" +#define LUA_LDIR "./hack/lua/" +#define LUA_CDIR "./hack/" #define LUA_PATH_DEFAULT \ - LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ - LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ - "./?.lua;" "./?/init.lua" + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" "./?.lua" #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" + LUA_CDIR"?.so;" "./?.so" #endif /* } */ +#define LUA_PATH "DFHACK_LUA_PATH" +#define LUA_CPATH "DFHACK_LUA_CPATH" /* @@ LUA_DIRSEP is the directory separator (for submodules). @@ -231,11 +226,15 @@ */ #if defined(LUA_BUILD_AS_DLL) /* { */ +#if defined(_MSC_VER) #if defined(LUA_CORE) || defined(LUA_LIB) /* { */ #define LUA_API __declspec(dllexport) #else /* }{ */ #define LUA_API __declspec(dllimport) #endif /* } */ +#else +#define LUA_API __attribute__ ((visibility("default"))) +#endif #else /* }{ */ @@ -606,7 +605,7 @@ /* -@@ lua_number2strx converts a float to an hexadecimal numeric string. +@@ lua_number2strx converts a float to an hexadecimal numeric string. ** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. ** Otherwise, you can leave 'lua_number2strx' undefined and Lua will ** provide its own implementation. diff --git a/depends/lua/src/lstrlib.c b/depends/lua/src/lstrlib.c index 12264f881..a4a356a3f 100644 --- a/depends/lua/src/lstrlib.c +++ b/depends/lua/src/lstrlib.c @@ -933,7 +933,7 @@ static void addquoted (luaL_Buffer *b, const char *s, size_t len) { static void checkdp (char *buff, int nb) { if (memchr(buff, '.', nb) == NULL) { /* no dot? */ char point = lua_getlocaledecpoint(); /* try locale point */ - char *ppoint = memchr(buff, point, nb); + char *ppoint = (char*)memchr(buff, point, nb); if (ppoint) *ppoint = '.'; /* change it to a dot */ } } @@ -1259,7 +1259,7 @@ static KOption getoption (Header *h, const char **fmt, int *size) { ** 'psize' is filled with option's size, 'notoalign' with its ** alignment requirements. ** Local variable 'size' gets the size to be aligned. (Kpadal option -** always gets its full alignment, other options are limited by +** always gets its full alignment, other options are limited by ** the maximum alignment ('maxalign'). Kchar option needs no alignment ** despite its size. */ From 3833ff2e0964bfe34e999122250546076393e100 Mon Sep 17 00:00:00 2001 From: Vitaly Pronkin Date: Tue, 19 Jul 2016 01:37:29 +1200 Subject: [PATCH 0124/1012] proper 64bit address formatting --- library/LuaWrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 08af93e78..0c5848408 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -993,7 +993,7 @@ static int meta_ptr_tostring(lua_State *state) lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); - lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (uintptr_t)ptr).c_str()); + lua_pushstring(state, stl_sprintf("<%s: 0x%llx>", cname, (uintptr_t)ptr).c_str()); return 1; } From fe18f176f7a4dabd0a1581b712516b12f284adbc Mon Sep 17 00:00:00 2001 From: Vitaly Pronkin Date: Tue, 26 Jul 2016 23:47:53 -0400 Subject: [PATCH 0125/1012] More 64-bit fixes Cherry-picked from 7eb3ba6 - Lua update already done in e2c6350, 4dd411e - Excluded library/modules/Buildings.cpp --- CMakeLists.txt | 4 ++-- library/Core.cpp | 8 ++++---- library/LuaTools.cpp | 13 +++++++++---- library/LuaTypes.cpp | 6 +++--- library/RemoteTools.cpp | 2 +- library/include/DataIdentity.h | 4 ++-- library/include/LuaTools.h | 12 +++++------- library/lua/dfhack.lua | 2 +- library/lua/memscan.lua | 4 +++- library/modules/World.cpp | 2 +- library/xml | 2 +- plugins/eventful.cpp | 10 +++++----- plugins/isoworldremote.cpp | 4 ++-- plugins/remotefortressreader.cpp | 2 +- plugins/rendermax/renderer_light.cpp | 4 ++-- 15 files changed, 42 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec339b037..291fab324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,8 +117,8 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.43.03") -SET(DFHACK_RELEASE "r1") +set(DF_VERSION "0.43.05") +SET(DFHACK_RELEASE "alpha0") SET(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/library/Core.cpp b/library/Core.cpp index fb1dd80bb..975290e52 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2107,7 +2107,7 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve if (!df::global::world) return; - std::string rawFolder = "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; + std::string rawFolder = "data/save/" + (df::global::world->save_dir) + "/raw/"; auto i = table.find(event); if ( i != table.end() ) { @@ -2167,9 +2167,9 @@ void Core::onStateChange(color_ostream &out, state_change_event event) case SC_WORLD_UNLOADED: case SC_MAP_LOADED: case SC_MAP_UNLOADED: - if (world && world->cur_savegame.save_dir.size()) + if (world && world->save_dir.size()) { - std::string save_dir = "data/save/" + world->cur_savegame.save_dir; + std::string save_dir = "data/save/" + world->save_dir; std::string evtlogpath = save_dir + "/events-dfhack.log"; std::ofstream evtlog; evtlog.open(evtlogpath, std::ios_base::app); // append @@ -2187,7 +2187,7 @@ void Core::onStateChange(color_ostream &out, state_change_event event) evtlog << timebuf; evtlog << "DFHack " << Version::git_description() << " on " << ostype << "; "; evtlog << "cwd md5: " << md5w.getHashFromString(getHackPath()).substr(0, 10) << "; "; - evtlog << "save: " << world->cur_savegame.save_dir << "; "; + evtlog << "save: " << world->save_dir << "; "; evtlog << sc_event_name(event) << "; "; if (gametype) evtlog << "game type " << ENUM_KEY_STR(game_type, *gametype) << " (" << *gametype << ")"; diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 008cb00c2..b44f309c3 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -319,7 +319,7 @@ static int yield_helper(lua_State *S) } namespace { - int dfhack_lineedit_cont(lua_State *L, int status, int) + int dfhack_lineedit_cont(lua_State *L, int status, lua_KContext) { if (Lua::IsSuccess(status)) return lua_gettop(L) - 2; @@ -636,7 +636,7 @@ static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space } namespace { - int safecall_cont(lua_State *L, int status, int) + int safecall_cont(lua_State *L, int status, lua_KContext) { bool success = do_finish_pcall(L, Lua::IsSuccess(status)); @@ -1138,7 +1138,7 @@ static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool succes return success; } -int dfhack_cleanup_cont(lua_State *L, int status, int) +int dfhack_cleanup_cont(lua_State *L, int status, lua_KContext) { bool success = Lua::IsSuccess(status); @@ -1246,6 +1246,11 @@ static int dfhack_open_plugin(lua_State *L) return 0; } +static int gettop_wrapper(lua_State *L, int, lua_KContext) +{ + return lua_gettop(L); +} + static int dfhack_curry_wrap(lua_State *L) { int nargs = lua_gettop(L); @@ -1261,7 +1266,7 @@ static int dfhack_curry_wrap(lua_State *L) for (int i = 1; i <= ncurry; i++) lua_copy(L, lua_upvalueindex(i+1), i); - lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop); + lua_callk(L, scount-1, LUA_MULTRET, 0, gettop_wrapper); return lua_gettop(L); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index fa9445a81..e8d07dc2f 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -305,7 +305,7 @@ void container_identity::lua_item_write(lua_State *state, int fname_idx, void *p id->lua_write(state, fname_idx, pitem, val_index); } -bool container_identity::lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +bool container_identity::lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) { auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); @@ -351,7 +351,7 @@ void ptr_container_identity::lua_item_write(lua_State *state, int fname_idx, voi df::pointer_identity::lua_write(state, fname_idx, pitem, id, val_index); } -bool ptr_container_identity::lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +bool ptr_container_identity::lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) { auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); @@ -887,7 +887,7 @@ static int method_container_insert(lua_State *state) int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN); int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true); - if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3)) + if (!id->lua_insert2(state, UPVAL_METHOD_NAME, ptr, idx, 3)) field_error(state, UPVAL_METHOD_NAME, "not supported", "call"); return 0; } diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 77374ea0c..909ad9133 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -386,7 +386,7 @@ static command_result GetWorldInfo(color_ostream &stream, if (df::global::gametype) gt = *df::global::gametype; - out->set_save_dir(world->cur_savegame.save_dir); + out->set_save_dir(world->save_dir); if (world->world_data->name.has_name) describeName(out->mutable_world_name(), &world->world_data->name); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index c94129f9b..10c905df3 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -132,7 +132,7 @@ namespace DFHack virtual bool erase(void *ptr, int index) { return false; } virtual bool insert(void *ptr, int index, void *pitem) { return false; } - virtual bool lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + virtual bool lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); protected: virtual int item_count(void *ptr, CountMode cnt) = 0; @@ -153,7 +153,7 @@ namespace DFHack virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); - virtual bool lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + virtual bool lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); }; class DFHACK_EXPORT bit_container_identity : public container_identity { diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index a36459d68..ae35cb52b 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -182,11 +182,9 @@ namespace DFHack {namespace Lua { } // Internal helper - template - int TailPCallK_Thunk(lua_State *state) { - int tmp; - int rv = lua_getctx(state, &tmp); - return cb(state, rv, tmp); + template + int TailPCallK_Thunk(lua_State *state, int rv, lua_KContext ctx) { + return cb(state, rv, ctx); } /** @@ -194,9 +192,9 @@ namespace DFHack {namespace Lua { * specifically, the callback is called with the same kind of arguments * in both yield and non-yield case. */ - template + template int TailPCallK(lua_State *state, int narg, int nret, int errfun, int ctx) { - int rv = lua_pcallk(state, narg, nret, errfun, ctx, &TailPCallK_Thunk); + int rv = lua_pcallk(state, narg, nret, errfun, ctx, cb); return cb(state, rv, ctx); } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 900607c08..f535d25ad 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -606,7 +606,7 @@ end function dfhack.getSavePath() if dfhack.isWorldLoaded() then - return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir + return dfhack.getDFPath() .. '/data/save/' .. df.global.world.save_dir end end diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index afee78aee..87e2ad58c 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -155,6 +155,8 @@ function MemoryArea.new(astart, aend) uint16_t = CheckedArray.new('uint16_t',astart,aend), int32_t = CheckedArray.new('int32_t',astart,aend), uint32_t = CheckedArray.new('uint32_t',astart,aend), + int64_t = CheckedArray.new('int64_t',astart,aend), + uint64_t = CheckedArray.new('uint64_t',astart,aend), float = CheckedArray.new('float',astart,aend) } setmetatable(obj, MemoryArea) @@ -321,7 +323,7 @@ end -- Validation function is_valid_vector(ref,size) - local ints = df.reinterpret_cast('uint32_t', ref) + local ints = df.reinterpret_cast('uint64_t', ref) return ints[0] <= ints[1] and ints[1] <= ints[2] and (size == nil or (ints[1] - ints[0]) % size == 0) end diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 3be400515..fcf516f4e 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -151,7 +151,7 @@ void World::SetCurrentWeather(uint8_t weather) string World::ReadWorldFolder() { - return world->cur_savegame.save_dir; + return world->save_dir; } bool World::isFortressMode(df::game_type t) diff --git a/library/xml b/library/xml index 7d312334c..369dfdd86 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7d312334c320022cd6275cddcdb8a64d4ed8d722 +Subproject commit 369dfdd862da961c74d35174cb55fed3d96a29c0 diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 45b94510c..14f1ec26e 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -158,12 +158,12 @@ void ev_mng_jobCompleted(color_ostream& out, void* job) } void ev_mng_unitDeath(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=*(int32_t*)&ptr; onUnitDeath(out,myId); } void ev_mng_itemCreate(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=*(int32_t*)&ptr; onItemCreated(out,myId); } void ev_mng_construction(color_ostream& out, void* ptr) @@ -178,12 +178,12 @@ void ev_mng_syndrome(color_ostream& out, void* ptr) } void ev_mng_invasion(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=*(int32_t*)&ptr; onInvasion(out,myId); } static void ev_mng_building(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=*(int32_t*)&ptr; onBuildingCreatedDestroyed(out,myId); } static void ev_mng_inventory(color_ostream& out, void* ptr) @@ -204,7 +204,7 @@ static void ev_mng_inventory(color_ostream& out, void* ptr) onInventoryChange(out,unitId,itemId,item_old,item_new); } static void ev_mng_report(color_ostream& out, void* ptr) { - onReport(out,(int32_t)ptr); + onReport(out,*(int32_t*)&ptr); } static void ev_mng_unitAttack(color_ostream& out, void* ptr) { EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; diff --git a/plugins/isoworldremote.cpp b/plugins/isoworldremote.cpp index 017fb45f6..bd3433b27 100644 --- a/plugins/isoworldremote.cpp +++ b/plugins/isoworldremote.cpp @@ -198,7 +198,7 @@ static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, return CR_OK; } if (in->has_save_folder()) { //If no save folder is given, it means we don't care. - if (!(in->save_folder() == world->cur_savegame.save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. + if (!(in->save_folder() == world->save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. out->set_available(false); return CR_OK; } @@ -345,7 +345,7 @@ static command_result GetRawNames(color_ostream &stream, const MapRequest *in, R return CR_OK; } if (in->has_save_folder()) { //If no save folder is given, it means we don't care. - if (!(in->save_folder() == world->cur_savegame.save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. + if (!(in->save_folder() == world->save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. out->set_available(false); return CR_OK; } diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 7a853db82..e210b7d50 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1576,7 +1576,7 @@ static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, out->set_block_pos_z(pos_z); out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); - out->set_save_name(df::global::world->cur_savegame.save_dir); + out->set_save_name(df::global::world->save_dir); return CR_OK; } diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 5e6f2e2de..716f3d0b6 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -1172,9 +1172,9 @@ void lightingEngineViewscreen::defaultSettings() void lightingEngineViewscreen::loadSettings() { std::string rawFolder; - if(df::global::world->cur_savegame.save_dir!="") + if(df::global::world->save_dir!="") { - rawFolder= "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; + rawFolder= "data/save/" + (df::global::world->save_dir) + "/raw/"; } else { From 99dbf20c08251e6117349c30ae1a6022d96b6dd8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2016 19:34:24 -0400 Subject: [PATCH 0126/1012] Reorganize win32 build scripts --- build/{ => win32}/build-debug.bat | 0 build/{ => win32}/build-release.bat | 0 build/{ => win32}/generate-MSVC-all-breakfast.bat | 0 build/{ => win32}/generate-MSVC-all.bat | 0 build/{ => win32}/generate-MSVC-gui.bat | 0 build/{ => win32}/generate-MSVC-minimal.bat | 0 build/{ => win32}/generate-MSVC-release.bat | 0 build/{ => win32}/install-debug.bat | 0 build/{ => win32}/install-release.bat | 0 build/{ => win32}/package-debug.bat | 0 build/{ => win32}/package-release.bat | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename build/{ => win32}/build-debug.bat (100%) rename build/{ => win32}/build-release.bat (100%) rename build/{ => win32}/generate-MSVC-all-breakfast.bat (100%) rename build/{ => win32}/generate-MSVC-all.bat (100%) rename build/{ => win32}/generate-MSVC-gui.bat (100%) rename build/{ => win32}/generate-MSVC-minimal.bat (100%) rename build/{ => win32}/generate-MSVC-release.bat (100%) rename build/{ => win32}/install-debug.bat (100%) rename build/{ => win32}/install-release.bat (100%) rename build/{ => win32}/package-debug.bat (100%) rename build/{ => win32}/package-release.bat (100%) diff --git a/build/build-debug.bat b/build/win32/build-debug.bat similarity index 100% rename from build/build-debug.bat rename to build/win32/build-debug.bat diff --git a/build/build-release.bat b/build/win32/build-release.bat similarity index 100% rename from build/build-release.bat rename to build/win32/build-release.bat diff --git a/build/generate-MSVC-all-breakfast.bat b/build/win32/generate-MSVC-all-breakfast.bat similarity index 100% rename from build/generate-MSVC-all-breakfast.bat rename to build/win32/generate-MSVC-all-breakfast.bat diff --git a/build/generate-MSVC-all.bat b/build/win32/generate-MSVC-all.bat similarity index 100% rename from build/generate-MSVC-all.bat rename to build/win32/generate-MSVC-all.bat diff --git a/build/generate-MSVC-gui.bat b/build/win32/generate-MSVC-gui.bat similarity index 100% rename from build/generate-MSVC-gui.bat rename to build/win32/generate-MSVC-gui.bat diff --git a/build/generate-MSVC-minimal.bat b/build/win32/generate-MSVC-minimal.bat similarity index 100% rename from build/generate-MSVC-minimal.bat rename to build/win32/generate-MSVC-minimal.bat diff --git a/build/generate-MSVC-release.bat b/build/win32/generate-MSVC-release.bat similarity index 100% rename from build/generate-MSVC-release.bat rename to build/win32/generate-MSVC-release.bat diff --git a/build/install-debug.bat b/build/win32/install-debug.bat similarity index 100% rename from build/install-debug.bat rename to build/win32/install-debug.bat diff --git a/build/install-release.bat b/build/win32/install-release.bat similarity index 100% rename from build/install-release.bat rename to build/win32/install-release.bat diff --git a/build/package-debug.bat b/build/win32/package-debug.bat similarity index 100% rename from build/package-debug.bat rename to build/win32/package-debug.bat diff --git a/build/package-release.bat b/build/win32/package-release.bat similarity index 100% rename from build/package-release.bat rename to build/win32/package-release.bat From 709111ea338fbf6d8da0b56b244bd0749e8c7048 Mon Sep 17 00:00:00 2001 From: Vitaly Pronkin Date: Wed, 27 Jul 2016 19:35:51 -0400 Subject: [PATCH 0127/1012] Add win64 build scripts --- build/win64/build-debug.bat | 4 ++++ build/win64/build-release.bat | 5 +++++ build/win64/generate-MSVC-all-breakfast.bat | 9 +++++++++ build/win64/generate-MSVC-all.bat | 6 ++++++ build/win64/generate-MSVC-gui.bat | 7 +++++++ build/win64/generate-MSVC-minimal.bat | 6 ++++++ build/win64/generate-MSVC-release.bat | 6 ++++++ build/win64/install-debug.bat | 4 ++++ build/win64/install-release.bat | 4 ++++ build/win64/package-debug.bat | 6 ++++++ build/win64/package-release.bat | 5 +++++ 11 files changed, 62 insertions(+) create mode 100644 build/win64/build-debug.bat create mode 100644 build/win64/build-release.bat create mode 100644 build/win64/generate-MSVC-all-breakfast.bat create mode 100755 build/win64/generate-MSVC-all.bat create mode 100755 build/win64/generate-MSVC-gui.bat create mode 100755 build/win64/generate-MSVC-minimal.bat create mode 100755 build/win64/generate-MSVC-release.bat create mode 100644 build/win64/install-debug.bat create mode 100644 build/win64/install-release.bat create mode 100644 build/win64/package-debug.bat create mode 100644 build/win64/package-release.bat diff --git a/build/win64/build-debug.bat b/build/win64/build-debug.bat new file mode 100644 index 000000000..f53df9063 --- /dev/null +++ b/build/win64/build-debug.bat @@ -0,0 +1,4 @@ +call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +cd VC2015 +msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj +cd .. \ No newline at end of file diff --git a/build/win64/build-release.bat b/build/win64/build-release.bat new file mode 100644 index 000000000..551e27b69 --- /dev/null +++ b/build/win64/build-release.bat @@ -0,0 +1,5 @@ +call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +cd VC2015 +msbuild /m /p:Platform=x64 /p:Configuration=Release ALL_BUILD.vcxproj +cd .. +pause \ No newline at end of file diff --git a/build/win64/generate-MSVC-all-breakfast.bat b/build/win64/generate-MSVC-all-breakfast.bat new file mode 100644 index 000000000..edf540f48 --- /dev/null +++ b/build/win64/generate-MSVC-all-breakfast.bat @@ -0,0 +1,9 @@ +@echo off +IF EXIST DF_PATH.txt SET /P _DF_PATH= Date: Wed, 27 Jul 2016 19:46:49 -0400 Subject: [PATCH 0128/1012] win64 fixes (partial) cherry-picked from 2f734ae2317060edb83021f17cffc966c435ad7b --- CMakeLists.txt | 5 +++-- depends/lua/src/lobject.c | 4 ++-- depends/tthread/fast_mutex.h | 2 +- library/DataStaticsFields.cpp | 5 +++++ library/Hooks-windows.cpp | 20 ++++++++++++++++++++ library/VTableInterpose.cpp | 6 +++++- library/include/DataIdentity.h | 5 +++++ library/include/VersionInfo.h | 2 +- plugins/CMakeLists.txt | 2 +- 9 files changed, 43 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 291fab324..de8c48846 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,8 +48,8 @@ if(UNIX) endif() if(WIN32) - if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1600)) - message(SEND_ERROR "MSVC 2010 is required") + if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1900)) + message(SEND_ERROR "MSVC 2015 is required") endif() endif() @@ -177,6 +177,7 @@ ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od") + ADD_DEFINITIONS(-D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS) ENDIF() # use shared libraries for protobuf diff --git a/depends/lua/src/lobject.c b/depends/lua/src/lobject.c index a44b3850f..0c6e7a9e7 100644 --- a/depends/lua/src/lobject.c +++ b/depends/lua/src/lobject.c @@ -280,7 +280,7 @@ static const char *l_str2d (const char *s, lua_Number *result) { endptr = l_str2dloc(s, result, mode); /* try to convert */ if (endptr == NULL) { /* failed? may be a different locale */ char buff[L_MAXLENNUM + 1]; - char *pdot = strchr(s, '.'); + char *pdot = (char*)strchr(s, '.'); if (strlen(s) > L_MAXLENNUM || pdot == NULL) return NULL; /* string too long or no dot; fail */ strcpy(buff, s); /* copy string to buffer */ @@ -394,7 +394,7 @@ static void pushstr (lua_State *L, const char *str, size_t l) { /* -** this function handles only '%d', '%c', '%f', '%p', and '%s' +** this function handles only '%d', '%c', '%f', '%p', and '%s' conventional formats, plus Lua-specific '%I' and '%U' */ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { diff --git a/depends/tthread/fast_mutex.h b/depends/tthread/fast_mutex.h index 6627408e4..a381b2c75 100644 --- a/depends/tthread/fast_mutex.h +++ b/depends/tthread/fast_mutex.h @@ -39,7 +39,7 @@ freely, subject to the following restrictions: // Check if we can support the assembly language level implementation (otherwise // revert to the system API) #if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \ - (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || \ + (defined(_MSC_VER) && (defined(_M_IX86) /*|| defined(_M_X64)*/)) || \ (defined(__GNUC__) && (defined(__ppc__))) #define _FAST_MUTEX_ASM_ #else diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 96f91dd66..9115784d7 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -30,8 +30,13 @@ namespace df { NUMBER_IDENTITY_TRAITS(uint32_t); NUMBER_IDENTITY_TRAITS(int64_t); NUMBER_IDENTITY_TRAITS(uint64_t); +#ifdef _WIN32 + NUMBER_IDENTITY_TRAITS(long); + NUMBER_IDENTITY_TRAITS(unsigned long); +#else NUMBER_IDENTITY_TRAITS(intptr_t); NUMBER_IDENTITY_TRAITS(uintptr_t); +#endif NUMBER_IDENTITY_TRAITS(float); NUMBER_IDENTITY_TRAITS(double); diff --git a/library/Hooks-windows.cpp b/library/Hooks-windows.cpp index 7ea1385a7..3d925bc27 100644 --- a/library/Hooks-windows.cpp +++ b/library/Hooks-windows.cpp @@ -726,6 +726,22 @@ DFhackCExport uint32_t SDL_ThreadID(void) return _SDL_ThreadID(); } +static char* (*_SDL_getenv)(const char *name) = 0; +DFhackCExport char* SDL_getenv(const char *name) +{ + if(!inited) + FirstCall(); + return _SDL_getenv(name); +} + +static size_t (*_SDL_strlcat)(char *dst, const char *src, size_t maxlen) = 0; +DFhackCExport size_t SDL_strlcat(char *dst, const char *src, size_t maxlen) +{ + if(!inited) + FirstCall(); + return _SDL_strlcat(dst, src, maxlen); +} + // FIXME: this has to be thread-safe. bool FirstCall() { @@ -813,6 +829,10 @@ bool FirstCall() _SDL_SemWait = (int (*)(void *))GetProcAddress(realSDLlib,"SDL_SemWait"); _SDL_ThreadID = (uint32_t (*)(void))GetProcAddress(realSDLlib,"SDL_ThreadID"); + // new in DF 0.43.05 + _SDL_getenv = (char* (*)(const char*))GetProcAddress(realSDLlib,"SDL_getenv"); + _SDL_strlcat = (size_t (*)(char*, const char*, size_t))GetProcAddress(realSDLlib,"SDL_strlcat"); + _SDL_EnableUNICODE(1); fprintf(stderr,"Initized HOOKS!\n"); diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index d81c6d730..63edb2205 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -82,7 +82,7 @@ using namespace DFHack; // multiple, but not virtual inheritance. struct MSVC_MPTR { void *method; - intptr_t this_shift; + uint32_t this_shift; // was intptr_t pre-0.43.05 }; // Debug builds sometimes use additional thunks that @@ -97,7 +97,11 @@ static uint32_t *follow_jmp(void *ptr) switch (*p) { case 0xE9: // jmp near rel32 +#ifdef DFHACK64 + p += 5 + *(int32_t*)(p+1) + 1; +#else p += 5 + *(int32_t*)(p+1); +#endif break; case 0xEB: // jmp short rel8 p += 2 + *(int8_t*)(p+1); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 10c905df3..32bbf0d5d 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -487,8 +487,13 @@ namespace df NUMBER_IDENTITY_TRAITS(uint32_t); NUMBER_IDENTITY_TRAITS(int64_t); NUMBER_IDENTITY_TRAITS(uint64_t); +#ifdef _WIN32 + NUMBER_IDENTITY_TRAITS(long); + NUMBER_IDENTITY_TRAITS(unsigned long); +#else NUMBER_IDENTITY_TRAITS(intptr_t); NUMBER_IDENTITY_TRAITS(uintptr_t); +#endif NUMBER_IDENTITY_TRAITS(float); NUMBER_IDENTITY_TRAITS(double); diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index 58ecb2c28..b3d6c8346 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -53,7 +53,7 @@ namespace DFHack std::map Addresses; std::map VTables; uintptr_t base; - int rebase_delta; + uintptr_t rebase_delta; std::string version; OSType OS; public: diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c24b940b9..46c4b8054 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -91,7 +91,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) - add_subdirectory(diggingInvaders) + # add_subdirectory(diggingInvaders) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) From 50144b60eab967687e6b382bd84e64b6cf61ca3b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2016 19:54:19 -0400 Subject: [PATCH 0129/1012] A few %08x to %p changes --- library/Process-darwin.cpp | 2 +- library/VTableInterpose.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 95c347368..d6988d5d6 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -207,7 +207,7 @@ void Process::getMemRanges( vector & ranges ) if (log_ranges) { fprintf(stderr, - "%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", + "%p-%p %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", address, (address + vmsize), (vmsize >> 10), (info.protection & VM_PROT_READ) ? 'r' : '-', (info.protection & VM_PROT_WRITE) ? 'w' : '-', diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 63edb2205..96cbfff9c 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -308,7 +308,7 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v * - interpose_method comes from method_pointer_to_addr_ */ - fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x (%s)\n", + fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %p (%s)\n", vmethod_idx, uintptr_t(interpose_method), name_str); fflush(stderr); abort(); From ddd56d7825f121deac70cd89676a1bf6d445067e Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2016 20:10:03 -0400 Subject: [PATCH 0130/1012] Fix (for win64) and avoid duplicating base address --- library/Process-darwin.cpp | 3 ++- library/Process-linux.cpp | 3 ++- library/Process-windows.cpp | 3 ++- library/VersionInfoFactory.cpp | 8 ++------ library/include/Memory.h | 22 ++++++++++++++++++++++ 5 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 library/include/Memory.h diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index d6988d5d6..69defbf92 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -41,6 +41,7 @@ using namespace std; #include #include "MemAccess.h" +#include "Memory.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" @@ -241,7 +242,7 @@ void Process::getMemRanges( vector & ranges ) uintptr_t Process::getBase() { - return 0x1000; + return DEFAULT_BASE_ADDR; // Memory.h } int Process::adjustOffset(int offset, bool /*to_file*/) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 14c70a802..7a72171ca 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -39,6 +39,7 @@ using namespace std; #include #include "MemAccess.h" +#include "Memory.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" @@ -167,7 +168,7 @@ void Process::getMemRanges( vector & ranges ) uintptr_t Process::getBase() { - return 0x8048000; + return DEFAULT_BASE_ADDR; // Memory.h } int Process::adjustOffset(int offset, bool /*to_file*/) diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 5474d7cfb..b9e58462b 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -43,6 +43,7 @@ using namespace std; #include "VersionInfoFactory.h" #include "Error.h" #include "MemAccess.h" +#include "Memory.h" using namespace DFHack; namespace DFHack { @@ -306,7 +307,7 @@ uintptr_t Process::getBase() { if(d) return (uintptr_t) d->base; - return 0x400000; + return DEFAULT_BASE_ADDR; // Memory.h } int Process::adjustOffset(int offset, bool to_file) diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 02dac568c..672f5a9c8 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -34,6 +34,7 @@ using namespace std; #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" +#include "Memory.h" using namespace DFHack; #include @@ -98,25 +99,20 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) if(os == "windows") { mem->setOS(OS_WINDOWS); - // set default image base. this is fixed for base relocation later - mem->setBase(0x400000); } else if(os == "linux") { mem->setOS(OS_LINUX); - // this is wrong... I'm not going to do base image relocation on linux though. - mem->setBase(0x8048000); } else if(os == "darwin") { mem->setOS(OS_APPLE); - // this is wrong... I'm not going to do base image relocation on linux though. - mem->setBase(0x1000); } else { return; // ignore it if it's invalid } + mem->setBase(DEFAULT_BASE_ADDR); // Memory.h // process additional entries //cout << "Entry " << cstr_version << " " << cstr_os << endl; diff --git a/library/include/Memory.h b/library/include/Memory.h new file mode 100644 index 000000000..1016f2966 --- /dev/null +++ b/library/include/Memory.h @@ -0,0 +1,22 @@ +// Default base address +#if defined(_WIN32) + #ifdef DFHACK64 + #define DEFAULT_BASE_ADDR 0x140000000 + #else + #define DEFAULT_BASE_ADDR 0x400000 + #endif +#elif defined(_DARWIN) + #ifdef DFHACK64 + #define DEFAULT_BASE_ADDR 0x1000 + #else + #define DEFAULT_BASE_ADDR 0x1000 + #endif +#elif defined(_LINUX) + #ifdef DFHACK64 + #define DEFAULT_BASE_ADDR 0x8048000 + #else + #define DEFAULT_BASE_ADDR 0x8048000 + #endif +#else + #error Unknown OS +#endif From 223c83071e9b72d59590a869043de25cec3db6b5 Mon Sep 17 00:00:00 2001 From: Vitaly Pronkin Date: Wed, 27 Jul 2016 20:21:57 -0400 Subject: [PATCH 0131/1012] win64 fixes: doReadClassName, ParseVersion cherry-picked from 2f734ae2317060edb83021f17cffc966c435ad7b --- library/Process-windows.cpp | 9 ++++++++- library/VersionInfoFactory.cpp | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index b9e58462b..cb2e16bd2 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -345,9 +345,16 @@ int Process::adjustOffset(int offset, bool to_file) string Process::doReadClassName (void * vptr) { - char * rtti = readPtr((char *)vptr - 0x4); + char * rtti = readPtr((char *)vptr - sizeof(void*)); +#ifdef DFHACK64 + char * typeinfo = d->base + readDWord(rtti + 0xC); + string raw = readCString(typeinfo + 0x10+4); // skips the .?AV +#else char * typeinfo = readPtr(rtti + 0xC); string raw = readCString(typeinfo + 0xC); // skips the .?AV +#endif + if (!raw.length()) + return "dummy"; raw.resize(raw.length() - 2);// trim @@ from end return raw; } diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 672f5a9c8..707c2809f 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -136,7 +136,11 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) } if ((is_vtable && no_vtables) || (!is_vtable && no_globals)) continue; +#ifdef DFHACK64 + uintptr_t addr = strtoull(cstr_value, 0, 0); +#else uintptr_t addr = strtol(cstr_value, 0, 0); +#endif if (is_vtable) mem->setVTable(cstr_key, addr); else From 828fee532a381ddde181edd7ef4380308b8126b7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2016 21:08:24 -0400 Subject: [PATCH 0132/1012] Switch to downloading zlib.lib and SDLreal.dll on Windows --- CMakeLists.txt | 48 ++++++++++++++++++++++++++++-- depends/zlib/lib/win32/.gitignore | 1 + depends/zlib/lib/win64/.gitignore | 1 + depends/zlib/lib/zlib.lib | Bin 108064 -> 0 bytes library/CMakeLists.txt | 2 +- package/windows/SDLreal.dll | Bin 321536 -> 0 bytes package/windows/win32/.gitignore | 1 + package/windows/win64/.gitignore | 1 + 8 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 depends/zlib/lib/win32/.gitignore create mode 100644 depends/zlib/lib/win64/.gitignore delete mode 100644 depends/zlib/lib/zlib.lib delete mode 100644 package/windows/SDLreal.dll create mode 100644 package/windows/win32/.gitignore create mode 100644 package/windows/win64/.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index de8c48846..1055ee623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,12 +195,56 @@ endif() #### expose depends #### +if(WIN32) + # Download zlib on Windows + set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH}) + if(NOT EXISTS ${ZLIB_DOWNLOAD_DIR}/zlib.lib) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + message("Downloading win64-zlib.lib") + file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-zlib.lib" + ${ZLIB_DOWNLOAD_DIR}/zlib.lib + EXPECTED_MD5 "a3b2fc6b68efafa89b0882e354fc8418") + else() + message("Downloading win32-zlib.lib") + file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-zlib.lib" + ${ZLIB_DOWNLOAD_DIR}/zlib.lib + EXPECTED_MD5 "f4ebaa21d9de28566e88b1edfcdff901") + endif() + endif() + # Move zlib to the build folder so possible 32 and 64-bit builds + # in the same source tree don't conflict + file(COPY ${CMAKE_SOURCE_DIR}/depends/zlib + DESTINATION ${CMAKE_BINARY_DIR}/depends/) + file(COPY ${ZLIB_DOWNLOAD_DIR}/zlib.lib + DESTINATION ${CMAKE_BINARY_DIR}/depends/zlib/lib/) + + + # Do the same for SDLreal.dll + # (DFHack doesn't require this at build time, so no need to move it to the build folder) + set(SDLREAL_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) + if(NOT EXISTS ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + message("Downloading win64-SDL.dll") + file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-SDL.dll" + ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + EXPECTED_MD5 "1ae242c4b94cb03756a1288122a66faf") + else() + message("Downloading win32-SDL.dll") + file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-SDL.dll" + ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + EXPECTED_MD5 "5a09604daca6b2b5ce049d79af935d6a") + endif() + endif() +endif() + # find and make available libz -if(NOT UNIX) - SET(ZLIB_ROOT depends/zlib/) +if(NOT UNIX) # Windows + # zlib is in here so 32-bit and 64-bit builds in the same source tree are possible + SET(ZLIB_ROOT ${CMAKE_BINARY_DIR}/depends/zlib/) else() set(ZLIB_ROOT /usr/lib/i386-linux-gnu) endif() + find_package(ZLIB REQUIRED) include_directories(depends/protobuf) include_directories(depends/lua/include) diff --git a/depends/zlib/lib/win32/.gitignore b/depends/zlib/lib/win32/.gitignore new file mode 100644 index 000000000..683bf139f --- /dev/null +++ b/depends/zlib/lib/win32/.gitignore @@ -0,0 +1 @@ +*.lib diff --git a/depends/zlib/lib/win64/.gitignore b/depends/zlib/lib/win64/.gitignore new file mode 100644 index 000000000..683bf139f --- /dev/null +++ b/depends/zlib/lib/win64/.gitignore @@ -0,0 +1 @@ +*.lib diff --git a/depends/zlib/lib/zlib.lib b/depends/zlib/lib/zlib.lib deleted file mode 100644 index 51f579dd775795ecc302b52bd47792f8d5e70401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108064 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 INkiWLKYjs1tpET3 diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index d3e3480ab..b0142a14a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -376,7 +376,7 @@ IF(UNIX) ELSE() if(NOT BUILD_EGGY) # On windows, copy the renamed SDL so DF can still run. - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/SDLreal.dll + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll DESTINATION ${DFHACK_LIBRARY_DESTINATION}) endif() ENDIF() diff --git a/package/windows/SDLreal.dll b/package/windows/SDLreal.dll deleted file mode 100644 index 3ce97a59deb86e64d75443ff5bbf55a697a501ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 321536 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⁡Y_0|PGD z3SES8INi=mA*F|6yGTZUibBqgE-}$Vw_#vokOyq+7!+tb*ElcG;4;n!a$E}K#1bFy z>b%7gwSqU=8mj)@*_i*M~EYBVQN6xE2Wf-wB2y z6?n%5oyYL16&^aS%x$%bzULtL)>I5gkbWMr_G5Z`eCH^VKo{MF@P4z9D_FH2_qKU_ zZbN!0W|O(9L`+j06Ly0QLQi<{Gkz`n^YGJ(Q7Uy29wcv?gufFa^UWoUU9}&D@?6KF4#qXdzSw7(8 zvDfg1^e^YC>eLp@v4_>6wAmW|`7-=WdK5|HMxh00`_>+4E0&xu;r4m=kF0YRvY{&T z`?E3029ge9LYi|IU|I|1<;{dHHxs9m4K4UI&Weio2bw*~LR9Y=U5-%B=rf5^;g79` zS=?R#DdRNy%UKrD(L(pKTp23NIyuSmSB}c#U}?#X1(uf6U}?#%1rmKsR?NQ7x$*uw zg?5!egd@aNj*v(`}h{i3mh;h#oEGD2bA6A+GgZ2wy*%0UaF@SazUXm zNSi2>p!KD^u%OVhWiP_mm=Ep{B#H4Q<^`w)Ofcgp{7_zHFIqaiOJk{N0 zdr9OW38}2Pl}UNLI zR?vs)J5jW4q?v~MTufLaCl!BnW&SEwd~uA-MshSGdLfQ~P>Bwh8csIaBb`{|;ya+0 zz5^nI(CFx=2vZz{VjrUaL>^qAcRFh6LLh?wj_BUg6TJv)J#06;gS2i3dD4kK4rTDO zuYo+^2Gc+KGD;T{%39!#RN!*QdX7{1|UU^99tG_zs65XR2hVrp1y1x{s-EMq&P!B6644Km z_H)KTkL4vp6<{Yo0p%wstzFt&dmKL??aBz8nUfVLm5W~nmDdI(FQ>xNU2JyzKqT9v zCyWaVk`f%0F`khEqyu2{6Ic*(Af;6{dnuAg?A|2h%m-f}YA0W;cJkoum?I3wx*9@C zHua0h<;gl7Ab~rOOco5ri8Q9{w*N+B z9+ucRjbR;$G^U?S8IR}%qUmYYLqj681ic{BP{llkv;`B0>i#Z}30`Pm&Pspd%XbqR z2z%@Z_b(zM9^sxLvpIGFc5ic;q++kIhdIny)9_~x!i@e@mdI}L!!cx9)*(*0*U@jf z*-Q;h;B^&lgPjPW@6pj-j~4t0`C%3x`!Mhvm+^awS;S5qxU20W@Q{dhSibGRG-AxL z&7obVGmZN8=zbpB$KtHuF&>0zi& z5skV43{l|#WDYjmsR#sCqeD5Pgk6Db1LGVThO)Un)yY)ZCHGCj=`8v=2S#cDIW;aY z()4}hyD+R}WF?;iKFfFGYn^@pj(JUa8I`!Yj-G>d#HZe*>9Ran%d;ePQmq($g-W>? zw)of>dW5j}6ncbEe9AfyWXw|GQ%>N`Z5(Bg79ql?m=YqnH!++WuLC*4p*8rb>*7;Z z)43jnfX6E-T1#6Y=(~TnVg& z{K+O3Y)C!O6k^hu9z$9*^tGWgA!5?~scOW4KrT#k97YBU4)u3qJBThML8~1m-AkYD zHGF z9w0Ib9;Z5wowDUy<4D$*9R|mcNuGC{I6xZh-K&L`!S+0wg5`kvNZKd~GmDw>1ou!m z*XXV&9J33tkco~;WM)&q@>EwPvsuYS%vw*jM3on0U=IfVo`E1lt+RnXX=KybR3bD= zAMB`lgXCWMExGK2oc5`!cWsXih3kz-AI&Qn$8y8L(D*CoHj1B|LQ%semq?Vxr?nW>q^B*Sv|5(Ywwbd<;~!4lAOY4+$wV zEVWdTrH@|9>8{$Ys@)`OM@rVEu4pL=im;%1D(dU9xa3<8vCq+kXt^aX75z8vYLIHm z7KU3H>@@?Ra=?YGL(><>euraM^v@s4BrW2I_#ccv9e##>hvdrGR5j_&H)CkZoiIpLz1xIf}lg^k=Zyy0Cni@|0GTH7DK-gHED6GOPBO^gImvLh7BN!L zKu_4;%I(Nzk)_QXb(|v7)O@;#$0+~xKnd`Ib|~*9>b@yO%@8Ana(+%26jd*+qyeMF zc6Tc?_GfF@-{r8!4%!#1>#X@SP037c9b$^L3)9gtrF4st6UI-jBmad%0BTmETpd1h zK|18x4#Zv58r4M|&-UyQFX~i}ipW)!1A)AQGbUDKH~YRhQUBt<*mVjKqy?4MOTd@tN>yzUh^U3LQgj2n@PH;ajLm!|9iN9b&PvSUd zEADpbn~A?t3yWz+N-bDV2il_AF&tQWs|LR=(U#C8(R)<;R}0uyC9+;y|lCBUYE#{#y8s`(#K%ecz)r^Bkq| zH+sAij7XADFc+eUZJg2`#E}iUpcYEb#E0mo2aZk{y~M4BUqCWNSuB6X#h|bSCz7?z zz7svQdiM!!^NbTtqdx^d;Ss|@wZqP2ePU}k{X#k}p;Snp*_QN~Ui@a*?V@)xP!5+q zu~~=ru`Us+I;(IUQ~c{uv>ewp)B}L!G1f4=<$w)9B+lsSaNf24&L79NK01rkLD{5k z2Swk?(#F{?)M>%Xdyu(tX#vpDHDP4?z+y)Q|3kI0RSEO&Onm}qFs9>J2vtr&@@A3x z7gCQY_&h?4N7yYR4F{cyKjIr+>W?zG*aP?T%4rS^b8%x2VV@)0+rHQpyUtLB6sN+y z`U-ee7dM{bFBN{)SHjQwQ|GDM4}K<(#rOI7qbD9+++<}ebn_C^p+7-gAHx%d+5zxv zO9#5y%~Lahn8K|Kc<^=`qk8dCF(AQSMoag_=f(=@uSvBFn!B>&d2xpbOXOV-ZtLedBO!NrBU20vBE%(d7vl%X*UQ6Whc-mG|g_; zS@wfbxKIJ*7q>ZB8Q2=QC6p2C*aCYR_*wiD_`EwhReaR)yZAkJZJuAWCEhgBr@QX}$_Gs{;h{iy6kA`U=%E#d!O#l@k z3FCE?YJ$GgKhR2`7J>`&U;Ge%(IO^0z_xJN0g#x5Z-Rf>hh2RXzc$RXd?Ab4pOtP7 zV{Sq^VL+6-24&Ase)Ru}|KI-R{~P}w#__LEU&~Qo*KPd6@)Ag~M&UK2R!GR|3~%CS zfGR*PBF9P2d3Qo`zrU8FB;$owZr~I~&N8g8HFj{BCVQ?f4L)AU*%rV%)1}0tD zfN}T&EDsq;;1))p2qv;k6K+FeP4;M91SNHab1JkZjz$C0shB?@Ce9C9{+}VHvb;Wn z+e=Q=V-R+b{*pf?dW5JLH@IQ15qgpVKZHG@ub1T;IqP`$^=1VBLj5W%D=$2B(5bWS z(fRzT@xoLZ<-`AwQ zn9E|Ei?i|sG7TvF$apZMySV4|S;x)E+89{$qFq4f9|-0A45yvm$PwpYMUT&EW{x9w zA}cvAW5_^Qm#<|ix5)l`I{jDr>o%FV)s5yuvQBda^1~2CD$yN0=yJv_?#`BoKLk-u znZP{)-d*L@OR^`{TOdMqX#cFpn?==>e>G!8E@>QIV0aU)(}E&Y-$K~vWj)jJ%PE+d zVvWpmaUXOES_+nRGPhSZjyG~{ve-!T_tz5>(q`t^Qz*>>Zmb5a;f_L?-7}mla#sdh zWEZsWuHpW$rH!?#_TXg@EXxY#Q}~OmfX{h4Ypabd;Yk0ap;6l@{3m$3-GT|YoP5!Bci zP42#4qeC$}WS+6u`cd^)zAsK9=Lh*H>&ZRe>=3mIDV$pX0(M6&deSeC44s;%! zPk}uH=rkYRajLT-IM8HrDK9^J(6R>A$2Gz*EO3R6`|dXuz&*9^p5rM{?pp$sXLSZn zIhKwz^5Kekvd6eFRr}N1NxL!gI~+?ds?=Ui9;>|^9KvE@-Jb8t%WhuwG0GU1eQMcj zD8yfk;kI05d1$Q-S}4y^aOX9bz|pzF4p+-{wq&c!sQ zq{4I7uKKx5gT9C5xWv(cE~EJ@QsR0sHy0a1FK9xDLWrf$i8DQXS}MH|D0brLII+x9 z0@dLF4*mnYJFrH&Us#~`T^f6qmJOzXB?eF$FIp(fY+_RRU-b8ylmRF9I9sI!R4>#C z7pFK)q1O%Es;l>6OIXMwdQb8#Y3OX(jTuRJc&&U%>aA0=uF~_t6}oZp;rkv!uD`W- zsxK3EWJrgXeeGpc$*GDWkFdxh6dC&?(m-^LN5ABJBZp9hI-Kg%O0g#RokhSMv0EdW zsNFS3aTu0UpxZ(_k61hll0+)~QU4A9O?PyM(+6cJs8crkeuVZpf@oiR30?p>-5NAe ziE$`Xt=NNJ&|Lyr<}ml(tt`QU7eU0{5T(~&JL=DfLXdijm`cA$}iw9}90prtq8Y+(e);RKwdVETEm5Gjo(bArhjKNG8?=G|l|tI%Gfmr-0{ z-0U&#NX_9O8vdtC^z-zqeHY}&2totyfPEeMWGBw>lU=cGuwUqsU+fpr0XNhx=>eN(AC);w(I0c|t2rXr9k$!%NqmUG zeu;k3qLzX%9_lb-**y&NV`bU^Ug41UkE&2QhGfH5VnU|-y;2;|rmZA2W;`?JvAK&cZ#JN>VQADUwj&^SAb zGgAESlqKn%6J7pK?wqMDf?yB-#I9KxH_sc98Q9^{KIznVmmh|Jx6_{-o2bp}sU6y> z&AZ8^9ePch*P`9?w)UeYZNX=0T5Ov(?|`=8^Wj=-yEgBzcF#xJk6zan?DJ>~j=8kh zUTs;gz)p|0^k!F}$sLF~{V(eK0*6z47p~~J;`XE!QS%sGye_~IW8O~a*G2fpw}n>PjxP1AQ{FGdrK*u;6w(#iwcp;xs-?`aQw znykedwFg>yX!A;3+PpWlLmz7Mc58?JrX6}+d*Cy~9?>3nvxoMR1KPbi*nVkipviST zo=TQh4n-@MCaow*ZZJ~@4?>$7V?D~>rxr?5D;oZjBx!Z>_5QcFCpPVjW+!y1TeGL1 zrP&KU_h=97>-%4tzTi`&*pC!n+x!iIBPqV2D+-g@`pf=%<8P40XSje@{2^Xredo9w zjtu$3?>T(8L;i3K0y{jM8y%b*J)-Z(I86n5f&YUTHjEGdXou1>F(8Qn?o939?;*GL z@7J|MZ(&T*WVn9n(P9U+2j1wZ#hSHwJJ9!@iMgDa+P!G`y+;!ZLaPqF71)uXEt}^;VQ5^S$rFgWd_AJK zl1yP9^zvO{T!N8?iSfZkGo|t4o`JSOw2{F1-|qw`YxtkNfJCOZ)-HqWqmU38jqZt1o5*q_mZW$x3=pYfwmOikpDHu zq)w)q%fT9mx2ml*N;<+QTDW3y*m{>z_kk zHJtx4Cj1KzW$C*IH*_|1mLKNdeP;RlZ1=vXYCrI`Pj`NzPx!>?_3uUdQ+$_xQ$?`@ zc&`+LaP?^&l|FFTkDOC}goA#@V7zAaW%&O!=h+5jpDDwV(gqcnGl%6IRzBAMMtoSV z{6E$yUF^yfXiLNF#^{5*HO6|PgKYplhCG%NF0eCs9H+LLYgfdp`X6$|_Q=4k7(j}@ zFuC!FXT{Q##`irdraD(F#5T~Q{74%9tytQtad$txwfy~F_&2x>{~Gu74eWF^Hhb`1 zZc;0!0FZBJvf3s)G8dw9vL?LzWhE*))j>32Bwn`s+?tqs&-qW@KC%K2v&$!V0a z=saUU25si zT5&si?pbluIr<$Pz@~ol$%)n?{koF9GQ5ULte3p9IF3+OO6_>0i6rSN|O6JMNwv>igilaNX z@a}f~tHApyzHfbFQlScg$iUGMLjPzg9WYtl2j@b#l%O|0_= zbKAB@W7+>O8fU9{JNIsI>Ptagr#c%x?x}BYFngAL(1vGN%RX!a8q3~qYxq}+t5tu) z#X#3*2yh*&c+2{wYjiJ$6+(L-rvSeBH>@LQWj6tpOEtzR2BoT zARWa})CT!UTPHv184Zlf zn2#U*@dgwYkoPWZ3C1Vz)QdBxXmc(2fyBaj-RbgcNaxCzxVNOh@3V8)wp5CY&~k+* z9)P`&&=EC!LI&8mXDkXj>SjvFmv~+mb7~I+N8z;{lo3MI6)DG|Sc>6g0}sl;jrm?S z-~-+8X(R<2o#D#PQ>RYB4trbqkuv=AMZefqRt9(IJuyE5jmeNhJNAoAHgF(i=mAk@ zc7>hivRw0?tED{@K%(Evc^Lm_%1K-dckogk?SqWLtja~rKse-ua@TLJMt}$(PM7D@ z;H1!Li7aGPx^+Is&X#Z=jN|!Cqf#)GH=(huhf&~hZ;!`EGFT;@B&YHs9GEp8?@Ajk zNLC8y@}%~vtAJ&Lv6Ssinve%OcXJRf*aK~+hHld*O;FbgFC&5D41ESyOv88p1+x)r zzBv>{J;9>t*k{o+2yBBUlCnDwyn)B9GOmB{lhC1MdvTB>szjbA_)6pTDa;QjCKz(CM;VR2}CcynSCC(+ZuPteUn|uci%mW>;@1vP9AM*~G0FVxTWSyQeBzg-P;t&M6@GxRg zPi?dJEq&tN61{jg_J3%pm%zGT+uQ*894>UAHUSs7dEbK= z=sKhbt(to)n5aQ<{SJFXayt^rne$dd{9pMMu`VAhc8g;-k zRec6RDWpfgosd_e(gD&fs?pui3FoLwb|ftwhPJAHH|0W*hOvu#?nH9~4Jm=Plkj-u zivAHKi)mM|Q_(w!K`(|Z*XiQ8zU6X6$0J2}Y)bS&61Qy;og~I-YgF3eR$yu!BknD- z9}@lt($+Aj^#sJm`;nlDUpF8@>|J~v=GPd0z20@>L3)~X9HQinXblpb7eb;(Fu^hJ zxzYHFYge-~q3LL-UL}8lfWaz-@@tsv%LcWLq$8_R^Cy|?Cn>PxloI6^t>4;7k~~Ib zq)fA&L?Ezb1`2jAd_VR|7d>#TpGBjM27mG{c^;}4GT{n|m#x)haIXeG<%owMSM~#I zKQ$YP%J`=G`L!4{wW*o_ zLPoM?qrXamcRPI~Q0JL9q71r=v$gV29yet3aa1Y@TuR)?9t~P7^WJH`j7TKRV8VOY zP-~ZlyR@5mzr;z(7XX{`T^W0^&NlAAh8t*LD7WaACfTAL&HPE7wTnM#8|5c`_#WA! zEAbPh+Z9xUYz}ksB<{^xkTw+@h&Qgm?90c;8T5)g~){mx>n<-nswUE3UUKL-7Hnw!CK5uCv+8`lru<}qm>CKYE+kq~1hv*Aw?x*iba_n>VvtLGz?wLZv?^cV%yU`7i+!Wr#E zz*+eb>{sC>?Nh`4qnHQ7M43&fuNa}*M9{jUP?|r!(QsMDF>ePxZSrI@9k}g?lg@6r zw9r8$uT<;@99DbVzqsE$dTgqH3lNRT<1ov=1!8RRE+~fACEQ!AQZfy%i4H)MY78E# zsZ2;{bCTU|f9-6;v7dk&u86l&RvuVQ>uR$oUJ&TO*H`K@qUQtMxW76*KeCCM4T$jz z7!~t*+=N6wBt;iCO7T0~euN7!#$s4puUVl?-zg2uO^c6#70@*X!bqaIG6rXBKFSJ;B94V+FE!e_He4wXS|SO<+O0_GK)RYy zu;o-8m?3Vm$yrDTlpiu1XY5Dx`xtVJ>mH12?9upml3tS4hBobFjHce`yVRN$MPl=4 z5>}2i2flG2DWi49g0+DQ zL)ys)lD(?~4NyeWy_I_LqgwD|wA<)upWnm+52wP;A}CIvM7eqOjWoQg^&*t2JijT9 z&bZYTI#hbTWm&}V0a&e zObc(=;*H=OOm1!A_2}F?n0Z^#;3S-gcwfwI=$=B$ahhxrr=!?rDuLoM)DoqsPLr3* z-4UMH3}ME^h~N;n-<5LH4u^QaXlIdI2&ER3 zi%~fqMV(06jo%jiqG7Of&N@^=|B24E>xR>iYd*);p`L)gne8!XlI$qP-`XjP(D!xltTr^DmKhw z*$kFr_CNvj5(x_vnC^viAWZ4#J(Lt)>%x|Xcp{$WmIvj zb!Qc4PM$RlB<#$o5c)R|ZH=I%#TX8;F}1%!(`z=U_FFppQ{bpbjSL2a$9oRPs>?W} zY_Ao^5w-CTYyx|R3=eV0c5lU!O+aHFNS8um6|o1z2}-gW>WD{Vw|O5MCt_4?i zA5K5``G6!zd)aY!yNscCwXxipg|!TOu;nfR))>p(0=!}@_XzN`u{>3P24i`e0PBq9 zLj>4iEFUJov&Qn_0yG=TM+mUNSUyUCy~gq}0I(KF2e7@E$FQ={5uQ->ydhd}6T*4& z?1SF}JvulXkTw|;SJykrfKz8!Xw)t}CH6g|AlwpKo{RGQmo*jaL2t^g4SLY!kcL>HR z1u{n+hok7*SdrGT*W$16O31l#9+Rg+E`(chE+|Z!PcRSi&EFsc$?``(q%^ymJXGF= zlR+;(x)&8vjwvr`G*L!}?{TS3-o0u9&}|F2A?V`c!5Yqj!3|iFJvtGqi;uyum9v%n z5avw=eq@4<8v^qK%*Z|iIU_M_z9l<*tQ&^Id7$H5Ew}_VX{&C-UvUhu8wCbBdTPPL zEE&tH<|GER)>VN~#q?IkaL#eeec+Gfm@i(6amR z3k4k7<`92M`Z(ZNJ~SJkG0uJ94+IXa<+1+2A0%15bGlK*Z%DRVdT5|F)c*r zEb9qc80I)cI1T{ZksCM`s7{Rq(RR&aSUD%d)SxEV>* zMeo6P{ZIIvBi}#7cb%W#1Ld2KEJPl|cXSzg3O1`a;&~UcvVTmgpViiHPR;y-{pL)q z9e$|a9Gs%ktB>x$gdYC)esk((A)>lnKHuXr5dV+8cLA%a`2L5tN`*$jQB*3;UEzhi zBHr+NxC@FBcnfbF;NStd$vG%yDu|d!nkA(c7ALXHsLaT`L?VW!W!}rw3@;I;k(HtK zHRt`T*?XUT7_IOB_dM_O{NMNYL@n3;?3p$9S+i#L%p7{kq^BG7Btf>YV=W$c1|VJQ zZ*q_Ha+pgE&U(zhT6o_@yen*5kAK%ocvm?8%4OH2mCzrt3#Vbof=POXFWjTYxqPyj zl1wFoJ{;Z&opSya6jJ@GU8p-_75}d6y+!>qM`OZr>w$$k$*B$ba``Y$QA57$E^?*N z7rc%Jbr`RASdcVs!|;w=Z-Sku&w=3y&Qks4VYnBSI)FNV!+bLDiYHpHa`^)6es1v~ zm9F?nR0@~UcmhiYm;mq-r?@=%X5nG|_hxp`CpPud8z~W*guKZ8^VvTl{t&sXKkAR& z(Nk~kK-4%x*jgzmYv05ifUH$kVaBUmotQcS;A!1J;R_Kcpy1tF`d9ns7Tt;k|c8>2|ZW=rH_uO?$=k_R}2;-^RM*731r?E`Imc#5@}H%0#O)! zhWt}8qQH%O!JdEs*anHbM%-9v>*H77?G24+I z3@EgNVTUWJHKiofJQd--M;sZ|G`VtKpPwfr|D{&}XQ`f0cO zSJpr6H2M`(E$B^0!{(9`tpN3zNhO85r*oR zv{DVG{$hVPD-wz#(dyf=p34tsw7|5)aX7<>fOs5~nKrQWl^@=KfuJ4Vco;r}_M8%u8Hof|{jjCBglQAmUw1xm<=(ZU@rGE`Oya z~{jiLUW<5#w4{7{L4K`OBLcBMTUTBEHcM5%kTuMitP*Ynt_UHEyY{43qy%A4g` z@?C|m@Kf=HBNjhQgfkpL`qEkE;L>xYnPnbqqtuV>lJ9)T6@Pq+JxJrmxk?M-7wjB*;P#Hhz=-Fuo{=mVF;83PKmm_AVlA)eE`^t00$FwL_X_ zb3H@9c+9=nzUBL=HWG$k+i_)o6<_dFq^3fObh5*qQ{zJsdQypyJFQ9CX4FSxxGHR+ zm2Cyq3k!qDu%KMjZ{X0Q=-z!nRB_nF=t2y?W^+$b46Z-VZm%eXo@h5u7M`TxQW3BkqB&!Ki;0 zFEPYA#%m>nm*By->T*3|ze=s+jUG4Ij47BnunSM?EqB%+y7>X+b=jdEq9)PO_P zfE#WgkgJXyK%c3ycR}ECsg{9(jtm^v^&x%1hT>BcwsLPvC&VJ24_ECY1VxjT^|dgz z>aXKGI879<= ziF{%(aoArxf3Ef8cVckB4qa*w(8nkpx?8CJEIKCypf6d^mcyh%M@>~9kh6{d|f>zz$g|Pdg!dM zVj=Mnr!~s=77vuJ8sZx~kj#~o%!x?mp28Z_3}SW){HwRtEwv{OKpg{epA{P6!X25poe&Z(8iQ9Wt}av+getxM0N8&*74mS{}ln32>4?G z#|hyX1-w%5-%oJ=RB$gAaG8K73GyBSb`|hRLH?qEpBC_80pAdCseq$|_@)ThCiveh zU^l_Pmw>+z{0|b`>jn4E1ng~VsvoXco8rR)?jZP=1?(x{8-l!t;O-(|F9CNGuq5D% zLik?@*j4arI5;AaKA zRKP0*TrA)c0hbAQqku~Vyrv1p_|Hw8{nc&~jbMs>WS4#Lb3211TYKh#A1eS@j{UUJ zF6-$Lvz1HSq=Mg=7<*iEc)h`Fo%uZIKA2j}a~-`LmEDM?C`;6DZE!z$M?Ur6zX$gE zutYdY_DdA%giH>o(Y%uO#^Ccz_%+6Kd~-ADp4QM|SbgGR3`Jo(6He8+>g+-$!~z|zHS>33`* z=KjFTT-hnXOIg_f!HZV;nMGcng?wj$QkHygfl{1&cY#`>xoU|n)=PBh@&by5+TTsI z2=|Y!Qn@x}2VlYJXm&?h7dnppcU10I;0{L=eZ4Q4+ItE+QX^R&-{^?%o$tPd*DjD~h~YG`^nJHLjn`KPp{6)#UpP z`n2NFcfbFsIY-Hf!fTX0`p<)3}UeN}bK!`LPNmhb+{8!!#JkEkV|DcM*pK}A0)MV=xrb2U$?Mk(j~z2#DcpU% z&i_>5-Z^dXmD2a<5F~eXb-`ix#dNka{L-)Z*QtbA=fh3LH|Un-*pVK^Y~CF-s^5po z&HuCmO(Dkf)sr4Y!QR0IRYgyG8;erCN8*ZB;KD%#Ke;dc4V^0guV%`Q-~7M4-Jd&$ zH%0f=BAUydEA2PIi_nBzqVj7oO|m@_mha!p{gd`K)U#`fYjpj~n5X4`iUl%7Ci}Z( z?R~3q|Llp}pxYvC?gb49G562z?u)<210v-I^>Y90lT_IX8%lo0Y>|g~%Dun~&w7-T z@@q*QEPfDg-9M?$ukRB^pJzuQ6#shnEFTsI@doOvxZ~X;?zE64tQDCd>(fm()@I|kc6n3*G z>T8g7HSFwtdbmfkr|99LLEv=i4g|(*ce{Ub&+@3Z3J)Xr?P#%K1wSEGi@ore{})bX zcEAHF+`XU;9+rhQ8#>A6IyA?{>qjb)Uw%Cdr6yx??|rF} z{86@J>L1sW=3Y<-DLr`yvODU@L)}ugJ1W_}RIP@O%+WDXWNume%irm;M{V9}^!pX( z+p>0PAw}lgADUl2Rb)fQYD9+`hYmFu6^>h{vxgOp_U=%yA8kU$dvEu}>yUg+qde~@ z0k@$!NtL)as^AtDCo5^o8{fcLKN3&-q(|{DFI>Kyb14>L=+CV4nI`?8b@meNe*s?= z{0|b`M+ z0`4c^(E{Ek%wMYn{BaY^=W`v%z8g>bvG}io;7AtPcen_CZ(-!s>>e=WVs%6A3iKG3 z_OHN}bG`yzou=#y;qKgiJEhrg_1Tfm_S;p>e%k{3?cKk%->y*jSooiR^Liu2w;Mkd zQiy2rOA#UU4r}cqjIVmtTDz!QYgg$tx7IFlYpwnmS!)+z%8|8JuUTtXZ?o2}s@7T? z41?j9?!&)M)*7GxPQCQQ{WrC@G+IW{d=-E6O#I)!|L>GQv*xs0NdLpMRC3iJo?UZ2 zay?dhNFE-pxvrA-ta0wX63D|t@e6iR_ zeHNipd=iX4&hbp|$=z9o+LZGjiO{EESguHq(&qlj9rHQv57R=4z7$dZv4kN}soW1D zD+T)>C48lj@X#_8nglX|W`bse z7J;4vtpmLSqJy$WK$N$?K<(AMJ^^k8tpV)@(bR(M^a-FBK*vEHP)4bsJs{fVv;x$e zrbHkQ6vW%03CL(`6i6*-4>F6Hh;$g4WytJkWORo+I5K(zY^&{ZU;wK`CyyCtsmTQh5eAvLww`c^){jB`s0;v*1*}FQ|MiII=0(RlW(F>VAvL zcY;$L?^gL|;3xv=u*$yyM{!8!RQ@A4wS%8k{yR9ei9c12$0mPrGlQiM$Owu9k>kn- z43^Rdfgb{OP~JtgGgzY98!R4P2FqcPb0E3h43?FT<6RH9^;FzSAHzFugwxqz$?U58 zuMNSugJ4gK5mXhRlpUJH-~LrewbE5w?{-tVg&#X+xw!SR_LlUsbVzz8_nurTS)&vu zWlA}?qH=+>P+FM#w3I4^qW`qOfB19taF=P0NsJ24OZIRl4n|l;UtS5SrG9;fzgk=r zO8?OfbM#+?r2Lm8DgPa0%6~{3|1VEc{;S9Alm5$f3M<-Z@9@}KIRJt-;w9c9XY zRduZ`he5TVxF5kYJuP*hs^42%jE$`=xwEid3aa`8_)k!lrzI40_-1QM)L*SFn?2ez z$%iq=(-H{^(k=n-c4c;5^exv%H+GOqIreWdLfBmB$ zh{*v}&1z%GwB0Tr^{P_NEik{NO_xSs>JJ+315DN9z@?rR(q|f$fk~%n*aH~d-GQr+ z2dZw(oiOP-4adR14D3PicZh4k5V6yn<`Hjs-y`f9qR1vC*Xbp?k`}d zr6%&EkDbFCBw&954;C;~ToZZH?atv16Yy{Wj}Wj?z)+t}!V45|kbr{)93tRQ0fz~A zq=3T(93kLQ0v;{kNCA%#Fop$3{+!w`-Zp8!95-pd{0(HmQQj(2#%5qU?^*X z9fEkMwJZGxVdm0%!lXa(y95qQ;}16RJ22@%w-!2fA?%`K>g#TI>6mna+ub^*@yG2R z9aH~wbJa18KW;5`OyiGRD;?AL<94r(d3m_$m~0id`*h69<9;3U@^IHN^?x@H9b?#+ z+zdL#P7}$kwT{X5aciSv(id)?Iwrfwt*wr+Ye;fyr(>8Bl3RNnlYVjY(lOanZV%`f zn-e9s2X*`q;fHig`pB(=jlQxG1tEXb#t+3e+TQB>)#q^9drE~spB!^K1Rn} z|Blsh6uC#~_zA*K=s22iw2os4$LM$*;c+@1Pk6kJClH>XV@wGow~0EQM0k>ppCtUG zj$;YO>Uc8Y$vU1wc#4jv5}vB#X@sZgcsk+fI*ub8r(+XglaAvF$LlzOaDt8#2`B2< zOxUdBB*IBLotm72IDLS4>c&3h138(5fjc}Td(+Q{RID>G8jxz~o z>Ub96Svt-loTXzcVXKa9gl#&`CY-I~*@S27cn;w?I?f@SqvN@R=j!+=!cXaV9^rX9 zewy&pI-XB>zK$0VUZCTJgcs^KmvF9*^9bkZcoE@6I?gAYuj9pp7wfoyaDk4O5MH9= zX9z!|bQ__p^ldkUZ&$B!bLiMmhiJWUQT$qj#m&~q2uQWKd0lBgjeeLdBV@@ z_yxi*=y(<3RXQ#vT&&|43BRc0mk7V4v#>}H9B5Pc&(0KCj7FFUm^U8j!Oua z=y)CBbvk~P@T)q0jqqzaE+t&5V>@BHj$bGIx{lWqUa#Xa!eu(%KzM_W-yr;kj^8Bw zrj9of-l*fZ2*0J{O@ue;_-(>(>$se7xsKl<{Em*_CH$_A-y{5bQn*jgI#c-mBw%g!k$A zGs2(gct7F&IzB-7fQ~;W{JD<5ApC`n4-!78<1Yz+spCV059#G(L|<2pV;_=JwXBK(z(zb5>(j!zOkspD@5f1~45giq=CG~v@aK12A7j=v@R zt&YzUKC9#J2!E&JTEewDK1cYRj?WW5uj31ZFX;Fp;fp%{p78fNt|MHh;~xnBpyPVN z^*X*p_>zu)B>ba}FB873<12)(==dkXKk2xEaD$G2Cj7IGuM)nh<6j8>qT^o)|ElBP z2>+(zYlN@q_;$s6{qmHi=zOLgNgm38h55j-w_)o%r>i9o||D)rZgm3EjFT#K6 z_!i+?I%b5Kf=l0NW2t(#jpZt+>b*9WxW%3p3n&WL;jaW;#q)L0)je%2wYY^hQ~h>@ zARK?%8n(zC9{zvy5EDFldcgSL@Ug+s;S)llCBhTJgF`gjEj>Fm)kl(cwezr4wf)~g zPy7$^LVh*>OP%DjcVd}1`zN;ps*JOLdLExXGd*KYx{UIc%~@F)S<={So1Bp(r-S2w(Z(` zJ@DW|9XdYTNq*$f$Gkgt>DtZb@$Nl(_Uhfox36En{sRUM@*g~8=&<1-S^r41D}6!@XJGoj~qRA z{KQvZpZw<3=`-J+{jT=h`3o1nulu3?(vO#~{M7LC)n9)7?b`2+*Khps=YMYgg&odB&U&1CzhwlO|E%bbugBR%;kYIMnHskXV9X1QB>hE1Lr zFd+o0SkiO{Vcxr`bt2rsa-W>ez53-)_(2(InW^R+F2lpI$`pj&yWuA?DkMxEZJLXQ z?x%#))Qc96)yy?hWX$w{;HaQT^aFEJQgTAFIo)QZxHNyA6MM!d+hp=Wx=KwIzJW&u z$ckhl@|K9`5=`lGMtbU88CoY1dF-6%P(T!*(_c_<&*-s{Jnd>XM&6Wn*6eueT$}**yxhk-c5R&ciV@_8U^=&?!GJ{8lawG*On`fg8}wCZw88S=iH%WwvE!rAwKnY^xbt9Uhasr7lue zshiXTn;m*dz40l}SL!bfz?O$W(&Li9)Eyfj{G`EBKWPZIKn$0LNzQ zDz-qxO9@h{WS4@ns`4=yq_lCUlZ@IrKi6p-d_{%uZj0} zh-)eAr)8&~L);OvmY)8a=l+`K0geYP_5qsb0h;FlT7Uzz00(NG2Rb}!Ngbdib%2)C z0a{WAY5@+^A|0rOJWvxKsEH5M#0P0n4AR60Y2t%4@j;sSAWhs~OQF9e?yrgaYvKbP z3Dfd5$RXtj*^vy*^B~Q$e}8FmpQ+f*1L_O%1Nnn`fqH}bf%=06fChpFfgT6-1a+S( zL3^3AB=f9nQ>s!G7WXVV~!z~etV}@DAd(IPK?@CF&Qc`~@X@Hb8P)Zsk zWhKwB*rX(XDb<{0lN29);i-?5Nd!xQ|YS0H`!`QhUX;oq4exDZYV;P zSrwDfnvj)|nyNhWz*XPMvnkc41VInVt2DFCq`cFgFiIj@syKhn%u0r$M&M?1x`a^& zs&$qW5A$-Slx9xPR{bW}vQm|>_!A%y8PcV1A-EXow%)v0VnR9HtxxGMRxH?q+Ls&s3 zve0P;0ShC8Pc(Y+5m}vokR1~}F%8Bt9n-+f@0u1D5fX>w;zA(Cyfr89x!^e6hsd} zdw@m?!0U1GkLEfNtAXktl_P$;0Vz5+$Nz4rq<|)aMuUcddVxBDJV3ReK5(EYs)mLGc=EXP5gfHs0&06h�<9il zuskr*VA&pOu&fO+Smp*BEKdd*EIxq-%O#`1ve{&?d_3J?$w@#u%m&M~83t$rlu-)O zkxKglvAU)FFEyu%!Qxu&J<8tHW`LEOv4;#q&zeVC4-@^8>)h>F1N-34emlXP!lm#j zEZh5kAFi`sc$J3k`1^3N?NF+1p{F-s%io9V?3cp5XXoFC%hP~xNruwv`w!td`=xLX z{7twNoO2lx3f*?+G>~6L9`t{tbARL4rHMB#Aju!yIb8Cqc!xj5tMu$Y`PJj+Pgk1w zRRa5`e>Y#$_n!WfzkhQ3$1t4QE49f^Ag!HIm{n+xjy5Uy7288$#ozno;i~O{!VUTG z&VH5hMNujLlUM#1b=<)BB|G=8JJlO`&ios{aJntvm4D|KKASWipv{_Ja{4cS|5)b# zMOJj?Nx@EFCduUF!7!b}pBLjh+9jD`&o2Iam}DpJ%cN@J{!BVgJb<|plOmX_Ogx&o z8i~g;R}=Ag=E~($m@AjJGFL7?kGXRBeC9#kq$1`a6E9{SM&c#RgUgpO4=!KMJh*%X z^WgH8%!A9D`EusVgC zl6RF^0Li=humFUkX z<>#>oE}ze$;azeqV$m}3Vis*AUc#ced>M=8^5ra=%U7^yE?>!_xqJP-TYZRmk(emfRbATOOc63vlJuoSeC-&<5>!q$B}C;Z)GW5ejZEV^7)LL z({4q~%H@lhmCKhfE0-^0RxV%8tX#f=S-E^AvvTdPZ$K~tT zJTBkB=5hH(Hjm3QHjm^zB$f{*dAPECnYahbHxl<^`6TZlvwV{G@L~BR@8OG|{w40u z^0|BfD*}`}B3O}3Jen05iN~@cE+5Z|xO@sL;__Bj#O3F)A}*iLis4=IC}PDj@nTkN zBwoUbxqKNb=JMsNn9EnNVlH3Fin)9ZD}i^(;~*=MiJxF4M&f5#374;9C0xFNm2mk+ zR>I{ODJ6eOMXEd-}36lK1pyWn4ah(S{Al zGlG@N#G_fck$5aC=koEaoXe-MaxQOW|cXSS6QlWR+YVXS7M) zOJcOaLGp5CH8ODzR%0aY#cD|2OJ+4B@8!d4NZ!kr)sVcGKda&L0gN^%NL~@_piDfP z9W)Y;We2%@JUhtcQ`kW+Z)FF${5*D$%jYxNU?6!Fu@f@!Vs^qvyo8@1gWWM{cNV`oXegT&~A zzSO~$)yc#?Se=o$7po)r4l=7F`3^p;j^sP|vO1FQ;Lqx~d;p^l@=}Kg)*us)W(`K- zv8;j1$Fl}5pTZiryp=U@`FX5?%jYxtpe}VNVvREKV%BIRUcwr=d>L!x^5v|N%U7^Q zE?>zSxqOYXb-u$v#s{(vCm7@MXBp%2b&PTO2AuY%aPdoKTppjd&ub>8q>SORq>RNz zNf}d3>e!ekDPx>nQpT!kNf~n>AAlgCY?U!Uc2#t_(N)pOCg@ah&U00Cq}^4~b=9tl z&N>gBL+-FSxDJs$6kTBSQ2Mk9eUY5=Jd{3W_fYyuwTIH?&!er9J8Tu+24yd$Eg8L( zHeo^?l5?JyQb%?#rL3!QWdyxH4;#mY9%SZ9T$Y&!aih$6VH>G4{^K9e2G`f z%%AvqjE%%$8$}S8eONScqYsNEZo-&BJkN)v5V!jH zBk^i~{6;hQdDw8=c!Lc`Tn=C!#Ek)r9|ki8Fqv>3Y#ieD0Om`)I)M2TKOex9A2!F> zPgss%(Zr1rES9(_g2fZh!`MjN9>J`{t0UMv;^!k+K5^KLMa1Q3R!rO&%}R)yqFEX7 zJm^&7_Gnf?ygHiUq6x@Hvl`;CH4hS(W7!Gf##lyIl}e^qR!2M!eUZ35mNgQuj%B>T zpO0ny^a;!S+Ja(42X&$R1o(Ek++&+&r60e@e=wPUHejei|V5NNK zN=CR0Tb#HtpLr2CWu4cy+OIzsUJw zRznzbg@eRpj2Xm@CG0G5Qwggho>#&eh})r4iC32}uISE}FpU5BD`T$2Wz02*8_Sp% zaZ?$SiRYCuAL4e{;>4@Vm_PCJWh{WWRL&xZ%dk<28_QWNaZ@>qC!SZ%Qi$7O!x678 zXY+`kFK7A0r3zL=T!w8!+*rX%h?^={8S%UdR!-cGv7dN#1*;@}zJk>dmnzvo;xfiW z;>JpLmbj^s)e+CDWOV3PvSZ93UR}u;@$;1o<3Ijtm@9F)hItS-VopWeRKxh;(7YPv zL)c!!e2G_M&O!Wq4GSQSxmN^n`5=B`6Wj<}oVe*Aizl9Ukfji}A7obI)v#5GpFha* ziDS-IL|i_>iisOx!x1-~U}ePfPOx&~_7kjvcr|Ps;^$AW8seDi9V9NFW%Nr&k`ZG+ zano5=M?CK=Yanhv%NmJSV{9aT{w(7sy`?(l+5uRuV;;nfm@^VL)iIeke$&KBTU=hUS1{O`+h`9!FQv-`9p4Y%qh}#>Om3TF5apLD2SUz#7krfe_ z8(A@NBWzUSrbbpqJgG=xhs(7)6Ff2aROG=qPq|Nl<^O{5O~o&J~Qp&b65{{K7u zx1$~W@0$K2L;UaG=|4?}|DFE-JN>6=@xRmmf2aR6J^pw4|L^plPoMwSPXAd8>^b=E zn8C6i^Z{rc=o!!~&}7i&uM8F&C=}!c`tbzzD}h#k;y?pV8!VPn*thfz_DO-3@-(t@e=r6dK8T zK*69vpdKI@*(?k|HSnHel)Qfte_ zX00vWEm~U=U0PdSy{ollg==fe*Y2$?FM-B`JVD3qZ*6%2iSImQ$cA&>Ns< zL0OeYs&{c;NGRR zkLI`c%kKTYyPoOQ^UV|94H({VBc z1r>&q7x9^y4v)8;5^-$oR1QDc=VTS zl6W(AEt=&yrmRdlmzI`6M+%U)>?|vGlv?O)N+$l%b~UCZ!*8<9g7a$$+>0ZY+#$GB zFbr^nB;^uu9A-*OGs!rgktwH{(vvf@u}$*!?TFaJ>XbZ-$%+?o@+B(Q9VxkHO+~w zD9-We{5TrQlOG6i4%V8Tl|%5g+tT86d5MdR<_WJ=An zn9vNYGc{imlM`*I3$!ycZ8)cnV#`30o3dt@rI55t+guzQ)GIX8g!3jiaDqRyn6S|@ zKR^U&t-+*bm=ci@#XWIux+yI=f&6KEanWj(cBTkYDF4vz#$wMjgiQ%H98>Bcqhs(5 zxM8EG$ILNh2J)kb)^0x11m3gsV^5SErN+>Powo{W`@Xe+ZMLlB_-vk`wRY$<&=SxB z&}>jTCH>Ng)DDD%g_&&uTBzcdUrCKt$gv}(PQ5LY&RXh+ zGSxEU2RKo-V`CiZKr2{b>oV>&)wPW^4O^G7d77VbqP@+}pT*%c?4O2@OdK&y zmAhr3Q*)&`Z&0sZz5M#l_i=RZIZYdk)4476P^;Bqf`-*vqckHm&^#kKoj6qQIBdqx z&N36iQL@C)?DPaY2W6yYWc5qJQ*g4CdOe;(c=zWIiTF1(HQQPB$VOTk`#h?b$ zXW)4>T3&b}`cblNE?}ey4mpw7j1Mq2%Vx;{kD~0w*r0Bc31ws|Ri!)!L9?c-m8ZN> z!k}kFu6qR#{o<9wGtw87k&%_ymofwpl0I8~3a1gGZ?N(@%0y@8a7u)d0&g+d@w)p2 z92PO>^d+YW$>uq9?wIdhXg67 zob{I@Ck6xsg^b3Y`SBBEQnffTh{MJh+$bVlQ$Y!+J%J8ONKwkEWqxK^l|p=XcuRUJ zk;ab%l!mi@qxh7Qo+u-JeuGp6z2(O(`R4m{*>^zizWsXp^_A%?rek>6zbmfixtWb4 zZgsCO|N2DTuBh&lemvIdt6e1veT_d%zd7R0@zC*c8r`U5l)rHnoEPVr#juu=f%BKi zI6$cPVe%EBz9xN1BOV`PGx#BF8DoL&t{wrk(hzCNFZ$E(nAoOYQ* z+i4~SM2`s{6E;|;LmkvEv#m6a+bn!sk$D{aNB}*#`8jubNaaLrC6X=GUgI-xF(&h0Fb+J9fOM$s6XJIfH{1u(Jb!- z;SvNYe7zy4CT-IzACy**;~YEc(v+2s%A<2(<{TWiqPqx^VT(`i+kXI^nMPrea;0v_ zcZoX6NFAq;9;gD6P|sTP15-F_#ScPZbGB0nlKP@!YM4qSDwZboo6d>7IxDs+rA)V1 z(@ZnXq@2~mXJn>lQGMZV^GOen(Zh)G}Z7i)o?Ld!$dVvOmLO~pdA^a1E?&<4<9&>oO4odGH8XTT~(pfntGm&2g%1k)pu%ge3l89gQ$&LNli$OO=*!< z#YhN<4wNQhkONmnx9QrLfiV+Ph6K#1W>Cc`R*-F+ab3k+m2YSW=$KFo%vXX2(Ee&-IqFe67&tV%E`Sh+if6ruBcs;Qpl3V~+k zP3=8?hB8Z5Z-}9>!a2TlvpLaDejh`2>18Q0P%DS5;(^T^Z;<|e7pMm%Lc z;}o!x;=Y3iqdO_jF*fKp>D#HeuilADou28vkATysw6QqTZBYy!9Amb@;>Y|6D_8 zbaruyBN$hZDB6n}#+}oF@d5e4OzQS_<=j@d9U`_oRox(Lhs%HIE0m5z9&V20=rGh< zWF>Wz;7+^FXa{055UtPk0By$aD`@YV1Cu))#mw|-`ko6x@G9O1hmVOGKaLwz!NJi1 z6O|_(c`&RvwSJsRMR^lI0~1v+pG-K-W)L0kCr$ej1@$*wN$oT0B7kzKl{OL2h)15P~7FCn+*-=LnD6RkD${-t7G84FNAR!1l^k^p86fhrAo_sykQIdFJ z#@TkIP3mTfR=^Z^Y6cFa!ygs2-jv4jR>qZd5>lFo%;=MIUhC+DWWcDG5K#eU$Mb0f zveeYgxlHsjSJZA0E}w9!h{1#JsJN(kz<&s?tibXQT{9GkRRM0Z2lJ+*7K_##6a#!t zRtAi<%w$+?l)_&E($b`ayG2FgBCqHteVpQO)PhzH`aKfR z9{3QIo{`=YCbrsM>CO=428nRJz|^#%t^!kSp}65tqE8H37V*i07M!Su_rL_2&xSCU zPNVw1V<1$pq}fK{0;y!5xt zgMW-l$iReHvrO-l-==z^TUbD3WB?r%*8Mh31KM(vG*B7ne$|}(Ltk^$mg<$ui~U|- zqzJ_)upmBhyJN%BbjNKeP->C_$KM>&Y;#7oO}BT{34%Nk4j5v&Q9-6Rc?6Eaon^2C zM&jZ|a~2BPS!Xx7Lr?WjGh=vjm|%()#mJmy%CukwT)0+KO2juXbjmxKUoqSzIA-kl zF~Lse5`PWNL@WAAhfoV$aZnB2+vgeF`EQJ)AmvMi_Oa7~ypuVQf#o-Di}FV2v|gn4 zbA9w{QZC8bK-#KjPdVHwD>X7n?%%5qttDgV#PkhUGubHC#1RtiC!{3+^^Qx&0NT{x z`P+i0uNil?4>c=*Um-*NfEK;fWg*xmG%djxrmhewGc9Fc)&w2amvEIQz5vq4VkKWn zxzcE%tkXngSaI_o?&NfK=A)Y8Q_UugP0B}3eBg{X+vd=RUs`v@J*b@%n+U6FkD7z> z@l9#U{9iRvZ8;j+hAt7bN-_Kkj)h?<8FNvLD8CS8 z4y@Vs{NA86O0TS5u-J6Atebnf%s~S0BdnS}MBngj8%x`>Z7d9Y zib}tO(|f8wQBkDfvuKX~c8&={59U6~YxU!dNx5iN@QT66Y{K^!r(0ZHc_|;Eu?wWIjdFz49l=`m<7JG^(T1q~ZHHzo?4hOwH!qk6xg`^Qpa_SZ?H} zo9ITi8Tdx7Rut4PR;AIY=|)OuP<%{R0+d2|dBC@c->0cA&FF~=dVA5ZQ}KrkZ~IH~ zd$TV3O%t%a;jzK*7aw-G=e~dUwITBk8J>%ljDG!#*Pmb6wag<&$L9oG`-uC(bGwch z7F9{5`;)U~l}?+waPSEopA+2U_9QHuzxJ>J$Fg!ibNh4NBis743v>$KIO5mFCA0PT zf&?5W;HsL3f7)23hc{iwU$B5f1o^mk3r5yE#UJPu&}_*Zr}zb&n||(Ce<%L|g8R8( z*8k~LUEV0TM+)gNc0KxTrwt7biyH=>r>Iu8fyD!$$A0U+19>IOLfJ24!ycPPb$7HASEKM=2*-@#t zk6%gz|nu2tPrnztT76#|%86w};$l+xKI)=;a$F$X5yJ-zcPatl&ScYnP4_ zFC8*$_^R7!m)(-|@r^|f7L7P+_{b*3x%#!+z1p&I!tqvL8a59`f4le1%s%Bm`%Zb{ zu;Hyus9)13s|vhV?$6zQ)Ueok{ITMYSEm(({@(4cyN?>SJca)AsP9XGLVRNX4G`Qz z1T409u|AA~|NMI%*?h3@u;D#d#NYW~yTQ*EgzmlXLdZvP#~Xf0{h;%$w}w`&yyX&@ zo3qGo^pQEIQc~KDO1kg2pxg@$@wsQum0bvQT{_*fF6ga(Z+hMR{asG7lGcmEmr->o=WbUNyVF|St!k6U`=r@$^jhgM7vDT=Io zYx&gSuMWR=oS|}5V|_9@45B+YR>HG3JbGVf#C;KkTU;hbCM9KID9`ZhBc=t^yZuMqQ z!yy6x+UERQaW7|#+%+V|Sd`Xb=j*nWd8y6d-&66wUcd(h`IVPSzkjFuy+O~MvIic@ zK08VB%gIkLE*rh>gGKqR7GC!Ct$DvyNEq^GSMt|KzyY_0w>i@n`TGy@*DC09qkwbo z3SIZ;#zTge+@#!nF=G~0UAJ`~_xvHlTL+O}%aRMFm&T1VpZ?PDLI>c~@GO_08Ta2c z+2yU5z2p5?-0QbNC4Iu~-*Be{@La&AwjV^T+7WpCNb%BR(N#!K zZzcZMh4c*`82zZ{q*q2RYUz9H?2casJ|)Dz|KzW()dThP4h$%n(Q9v5z?O=26JGwj z^XS?G)fqV-A2q!92Fl0#W2q{TcwP#w|g@7=@Vz4eZBn8-Up`Y;msHPe`!eEb4Of&)J=)hV2sEpB8YAfU5-jyYKAJK6z|O_|gG)<@f$# ze%-ciF2d~l%lU}_UxxNOrQ!>)b7Au;15 zY4F;;4S#)b)bLh2scP)@C4>H)f8XVm4|E+{@zqbiHhfw(`u)exb^Lw5?!Ql7O!McU zk6%8F@+eL3Uv>T54@Z~kIIk}F$zD$Wa|N8|aeQ3cGkzn>9yREx@Vg%iI9~{Fk$~fb{Q2h2-8FdR>yew*PEGu2z-pSXs_G-CgjG>xcikMXy51` zt5^RxxzCdB_x6A1uwlg;3YNyWoZ6Cn*kC97YTI`^3nt}Gypn#%P#m!Fu@<}U@0xeo zGu7`+UEePV8(Q@M^zF0JD4%co)GQX!8-(kZmcf)^+_1g-B@T#WOwJmW<|I1NNmQ?yfK0=lM z?Tx1S735Q+y*d~EU?w&gWpZ9%o!F!u}y;Iljn)i%OZ$@le zaco3}Z#Vnz;nvDuJTC^8%w7Fh?rCF{X~^4~zxF#~cqoYTIqqjb{%V3g1S0 z?+Wk<-Q7O;9lt|{ybRneDYs2uFuvwzb9|f7WyL`SIfa)8l+?8O_Rj%pj2r*6?*$w;@{uTyL7nYYsm*GvPPmQW?jm4=fa@N5DB>r~pH`ei{%4deYA(3f z4r}*hP}(6wd4v+5b>U2Qf7ubk^L+eSkyO>ky|qs}-QH~}$afx7v1C!n5yQ@V5q@6j z8@a3dzqfv-9^Tw5?Ly?|&_1z3T$S_Co_2!#Ji)zsW^eECLAw8^1$Rl1UnIEC5peVS zHm+}t{;1`znSir}{N5{s?ZC7+;pe&K1&ozYu=yfnQF1 zJ?e;IJIzOihx|52@PD7+KVOLdGXa}cecHWizFt3ZQV>7!j1TD(X_3BZk6FXm8LT@=VuXUvuh@Q>z;8n*UsU(Dt=QcKkMRpG%zQoNr!y zqs!Qx$GUDaei-n@nbSkknon*Uye<06X+>XbDSY(18P9yN1K}kjJm_0{_ybFS`%2eO z*FsAU)WClFC>HsR{-AXWK_3JQ`YTYt)4p2w^z~ytA>|D@llqjO?f-I#<^ExJl?JbS z;q?8(vfY=|4y#+dDOA^gA%gr%LmoZ+)ULmRUZ^Y^a^Irum%sbIz*_&@iGU?rzpf|< z+j?lOpwHq2ecMb(ue8)7asG>X{_=YN#$vu19QMLvtGhd{?cWbS@OZz`MISD3 z8}`6QQ42P@O&Z#9T;$r0A04|;z2?h5J3Lac{fXBi-p)C_cFWEgD-L{KnRh%fE%4pt z?=%a$)^*CM`fobU>0pf9d}z(Bzu1ed=LqRD_I-3`_a87{t$_ZlT04CA!|$d%o^;r- zbRF={k5Y1_9YY@R*6Tm6Yt!*_`9Sy5x$m_PU%4PMVOrgw;3=g;$FSpu$Yssjg}mz8 zc0_&y)4%PK3;%gwQT3Cdt1CZW@``Ij>X0e#esLtyJ#y`W(8UK5 z$1m(-?SEm~7Z@-4DfNG2)~3{~mZwK=m>jg-vNpSITGjb?!Un!JYJ+D#pvf;+wo1SBr)hF2AeQAZ?NWS z{cle8+BpH&3V4s(S3my{u9x?20e{)!x}mPi!7=ak`pQx_s-rQ=s zvDzL7`+@97^q2GXo3E~(uH(QKJzo2?hUPzt|BZtE`Kut~sui+6Up6>@Ck`fTs4nhQTH$@Yl9yVs>7hK)6_U)E=( zKPsfRtAH;G`Fl)o-;nWcep_IqED-z?xQ0-ijf<$ruJU;TI++5_g7F5Tv?+vwCDvKCLNJ^lpR z$CpZbIeX<&M&Wly4C^*SpLU(KW33Qg_kkmiO)h>nWW%E&?yV;;-cU37dfwDA2!Edv z{>y^@R|H%l;M_kyU9xyN?3a3!_nv@(?+NZ1mj*<3zVM}C*KSv-`?(fg#_p}(-!S!v z;kiTb4|}^x=pRz=na0@RM+{qy=zsPHC(4 z-h2M>N9J~F_OoP6Gd}a)uK$eKvu4_#^`3hhzUleE_R9xf9=L4eGv?oi{x#sNam31nwSUFOjd}Ll zvu{3@Wd1OB*h{;ftT}90)*a>1Id*q|pzq!k%A-r~=h`Rk!TM92GJm(f{Zis)lfHgo zcVE`!*Ek)Q3i#0ppSyhDS9kCH?svD+FCH;$`cermBo8yhI_YW>f>*B zAv{^YGv9r`qx1Ol)UC(E9_n_?kT+k^H`f>Z(QVL+&_8b}c%4ul)q?)2yVbIwy_3Fr zTkwBX7#~7vQoVdm)A)e(&$g{>0Rj#aaG-!g1w811oz06rKWZr2&dY!6_mPH8@zu!z z_Rk*sYRwnc51UI@&!(096t?M?$WzaCpVFd*fU%pliF-2vkMIkqeyGDyL-Fq@kLqOy z0tIXo@U6FkZyjH;Yvj75haUXy50+*MdhzRfW0BvB(c`3V3Vx9OI2&V`^7^9rfsZa4`<(w&L(F%>1CIATxBP>rza05yeM#8sy_VI)Jv-a`J}3Fz(XS*I zY}^>S-gR|*?+4!57x(6m!IsYt8A`^Wyzu<22#LP9VZ{-{vX_+g{nE${pE`#xeR=g)XB_uS4?okXLyq;W z+Bqe)an88;Q>D&W?wj+1GtOQ6+c;-j+GYJsXB@X7x{Y)A@3k4fwrii4N4=Q7FYANr zxp$Y&yLEYa{87WZ?n-*&rVg<=*pzt&Ds9LTOHe!e|&N6Lf7z@SJ*D>^4@wb zSNOg(;)`bfA1*E%yQBZ6ysT}duY`VcJggqy<5zvG*x#dCy1RGS{JhZ~`K{Z@duBaY zx9#_1Ij@Y4DE{%lW2?6&94ol|+xEPVc1J8K zq@spF33Jc$kO>HoKpe_w$)wp%p!c$|KJ{on) zO?|H?r3nM}7cGry|J`h(Xk0gaulM=hOTOLI@XeJYf8H^4dX_%%Sj_%@2eS9P{MYO^ z@3aeF9`!`@qR-z*2)@TW)Ny716~{KeFm@j^F=L_=3wxJ86X?regns#)7_)ELd zYyV2FEC2F<>lX#YR|Ui$3y9ATh!+Q}r<4SoUmOrGuW1&yE8;}+*RGOSg-f5pL`Jz zzZej&3W%5VJ-7eIdiM9SHp<8sAFNw1ST&e2K1uwb`T9Rm9>^b6x4iUGWX$``%hU1v zL+^?)0rsy|!!{4HF<$-l$tS=4rs;gW&i?sW!1;9n@ofS5R|cGaJRlw!@chjI*Dnvq zzbqiWH6Z`$fb&lWoG%SHzbW8+Nx=CP0r8~)@g4#8%C!OK*963)0_xi%zF&0Tn3&-_ z*Q0k_557lI|M>nr`X>5+gf+DOK$O;hm&NcfY$Mhb6Xeh9xP4lg_pXrvd8tx<`t_fN%Cpy3dVteeBjlr72XhKSWis3i{Vc5i0 zav$;mB_km{<5-TgNaP7?gFS2Tmsaw1FbM;yF9j0@sES6O$2`RQRWR|6@sKKf5Rh5o zA(hKLB=LMOA)9gjh=+uIi!kz}et~O|Ub)gkB3F3`^CpgXcfSN1Sr|N{I2++dJfwJ? zhg94TJcRNr#{Gt&Ja2hO`IC4SuJxATx=l#OF$~AVZFu$;$*D6?Up%V{s6aSzGRojZK7`3rh<_7I%2#+u3E)M!3GNpT6ay70xDF@- zA`wreX)SO~b3!Vwcu4p!D987>7f_6N8IGx!ar_SV!?79&M_L7rRX-q(^y(U<;kl$G z?ty0}MxsvN1(Qgi0mqhUO**!y@cKu=fi+ZwL{Y%jA zVL_mUwYcYMw8wnpL;b_bfl~DIPSp7sJOjt#!?-v4w;J(sgo{zX@+~NH0`j1aiAzxz zfO!mQIFtT(3N%a&CGCJNfCBUZl7LiT5-VX#;cyWS}>Y1f&85KryfkcoNtH905)M=Yb!Aps}Ik7T|86 z7Z3#u1I7bWfk%Lqz%#&0z+1p+;A`L-&@>&_0zH8~Kr)aHOa>kV76EI4t-v1OFmM7m z2Ydzm3WR2$FM;+zcR&WBfq}p%fbMYiV4R1LP;wKw8DAM{NE(sG_}WfWatmpOJ2@NOhF%5Mjoya}pUecLFkS?Sv>4yE%JxEW| zi!g*G9N~#TL?RKHC`2V1(TPD!V!?G_B#9!?B!x89_#pQRF@{n%qyukQ9=NZ(oij=_G?>68OC$<4HD|Kqg{(m_%|( z9`TaNB%c(JLQ+Jgkf~%EnNDVq2k^<#2gxiln-t@lnRCfJGM_v|9wv{F1>{k(kSrpL z$r7>@jHiSwBg@GOQc6~mRb(|;L)MaYeN*kT=O& zhUahu0M6Q`F85|0uY(Ngp!Y4O5RnvDjRaneS{@!9^)iG_4=L7hRWQ zaFH(-U6h@Z;be#zmh7C5iH*T_zD!@Zx3d+T-X$is_J#;zn=E|@cGftj=squBFfonp z26B@Vv5&T1(D5DQzXY3Yim;!?%}Bi>6#37E}<#NjEMzK30 zIv+a`(&?^5Cj(NFvd3XRLv569>$o98n~m;x^W{f772a@5n&97n=j3sAkYSq;Hnr2L zx>48XWnDsovrnZijxC>mze!yp{cwA|+tuzOsh5`QY`mzK07qhjP&j{!!Z(huZL#$h zn;@sx7SR{4E0Qna#;5ppIb+KU-9AW5enUzWb<$WrIpFcGXFTlU9FpmU*B&RQn>KV{ zznDR>z1@`BzH|1SxV|swv#9^bA=Jr$6QNr>oj`sjb?;N)>+e2@`pB9F>o+Kk504sF zdsRT`0$d<8)NkqkAOLjlQ+5LgRI9fmq!@pb_fy0T-F?m}|Ngl;9_ddBpm2XOst^Td5I0Kb0wU)#C zZ%7lcHLm`3LmjGjk_KRa*JEMv*}2r6YUBV5f2X;u8#$J3pxnl@;Jpe-f7go zlhw_Q1b0_nZ4~3bE_?dKIxbFkm)55u8V_I)@p;&{PB*eUcS}g~`YGEDx|EHLxOF#0 zoBC_+HVmzQ-Dzjms~648^g0CL=1t7Y$#J62;}VM|-Egv~V0_)l+L@UKZlt{-!|A*M zK97dO7DU=?Zc?Ab`Vs6{tQW1#iEVt*y;;dUQIQ&wC% zFClURzoc=$X8~F*f86Z>8mr@`h}CgZ8VTU0NOHFFJ9YC%sBZy(!VRnv84xeZ$&ABx zS6|~J&F^%EBHW7y(0b{Z6a=edvzf+WDt0>b795dugXkP6YzwOdmh@4XWBI4jNE*MJ;s?PoPeU zW3b2A&ktxye%e^a|Ce(C68gbdnw>S>b@t*0nRy%ZjvHCe{2hTV- z+|8ZhJc@dLtQSg2uNA$WGbwp~x!R3F(4(${C`G~POS+F9oh{BT3vhz2ScJ{m{-@xw z960iFu5-d(S1L!iFb`Ixe}4a|EwIH)b#eC>GB1?uD*)=H6(a2&q=iqe%hz&#C=m)n z$$@%l5lE{<+R=Jx6-YaWw9)x>*PeL@wxl8e_w@fdJse8rPpOOh)BVSzICloPR4>nU zgd0qSy#-K!L?8nw1d4$YU>mR-I0Bplt^r}wLP;k;0Ahg=fESn#tO3e_3c&w6g>V&6 zP1C01dLSGSfJh)2$N;=RF;EI@2Mz#dfJ?x2AZ!Mn3kX0wkO33|i-B#xZr})T2B-l- z9)SH1=ne=#9FPi31N^`F^};0xmjUI#0iY770xknJ0GSy|ngDHq2!H_;AQFfNMgSQ= z4p0cp1m*)Jz#3o+P!1dbjsWifr+_NpGH?wDc@Xsk{J(YxGe8_L0>}Xt0PBJ6zyaV4 za2X)8a4irHFhDGj2#g1&0SkaqU<nM@z0p}XH3+MiMc^T zGRHYOGprC8BpYa)YEX#wo)$&6(zxSr%TWes5^T-U*@e^{7=F!2OrX79M)@JpNIcmAdL(){vzANZNY2SzgK75#i+pXv!4G>mC**f+Mm z`(@mB8C|)@P4Ni}*oA4>@umYgwE$M)+IX#hFj|(kX}W7#@IAUW981AD5Y8R@+qt1I zs=_Ldc1p{^+W`K;BCT~$63Uxi=r3a%U)-Oi6XLXNj&_vndXeeuq&a20p-enY4=9c3 zq|uL7_>6^kra{wgD7#y}fk8?2Ok;!$38H$2Q(}Mdu(AN_brfE0Kp)>r9HQ_kPFS1t z^@Ss;<2;-ZIR;l)x-t=k<=>4v#!Vy)_r;+&x(>yV_B`nW$C2&>Dv4X>1h^7S8|U1D zoF>$I=(qS0@(FG0sF+0Do}6>SC>yn@;9hqG;T%*m>ZjCa&LHjnme$Xap(ALzqY@=g z_u}FnG>-3XWWz}${j|<7pAR2ej~IF`9(HS24JEyucphAHWm0n&%|ZszwEBuAQYX-~ z`Zi4Dd!DBGEGu;2TnTc#`cQLZW=3o#R3Q3vLVl{B4p*oC^hTa~7EnB+r^oRy6^)M~ zCFkMOJee_g`328O@;C!D5ne&5(Kt69uG$?Bqd1r80bOR$ULqtPVaNB9Yk?*O{&;OC z5whC@`uR6)v)4ICEys?6Ob*aEy>()q<4O&WBj;$E+ad6z`BUj*@gV#NzTll zM8E!&>F+YqIhbmEpoRMWop7IS!Tt&+=hfC4=}NGpz&RBfOj4jIWfW1y(ut&`U=1b@ z-u|KHVr?1mNn=yei}F(@rA@=qJxQV85ph10Qbbb5(yz`Gx-OJ(mSD9g1)j0$Qq~}z zpP!4}&V+nJQnK^L67n661?h#LAwtFlrFeZo@^}y}M>f^^$g4pqQwnn7lPxhe82`_)st0wvE|9a7|S3wJ>r=$o~(k* z%!#D8C#x_MR?5Dftf^EBrq9Xqz)=*A`5yW;pS*OMHqAqIQK*#o&7k1|_j_HlJYLvp z33*yfxBLVe5*VK1?g$IPJPyRP}TJDli}R z(D2hVjQ=!D`N)K#f^us*Kc|MZ@@*6bG>$BS+-rvWI{3%JkI5kaB!=2x|94*mE z-#NDg&UA;B#@NiP8;gYXM*r%LYrIYwx7q%}=T z$jmK5yL58HwM@SkjVIzAK6JmsYu&jkfqoDo9d$^i=iSo9!=Q^udUVmW-Crg4Hqc?TDM%DyTp6Yx`$+_1$Cyol&s~|nGEeHnZVcGeG zMQJ%W>*Rgr#x#dGw$n7Xd=3|+GQulC2?gVPY|Y_a_=KA~$rg}zgAy@>>uBKpAZmZD zi=S}f)EehY!~H%AN-XS`>yD#yR33wYJ~Q7T3a4C`kmm4pYJH>M&%*B;e;WG#hah)s zBwurBP-RTz!;$xFw&Flp|F|3*hWX}W7!B?1ix z^r2jr=G;@Qi{ooog-*R?){-U}>`BgxDoD@H#y}l{@X%Zs-(ab|&x7^r+yd&a zv$oO3dg|<`{+25Axc8!C-svnfLmM)|@#YAzkjjM4b8qvd(Y5rk;Nr= z>%n(#XWB&qwQ>0n6+0{{v0vS{Sz03f|0epz(udJEE%1&dp7o!G0lkL|>`lK_;}($^ zDC4ApDe3uzZb~BJ|0$?nt_+mxAGmGc`=FN7gVWG1{~h219|;MLM`SvZ{`vj?)&dE> z_rIyc8-~iY#jDD+j`h=n5NG}u=?y~&-kl?4LmHS;Ys+$r;~%0-ivb6645sgslxI1v~au;NT15!pNvqF zQ_FDNht`_bgSK3nQ~n0H{~+J}XgV#2zy37lwvJn#o_Oam2mfGu!8@5jcvCVT<;lQ% zj_J5-{kBZPGiWQ*o(V@=Wg};%lPdwo48+rrPRlbLaYyBX&O_Vs#(M^~=)Y^vSd@v< zTpI4>FMSOmFBdAzdsv>l+z6Vvwj%xrKs`EOmn6SqcX9~0_H`g zg1MP(!ggaBHjOP{A71`%23bPJgv!CRWZE&E zn4XNn#51YPBxV(}llhBT!meXCp`~`Sud!#@Tewb~in4Cu%DIoYFSwt$-?(P{7yOTW zqA*H$K{zG+B6Jng#fjn@;(Ows;%`z@xs}{S9xRWMv*kkhS@{$Bf_zC1Qo1XtN{%v1 z`9o=?PSECQ4fVTpUZ1PKte??uHxy%nvB21ETrz$)ZZRWF&D>!AY{ptEtaDa(dyqZS zUSKb`@xn5R41zw$yvh8?1hEZSp1lwK`V3pnzRG^eUSk__VO)34<_2;}Tq>8%&EXbu z>$oSl*SVA2H{1{0H7=aLm+!_$^L_bA{4{*5#UZ{khTEm9jPTdHHMGxdN(aSK z^VN;&bLwmA6}6>yucm7`+6wKMX6PgI<@%HQN&UQjO%F3V7$c3xji-#~jiW}T@uBgx zk!2Q`7tC|km)3Vywe>H&9Nx%YWWQj)WuLLn+n4R1?cXU)dI@OGPf|dG50b_ z%xETqnZOh@M~t_5gd3eUm-TR|bmM*NAJzwc>8)?goW*<$7@~Q#h#Sg{LhogA6FDze#LeJlbMv`JL4_;0 zHQWYn6StMy&b`3xjzWacL*N8iFoYv} z;Wyz=Ay{lE-XgXXZxinZ?R61*ioB?ZrWh^u730Ohpu_vbR54SWAm)jM;&gGAI8R(4 zE)kcDtHt%=lj76jb7Hx;M|>G{dPIC1WAp>@jQFW|Uc4lJFa9iE!ieq<+#sDOnmRjgiu&Y$;bNl%`9wrTNmMpzamY8fgRQd#kivdO_MN zy&_eB(vL~+OQ)o>(r3~I>5}xl!;`K{LGn#t|*;dc3MxfA$9Pnnk$jD~2ruQMKo z%lFG;1uKa;~M*a+Z}h`K~WV;iBaN|1Z9XaTp6vTDOt)y#j6x4GnCoN zeC1JPsj@;@qij$%DO;88$_vU~LvAi^=I{(`llMKHPD)Bw`y&*yR?p47p!Sy|n?_U@cwqY74aG+6HZl_N-Q^eX4zh?I2C{wtBeUS=YcD8W?TBOXG~G#v)^t zvCTMX)EJieta-DQX60Fnt-aQ#){jsX!}`zq?|P8Y0P|oJo;%2R#8_sm zF`h8qu-^ga0oN&^Heh`7g0YxB%t$7anaeC>)-%sDdziD#pG%m_ zZR!s7tooh$BYLu-7N)fZ2ge7#v?MJJ{YLeJaMYy%uh_lhjcvXQ|duE5*Wz zupsOU!kPiU9Wzn5Fhgh}ZWjL%ZTBPB`Ai9l`WHFPOIm~0sKFa$mgPeZIW2P`=%x>lkbCJ=Umb}bfWe0NO zxdQOla_$gk@k4}sVY#qNP{bnnQQCP`u1*!f=ku{5;??f6B29#$+0f_SFX2y^8*a*1 z2qfGMt5lX@uzwMUIO_+C1;q`pN7$3?$L$^VF8c^ATcihi1R)&r5>vtEa3SJY{Tah! zwl^)atEGW*Emx*W^k9z>bUWrW+l0FlGsY#ZDc_P8FrV)SSH2aqc6)Htbh&}TDm#^T zm7CQURYp^_Ioeunhjv8!TKipVgwfJPAEKw}MfyB_vwl!NrC)+17mX3K5o6{Xqp5kj z+1X@G#f&%Sn2RuePMe>am&{RCvGpu%-&BvIGmT)2tk-H9>5qlES?n^DPDS3ikD}~d*v6D zlS&XM@?rIP^^p3dTB;4yQ}o$-nf{`FOuxq%YXhXaYwmNxj(tjAdUUVYeJ;3TliVfG3%v^c~S^!8;&u# zP8kkPyG-4w9tRH%)jDXlHd>pmtBL8?4d6ed-S$o|gW#5Lhsa0aA?YGJB4N30UR7Jn805Opb9njtNeHcR`YjdBoZ zezZOjcUq;F>#ymbfOFf>3l^H6nB%NhtVZ_jwihGvDs6*m5BB&G(tsHN&1oK+z}*kt zx|KW4iKyRczOnECDCr&HcW5GSi(iWCrKcbT?3b#fuccog>juk>tX73F*v#kxdKzUMG|n2o7+cI8=3etv^N4xe)baFSauLs71rGH!TE8R4 zQ4~9r&0r_8h3p+%1lJF3KbFhkWBK<~Xz1T#C9JF+Vh~ zm`$waRvW7wM(!2sn$-$3ZJJ$bS3(v?<-&qt@gSrjV=x1miOj>`$Q{|^;JdSgozU<8 z5Za1E#mVAUXjRRnK`7~3=`Bf-=R+2HReoDOtL{S?enttp8T*WCqnjCHCYU45N6lkq z3(O}kLjJ#nJ|`S|yKxBR{>9w^*>@=T_hXP`yJ9ws6&`_fbyU76|0st+`xp(WXs)tQ zc|&QV_E+!LMrz&ke7zJk{!C9nO&gfHnQTrr51a3sHRhexL5$^JAW4MVU2Va(>^OUf zJ=)F!4b8CU+e_^=_9lC~z1Oa=kJ+c~D*Ide7n?A_Bo_J`VHz{7m~f^WTz*6{1DIjp zqZ7dcC?8$HYyekz0es~R=6y&A7rpu>4M%@Pt?cibMzg?5#w#+6sY_nW|bP_FQbv!$_zKVnUWcW zxh2UQWu}?q%{=o#bH2IN+-V*_`yMk-m}ksy(8|A=f11Ho1FNak!n)JC2R!OWi$n%H ze0>7jlk3Vq1isQp>Msq3JpQ-9-Uc>nNSo=k5 zqetlr!2xvR0qBsM&DX#I)2ul37$`FlJ|ECMH!)u_JX&`H^lFQn%>Bse(3mD-R6Pwn z`8Vjt=fKsjflfW*&Eosee-}${N?%IfNv-A1G9%A}^xj6vRZ1Z_he4b+nM*kink*89+CX6BovW;ZLt znhnkNn)MfGu0Ljw$L#lD4+zE@GT!50BJcs~zDx>a*ICRV<`3pBb~$v>J2?Zh(KFmZ z%q7A6c=Y6I==#s|XZat{o1wy;(AI_uHfjk6|MORZ4o5i-VpZ^&~Qb^*ry2K%`E zt4+M_IB&-Au*D5!vY4r`$gO3b#SH%$B>9Hy9c*{l6#B7)*-@|~EQGb-1S|usxy#&C zz8L!VQ~W{xO@1~kFOLgP344Vj!U<@`u4cPI+$8RR41XBf>{;=m_=EVn7%GKHcSs$e zy)u#v-8EiHl152s(s(HkHnInyvo4aBgNv3yU)?3WDjfnpJt=)8os+(RmDu0N-#`l42u>dxNZ9;cg0CJ_5q!XBX zpOn9Xi-4}mt!e{spIwmJK7w3p83T;_U@Lmb_ypW1%>g7rYUS-`!7*>$Gy2rLo7uz97xwy+EB{=OKF77ml|gwE7e?gd-c zT+B)*p@p|pqM=cjD$gprp)ZH2F=~Q3UtNgyI|kYG6Hx3=YA-DcZI+;~*MHIH8JEp# z7G%M)U}ybh4Ya}@d>;QepC;s??{^442<;(X{2{hM5B(;IuqZZBnnJ_+L}M}g3D7n> zW9EC#>H#|344E?6(V-~k^jHn8FwDSrfp6}!7@JfCJGw&`ZUr=~*ZFrL5fed2?Vc32 z!rJnu&{VubY%eYoSBX!F+r<}QGdU<86`M&zrAg8>X(eR0Cm}7rCH(>Uv!UD!_3r@d zaHc#N7K=IHc+2JWn4h;pmOd)StGl#L`f=mDvC+I>wY6i=ue0oDX**ZKrwk4X#ziW$ zhh7*5``BL~$4!Opstac6Dg11H0l$o22P@QeNHMQLAFKq=x(RlsF6j4V!g`@0O8Aj@ zPOO23*i>o(*}#%x75qk2hpRKeW!9-%)xXrWVa(wuc$HiJHLw;Q zVvaMNFyq#+oxy3J8~P^4Klx4zO%QV>FGm^X<8?8*hT1dB{Eqd&EyR zsfO%@Im5%WU^*}yw2vXoSSBA?Z9$uG^`+F*?e{`W|U3P z01mMy+4JmA7$Gg7$8%gXHw4mXK6HWQuuSfP{(O=<4;!S1Zvm@5#}9|ZoCmFaHNOe= z#0ppvKjUloV4;c70TjLhwD$ry+pj`1@gCTg`-?-wQQ}xJ2h{hbcpQEF75Lj#NUY5q z`*MV&OOcRWi=<+3v{j(U?b2@PHAviz6`x0b;YH)#T!gV-X2@xBJO|Zri4l0fiyNiq{!0H++#=+*A zD2|5Rzf@cU`)avZAyz`)s>Vu87_9#c`X*6ImApzZX83AY48zoLjIBt_n*joCX4^9Z9wV%|=Lji^{F(aN-PtpeI>l~xVgUKnf@3}%)@JyrMW#d-c$Bq?Kr;T3%Q;N~|)>2o+YPRRw8_*kO?G89UNWv{ND37uzLv znOzQC$r+JxTSVPK~a;(5rGF419Y>8oPIM(4J*+j_g zUP$dFklD-G3akxOvDI*L69y{@1N(m>?EYS^7(HKxUavrpSE08FdODnE(9enJT`wer z67**|MpGr^@@nw$Fd-Z(WRbABrwU%7SSSGxD#w~lC3?3SJsO6-WW-4HUaII7i_wE+ z|GxJywn`u=l*6K3iFvgeu6M%ZaLBcha-y7yHJf6&L@txdBL4r8e>=inzn&Hie=;2 zc=S{<>{p}FUm5Ip$Y@?@-|JE9EwILKN8NYB9)AEWa0Ij6J7|Yac7)v>v?ADwZGd88 zp%KP|ZjvDzjt2E)*yFJ>>a`1D$(spEB9S4a0ml1(QvIf}GeMj4*#+!kP-rPu7T1GL zw_rtUJE(Ox=IH~V*Nd<{hH(mJybM@POSn?*04VhmWY?D9N0b*)?n8Ob5!fH9!R^Db z22>_&fyJcK;V=BBbu%Ap%xk3~-WIDGqXMB358VNTb13 zG9b<7NM5YMOv4IeF=W~W(qd3*DOO_EgI>2t+b|oHV?Nj?9RTefk&a65fPzm+XCUWR zNf)sqa~afJBVB`yhR7ju1Gxz(yQSO~_U~}4%S6cCLFEE0cLr!Z78db%P<%2ZywRZh z3`iC^p#DO68Z3szutqG97h@!p!j7UDFmwlO~9F28f}es;7^?(`*sJH zLM6Q+qy~qJPHRA%8v*~owhwC{P*ke?(;0G5Wg(2ae-~X`|_%8<4 BhV%dc diff --git a/package/windows/win32/.gitignore b/package/windows/win32/.gitignore new file mode 100644 index 000000000..6a7461313 --- /dev/null +++ b/package/windows/win32/.gitignore @@ -0,0 +1 @@ +*.dll diff --git a/package/windows/win64/.gitignore b/package/windows/win64/.gitignore new file mode 100644 index 000000000..6a7461313 --- /dev/null +++ b/package/windows/win64/.gitignore @@ -0,0 +1 @@ +*.dll From 42c620bb9c61fdacac80730d3ac2b262b5e992f6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2016 21:43:38 -0400 Subject: [PATCH 0133/1012] Download non-broken libstdc++ on OS X --- CMakeLists.txt | 27 ++++++++++++++++++++++++++- library/CMakeLists.txt | 6 +----- package/darwin/libstdc++.6.dylib.bz2 | Bin 360865 -> 0 bytes package/darwin/osx32/.gitignore | 1 + package/darwin/osx64/.gitignore | 1 + 5 files changed, 29 insertions(+), 6 deletions(-) delete mode 100644 package/darwin/libstdc++.6.dylib.bz2 create mode 100644 package/darwin/osx32/.gitignore create mode 100644 package/darwin/osx64/.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index 1055ee623..a854a74aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,7 +193,7 @@ elseif(WIN32) add_definitions(-DWIN32) endif() -#### expose depends #### +#### download depends #### if(WIN32) # Download zlib on Windows @@ -237,6 +237,31 @@ if(WIN32) endif() endif() +if(APPLE) + # libstdc++ (GCC 4.8.5 for OS X 10.6) + # fixes crash-on-unwind bug in DF's libstdc++ + set(LIBSTDCXX_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}) + # check for existence of uncompressed library + if(NOT EXISTS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + message("Downloading osx64-libstdcxx.6.dylib.gz") + file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-libstdcxx.6.dylib.gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + EXPECTED_MD5 "cf26ed588be8e83c8e3a49919793b416") + execute_process(COMMAND gunzip ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz) + else() + message("Downloading osx32-libstdcxx.6.dylib.gz") + file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libstdcxx.6.dylib.gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + EXPECTED_MD5 "40f3d83871b114f0279240626311621b") + execute_process(COMMAND gunzip ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz) + endif() + endif() +endif() + +#### expose depends #### + + # find and make available libz if(NOT UNIX) # Windows # zlib is in here so 32-bit and 64-bit builds in the same source tree are possible diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b0142a14a..0958a6efc 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -360,12 +360,8 @@ IF(UNIX) DESTINATION .) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run DESTINATION .) - OPTION(INSTALL_NEW_LIBSTDCXX "Install a version of libstdc++ from GCC 4.5.4 to fix various crashes" ON) - IF(INSTALL_NEW_LIBSTDCXX) - execute_process(COMMAND bunzip2 --keep --force ${dfhack_SOURCE_DIR}/package/darwin/libstdc++.6.dylib.bz2) - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/libstdc++.6.dylib + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}/libstdc++.6.dylib DESTINATION ./hack/) - ENDIF(INSTALL_NEW_LIBSTDCXX) else() # On linux, copy our version of the df launch script which sets LD_PRELOAD install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack diff --git a/package/darwin/libstdc++.6.dylib.bz2 b/package/darwin/libstdc++.6.dylib.bz2 deleted file mode 100644 index a25b10b26ae833090ea40f1aa61b89fa1d78682d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 360865 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

    )%!HeaY!|TMO5c)+^BXBsAJ}J%%`lZBy}j zm}6o?rY=c;`l0)6)!s{L*U)#{F2;?gD=Zv|Znw*>B1d=W_&4WZsKz%yi?oJ9L4i88 za-Xs!Ql;lxQ9*zgLVt(#2bbXZ%r&(`nHu2#EB>vT%-az6Ur*HzevGb2CjEiXuyY*e zG0t-kKpg}qFsPy7jK=k-u)w$&=u&{oN?f|6axLAU78g_VBZHO@;GHRT@C(`7Db92$ z*zwH1E1E<>Un#eE0>mPZ@$nQHD(M2Qk4N#A&R1x0eX7>I&JWo!9$Q)8Up;0|-45_p)oWF?pUD;HE9tMxT9&0P&1N?(L#*`Q zu@_WQ3P%?!c-4C`IMfZG#TWSJh(#fs7+9iFbV^5-5~Fd+=gf(a?{yo|Y_#kQI)eCHfTDI`NMxg%K<*j1$t_KqJYQH&ME)uW)mhA*K?!#EK z)RNbE^QIGDP?jKUp&w=_C-kDxR1-eS_C5Nom2dan9u{V`eY$NN#3i#A+rn&MtM0vA z#i9*6{gGzJg!QkM<6g$AdBvf9w@SH3{^$LV{U34mKkfc!x7v}J>{{Bty3V@jDwo7fSQF&@#Q}}JnOdEZ$ZhwFI3Uk=Ulfb2_&~}^{n)1IajV-a&+nQiWDfgF=sAoGww|c zUY;)_cfs(ZtJr8VAe$!k`Fl!Aip;&r+WPfjpV1!Im0(zo7j-kt-`;&Xy=K|b(UYYC z=}xI%qVtU8#L@GRK?D*cj$MAPV1w9mHPW)#46@svO_7G4N7DyrLc>x;Wcrc_AakpO z_^3JZlQL>zqc)0p_b4CC)e1eQp2hW{!Se&h&!XguUC3 z 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�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%0APWhk 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�D%oO?^g)X z;I;Ta)p>2*?o+{M!TCH_Y~`Y->qYGIl^*2psWy%gRj+?&Ojo3xX#K#kBp%K8lU}i=; zI1PxXuDd~Tea=9KVgO0J4ml3^XDl$$$#U!Vrnlc?baLl&?T5eL0suIAz_x*%_v;0E^(@BSxmK3qDUFIqNu2` zR|f{`9tf8-t4wj;+J!MwR^acf9bG%JP4dkNuv4re;fM;1I3#MTB4&_CB^1SPG?|o@ zRV}*TT!MR(b&mM)qWl?d?OU)1nvj{T4wqJ~lcQ+ATelz+Tp6`-=ru0eCAg6eI+-@w z93gk!H!{KsHx8|6(p|Q~<*QRkjsq%pOv2_mE z>~b}u1m6|89hERfF%}|=rD|nI7T%4v9a?O0c&va+t)?{1gxfeWN>z3))WarkhGvRv zQjFhYVH1jNz21YE8HMCpZW`M5kCFRRx3LHq_rCzY)cc)IdSDFz9%<*)K$ySv1?1>X zL=cHYqwsGw@i86+@DF%(K#uvJ80qcmt^6=>USF>?nZXu5!`W;QXw zDKtFrw8)A|zC8Eu|D=5{#l!+A9yl|bLYI-mw%WAh7*{Eb&PuAPs;a8)WI6^#gxqQrNeUz)#Uxn@h|Sr80^opD ztPy}Lk}QIOwy8?3Fi3OI+8|>jhFTHuJm4G?3hJ-Yb#<5_t1j|Z< zD3sNP+KFMLHbfNy#gwYrwAT3zIKgBTHzNy!c4ls>rkWzEtya>Cs*FTMM=(Ip$XS*d z7^4$5vP7uLLN(di-D3jXsa2UE2fw?f4%u;%NNp7ZLjZ;ey-^Wc36Rt*D3gH#LlP`f zMT)Wz+0|gsB}owp4bmSJ&4CDI*u!haYh|OVY~5I}V1p7E<%LJf@nxGQ$);6!$=|-48oXhVnap?h?fZ{P(;X5P#~#s2su*-p)mp_ z7KFell9{6MfV~W+w ziH@#bH+N&I8-+Qnwe?M%*-4!Gc&K5A!a9LUNbYtUNd9N%*^ZTChj7I8FsYX4c?*5jtJ94ynb#!oG(4=u;AnWm%6;htc*Uiqg%jO%J zE0Gb^>bY`2OZad(-HD1#!s;fzq41r}t5i^|(~f$T3GG;*eswoP`)K4b)>K9CB|$XjEc(<}u2OqKKw42S$ciOr?Oa zkmC)v&zCAWDGFU(Yc}&V8EvFf2AP<$LbWToI zn4UbAZW^y)WHKcjM+uP1vdLP9sAN!wBPE0~OVSW�E7TidP4dDJvy5Oq;3^J3d{< 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 diff --git a/package/darwin/osx32/.gitignore b/package/darwin/osx32/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx32/.gitignore @@ -0,0 +1 @@ +libstdc++* diff --git a/package/darwin/osx64/.gitignore b/package/darwin/osx64/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx64/.gitignore @@ -0,0 +1 @@ +libstdc++* From adff13f4abab1dc3a3665fe5ecde56117c5f0b85 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2016 21:44:25 -0400 Subject: [PATCH 0134/1012] Lua: re-enable C++ exceptions (re 42c620b) --- depends/lua/include/luaconf.h | 1 - 1 file changed, 1 deletion(-) diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index 6578a6d07..1c19bbeb4 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -11,7 +11,6 @@ #include #include -#define LUA_USE_LONGJMP //TODO: this is bad #define LUA_COMPAT_APIINTCASTS #define LUA_COMPAT_IPAIRS From 41a81f902104ad20a50ce62be1edf10586df099c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Jul 2016 11:00:52 -0400 Subject: [PATCH 0135/1012] Fix some more warnings (GCC 4.8) --- library/include/MemAccess.h | 2 +- library/modules/EventManager.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 36100b400..3022688f9 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -207,7 +207,7 @@ namespace DFHack * attempt to copy a string from source address to target address. may truncate or leak, depending on platform * @return length copied */ - size_t copySTLString(const void * address, const uint32_t target) + size_t copySTLString(const void * address, const uintptr_t target) { std::string * strsrc = (std::string *) address; std::string * str = (std::string *) target; diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 9133b48bb..dfb978022 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -348,7 +348,7 @@ static void manageTickEvent(color_ostream& out) { break; EventHandler handle = (*tickQueue.begin()).second; tickQueue.erase(tickQueue.begin()); - handle.eventHandler(out, (void*)tick); + handle.eventHandler(out, (void*)intptr_t(tick)); toRemove.insert(handle); } if ( toRemove.empty() ) @@ -546,7 +546,7 @@ static void manageUnitDeathEvent(color_ostream& out) { continue; for ( auto i = copy.begin(); i != copy.end(); i++ ) { - (*i).second.eventHandler(out, (void*)unit->id); + (*i).second.eventHandler(out, (void*)intptr_t(unit->id)); } livingUnits.erase(unit->id); } @@ -582,7 +582,7 @@ static void manageItemCreationEvent(color_ostream& out) { if ( item->flags.bits.spider_web ) continue; for ( auto i = copy.begin(); i != copy.end(); i++ ) { - (*i).second.eventHandler(out, (void*)item->id); + (*i).second.eventHandler(out, (void*)intptr_t(item->id)); } } nextItem = *df::global::item_next_id; @@ -609,7 +609,7 @@ static void manageBuildingEvent(color_ostream& out) { buildings.insert(a); for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)a); + bob.eventHandler(out, (void*)intptr_t(a)); } } nextBuilding = *df::global::building_next_id; @@ -625,7 +625,7 @@ static void manageBuildingEvent(color_ostream& out) { for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)id); + bob.eventHandler(out, (void*)intptr_t(id)); } a = buildings.erase(a); } @@ -708,7 +708,7 @@ static void manageInvasionEvent(color_ostream& out) { for ( auto a = copy.begin(); a != copy.end(); a++ ) { EventHandler handle = (*a).second; - handle.eventHandler(out, (void*)(nextInvasion-1)); + handle.eventHandler(out, (void*)intptr_t(nextInvasion-1)); } } @@ -831,7 +831,7 @@ static void manageReportEvent(color_ostream& out) { df::report* report = reports[a]; for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler handle = (*b).second; - handle.eventHandler(out, (void*)report->id); + handle.eventHandler(out, (void*)intptr_t(report->id)); } lastReport = report->id; } From 98230e90789879fbbeaf87afe94b6b9eadcb2f3b Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 28 Jul 2016 20:52:14 +0530 Subject: [PATCH 0136/1012] Send surface material with the worldmap in remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 3 + plugins/remotefortressreader.cpp | 142 +++++++++++++++++++---- 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index c5c778b1f..889159682 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -408,6 +408,7 @@ message WorldMap optional WorldPoles world_poles = 22; repeated RiverTile river_tiles = 23; repeated int32 water_elevation = 24; + repeated RegionTile region_tiles = 25; } message RegionTile @@ -423,6 +424,8 @@ message RegionTile optional int32 salinity = 9; optional RiverTile river_tiles = 10; optional int32 water_elevation = 11; + optional MatPair surface_material = 12; + repeated MatPair plant_materials = 13; } message RegionMap diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 7a853db82..3e372547d 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -58,6 +58,9 @@ #include "df/world_region.h" #include "df/army.h" #include "df/army_flags.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_population.h" #include "df/unit.h" #include "df/creature_raw.h" @@ -128,8 +131,10 @@ static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage * static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); +static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); +static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out); static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out); static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); @@ -229,9 +234,10 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("ResetMapHashes", ResetMapHashes); svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); - svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetRegionMaps", GetRegionMaps); - svc->addFunction("GetRegionMapsNew", GetRegionMaps); + svc->addFunction("GetWorldMap", GetWorldMap); + svc->addFunction("GetWorldMapNew", GetWorldMapNew); + svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetRegionMapsNew", GetRegionMapsNew); svc->addFunction("GetCreatureRaws", GetCreatureRaws); svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); svc->addFunction("GetPlantRaws", GetPlantRaws); @@ -1931,6 +1937,116 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, return CR_OK; } +static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) +{ + df::world_region * region = df::world_region::find(e1->region_id); + df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); + out->set_rainfall(e1->rainfall); + out->set_vegetation(e1->vegetation); + out->set_temperature(e1->temperature); + out->set_evilness(e1->evilness); + out->set_drainage(e1->drainage); + out->set_volcanism(e1->volcanism); + out->set_savagery(e1->savagery); + out->set_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->set_water_elevation(region->lake_surface); + else + out->set_water_elevation(99); + + int topLayer = 0; + for (int i = 0; i < geoBiome->layers.size(); i++) + { + auto layer = geoBiome->layers[i]; + if (layer->top_height == 0) + { + topLayer = layer->mat_index; + break; + } + } + auto surfaceMat = out->mutable_surface_material(); + surfaceMat->set_mat_index(topLayer); + surfaceMat->set_mat_type(0); + + for (int i = 0; i < region->population.size(); i++) + { + auto pop = region->population[i]; + if (pop->type != world_population_type::Grass) + continue; + + auto plantMat = out->add_plant_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } +} + +static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) +{ + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + auto poles = data->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + + auto regionTile = out->add_region_tiles(); + regionTile->set_elevation(map_entry->elevation); + SetRegionTile(regionTile, map_entry); + auto clouds = out->add_clouds(); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + + + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; +} + static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_data * worldData) { df::world_region * region = worldData->regions[e1->region_id]; @@ -1948,22 +2064,6 @@ static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_ out->add_water_elevation(99); } -static void AddRegionTiles(RegionTile * out, df::region_map_entry * e1, df::world_data * worldData) -{ - df::world_region * region = worldData->regions[e1->region_id]; - out->set_rainfall(e1->rainfall); - out->set_vegetation(e1->vegetation); - out->set_temperature(e1->temperature); - out->set_evilness(e1->evilness); - out->set_drainage(e1->drainage); - out->set_volcanism(e1->volcanism); - out->set_savagery(e1->savagery); - out->set_salinity(e1->salinity); - if (region->type == world_region_type::Lake) - out->set_water_elevation(region->lake_surface); - else - out->set_water_elevation(99); -} static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) { @@ -1988,7 +2088,7 @@ static void AddRegionTiles(RegionTile * out, df::coord2d pos, df::world_data * w pos.x = worldData->world_width - 1; if (pos.y >= worldData->world_height) pos.y = worldData->world_height - 1; - AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); + SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); } static df::coord2d ShiftCoords(df::coord2d source, int direction) @@ -2235,7 +2335,7 @@ static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage df::world_region_details * region = data->region_details[i]; if (!region) continue; - WorldMap * regionMap = out->add_world_maps(); + RegionMap * regionMap = out->add_region_maps(); CopyLocalMap(data, region, regionMap); } return CR_OK; From 71e4f4ec626a83f75a269edc7a70f892996121ae Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Jul 2016 11:17:55 -0400 Subject: [PATCH 0137/1012] Fix pointer-size-related compile errors in dev plugins --- plugins/devel/eventExample.cpp | 12 ++++++------ plugins/devel/memview.cpp | 4 ++-- plugins/devel/rprobe.cpp | 2 +- plugins/devel/vectors.cpp | 28 ++++++++++++++-------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 35c5253b4..d64969c91 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -128,26 +128,26 @@ void jobCompleted(color_ostream& out, void* job) { } void timePassed(color_ostream& out, void* ptr) { - out.print("Time: %d\n", (int32_t)(ptr)); + out.print("Time: %d\n", (intptr_t)(ptr)); } void unitDeath(color_ostream& out, void* ptr) { - out.print("Death: %d\n", (int32_t)(ptr)); + out.print("Death: %d\n", (intptr_t)(ptr)); } void itemCreate(color_ostream& out, void* ptr) { - int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (int32_t)ptr); + int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (intptr_t)ptr); if ( item_index == -1 ) { out.print("%s, %d: Error.\n", __FILE__, __LINE__); } df::item* item = df::global::world->items.all[item_index]; df::item_type type = item->getType(); df::coord pos = item->pos; - out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); + out.print("Item created: %d, %s, at (%d,%d,%d)\n", (intptr_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); } void building(color_ostream& out, void* ptr) { - out.print("Building created/destroyed: %d\n", (int32_t)ptr); + out.print("Building created/destroyed: %d\n", (intptr_t)ptr); } void construction(color_ostream& out, void* ptr) { @@ -168,7 +168,7 @@ void syndrome(color_ostream& out, void* ptr) { } void invasion(color_ostream& out, void* ptr) { - out.print("New invasion! %d\n", (int32_t)ptr); + out.print("New invasion! %d\n", (intptr_t)ptr); } void unitAttack(color_ostream& out, void* ptr) { diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index 07158babc..f33ff110f 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -51,7 +51,7 @@ size_t convert(const std::string& p,bool ishex=false) conv>>ret; return ret; } -bool isAddr(uint32_t *trg,vector & ranges) +bool isAddr(uintptr_t *trg,vector & ranges) { if(trg[0]%4==0) for(size_t i=0;i & parameters) int c = sizeof(*rd) / sizeof(int32_t); for (int j = 0; j < c; j++) { if (j % 8 == 0) - out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": "; + out << endl << setfill('0') << setw(8) << hex << (intptr_t)(rd+j) << ": "; out << " " << setfill('0') << setw(8) << hex << p[j]; } out << setfill(' ') << setw(0) << dec << endl; diff --git a/plugins/devel/vectors.cpp b/plugins/devel/vectors.cpp index c7e81dfe7..52c74e83b 100644 --- a/plugins/devel/vectors.cpp +++ b/plugins/devel/vectors.cpp @@ -50,7 +50,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static bool hexOrDec(string &str, uint32_t &value) +static bool hexOrDec(string &str, uintptr_t &value) { if (str.find("0x") == 0 && sscanf(str.c_str(), "%x", &value) == 1) return true; @@ -69,7 +69,7 @@ static bool mightBeVec(vector &ranges, // Vector length might not be a multiple of 4 if, for example, // it's a vector of uint8_t or uint16_t. However, the actual memory // allocated to the vector should be 4 byte aligned. - if (((int)vec->start % 4 != 0) || ((int)vec->alloc_end % 4 != 0)) + if (((intptr_t)vec->start % 4 != 0) || ((intptr_t)vec->alloc_end % 4 != 0)) return false; for (size_t i = 0; i < ranges.size(); i++) @@ -130,7 +130,7 @@ static void vectorsUsage(color_ostream &con) static void printVec(color_ostream &con, const char* msg, t_vecTriplet *vec, uint32_t start, uint32_t pos, std::vector &ranges) { - uint32_t length = (int)vec->end - (int)vec->start; + uint32_t length = (intptr_t)vec->end - (intptr_t)vec->start; uint32_t offset = pos - start; con.print("%8s offset %06p, addr %010p, start %010p, length %u", @@ -158,7 +158,7 @@ command_result df_vectors (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t start = 0, bytes = 0; + uintptr_t start = 0, bytes = 0; if (!hexOrDec(parameters[0], start)) { @@ -172,7 +172,7 @@ command_result df_vectors (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t end = start + bytes; + uintptr_t end = start + bytes; // 4 byte alignment. while (start % 4 != 0) @@ -200,10 +200,10 @@ command_result df_vectors (color_ostream &con, vector & parameters) { con.print("Scanning %u bytes would read past end of memory " "range.\n", bytes); - uint32_t diff = end - (int)range.end; + size_t diff = end - (intptr_t)range.end; con.print("Cutting bytes down by %u.\n", diff); - end = (uint32_t) range.end; + end = (uintptr_t) range.end; } startInRange = true; break; @@ -215,11 +215,11 @@ command_result df_vectors (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t pos = start; + uintptr_t pos = start; - const uint32_t ptr_size = sizeof(void*); + const size_t ptr_size = sizeof(void*); - for (uint32_t pos = start; pos < end; pos += ptr_size) + for (uintptr_t pos = start; pos < end; pos += ptr_size) { // Is it an embeded vector? if (pos <= ( end - sizeof(t_vecTriplet) )) @@ -238,7 +238,7 @@ command_result df_vectors (color_ostream &con, vector & parameters) // Is it a vector pointer? if (pos <= (end - ptr_size)) { - uint32_t ptr = * ( (uint32_t*) pos); + uintptr_t ptr = * ( (uintptr_t*) pos); if (inAnyRange(ranges, (void *) ptr)) { @@ -275,7 +275,7 @@ command_result df_clearvec (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t start = 0, bytes = 0; + uintptr_t start = 0, bytes = 0; if (!hexOrDec(parameters[0], start)) { @@ -301,7 +301,7 @@ command_result df_clearvec (color_ostream &con, vector & parameters) for (size_t i = 0; i < parameters.size(); i++) { std::string addr_str = parameters[i]; - uint32_t addr; + uintptr_t addr; if (!hexOrDec(addr_str, addr)) { con << "'" << addr_str << "' not a number." << std::endl; @@ -329,7 +329,7 @@ command_result df_clearvec (color_ostream &con, vector & parameters) else { // Is it a pointer to a vector? - addr = * ( (uint32_t*) addr); + addr = * ( (uintptr_t*) addr); vec = (t_vecTriplet*) addr; if (inAnyRange(ranges, (void *) addr) && mightBeVec(ranges, vec)) From eeb7f0548332c339b3ad8aec6381e23cbed841d5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Jul 2016 11:37:24 -0400 Subject: [PATCH 0138/1012] vectors: Fix pointer parsing and display --- plugins/devel/vectors.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/devel/vectors.cpp b/plugins/devel/vectors.cpp index 52c74e83b..717f4bdae 100644 --- a/plugins/devel/vectors.cpp +++ b/plugins/devel/vectors.cpp @@ -52,9 +52,11 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) static bool hexOrDec(string &str, uintptr_t &value) { - if (str.find("0x") == 0 && sscanf(str.c_str(), "%x", &value) == 1) + std::stringstream ss; + ss << str; + if (str.find("0x") == 0 && (ss >> std::hex >> value)) return true; - else if (sscanf(str.c_str(), "%u", &value) == 1) + else if (ss >> std::dec >> value) return true; return false; @@ -128,10 +130,10 @@ static void vectorsUsage(color_ostream &con) } static void printVec(color_ostream &con, const char* msg, t_vecTriplet *vec, - uint32_t start, uint32_t pos, std::vector &ranges) + uintptr_t start, uintptr_t pos, std::vector &ranges) { - uint32_t length = (intptr_t)vec->end - (intptr_t)vec->start; - uint32_t offset = pos - start; + uintptr_t length = (intptr_t)vec->end - (intptr_t)vec->start; + uintptr_t offset = pos - start; con.print("%8s offset %06p, addr %010p, start %010p, length %u", msg, offset, pos, vec->start, length); From afde73a6739da0172925ba590232edee29b890ed Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Jul 2016 11:40:02 -0400 Subject: [PATCH 0139/1012] memview: Fix pointer size on x64 --- plugins/devel/memview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index f33ff110f..53ee86755 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -70,7 +70,7 @@ void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream con.print("0x%08X ",i+start); for(size_t j=0;(j Date: Thu, 28 Jul 2016 16:36:02 -0400 Subject: [PATCH 0140/1012] Fix support for integer fields in Lua 5.3 --- library/DataStaticsFields.cpp | 38 +++++++++------- library/LuaTypes.cpp | 17 ++++++- library/include/DataIdentity.h | 83 +++++++++++++++++++++++++--------- library/include/LuaTools.h | 6 +++ 4 files changed, 103 insertions(+), 41 deletions(-) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 9115784d7..71221effe 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -17,28 +17,30 @@ #pragma GCC diagnostic ignored "-Winvalid-offsetof" namespace df { -#define NUMBER_IDENTITY_TRAITS(type) \ - number_identity identity_traits::identity(#type); +#define NUMBER_IDENTITY_TRAITS(category, type) \ + category##_identity identity_traits::identity(#type); +#define INTEGER_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(integer, type) +#define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type) #ifndef STATIC_FIELDS_GROUP - NUMBER_IDENTITY_TRAITS(char); - NUMBER_IDENTITY_TRAITS(int8_t); - NUMBER_IDENTITY_TRAITS(uint8_t); - NUMBER_IDENTITY_TRAITS(int16_t); - NUMBER_IDENTITY_TRAITS(uint16_t); - NUMBER_IDENTITY_TRAITS(int32_t); - NUMBER_IDENTITY_TRAITS(uint32_t); - NUMBER_IDENTITY_TRAITS(int64_t); - NUMBER_IDENTITY_TRAITS(uint64_t); + INTEGER_IDENTITY_TRAITS(char); + INTEGER_IDENTITY_TRAITS(int8_t); + INTEGER_IDENTITY_TRAITS(uint8_t); + INTEGER_IDENTITY_TRAITS(int16_t); + INTEGER_IDENTITY_TRAITS(uint16_t); + INTEGER_IDENTITY_TRAITS(int32_t); + INTEGER_IDENTITY_TRAITS(uint32_t); + INTEGER_IDENTITY_TRAITS(int64_t); + INTEGER_IDENTITY_TRAITS(uint64_t); #ifdef _WIN32 - NUMBER_IDENTITY_TRAITS(long); - NUMBER_IDENTITY_TRAITS(unsigned long); + INTEGER_IDENTITY_TRAITS(long); + INTEGER_IDENTITY_TRAITS(unsigned long); #else - NUMBER_IDENTITY_TRAITS(intptr_t); - NUMBER_IDENTITY_TRAITS(uintptr_t); + INTEGER_IDENTITY_TRAITS(intptr_t); + INTEGER_IDENTITY_TRAITS(uintptr_t); #endif - NUMBER_IDENTITY_TRAITS(float); - NUMBER_IDENTITY_TRAITS(double); + FLOAT_IDENTITY_TRAITS(float); + FLOAT_IDENTITY_TRAITS(double); bool_identity identity_traits::identity; stl_string_identity identity_traits::identity; @@ -60,6 +62,8 @@ namespace df { buffer_container_identity buffer_container_identity::base_instance; #endif #undef NUMBER_IDENTITY_TRAITS +#undef INTEGER_IDENTITY_TRAITS +#undef FLOAT_IDENTITY_TRAITS } #define TID(type) (&identity_traits< type >::identity) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index e8d07dc2f..71fca0b6b 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -115,12 +115,25 @@ void enum_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int va base_type->lua_write(state, fname_idx, ptr, val_index); } -void df::number_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +void df::integer_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushinteger(state, read(ptr)); +} + +void df::integer_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + if (!lua_isinteger(state, val_index)) + field_error(state, fname_idx, "integer expected", "write"); + + write(ptr, lua_tointeger(state, val_index)); +} + +void df::float_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) { lua_pushnumber(state, read(ptr)); } -void df::number_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +void df::float_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) { if (!lua_isnumber(state, val_index)) field_error(state, fname_idx, "number expected", "write"); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 32bbf0d5d..a04311ac4 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -196,6 +196,29 @@ namespace df std::string getFullName() { return name; } + virtual void lua_read(lua_State *state, int fname_idx, void *ptr) = 0; + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) = 0; + + }; + + class DFHACK_EXPORT integer_identity_base : public number_identity_base { + public: + integer_identity_base(size_t size, const char *name) + : number_identity_base(size, name) {} + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + + protected: + virtual int64_t read(void *ptr) = 0; + virtual void write(void *ptr, int64_t val) = 0; + }; + + class DFHACK_EXPORT float_identity_base : public number_identity_base { + public: + float_identity_base(size_t size, const char *name) + : number_identity_base(size, name) {} + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); @@ -205,9 +228,20 @@ namespace df }; template - class number_identity : public number_identity_base { + class integer_identity : public integer_identity_base { + public: + integer_identity(const char *name) : integer_identity_base(sizeof(T), name) {} + + protected: + virtual int64_t read(void *ptr) { return int64_t(*(T*)ptr); } + virtual void write(void *ptr, int64_t val) { *(T*)ptr = T(val); } + }; + + template + class float_identity : public float_identity_base { public: - number_identity(const char *name) : number_identity_base(sizeof(T), name) {} + float_identity(const char *name) : float_identity_base(sizeof(T), name) {} + protected: virtual double read(void *ptr) { return double(*(T*)ptr); } virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } @@ -472,30 +506,33 @@ namespace df }; #endif -#define NUMBER_IDENTITY_TRAITS(type) \ +#define NUMBER_IDENTITY_TRAITS(category, type) \ template<> struct DFHACK_EXPORT identity_traits { \ - static number_identity identity; \ - static number_identity_base *get() { return &identity; } \ - }; - - NUMBER_IDENTITY_TRAITS(char); - NUMBER_IDENTITY_TRAITS(int8_t); - NUMBER_IDENTITY_TRAITS(uint8_t); - NUMBER_IDENTITY_TRAITS(int16_t); - NUMBER_IDENTITY_TRAITS(uint16_t); - NUMBER_IDENTITY_TRAITS(int32_t); - NUMBER_IDENTITY_TRAITS(uint32_t); - NUMBER_IDENTITY_TRAITS(int64_t); - NUMBER_IDENTITY_TRAITS(uint64_t); + static category##_identity identity; \ + static category##_identity_base *get() { return &identity; } \ + }; + +#define INTEGER_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(integer, type) +#define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type) + + INTEGER_IDENTITY_TRAITS(char); + INTEGER_IDENTITY_TRAITS(int8_t); + INTEGER_IDENTITY_TRAITS(uint8_t); + INTEGER_IDENTITY_TRAITS(int16_t); + INTEGER_IDENTITY_TRAITS(uint16_t); + INTEGER_IDENTITY_TRAITS(int32_t); + INTEGER_IDENTITY_TRAITS(uint32_t); + INTEGER_IDENTITY_TRAITS(int64_t); + INTEGER_IDENTITY_TRAITS(uint64_t); #ifdef _WIN32 - NUMBER_IDENTITY_TRAITS(long); - NUMBER_IDENTITY_TRAITS(unsigned long); + INTEGER_IDENTITY_TRAITS(long); + INTEGER_IDENTITY_TRAITS(unsigned long); #else - NUMBER_IDENTITY_TRAITS(intptr_t); - NUMBER_IDENTITY_TRAITS(uintptr_t); + INTEGER_IDENTITY_TRAITS(intptr_t); + INTEGER_IDENTITY_TRAITS(uintptr_t); #endif - NUMBER_IDENTITY_TRAITS(float); - NUMBER_IDENTITY_TRAITS(double); + FLOAT_IDENTITY_TRAITS(float); + FLOAT_IDENTITY_TRAITS(double); template<> struct DFHACK_EXPORT identity_traits { static bool_identity identity; @@ -538,6 +575,8 @@ namespace df }; #undef NUMBER_IDENTITY_TRAITS +#undef INTEGER_IDENTITY_TRAITS +#undef FLOAT_IDENTITY_TRAITS // Container declarations diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index ae35cb52b..3945ae524 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -286,6 +286,12 @@ namespace DFHack {namespace Lua { #undef NUMBER_PUSH #else template inline void Push(lua_State *state, T value) { + lua_pushinteger(state, lua_Number(value)); + } + inline void Push(lua_State *state, float value) { + lua_pushnumber(state, lua_Number(value)); + } + inline void Push(lua_State *state, double value) { lua_pushnumber(state, lua_Number(value)); } #endif From b7856fd9f287203598fd52d6a37a4af25c04d1bf Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Jul 2016 22:04:46 -0400 Subject: [PATCH 0141/1012] Use enable_if for Lua::Push specializations --- library/include/LuaTools.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 3945ae524..428cbf1f7 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -28,6 +28,7 @@ distribution. #include #include #include +#include #include "DataDefs.h" @@ -285,13 +286,14 @@ namespace DFHack {namespace Lua { NUMBER_PUSH(float) NUMBER_PUSH(double) #undef NUMBER_PUSH #else - template inline void Push(lua_State *state, T value) { - lua_pushinteger(state, lua_Number(value)); - } - inline void Push(lua_State *state, float value) { - lua_pushnumber(state, lua_Number(value)); + template + inline typename std::enable_if::value>::type + Push(lua_State *state, T value) { + lua_pushinteger(state, value); } - inline void Push(lua_State *state, double value) { + template + inline typename std::enable_if::value>::type + Push(lua_State *state, T value) { lua_pushnumber(state, lua_Number(value)); } #endif From dc79e2477b2b5b49748d76eadba3d692ba675772 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Jul 2016 00:02:51 -0400 Subject: [PATCH 0142/1012] Expose long/[u]intptr_t to lua and fix some integer return values --- library/LuaApi.cpp | 2 +- library/LuaWrapper.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 74019c4e1..387ac4d97 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2384,7 +2384,7 @@ static int internal_getVTable(lua_State *L) const char *name = luaL_checkstring(L, 1); uintptr_t addr = (uintptr_t)Core::getInstance().vinfo->getVTable(name); if (addr) - lua_pushnumber(L, addr); + lua_pushinteger(L, addr); else lua_pushnil(L); return 1; diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 0c5848408..813dec6b7 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -451,6 +451,7 @@ static const char *const primitive_types[] = { "ptr-string", "int8_t", "uint8_t", "int16_t", "uint16_t", "int32_t", "uint32_t", "int64_t", "uint64_t", + "intptr_t", "uintptr_t", "long", "bool", "float", "double", "pointer", "ptr-vector", @@ -465,6 +466,8 @@ static type_identity *const primitive_identities[] = { df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), @@ -572,7 +575,7 @@ static int meta_sizeof(lua_State *state) if (lua_isnil(state, 1) || lua_islightuserdata(state, 1)) { lua_pushnil(state); - lua_pushnumber(state, (size_t)lua_touserdata(state, 1)); + lua_pushinteger(state, (size_t)lua_touserdata(state, 1)); return 2; } @@ -595,7 +598,7 @@ static int meta_sizeof(lua_State *state) // Add the address if (lua_isuserdata(state, 1)) { - lua_pushnumber(state, (size_t)get_object_ref(state, 1)); + lua_pushinteger(state, (size_t)get_object_ref(state, 1)); return 2; } else From e8fe72826f4bda2e54940960787958d6b0a965a2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Jul 2016 00:05:12 -0400 Subject: [PATCH 0143/1012] Add new integer types to memscan and update find-offsets --- library/lua/memscan.lua | 11 ++++++++++- scripts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 87e2ad58c..47a860f83 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -157,8 +157,17 @@ function MemoryArea.new(astart, aend) uint32_t = CheckedArray.new('uint32_t',astart,aend), int64_t = CheckedArray.new('int64_t',astart,aend), uint64_t = CheckedArray.new('uint64_t',astart,aend), - float = CheckedArray.new('float',astart,aend) + float = CheckedArray.new('float',astart,aend), + intptr_t = CheckedArray.new('intptr_t',astart,aend), + uintptr_t = CheckedArray.new('uintptr_t',astart,aend), } + if dfhack.getOSType() == 'windows' then + -- always 32 bits on Windows + obj.long = obj.int32_t + else + -- size of pointer on Linux/OS X + obj.long = obj.intptr_t + end setmetatable(obj, MemoryArea) return obj end diff --git a/scripts b/scripts index b9f223612..43bf43767 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b9f223612bbcebfa8aa3de368fd5deadc1d4589d +Subproject commit 43bf437671380c78d4e0a6c55c6b94316dce930c From 55970a71ab7696493b4c3ab2956c3c6539fdf9f1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Jul 2016 11:06:16 -0400 Subject: [PATCH 0144/1012] Fix Lua::Push for enums --- library/include/LuaTools.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 428cbf1f7..c8237eb56 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -287,7 +287,7 @@ namespace DFHack {namespace Lua { #undef NUMBER_PUSH #else template - inline typename std::enable_if::value>::type + inline typename std::enable_if::value || std::is_enum::value>::type Push(lua_State *state, T value) { lua_pushinteger(state, value); } From 497503b7074c203135dbeeedff4891e4354ba865 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Jul 2016 19:12:14 -0400 Subject: [PATCH 0145/1012] Update scripts (find-offsets) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 43bf43767..b1e7544d8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 43bf437671380c78d4e0a6c55c6b94316dce930c +Subproject commit b1e7544d87a59e86ae74a2468b18c0b319526932 From 6e1777e1e37a1f3df71f4a22e006ce19c74f3aae Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Jul 2016 19:13:19 -0400 Subject: [PATCH 0146/1012] Remove _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS Fixed by #959 --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a854a74aa..64883688e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,7 +177,6 @@ ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od") - ADD_DEFINITIONS(-D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS) ENDIF() # use shared libraries for protobuf From 625fbbec1d7261bfc76ba9b299f84414b8ffca01 Mon Sep 17 00:00:00 2001 From: Carter Bray Date: Sun, 31 Jul 2016 17:01:47 -0700 Subject: [PATCH 0147/1012] Automatically detect architecture based on Visual Studio generator DFHACK_BUILD_ARCH is no longer cached on Windows since it is not possible to build Win32 targets with the Win64 generator or vice versa. No change for GCC builds. --- CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c2db0902..fbafbf659 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,17 @@ add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS") add_definitions( "/wd4503") endif() -SET(DFHACK_BUILD_ARCH "32" CACHE STRING "Architecture to build ('32' or '64')") +# Automatically detect architecture based on Visual Studio generator +IF(MSVC AND NOT DEFINED DFHACK_BUILD_ARCH) + IF(${CMAKE_GENERATOR} MATCHES "Win64") + SET(DFHACK_BUILD_ARCH "64") + ELSE() + SET(DFHACK_BUILD_ARCH "32") + ENDIF() +ELSE() + SET(DFHACK_BUILD_ARCH "32" CACHE STRING "Architecture to build ('32' or '64')") +ENDIF() + IF("${DFHACK_BUILD_ARCH}" STREQUAL "32") SET(DFHACK_BUILD_32 1) SET(DFHACK_BUILD_64 0) From 3b9d37abe5765934def3ba1726b35dde1d5e0aed Mon Sep 17 00:00:00 2001 From: Carter Bray Date: Sun, 31 Jul 2016 17:01:52 -0700 Subject: [PATCH 0148/1012] Suppress integer conversion warnings inside protobuf source --- depends/protobuf/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 085af746f..5b54c8335 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -202,6 +202,9 @@ LIST(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS}) IF(CMAKE_COMPILER_IS_GNUCC) SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result") +ELSEIF(MSVC) + # Disable warnings for integer conversion to smaller type + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") ENDIF() INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) From 9da2dcb8a26f7d953ea73a0c0e85ed370f900c8e Mon Sep 17 00:00:00 2001 From: Carter Bray Date: Sun, 31 Jul 2016 17:01:57 -0700 Subject: [PATCH 0149/1012] Fix raw_vcall on Win64 builds MSVC's call conventions on x64 are the same for normal function calls and member function calls (with the addition of the implicit 'this' parameter). --- plugins/ruby/ruby.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index adc451a19..127a49b69 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -1023,7 +1023,7 @@ static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set) /* call an arbitrary object virtual method */ -#ifdef WIN32 +#if defined(_WIN32) && !defined(_WIN64) __declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { From 44913609c8caad7aa8e8240b75273f7e8dc148de Mon Sep 17 00:00:00 2001 From: Carter Bray Date: Sun, 31 Jul 2016 17:02:02 -0700 Subject: [PATCH 0150/1012] Fix format string warnings --- library/LuaApi.cpp | 2 +- library/VTableInterpose.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 387ac4d97..df077ccc1 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2373,7 +2373,7 @@ static int internal_setAddress(lua_State *L) // Print via printerr, so that it is definitely logged to stderr.log. uintptr_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta(); - fprintf(stderr, "Setting global '%s' to %x (%x)\n", name.c_str(), addr, iaddr); + fprintf(stderr, "Setting global '%s' to %p (%p)\n", name.c_str(), (void*)addr, (void*)iaddr); fflush(stderr); return 1; diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 96cbfff9c..c547800c2 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -309,7 +309,7 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v */ fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %p (%s)\n", - vmethod_idx, uintptr_t(interpose_method), name_str); + vmethod_idx, interpose_method, name_str); fflush(stderr); abort(); } From 9189e3dc7e765fad822708c8869163cc1b4af4b7 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 2 Aug 2016 10:30:17 +0530 Subject: [PATCH 0151/1012] Send world buildings through remotefortressreader.cpp --- library/xml | 2 +- plugins/proto/RemoteFortressReader.proto | 39 +++++++++++++++++++ plugins/remotefortressreader.cpp | 48 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 7d312334c..84f5de34c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7d312334c320022cd6275cddcdb8a64d4ed8d722 +Subproject commit 84f5de34c1a269420204c81f3244a70843bb9c5c diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 889159682..7422d4962 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -411,6 +411,44 @@ message WorldMap repeated RegionTile region_tiles = 25; } +enum SiteRealizationBuildingType +{ + cottage_plot = 0; + castle_wall = 1; + castle_tower = 2; + castle_courtyard = 3; + house = 4; + temple = 5; + tomb = 6; + shop_house = 7; + warehouse = 8; + market_square = 9; + pasture = 10; + waste = 11; + courtyard = 12; + well = 13; + vault = 14; + great_tower = 15; + trenches = 16; + tree_house = 17; + hillock_house = 18; + mead_hall = 19; + fortress_entrance = 20; + library = 21; + tavern = 22; +} + +message SiteRealizationBuilding +{ + optional int32 id = 1; + optional SiteRealizationBuildingType type = 2; + optional int32 min_x = 3; + optional int32 min_y = 4; + optional int32 max_x = 5; + optional int32 max_y = 6; + optional MatPair material = 7; +} + message RegionTile { optional int32 elevation = 1; @@ -426,6 +464,7 @@ message RegionTile optional int32 water_elevation = 11; optional MatPair surface_material = 12; repeated MatPair plant_materials = 13; + repeated SiteRealizationBuilding buildings = 14; } message RegionMap diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 3e372547d..9ab407e7d 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -61,6 +61,9 @@ #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" #include "df/world_population.h" +#include "df/world_site.h" +#include "df/world_site_realization.h" +#include "df/site_realization_building.h" #include "df/unit.h" #include "df/creature_raw.h" @@ -2251,10 +2254,13 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w south = region; } + RegionTile* outputTiles[17][17]; + for (int yy = 0; yy < 17; yy++) for (int xx = 0; xx < 17; xx++) { auto tile = out->add_tiles(); + outputTiles[xx][yy] = tile; //This is because the bottom row doesn't line up. if (xx == 16 && yy == 16 && southEast != NULL) { @@ -2303,6 +2309,48 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); } + + auto regionMap = worldData->region_map[pos_x][pos_y]; + + for (int i = 0; i < regionMap.sites.size(); i++) + { + df::world_site* site = regionMap.sites[i]; + + if (site->realization == NULL) + continue; + + int region_min_x = pos_x * 16; + int region_min_y = pos_y * 16; + + auto realization = site->realization; + for (int site_x = 0; site_x < 17; site_x++) + for (int site_y = 0; site_y < 17; site_y++) + { + int region_x = site->global_min_x - region_min_x + site_x; + int region_y = site->global_min_y - region_min_y + site_y; + + if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) + continue; + + for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) + { + auto in_building = realization->building_map[site_x][site_y].buildings[j]; + auto out_building = outputTiles[region_x][region_y]->add_buildings(); + + out_building->set_id(in_building->id); + out_building->set_type((SiteRealizationBuildingType)in_building->type); + out_building->set_min_x(in_building->min_x - (site_x * 48)); + out_building->set_min_y(in_building->min_y - (site_y * 48)); + out_building->set_max_x(in_building->max_x - (site_x * 48)); + out_building->set_max_y(in_building->max_y - (site_y * 48)); + + auto mat = out_building->mutable_material(); + mat->set_mat_type(in_building->item.mat_type); + mat->set_mat_index(in_building->item.mat_index); + } + + } + } } static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) From 2fda01d90befa6e3e76ae1b92c208c977c860398 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 3 Aug 2016 10:22:30 +0530 Subject: [PATCH 0152/1012] Add support for site towers. --- library/xml | 2 +- plugins/proto/RemoteFortressReader.proto | 33 ++++++++++++++++++++++++ plugins/remotefortressreader.cpp | 16 ++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 84f5de34c..650adfc5a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 84f5de34c1a269420204c81f3244a70843bb9c5c +Subproject commit 650adfc5a91fc3088568b297e170126942cf73db diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 7422d4962..1d0b5da90 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -438,6 +438,36 @@ enum SiteRealizationBuildingType tavern = 22; } +message SiteRealizationBuildingWall +{ + optional int32 start_x = 1; + optional int32 start_y = 2; + optional int32 start_z = 3; + optional int32 end_x = 4; + optional int32 end_y = 5; + optional int32 end_z = 6; +} + +message SiteRealizationBuildingTower +{ + optional int32 roof_z = 1; + optional bool round = 2; + optional bool goblin = 3; +} + +message TrenchSpoke +{ + optional int32 mound_start = 1; + optional int32 trench_start = 2; + optional int32 trench_end = 3; + optional int32 mound_end = 4; +} + +message SiteRealizationBuildingTrenches +{ + repeated TrenchSpoke spokes = 1; +} + message SiteRealizationBuilding { optional int32 id = 1; @@ -447,6 +477,9 @@ message SiteRealizationBuilding optional int32 max_x = 5; optional int32 max_y = 6; optional MatPair material = 7; + optional SiteRealizationBuildingWall wall_info = 8; + optional SiteRealizationBuildingTower tower_info = 9; + optional SiteRealizationBuildingTrenches trench_info = 10; } message RegionTile diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 9ab407e7d..2af3d6105 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -64,6 +64,9 @@ #include "df/world_site.h" #include "df/world_site_realization.h" #include "df/site_realization_building.h" +#include "df/site_realization_building_info_castle_towerst.h" +#include "df/site_realization_building_info_castle_wallst.h" +#include "df/site_realization_building_info_trenchesst.h" #include "df/unit.h" #include "df/creature_raw.h" @@ -2347,6 +2350,19 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w auto mat = out_building->mutable_material(); mat->set_mat_type(in_building->item.mat_type); mat->set_mat_index(in_building->item.mat_index); + + STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); + if (tower_info) + { + mat->set_mat_index(tower_info->wall_item.mat_index); + mat->set_mat_type(tower_info->wall_item.mat_type); + + auto out_tower = out_building->mutable_tower_info(); + out_tower->set_roof_z(tower_info->roof_z); + out_tower->set_round(tower_info->shape.bits.round); + out_tower->set_goblin(tower_info->shape.bits.goblin); + } + } } From 8a97b54bcdb248e2d9b35765cfb0fb34e8371f20 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 3 Aug 2016 16:32:51 +0530 Subject: [PATCH 0153/1012] Send wall info --- plugins/proto/RemoteFortressReader.proto | 2 ++ plugins/remotefortressreader.cpp | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 1d0b5da90..996e01a30 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -498,6 +498,8 @@ message RegionTile optional MatPair surface_material = 12; repeated MatPair plant_materials = 13; repeated SiteRealizationBuilding buildings = 14; + repeated MatPair stone_materials = 15; + repeated MatPair tree_materials = 16; } message RegionMap diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 2af3d6105..0b3d0fb12 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -148,7 +148,6 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); -void FindChangedBlocks(); const char* growth_locations[] = { "TWIGS", @@ -2362,7 +2361,21 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w out_tower->set_round(tower_info->shape.bits.round); out_tower->set_goblin(tower_info->shape.bits.goblin); } + STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); + if (wall_info) + { + mat->set_mat_index(wall_info->wall_item.mat_index); + mat->set_mat_type(wall_info->wall_item.mat_type); + + auto out_wall = out_building->mutable_wall_info(); + out_wall->set_start_x(wall_info->start_x - (site_x * 48)); + out_wall->set_start_y(wall_info->start_y - (site_y * 48)); + out_wall->set_start_z(wall_info->start_z); + out_wall->set_end_x(wall_info->end_x - (site_x * 48)); + out_wall->set_end_y(wall_info->end_y - (site_y * 48)); + out_wall->set_end_z(wall_info->end_z); + } } } From 697aa54fa672fe343c305280efcde3ef5225389d Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 5 Aug 2016 00:40:10 +0530 Subject: [PATCH 0154/1012] Sent tree and stone layers through remotefortressreader.cpp, and correct town placement. --- plugins/remotefortressreader.cpp | 42 ++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 0b3d0fb12..6933826c7 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1966,7 +1966,14 @@ static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) if (layer->top_height == 0) { topLayer = layer->mat_index; - break; + } + if (layer->type != geo_layer_type::SOIL + && layer->type != geo_layer_type::SOIL_OCEAN + && layer->type != geo_layer_type::SOIL_SAND) + { + auto mat = out->add_stone_materials(); + mat->set_mat_index(layer->mat_index); + mat->set_mat_type(0); } } auto surfaceMat = out->mutable_surface_material(); @@ -1976,13 +1983,20 @@ static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) for (int i = 0; i < region->population.size(); i++) { auto pop = region->population[i]; - if (pop->type != world_population_type::Grass) - continue; + if (pop->type == world_population_type::Grass) + { + auto plantMat = out->add_plant_materials(); - auto plantMat = out->add_plant_materials(); + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + else if (pop->type == world_population_type::Tree) + { + auto plantMat = out->add_tree_materials(); - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } } } @@ -2314,16 +2328,24 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w auto regionMap = worldData->region_map[pos_x][pos_y]; - for (int i = 0; i < regionMap.sites.size(); i++) + for (int i = 0; i < worldData->sites.size(); i++) { - df::world_site* site = regionMap.sites[i]; - - if (site->realization == NULL) + df::world_site* site = worldData->sites[i]; + if (!site) continue; int region_min_x = pos_x * 16; int region_min_y = pos_y * 16; + if ((site->global_min_x > (region_min_x + 16)) || + (site->global_min_y > (region_min_y + 16)) || + (site->global_max_x < (region_min_x)) || + (site->global_max_y < (region_min_y))) + continue; + + if (site->realization == NULL) + continue; + auto realization = site->realization; for (int site_x = 0; site_x < 17; site_x++) for (int site_y = 0; site_y < 17; site_y++) From ef569dc5a1521af1eba5e586d6d3e9ad0149d3b9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Aug 2016 17:09:25 -0400 Subject: [PATCH 0155/1012] Pass OS and architecture to ruby codegen explicitly - Passing the architecture is needed to determine sizeof(long) - Passing the OS prevents potential issues when cross-compiling between Windows and non-Windows (although this is unlikely) --- plugins/ruby/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 3aeac6dc5..dfb0dc3c4 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -22,7 +22,7 @@ ENDIF (APPLE OR UNIX) ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} - COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} ${CMAKE_SYSTEM_NAME} ${DFHACK_BUILD_ARCH} # cmake quirk: depending on codegen.out.xml or generate_headers only is not enough, needs both # test by manually patching any library/xml/moo.xml, run make ruby-autogen-rb -j2, and check build/plugins/ruby/ruby-autogen.rb for patched xml data DEPENDS generate_headers ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl From dbd2e7192056cdd72a5427acc9d128af03dc6f2b Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Aug 2016 17:12:07 -0400 Subject: [PATCH 0156/1012] Ruby codegen integer improvements - Add uint64_t - Add special case for long on different platforms - Require OS and architecture to be specified NOTE: integer alignment on x64 is probably still incorrect --- plugins/ruby/codegen.pl | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 8a670718e..7d0a745c0 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -7,13 +7,32 @@ use XML::LibXML; our @lines_rb; -my $os; -if ($^O =~ /linux/i or $^O =~ /darwin/i) { +my $os = $ARGV[2] or die('os not provided (argv[2])'); +if ($os =~ /linux/i or $os =~ /darwin/i) { $os = 'linux'; -} else { +} elsif ($os =~ /windows/i) { $os = 'windows'; +} else { + die "Unknown OS: " . $ARGV[2] . "\n"; +} + +my $arch = $ARGV[3] or die('arch not provided (argv[3])'); +if ($arch =~ /64/i) { + $arch = 64; +} elsif ($arch =~ /32/i) { + $arch = 32; +} else { + die "Unknown architecture: " . $ARGV[3] . "\n"; } -$os = $ARGV[2] if ($ARGV[2]); + +# 32 bits on Windows and 32-bit *nix, 64 bits on 64-bit *nix +my $SIZEOF_LONG; +if ($os eq 'windows' || $arch == 32) { + $SIZEOF_LONG = 4; +} else { + $SIZEOF_LONG = 8; +} + sub indent_rb(&) { my ($sub) = @_; @@ -486,7 +505,7 @@ sub render_class_vmethod_ret { { # raw method call returns an int32, mask according to actual return type my $retsubtype = $ret->getAttribute('ld:subtype'); - my $retbits = $ret->getAttribute('ld:bits'); + my $retbits = sizeof($ret) * 8; push @lines_rb, "val = $call"; if ($retsubtype eq 'bool') { @@ -582,7 +601,7 @@ sub get_field_align { my $meta = $field->getAttribute('ld:meta'); if ($meta eq 'number') { - $al = $field->getAttribute('ld:bits')/8; + $al = sizeof($field); # linux aligns int64_t to 4, windows to 8 # floats are 4 bytes so no pb $al = 4 if ($al > 4 and ($os eq 'linux' or $al != 8)); @@ -655,6 +674,10 @@ sub sizeof { my $meta = $field->getAttribute('ld:meta'); if ($meta eq 'number') { + if ($field->getAttribute('ld:subtype') eq 'long') { + return $SIZEOF_LONG; + } + return $field->getAttribute('ld:bits')/8; } elsif ($meta eq 'pointer') { @@ -859,7 +882,9 @@ sub render_item_number { $subtype ||= $g->getAttribute('base-type') if ($g); $subtype = 'int32_t' if (!$subtype); - if ($subtype eq 'int64_t') { + if ($subtype eq 'uint64_t') { + push @lines_rb, 'number 64, false'; + } elsif ($subtype eq 'int64_t') { push @lines_rb, 'number 64, true'; } elsif ($subtype eq 'uint32_t') { push @lines_rb, 'number 32, false'; @@ -877,6 +902,8 @@ sub render_item_number { push @lines_rb, 'number 8, true'; $initvalue ||= 'nil'; $typename ||= 'BooleanEnum'; + } elsif ($subtype eq 'long') { + push @lines_rb, 'number ' . $SIZEOF_LONG . ', true'; } elsif ($subtype eq 's-float') { push @lines_rb, 'float'; return; From ddbb1b5bb8ba6fa9f7be339e5614a475e56d42c3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Aug 2016 17:29:24 -0400 Subject: [PATCH 0157/1012] ruby: Update sizes for some types Many are still incomplete on x64, especially on Windows --- plugins/ruby/codegen.pl | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 7d0a745c0..2d3b8c420 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -33,6 +33,7 @@ if ($os eq 'windows' || $arch == 32) { $SIZEOF_LONG = 8; } +my $SIZEOF_PTR = ($arch == 64) ? 8 : 4; sub indent_rb(&) { my ($sub) = @_; @@ -681,7 +682,7 @@ sub sizeof { return $field->getAttribute('ld:bits')/8; } elsif ($meta eq 'pointer') { - return 4; + return $SIZEOF_PTR; } elsif ($meta eq 'static-array') { my $count = $field->getAttribute('count'); @@ -716,36 +717,36 @@ sub sizeof { if ($subtype eq 'stl-vector') { if ($os eq 'linux') { - return 12; + return ($arch == 64) ? 24 : 12; } elsif ($os eq 'windows') { - return 16; + return ($arch == 64) ? 32 : 16; # TODO: fix on x64 } else { print "sizeof stl-vector on $os\n"; } } elsif ($subtype eq 'stl-bit-vector') { if ($os eq 'linux') { - return 20; + return ($arch == 64) ? 40 : 20; } elsif ($os eq 'windows') { - return 20; + return ($arch == 64) ? 40 : 20; # TODO: fix on x64 } else { print "sizeof stl-bit-vector on $os\n"; } } elsif ($subtype eq 'stl-deque') { if ($os eq 'linux') { - return 40; + return ($arch == 64) ? 80 : 40; } elsif ($os eq 'windows') { - return 24; + return ($arch == 64) ? 48 : 24; # TODO: fix on x64 } else { print "sizeof stl-deque on $os\n"; } } elsif ($subtype eq 'df-linked-list') { - return 12; + return 3 * $SIZEOF_PTR; } elsif ($subtype eq 'df-flagarray') { - return 8; + return 4 + $SIZEOF_PTR; } elsif ($subtype eq 'df-static-flagarray') { return $field->getAttribute('count'); } elsif ($subtype eq 'df-array') { - return 8; # XXX 6 ? + return 4 + $SIZEOF_PTR; # XXX 4->2 ? } else { print "sizeof container $subtype\n"; } @@ -753,18 +754,20 @@ sub sizeof { } elsif ($meta eq 'primitive') { my $subtype = $field->getAttribute('ld:subtype'); - if ($subtype eq 'stl-string') { if ($os eq 'linux') { - return 4; + if ($subtype eq 'stl-string') { + if ($os eq 'linux') { + return ($arch == 64) ? 8 : 4; } elsif ($os eq 'windows') { - return 28; + return 28; # TODO: fix on x64 } else { print "sizeof stl-string on $os\n"; } print "sizeof stl-string\n"; - } elsif ($subtype eq 'stl-fstream') { if ($os eq 'linux') { - return 284; + } elsif ($subtype eq 'stl-fstream') { + if ($os eq 'linux') { + return 284; # TODO: fix on x64 } elsif ($os eq 'windows') { - return 184; + return 184; # TODO: fix on x64 } else { print "sizeof stl-fstream on $os\n"; } From dde6901534d95ade48b2605e6d5301ee43818a9b Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Aug 2016 17:50:49 -0400 Subject: [PATCH 0158/1012] x86: fix lua address display --- library/LuaWrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 813dec6b7..992ab355c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -996,7 +996,7 @@ static int meta_ptr_tostring(lua_State *state) lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); - lua_pushstring(state, stl_sprintf("<%s: 0x%llx>", cname, (uintptr_t)ptr).c_str()); + lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str()); return 1; } From ee7357b60efb81915cbdd7de8a9335c52c03a2e0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Aug 2016 17:51:08 -0400 Subject: [PATCH 0159/1012] x86: fix vector validity check --- library/lua/memscan.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 47a860f83..534830724 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -332,7 +332,7 @@ end -- Validation function is_valid_vector(ref,size) - local ints = df.reinterpret_cast('uint64_t', ref) + local ints = df.reinterpret_cast('uintptr_t', ref) return ints[0] <= ints[1] and ints[1] <= ints[2] and (size == nil or (ints[1] - ints[0]) % size == 0) end From aeac8b4fef595fb12fa0f9092808c0d739ab7610 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Aug 2016 18:04:42 -0400 Subject: [PATCH 0160/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 369dfdd86..402889961 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 369dfdd862da961c74d35174cb55fed3d96a29c0 +Subproject commit 40288996152af8460e747a7479ebc6dc0e617cd9 From 5fef2ad075aa384b1a86627d6fb1fbed4a1e6fee Mon Sep 17 00:00:00 2001 From: Carter Bray Date: Sun, 7 Aug 2016 10:11:17 -0700 Subject: [PATCH 0161/1012] Add windows container sizes to ruby codegen --- plugins/ruby/codegen.pl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 2d3b8c420..3e41f93c9 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -716,10 +716,8 @@ sub sizeof { my $subtype = $field->getAttribute('ld:subtype'); if ($subtype eq 'stl-vector') { - if ($os eq 'linux') { + if ($os eq 'linux' or $os eq 'windows') { return ($arch == 64) ? 24 : 12; - } elsif ($os eq 'windows') { - return ($arch == 64) ? 32 : 16; # TODO: fix on x64 } else { print "sizeof stl-vector on $os\n"; } @@ -727,7 +725,7 @@ sub sizeof { if ($os eq 'linux') { return ($arch == 64) ? 40 : 20; } elsif ($os eq 'windows') { - return ($arch == 64) ? 40 : 20; # TODO: fix on x64 + return ($arch == 64) ? 32 : 16; } else { print "sizeof stl-bit-vector on $os\n"; } @@ -735,7 +733,7 @@ sub sizeof { if ($os eq 'linux') { return ($arch == 64) ? 80 : 40; } elsif ($os eq 'windows') { - return ($arch == 64) ? 48 : 24; # TODO: fix on x64 + return ($arch == 64) ? 40 : 20; } else { print "sizeof stl-deque on $os\n"; } @@ -758,7 +756,7 @@ sub sizeof { if ($os eq 'linux') { return ($arch == 64) ? 8 : 4; } elsif ($os eq 'windows') { - return 28; # TODO: fix on x64 + return ($arch == 64) ? 32 : 24; } else { print "sizeof stl-string on $os\n"; } @@ -767,7 +765,7 @@ sub sizeof { if ($os eq 'linux') { return 284; # TODO: fix on x64 } elsif ($os eq 'windows') { - return 184; # TODO: fix on x64 + return ($arch == 64) ? 280 : 192; } else { print "sizeof stl-fstream on $os\n"; } From 298d16238ebfb484a8e622ef93fb7b0754137bc1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2016 11:45:00 -0400 Subject: [PATCH 0162/1012] Remove a few unneeded includes in DataStaticsFields.cpp --- library/DataStaticsFields.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 71221effe..11a270251 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -1,20 +1,14 @@ -#include "Internal.h" -#include "DataDefs.h" -#include "MiscUtils.h" -#include "VersionInfo.h" +#include #ifndef STATIC_FIELDS_GROUP -#include "df/world.h" -#include "df/world_data.h" -#include "df/ui.h" +#include "DataDefs.h" #endif -#include "DataIdentity.h" #include "DataFuncs.h" -#include - +#ifdef __GNUC__ #pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif namespace df { #define NUMBER_IDENTITY_TRAITS(category, type) \ From e22b3b1de7dcdf1d1ee98a4b28eaf3a6339609ed Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2016 11:46:20 -0400 Subject: [PATCH 0163/1012] Use non-fixed-width types in integer identity_traits definitions In some situations (e.g. 32-bit Linux), "intptr_t" is defined as "int", which is equivalent to "int32_t", leading to issues with duplicate definitions. In other situations with GCC, "intptr_t" is "long", which isn't covered by any intNN_t types. Also, definitions for "long" already had to be added on Windows, because no fixed-width types in MSVC are equivalent to "long". Switching to non-fixed-width types should hopefully cover all of these situations. If this doesn't cover any integer types that we need, it will be caught quickly, e.g. by references to integer_traits in LuaWrapper.cpp. --- library/DataStaticsFields.cpp | 21 ++++++++------------- library/include/DataIdentity.h | 21 ++++++++------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 11a270251..8ff0770fc 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -18,21 +18,16 @@ namespace df { #ifndef STATIC_FIELDS_GROUP INTEGER_IDENTITY_TRAITS(char); - INTEGER_IDENTITY_TRAITS(int8_t); - INTEGER_IDENTITY_TRAITS(uint8_t); - INTEGER_IDENTITY_TRAITS(int16_t); - INTEGER_IDENTITY_TRAITS(uint16_t); - INTEGER_IDENTITY_TRAITS(int32_t); - INTEGER_IDENTITY_TRAITS(uint32_t); - INTEGER_IDENTITY_TRAITS(int64_t); - INTEGER_IDENTITY_TRAITS(uint64_t); -#ifdef _WIN32 + INTEGER_IDENTITY_TRAITS(signed char); + INTEGER_IDENTITY_TRAITS(unsigned char); + INTEGER_IDENTITY_TRAITS(short); + INTEGER_IDENTITY_TRAITS(unsigned short); + INTEGER_IDENTITY_TRAITS(int); + INTEGER_IDENTITY_TRAITS(unsigned int); INTEGER_IDENTITY_TRAITS(long); INTEGER_IDENTITY_TRAITS(unsigned long); -#else - INTEGER_IDENTITY_TRAITS(intptr_t); - INTEGER_IDENTITY_TRAITS(uintptr_t); -#endif + INTEGER_IDENTITY_TRAITS(long long); + INTEGER_IDENTITY_TRAITS(unsigned long long); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index a04311ac4..556731e46 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -516,21 +516,16 @@ namespace df #define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type) INTEGER_IDENTITY_TRAITS(char); - INTEGER_IDENTITY_TRAITS(int8_t); - INTEGER_IDENTITY_TRAITS(uint8_t); - INTEGER_IDENTITY_TRAITS(int16_t); - INTEGER_IDENTITY_TRAITS(uint16_t); - INTEGER_IDENTITY_TRAITS(int32_t); - INTEGER_IDENTITY_TRAITS(uint32_t); - INTEGER_IDENTITY_TRAITS(int64_t); - INTEGER_IDENTITY_TRAITS(uint64_t); -#ifdef _WIN32 + INTEGER_IDENTITY_TRAITS(signed char); + INTEGER_IDENTITY_TRAITS(unsigned char); + INTEGER_IDENTITY_TRAITS(short); + INTEGER_IDENTITY_TRAITS(unsigned short); + INTEGER_IDENTITY_TRAITS(int); + INTEGER_IDENTITY_TRAITS(unsigned int); INTEGER_IDENTITY_TRAITS(long); INTEGER_IDENTITY_TRAITS(unsigned long); -#else - INTEGER_IDENTITY_TRAITS(intptr_t); - INTEGER_IDENTITY_TRAITS(uintptr_t); -#endif + INTEGER_IDENTITY_TRAITS(long long); + INTEGER_IDENTITY_TRAITS(unsigned long long); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); From 9c28d1a08509faa50af20bf88edecb99b711805a Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2016 16:49:22 -0400 Subject: [PATCH 0164/1012] Improve CMake file downloads - Files are redownloaded if their expected MD5 changes in CMakeLists.txt - Refactored some --- CMake/DownloadFile.cmake | 48 +++++++++++++++++ CMakeLists.txt | 110 ++++++++++++++++++--------------------- 2 files changed, 100 insertions(+), 58 deletions(-) create mode 100644 CMake/DownloadFile.cmake diff --git a/CMake/DownloadFile.cmake b/CMake/DownloadFile.cmake new file mode 100644 index 000000000..1a8a40562 --- /dev/null +++ b/CMake/DownloadFile.cmake @@ -0,0 +1,48 @@ +# Helper to download files as needed + +function(file_md5_if_exists FILE VAR) + if(EXISTS "${FILE}") + file(MD5 "${FILE}" "${VAR}") + set(${VAR} "${${VAR}}" PARENT_SCOPE) + else() + set(${VAR} "" PARENT_SCOPE) + endif() +endfunction() + +function(download_file URL DEST EXPECTED_MD5) + get_filename_component(FILENAME "${URL}" NAME) + file_md5_if_exists("${DEST}" CUR_MD5) + + if(NOT "${EXPECTED_MD5}" STREQUAL "${CUR_MD5}") + message("* Downloading ${FILENAME}") + file(DOWNLOAD "${URL}" "${DEST}" EXPECTED_MD5 "${EXPECTED_MD5}" SHOW_PROGRESS) + endif() +endfunction() + +# Download a file and uncompress it +function(download_file_unzip URL ZIP_TYPE ZIP_DEST ZIP_MD5 UNZIP_DEST UNZIP_MD5) + get_filename_component(FILENAME "${URL}" NAME) + file_md5_if_exists("${UNZIP_DEST}" CUR_UNZIP_MD5) + + # Redownload if the MD5 of the uncompressed file doesn't match + if(NOT "${UNZIP_MD5}" STREQUAL "${CUR_UNZIP_MD5}") + download_file("${URL}" "${ZIP_DEST}" "${ZIP_MD5}") + + if(EXISTS "${ZIP_DEST}") + message("* Decompressing ${FILENAME}") + if("${ZIP_TYPE}" STREQUAL "gz") + execute_process(COMMAND gunzip --force "${ZIP_DEST}") + else() + message(SEND_ERROR "Unknown ZIP_TYPE: ${ZIP_TYPE}") + endif() + if(NOT EXISTS "${UNZIP_DEST}") + message(SEND_ERROR "File failed to unzip to ${UNZIP_DEST}") + else() + file(MD5 "${UNZIP_DEST}" CUR_UNZIP_MD5) + if(NOT "${UNZIP_MD5}" STREQUAL "${CUR_UNZIP_MD5}") + message(SEND_ERROR "MD5 mismatch: ${UNZIP_DEST}: expected ${UNZIP_MD5}, got ${CUR_UNZIP_MD5}") + endif() + endif() + endif() + endif() +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c2db0902..c984c9e1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,68 +203,62 @@ endif() #### download depends #### +include(CMake/DownloadFile.cmake) + if(WIN32) - # Download zlib on Windows - set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH}) - if(NOT EXISTS ${ZLIB_DOWNLOAD_DIR}/zlib.lib) - if(${DFHACK_BUILD_ARCH} STREQUAL "64") - message("Downloading win64-zlib.lib") - file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-zlib.lib" - ${ZLIB_DOWNLOAD_DIR}/zlib.lib - EXPECTED_MD5 "a3b2fc6b68efafa89b0882e354fc8418") - else() - message("Downloading win32-zlib.lib") - file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-zlib.lib" - ${ZLIB_DOWNLOAD_DIR}/zlib.lib - EXPECTED_MD5 "f4ebaa21d9de28566e88b1edfcdff901") - endif() - endif() - # Move zlib to the build folder so possible 32 and 64-bit builds - # in the same source tree don't conflict - file(COPY ${CMAKE_SOURCE_DIR}/depends/zlib - DESTINATION ${CMAKE_BINARY_DIR}/depends/) - file(COPY ${ZLIB_DOWNLOAD_DIR}/zlib.lib - DESTINATION ${CMAKE_BINARY_DIR}/depends/zlib/lib/) - - - # Do the same for SDLreal.dll - # (DFHack doesn't require this at build time, so no need to move it to the build folder) - set(SDLREAL_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) - if(NOT EXISTS ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll) - if(${DFHACK_BUILD_ARCH} STREQUAL "64") - message("Downloading win64-SDL.dll") - file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-SDL.dll" - ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll - EXPECTED_MD5 "1ae242c4b94cb03756a1288122a66faf") - else() - message("Downloading win32-SDL.dll") - file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-SDL.dll" - ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll - EXPECTED_MD5 "5a09604daca6b2b5ce049d79af935d6a") - endif() - endif() + # Download zlib on Windows + set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH}) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-zlib.lib" + ${ZLIB_DOWNLOAD_DIR}/zlib.lib + "a3b2fc6b68efafa89b0882e354fc8418") + else() + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-zlib.lib" + ${ZLIB_DOWNLOAD_DIR}/zlib.lib + "f4ebaa21d9de28566e88b1edfcdff901") + endif() + + # Move zlib to the build folder so possible 32 and 64-bit builds + # in the same source tree don't conflict + file(COPY ${CMAKE_SOURCE_DIR}/depends/zlib + DESTINATION ${CMAKE_BINARY_DIR}/depends/) + file(COPY ${ZLIB_DOWNLOAD_DIR}/zlib.lib + DESTINATION ${CMAKE_BINARY_DIR}/depends/zlib/lib/) + + + # Do the same for SDLreal.dll + # (DFHack doesn't require this at build time, so no need to move it to the build folder) + set(SDLREAL_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-SDL.dll" + ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + "1ae242c4b94cb03756a1288122a66faf") + else() + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-SDL.dll" + ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + "5a09604daca6b2b5ce049d79af935d6a") + endif() endif() if(APPLE) - # libstdc++ (GCC 4.8.5 for OS X 10.6) - # fixes crash-on-unwind bug in DF's libstdc++ - set(LIBSTDCXX_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}) - # check for existence of uncompressed library - if(NOT EXISTS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib) - if(${DFHACK_BUILD_ARCH} STREQUAL "64") - message("Downloading osx64-libstdcxx.6.dylib.gz") - file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-libstdcxx.6.dylib.gz" - ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz - EXPECTED_MD5 "cf26ed588be8e83c8e3a49919793b416") - execute_process(COMMAND gunzip ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz) - else() - message("Downloading osx32-libstdcxx.6.dylib.gz") - file(DOWNLOAD "https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libstdcxx.6.dylib.gz" - ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz - EXPECTED_MD5 "40f3d83871b114f0279240626311621b") - execute_process(COMMAND gunzip ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz) - endif() - endif() + # libstdc++ (GCC 4.8.5 for OS X 10.6) + # fixes crash-on-unwind bug in DF's libstdc++ + set(LIBSTDCXX_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-libstdcxx.6.dylib.gz" + "gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "cf26ed588be8e83c8e3a49919793b416" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "16dc6dbd4ecde7f9b95bb6dc91f07404") + else() + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libstdcxx.6.dylib.gz" + "gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "40f3d83871b114f0279240626311621b" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "c3f5678b8204917e03870834902c3e8b") + endif() endif() #### expose depends #### From b211177a272febd1588a9f4ffe7b2f69280e9c7d Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Aug 2016 10:05:46 -0400 Subject: [PATCH 0165/1012] Fix version in docs for non-stable releases --- conf.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/conf.py b/conf.py index 10df1b551..dc033016f 100644 --- a/conf.py +++ b/conf.py @@ -19,6 +19,7 @@ import fnmatch from io import open from itertools import starmap import os +import re import shlex # pylint:disable=unused-import import sys @@ -149,13 +150,15 @@ author = 'The DFHack Team' def get_version(): """Return the DFHack version string, from CMakeLists.txt""" version = release = '' #pylint:disable=redefined-outer-name + pattern = re.compile(r'set\((df_version|dfhack_release)\s+"(.+?)"\)') try: with open('CMakeLists.txt') as f: for s in f.readlines(): - if fnmatch.fnmatch(s.upper(), 'SET(DF_VERSION "?.??.??")\n'): - version = s.upper().replace('SET(DF_VERSION "', '') - elif fnmatch.fnmatch(s.upper(), 'SET(DFHACK_RELEASE "r*")\n'): - release = s.upper().replace('SET(DFHACK_RELEASE "', '').lower() + for match in pattern.findall(s.lower()): + if match[0] == 'df_version': + version = match[1] + elif match[0] == 'dfhack_release': + release = match[1] return (version + '-' + release).replace('")\n', '') except IOError: return 'unknown' From aa47484b2abc1ac1a0e53582565152c696317d9d Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Aug 2016 18:49:39 -0400 Subject: [PATCH 0166/1012] Enable bit32 library Deprecated in Lua 5.3, but still contains some useful things, like extract() Also helps maintain backward-compatibility with scripts that target older DF versions (e.g. Mifki's dfremote project) --- depends/lua/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index bd244a126..8b9ce8e2e 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -3,6 +3,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_APICHECK") +# Make bit32 library available (for things like bit32.extract()) +ADD_DEFINITIONS(-DLUA_COMPAT_BITLIB) + IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) ELSE() From b9092c94f570216d2635fc550f65e6ecac3df836 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Aug 2016 19:10:38 -0400 Subject: [PATCH 0167/1012] Don't search for 32-bit zlib when targeting 64-bit Linux --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c984c9e1f..cd3e576d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,9 +267,12 @@ endif() # find and make available libz if(NOT UNIX) # Windows # zlib is in here so 32-bit and 64-bit builds in the same source tree are possible - SET(ZLIB_ROOT ${CMAKE_BINARY_DIR}/depends/zlib/) + set(ZLIB_ROOT ${CMAKE_BINARY_DIR}/depends/zlib/) else() - set(ZLIB_ROOT /usr/lib/i386-linux-gnu) + if(NOT APPLE AND DFHACK_BUILD_32) + # 32-bit Linux + set(ZLIB_ROOT /usr/lib/i386-linux-gnu) + endif() endif() find_package(ZLIB REQUIRED) From 02eef951474871ca9726d5aef300a8ab298c21d3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Aug 2016 20:00:31 -0400 Subject: [PATCH 0168/1012] Fix base address for Darwin x64 --- library/include/Memory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/Memory.h b/library/include/Memory.h index 1016f2966..33d40c3cc 100644 --- a/library/include/Memory.h +++ b/library/include/Memory.h @@ -7,7 +7,7 @@ #endif #elif defined(_DARWIN) #ifdef DFHACK64 - #define DEFAULT_BASE_ADDR 0x1000 + #define DEFAULT_BASE_ADDR 0x100000000 #else #define DEFAULT_BASE_ADDR 0x1000 #endif From 53a0d73d2359b244999163afc3638e9bf649d0f8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Aug 2016 20:09:50 -0400 Subject: [PATCH 0169/1012] Add new title-folder plugin --- NEWS.rst | 7 +++ docs/Plugins.rst | 6 ++ plugins/CMakeLists.txt | 1 + plugins/title-folder.cpp | 116 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 plugins/title-folder.cpp diff --git a/NEWS.rst b/NEWS.rst index 106cae801..2d3dd858a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -30,6 +30,13 @@ Changelog .. contents:: :depth: 2 +DFHack future +============= + +New Plugins +----------- +- `title-folder`: shows DF folder name in window title bar when enabled + DFHack 0.43.03-r1 ================= diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 1a353acbd..efd5da0e6 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -601,6 +601,12 @@ resume Allows automatic resumption of suspended constructions, along with colored UI hints for construction status. +.. _title-folder: + +title-folder +============= +Displays the DF folder name in the window title bar when enabled. + .. _title-version: title-version diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index bc66bda75..f36b0fa0b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -139,6 +139,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) + DFHACK_PLUGIN(title-folder title-folder.cpp) DFHACK_PLUGIN(title-version title-version.cpp) DFHACK_PLUGIN(trackstop trackstop.cpp) # DFHACK_PLUGIN(treefarm treefarm.cpp) diff --git a/plugins/title-folder.cpp b/plugins/title-folder.cpp new file mode 100644 index 000000000..ee69ccc5a --- /dev/null +++ b/plugins/title-folder.cpp @@ -0,0 +1,116 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "MemAccess.h" +#include "PluginManager.h" + +using namespace DFHack; + +DFHACK_PLUGIN("title-folder"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +// SDL frees the old window title when changed +static std::string original_title; + +static DFLibrary *sdl_handle = NULL; +static const std::vector sdl_libs { + "SDLreal.dll", + "SDL.framework/SDL", + "libSDL-1.2.so.0" +}; + +void (*_SDL_WM_GetCaption)(const char**, const char**) = NULL; +void SDL_WM_GetCaption(const char **title, const char **icon) { + _SDL_WM_GetCaption(title, icon); +} + +void (*_SDL_WM_SetCaption)(const char*, const char*) = NULL; +void SDL_WM_SetCaption(const char *title, const char *icon) { + _SDL_WM_SetCaption(title, icon); +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool state); +DFhackCExport command_result plugin_shutdown (color_ostream &out); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + for (auto it = sdl_libs.begin(); it != sdl_libs.end(); ++it) + { + if ((sdl_handle = OpenPlugin(it->c_str()))) + break; + } + if (!sdl_handle) + { + out.printerr("title-folder: Could not load SDL.\n"); + return CR_FAILURE; + } + + #define bind(name) \ + _##name = (decltype(_##name))LookupPlugin(sdl_handle, #name); \ + if (!_##name) { \ + out.printerr("title-folder: Bind failed: " #name "\n"); \ + plugin_shutdown(out); \ + return CR_FAILURE; \ + } + + bind(SDL_WM_GetCaption); + bind(SDL_WM_SetCaption); + #undef bind + + const char *title = NULL; + SDL_WM_GetCaption(&title, NULL); + if (!title) + { + out.printerr("title-folder: Failed to get original title\n"); + plugin_shutdown(out); + return CR_FAILURE; + } + original_title = title; + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + if (is_enabled) + { + plugin_enable(out, false); + } + if (sdl_handle) + { + ClosePlugin(sdl_handle); + sdl_handle = NULL; + } + return CR_OK; +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool state) +{ + if (state == is_enabled) + return CR_OK; + + if (state) + { + std::string path = Core::getInstance().p->getPath(); + std::string folder; + size_t pos = path.find_last_of('/'); + if (pos == std::string::npos) + pos = path.find_last_of('\\'); + + if (pos != std::string::npos) + folder = path.substr(pos + 1); + else + folder = path; + + std::string title = original_title + " (" + folder + ")"; + SDL_WM_SetCaption(title.c_str(), NULL); + } + else + { + SDL_WM_SetCaption(original_title.c_str(), NULL); + } + + is_enabled = state; + return CR_OK; +} From 1b11c46a98699d534fe1b3573f2949e668831dc8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 Aug 2016 15:24:07 -0400 Subject: [PATCH 0170/1012] ruby.cpp: uint32_t -> uintptr_t --- plugins/ruby/ruby.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 127a49b69..340f38c58 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -572,7 +572,7 @@ static VALUE rb_dfget_global_address(VALUE self, VALUE name) static VALUE rb_dfget_vtable(VALUE self, VALUE name) { - return rb_uint2inum((uint32_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name))); + return rb_uint2inum((uintptr_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name))); } // read the c++ class name from a vtable pointer, inspired from doReadClassName @@ -622,7 +622,7 @@ static VALUE rb_dfmalloc(VALUE self, VALUE len) if (!ptr) return Qnil; memset(ptr, 0, FIX2INT(len)); - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dffree(VALUE self, VALUE ptr) @@ -746,7 +746,7 @@ static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len) { void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len)); - return (ret == (void*)-1) ? Qnil : rb_uint2inum((uint32_t)ret); + return (ret == (void*)-1) ? Qnil : rb_uint2inum((uintptr_t)ret); } // free memory from pagealloc @@ -789,7 +789,7 @@ static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE pro static VALUE rb_dfmemory_stlstring_new(VALUE self) { std::string *ptr = new std::string; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_stlstring_delete(VALUE self, VALUE addr) { @@ -821,7 +821,7 @@ static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val) static VALUE rb_dfmemory_vec_new(VALUE self) { std::vector *ptr = new std::vector; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_vec_delete(VALUE self, VALUE addr) { @@ -844,7 +844,7 @@ static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr) static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx) { std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx))); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); } static VALUE rb_dfmemory_vec8_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) { @@ -868,7 +868,7 @@ static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr) static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx) { std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx))); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); } static VALUE rb_dfmemory_vec16_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) { @@ -892,7 +892,7 @@ static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr) static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx) { std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx))); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); } static VALUE rb_dfmemory_vec32_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) { @@ -911,7 +911,7 @@ static VALUE rb_dfmemory_vec32_deleteat(VALUE self, VALUE addr, VALUE idx) static VALUE rb_dfmemory_vecbool_new(VALUE self) { std::vector *ptr = new std::vector; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr) { @@ -983,7 +983,7 @@ static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE v static VALUE rb_dfmemory_set_new(VALUE self) { std::set *ptr = new std::set; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_set_delete(VALUE self, VALUE set) From acc4a6a0b15fcdb98a225afe38ff5bac8b07a787 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 Aug 2016 15:48:47 -0400 Subject: [PATCH 0171/1012] Set CMAKE_POSITION_INDEPENDENT_CODE Needed on 64-bit Linux --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 441ad607e..a55940eff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ SET(DFHACK_DEVDOC_DESTINATION hack) OPTION(BUILD_LIBRARY "Build the library that goes into DF." ON) OPTION(BUILD_PLUGINS "Build the plugins." ON) +SET(CMAKE_POSITION_INDEPENDENT_CODE TRUE) IF(UNIX) ## flags for GCC # default to hidden symbols From 215afa34f3c4754184a7355df2ab6da79d4ea1d3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 Aug 2016 23:50:00 -0400 Subject: [PATCH 0172/1012] Update for 64-bit unit changes Includes xml, stonesense, scripts Ref DFHack/df-structures@25cb373 --- library/modules/Buildings.cpp | 9 +++--- library/modules/Units.cpp | 20 +++++++------ library/xml | 2 +- plugins/cursecheck.cpp | 6 ++-- plugins/devel/nestboxes.cpp | 2 +- plugins/fastdwarf.cpp | 14 +++++---- plugins/petcapRemover.cpp | 10 +++---- plugins/siege-engine.cpp | 53 ++++++++++++++++++----------------- plugins/stonesense | 2 +- plugins/strangemood.cpp | 35 ++++++++++++----------- plugins/zone.cpp | 11 ++++---- scripts | 2 +- 12 files changed, 87 insertions(+), 79 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 0d4de546d..d964c8a58 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -77,6 +77,7 @@ using namespace DFHack; #include "df/ui.h" #include "df/ui_look_list.h" #include "df/unit.h" +#include "df/unit_relationship_type.h" #include "df/world.h" using namespace df::enums; @@ -224,7 +225,7 @@ bool Buildings::setOwner(df::building *bld, df::unit *unit) auto &blist = bld->owner->owned_buildings; vector_erase_at(blist, linear_index(blist, bld)); - if (auto spouse = df::unit::find(bld->owner->relations.spouse_id)) + if (auto spouse = df::unit::find(bld->owner->relationship_ids[df::unit_relationship_type::Spouse])) { auto &blist = spouse->owned_buildings; vector_erase_at(blist, linear_index(blist, bld)); @@ -237,7 +238,7 @@ bool Buildings::setOwner(df::building *bld, df::unit *unit) { unit->owned_buildings.push_back(bld); - if (auto spouse = df::unit::find(unit->relations.spouse_id)) + if (auto spouse = df::unit::find(unit->relationship_ids[df::unit_relationship_type::Spouse])) { auto &blist = spouse->owned_buildings; if (bld->canUseSpouseRoom() && linear_index(blist, bld) < 0) @@ -1269,14 +1270,14 @@ bool Buildings::isHospital(df::building * building) return false; return ((df::building_civzonest*) building)->zone_flags.bits.hospital != 0; } - + bool Buildings::isAnimalTraining(df::building * building) { if (!isActivityZone(building)) return false; return ((df::building_civzonest*) building)->zone_flags.bits.animal_training != 0; } - + // returns building of pen/pit at cursor position (NULL if nothing found) df::building* Buildings::findPenPitAt(df::coord coord) { diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index a8e4c7452..0596d529f 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -70,6 +70,7 @@ using namespace std; #include "df/ui.h" #include "df/unit_inventory_item.h" #include "df/unit_misc_trait.h" +#include "df/unit_relationship_type.h" #include "df/unit_skill.h" #include "df/unit_soul.h" #include "df/unit_wound.h" @@ -170,9 +171,9 @@ void Units::CopyCreature(df::unit * source, t_unit & furball) // labors memcpy(&furball.labors, &source->status.labors, sizeof(furball.labors)); - furball.birth_year = source->relations.birth_year; - furball.birth_time = source->relations.birth_time; - furball.pregnancy_timer = source->relations.pregnancy_timer; + furball.birth_year = source->birth_year; + furball.birth_time = source->birth_time; + furball.pregnancy_timer = source->pregnancy_timer; // appearance furball.nbcolors = source->appearance.colors.size(); if(furball.nbcolors>MAX_COLORS) @@ -1085,10 +1086,10 @@ double Units::getAge(df::unit *unit, bool true_age) return -1; double year_ticks = 403200.0; - double birth_time = unit->relations.birth_year + unit->relations.birth_time/year_ticks; + double birth_time = unit->birth_year + unit->birth_time/year_ticks; double cur_time = *cur_year + *cur_year_tick / year_ticks; - if (!true_age && unit->relations.curse_year >= 0) + if (!true_age && unit->curse_year >= 0) { if (auto identity = getIdentity(unit)) { @@ -1453,7 +1454,8 @@ int Units::computeMovementSpeed(df::unit *unit) // Activity state - if (unit->relations.draggee_id != -1) speed += 1000; + if (unit->relationship_ids[df::unit_relationship_type::Draggee] != -1) + speed += 1000; if (unit->flags1.bits.on_ground) speed += 2000; @@ -1522,7 +1524,7 @@ int Units::computeMovementSpeed(df::unit *unit) if (is_adventure) { auto player = vector_get(world->units.active, 0); - if (player && player->id == unit->relations.group_leader_id) + if (player && player->id == unit->relationship_ids[df::unit_relationship_type::GroupLeader]) speed = std::min(speed, computeMovementSpeed(player)); } @@ -1551,14 +1553,14 @@ float Units::computeSlowdownFactor(df::unit *unit) { if (!unit->flags1.bits.marauder && casteFlagSet(unit->race, unit->caste, caste_raw_flags::MEANDERER) && - !(unit->relations.following && isCitizen(unit)) && + !(unit->following && isCitizen(unit)) && linear_index(unit->inventory, &df::unit_inventory_item::mode, df::unit_inventory_item::Hauled) < 0) { coeff *= 4.0f; } - if (unit->relations.group_leader_id < 0 && + if (unit->relationship_ids[df::unit_relationship_type::GroupLeader] < 0 && unit->flags1.bits.active_invader && !unit->job.current_job && !unit->flags3.bits.no_meandering && unit->profession != profession::THIEF && unit->profession != profession::MASTER_THIEF && diff --git a/library/xml b/library/xml index 402889961..d5036d833 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 40288996152af8460e747a7479ebc6dc0e617cd9 +Subproject commit d5036d8336d266bcb13e27e702a0acada01227a4 diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index 82404b288..263f91490 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -229,7 +229,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) } // non-cursed creatures have curse_year == -1 - if(unit->relations.curse_year != -1) + if(unit->curse_year != -1) { cursecount++; @@ -268,8 +268,8 @@ command_result cursecheck (color_ostream &out, vector & parameters) } out.print("born in %d, cursed in %d to be a %s. (%s%s%s)\n", - unit->relations.birth_year, - unit->relations.curse_year, + unit->birth_year, + unit->curse_year, cursetype.c_str(), // technically most cursed creatures are undead, // therefore output 'active' if they are not completely dead diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp index f4f423515..d29e97558 100644 --- a/plugins/devel/nestboxes.cpp +++ b/plugins/devel/nestboxes.cpp @@ -48,7 +48,7 @@ static void eggscan(color_ostream &out) if (nb->claimed_by != -1) { df::unit* u = df::unit::find(nb->claimed_by); - if (u && u->relations.pregnancy_timer > 0) + if (u && u->pregnancy_timer > 0) fertile = true; } for (int j = 1; j < nb->contained_items.size(); j++) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index e0b2882b6..e8ae85c99 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -1,16 +1,17 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" #include "modules/Units.h" #include "modules/Maps.h" -#include "DataDefs.h" -#include "df/world.h" +#include "df/map_block.h" #include "df/unit.h" #include "df/unit_action.h" -#include "df/map_block.h" +#include "df/unit_relationship_type.h" #include "df/units_other_id.h" +#include "df/world.h" using std::string; using std::vector; @@ -56,11 +57,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_teledwarf) do { // skip dwarves that are dragging creatures or being dragged - if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) + if ((unit->relationship_ids[df::unit_relationship_type::Draggee] != -1) || + (unit->relationship_ids[df::unit_relationship_type::Dragger] != -1)) break; // skip dwarves that are following other units - if (unit->relations.following != 0) + if (unit->following != 0) break; // skip unconscious units @@ -105,7 +107,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (size_t j = 0; j < world->units.other[units_other_id::ANY_RIDER].size(); j++) { df::unit *rider = world->units.other[units_other_id::ANY_RIDER][j]; - if (rider->relations.rider_mount_id == unit->id) + if (rider->relationship_ids[df::unit_relationship_type::RiderMount] == unit->id) rider->pos = unit->pos; } } diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp index c059db4c3..d35906a05 100644 --- a/plugins/petcapRemover.cpp +++ b/plugins/petcapRemover.cpp @@ -67,7 +67,7 @@ void impregnateMany() { if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) continue; popcount[unit->race]++; - if ( unit->relations.pregnancy_genes ) { + if ( unit->pregnancy_genes ) { //already pregnant //for player convenience and population stability, count the fetus toward the population cap popcount[unit->race]++; @@ -122,14 +122,14 @@ void impregnateMany() { bool impregnate(df::unit* female, df::unit* male) { if ( !female || !male ) return false; - if ( female->relations.pregnancy_genes ) + if ( female->pregnancy_genes ) return false; df::unit_genes* preg = new df::unit_genes; *preg = male->appearance.genes; - female->relations.pregnancy_genes = preg; - female->relations.pregnancy_timer = pregtime; //300000 for dwarves - female->relations.pregnancy_caste = male->caste; + female->pregnancy_genes = preg; + female->pregnancy_timer = pregtime; //300000 for dwarves + female->pregnancy_caste = male->caste; return true; } diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 4ad8fa341..323e4292e 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -22,40 +22,41 @@ #include #include -#include "df/graphic.h" +#include "df/building_drawbuffer.h" #include "df/building_siegeenginest.h" -#include "df/builtin_mats.h" -#include "df/world.h" +#include "df/building_stockpilest.h" #include "df/buildings_other_id.h" -#include "df/job.h" -#include "df/building_drawbuffer.h" -#include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" -#include "df/proj_itemst.h" -#include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" -#include "df/physical_attribute_type.h" -#include "df/creature_raw.h" +#include "df/builtin_mats.h" #include "df/caste_raw.h" #include "df/caste_raw_flags.h" -#include "df/identity.h" +#include "df/creature_raw.h" +#include "df/flow_info.h" +#include "df/flow_type.h" #include "df/game_mode.h" -#include "df/unit_misc_trait.h" -#include "df/job.h" -#include "df/job_item.h" +#include "df/graphic.h" +#include "df/identity.h" +#include "df/invasion_info.h" #include "df/item_actual.h" #include "df/items_other_id.h" -#include "df/building_stockpilest.h" +#include "df/job.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/material.h" +#include "df/physical_attribute_type.h" +#include "df/proj_itemst.h" +#include "df/report.h" #include "df/stockpile_links.h" -#include "df/workshop_profile.h" #include "df/strain_type.h" -#include "df/material.h" -#include "df/flow_type.h" -#include "df/invasion_info.h" +#include "df/ui.h" +#include "df/ui_build_selector.h" +#include "df/unit.h" +#include "df/unit_misc_trait.h" +#include "df/unit_relationship_type.h" +#include "df/unit_skill.h" +#include "df/unit_soul.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/workshop_profile.h" +#include "df/world.h" #include "MiscUtils.h" @@ -1193,7 +1194,7 @@ struct UnitPath { { if (unit->flags1.bits.rider) { - auto mount = df::unit::find(unit->relations.rider_mount_id); + auto mount = df::unit::find(unit->relationship_ids[df::unit_relationship_type::RiderMount]); if (mount) { diff --git a/plugins/stonesense b/plugins/stonesense index bd6e8f9f9..9270ceb54 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit bd6e8f9f90af1586f5b1eb0003d7e0e133acecc8 +Subproject commit 9270ceb5463657a6d039bdc3389fe69c78a0784b diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 783f73d1f..2a0ed8509 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -2,6 +2,7 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" #include "modules/Gui.h" @@ -11,24 +12,24 @@ #include "modules/Translation.h" #include "modules/Random.h" -#include "DataDefs.h" +#include "df/builtin_mats.h" +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/creature_raw.h" #include "df/d_init.h" -#include "df/world.h" +#include "df/entity_raw.h" +#include "df/general_ref_unit_workerst.h" +#include "df/historical_entity.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/map_block.h" #include "df/ui.h" #include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" #include "df/unit_preference.h" -#include "df/map_block.h" -#include "df/job.h" -#include "df/job_item.h" -#include "df/historical_entity.h" -#include "df/entity_raw.h" -#include "df/builtin_mats.h" -#include "df/general_ref_unit_workerst.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/caste_raw_flags.h" +#include "df/unit_relationship_type.h" +#include "df/unit_skill.h" +#include "df/unit_soul.h" +#include "df/world.h" using std::string; using std::vector; @@ -579,9 +580,9 @@ command_result df_strangemood (color_ostream &out, vector & parameters) df::unit *cur = moodable_units[i]; if (cur->flags1.bits.had_mood) continue; - if (cur->relations.dragger_id != -1) + if (cur->relationship_ids[df::unit_relationship_type::Dragger] != -1) continue; - if (cur->relations.draggee_id != -1) + if (cur->relationship_ids[df::unit_relationship_type::Draggee] != -1) continue; tickets.push_back(i); for (int j = 0; j < 5; j++) @@ -699,7 +700,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } unit->mood = type; - unit->relations.mood_copy = unit->mood; + unit->mood_copy = unit->mood; Gui::showAutoAnnouncement(announcement_type::STRANGE_MOOD, unit->pos, msg, color, bright); // TODO: make sure unit drops any wrestle items diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 7772968a3..fe98e8811 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -72,6 +72,7 @@ #include "df/general_ref_building_civzone_assignedst.h" #include #include +#include "df/unit_relationship_type.h" #include "df/unit_soul.h" #include "df/unit_wound.h" #include "df/viewscreen_dwarfmodest.h" @@ -982,7 +983,7 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building } // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug) - if(unit->relations.pet_owner_id != -1) + if(unit->relationship_ids[df::unit_relationship_type::Pet] != -1) return CR_OK; // check if unit is already pastured or caged, remove refs where necessary @@ -2362,8 +2363,8 @@ bool compareUnitAgesYounger(df::unit* i, df::unit* j) int32_t age_j = (int32_t) getAge(j, true); if(age_i == 0 && age_j == 0) { - age_i = i->relations.birth_time; - age_j = j->relations.birth_time; + age_i = i->birth_time; + age_j = j->birth_time; } return (age_i < age_j); } @@ -2373,8 +2374,8 @@ bool compareUnitAgesOlder(df::unit* i, df::unit* j) int32_t age_j = (int32_t) getAge(j, true); if(age_i == 0 && age_j == 0) { - age_i = i->relations.birth_time; - age_j = j->relations.birth_time; + age_i = i->birth_time; + age_j = j->birth_time; } return (age_i > age_j); } diff --git a/scripts b/scripts index b1e7544d8..9558feba7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b1e7544d87a59e86ae74a2468b18c0b319526932 +Subproject commit 9558feba77c57168a7970e1ebcbc1c7b3a82ab97 From a1ff432bf8165d29c6f76857147f72c050006690 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 Aug 2016 23:58:45 -0400 Subject: [PATCH 0173/1012] Restore fixed-width types in string representations of lua fields e.g. tostring(df.new('int16_t')) was returning "" --- library/DataStaticsFields.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 8ff0770fc..d6a622a9e 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -11,23 +11,23 @@ #endif namespace df { -#define NUMBER_IDENTITY_TRAITS(category, type) \ - category##_identity identity_traits::identity(#type); -#define INTEGER_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(integer, type) -#define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type) +#define NUMBER_IDENTITY_TRAITS(category, type, name) \ + category##_identity identity_traits::identity(name); +#define INTEGER_IDENTITY_TRAITS(type, name) NUMBER_IDENTITY_TRAITS(integer, type, name) +#define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type, #type) #ifndef STATIC_FIELDS_GROUP - INTEGER_IDENTITY_TRAITS(char); - INTEGER_IDENTITY_TRAITS(signed char); - INTEGER_IDENTITY_TRAITS(unsigned char); - INTEGER_IDENTITY_TRAITS(short); - INTEGER_IDENTITY_TRAITS(unsigned short); - INTEGER_IDENTITY_TRAITS(int); - INTEGER_IDENTITY_TRAITS(unsigned int); - INTEGER_IDENTITY_TRAITS(long); - INTEGER_IDENTITY_TRAITS(unsigned long); - INTEGER_IDENTITY_TRAITS(long long); - INTEGER_IDENTITY_TRAITS(unsigned long long); + INTEGER_IDENTITY_TRAITS(char, "char"); + INTEGER_IDENTITY_TRAITS(signed char, "int8_t"); + INTEGER_IDENTITY_TRAITS(unsigned char, "uint8_t"); + INTEGER_IDENTITY_TRAITS(short, "int16_t"); + INTEGER_IDENTITY_TRAITS(unsigned short, "uint16_t"); + INTEGER_IDENTITY_TRAITS(int, "int32_t"); + INTEGER_IDENTITY_TRAITS(unsigned int, "uint32_t"); + INTEGER_IDENTITY_TRAITS(long, "long"); + INTEGER_IDENTITY_TRAITS(unsigned long, "unsigned long"); + INTEGER_IDENTITY_TRAITS(long long, "int64_t"); + INTEGER_IDENTITY_TRAITS(unsigned long long, "uint64_t"); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); From eef6f9bfb7468096fbe4f7618463368338cbd87f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Aug 2016 00:01:52 -0400 Subject: [PATCH 0174/1012] Add df.new() support for char and unsigned long --- library/LuaWrapper.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 992ab355c..f7b2bbb1c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -449,10 +449,12 @@ Lua::ObjectClass Lua::IsDFObject(lua_State *state, int val_index) static const char *const primitive_types[] = { "string", "ptr-string", + "char", "int8_t", "uint8_t", "int16_t", "uint16_t", "int32_t", "uint32_t", "int64_t", "uint64_t", - "intptr_t", "uintptr_t", "long", - "bool", "float", "double", + "intptr_t", "uintptr_t", "long", "unsigned long", + "bool", + "float", "double", "pointer", "ptr-vector", "bit-vector", @@ -462,12 +464,13 @@ static const char *const primitive_types[] = { static type_identity *const primitive_identities[] = { df::identity_traits::get(), df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), - df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), From fe6968f01d7de19e4fcbec41fbc73099045eb6bd Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Aug 2016 11:57:42 -0400 Subject: [PATCH 0175/1012] Fix script-docs.py error with missing opening token --- travis/script-docs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/travis/script-docs.py b/travis/script-docs.py index 2bce4bfd2..33b2818c8 100644 --- a/travis/script-docs.py +++ b/travis/script-docs.py @@ -42,6 +42,11 @@ def check_file(fname): else: print('Error: no documentation in: ' + fname) return 1 + + if not doclines: + print('Error: missing or malformed documentation in: ' + fname) + return 1 + title, underline = [d for d in doclines if d and '=begin' not in d and '[====[' not in d][:2] if underline != '=' * len(title): From 9b495fe08b2604e246e59eb285dea235af4dd101 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Aug 2016 12:51:18 -0400 Subject: [PATCH 0176/1012] Add load-save.lua Ref #755 --- NEWS.rst | 4 ++++ scripts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 2d3dd858a..97daeee32 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -37,6 +37,10 @@ New Plugins ----------- - `title-folder`: shows DF folder name in window title bar when enabled +New Scripts +----------- +- `load-save`: loads a save non-interactively + DFHack 0.43.03-r1 ================= diff --git a/scripts b/scripts index 9558feba7..274e7c372 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9558feba77c57168a7970e1ebcbc1c7b3a82ab97 +Subproject commit 274e7c372c83611d8fad53345a0cb9ad2a1f10df From 5ab930ba4dc7b6614aea5ad6c9c417b55a47d2e0 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 11 Aug 2016 16:56:34 -0500 Subject: [PATCH 0177/1012] labormanager: small tweaks Handle cloth crafts correctly; handle pit jobs correctly; handle bookcase construction correctly; deal with new break behavior better; change assignment of clean labor; tweak hauling assignments slightly to avoid overallocation; assign pull lever to everyone and clean to all nonbusy dwarfs --- plugins/devel/labormanager.cpp | 73 ++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index cb5f11dfc..edae051dc 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -999,6 +999,8 @@ private: } case df::item_type::WOOD: return df::unit_labor::WOOD_CRAFT; + case df::item_type::CLOTH: + return df::unit_labor::CLOTHESMAKER; default: debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); @@ -1304,7 +1306,7 @@ public: job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; - job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); @@ -1339,7 +1341,7 @@ public: job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::MakeTool] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeTool] = jlf_make_object; job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); @@ -1551,7 +1553,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; + if ((*p)->id == misc_trait_type::Migrant) + is_migrant = true; + } + + if (dwarf->dwarf->social_activities.size() > 0) + { + if (print_debug) + out.print ("Dwarf %s is engaged in a social activity. Info only.\n", dwarf->dwarf->name.first_name.c_str()); } if (dwarf->dwarf->profession == profession::BABY || @@ -1985,7 +1993,7 @@ private: else if (dwarf->dwarf->job.current_job == NULL) { - if (is_on_break || dwarf->dwarf->flags1.bits.chained || dwarf->dwarf->flags1.bits.caged) + if (is_migrant || dwarf->dwarf->flags1.bits.chained || dwarf->dwarf->flags1.bits.caged) { state = OTHER; dwarf->clear_all = true; @@ -2351,6 +2359,8 @@ public: } + labor_needed[df::unit_labor::CLEAN] = 1; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -2374,7 +2384,10 @@ public: if (i->second > 0) { int priority = labor_infos[l].priority(); - priority += labor_infos[l].time_since_last_assigned()/12; + + if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) + priority += labor_infos[l].time_since_last_assigned()/12; + for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) priority /= 2; pq.push(make_pair(priority, l)); @@ -2511,7 +2524,8 @@ public: continue; int score = score_labor (*d, l); - score += labor_infos[l].time_since_last_assigned()/12; + if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) + score += labor_infos[l].time_since_last_assigned()/12; if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) score += 1000000; @@ -2521,11 +2535,6 @@ public: if (t == TOOL_NONE || (*d)->has_tool[t]) { set_labor(*d, l, true); - if (print_debug) - out.print("assign \"%s\" extra labor %s score=%d current %s score=%d\n", - (*d)->dwarf->name.first_name.c_str(), - ENUM_KEY_STR(unit_labor, l).c_str(), score, - ENUM_KEY_STR(unit_labor, (*d)->using_labor).c_str(), current_score); } if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); @@ -2582,6 +2591,39 @@ public: } } + /* check for dwarfs assigned no labors and assign them the bucket list if there are */ + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + if ((*d)->state == CHILD) + continue; + + bool any = false; + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if ((*d)->dwarf->status.labors[l]) + { + any = true; + break; + } + } + + set_labor (*d, df::unit_labor::PULL_LEVER, true); + + if (any) continue; + + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + if (to_assign[l] > 0 || l == df::unit_labor::CLEAN) + set_labor(*d, l, true); + } + } + + /* set reequip on any dwarfs who are carrying tools needed by others */ for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) @@ -2606,9 +2648,6 @@ public: if (has_tool != needs_tool) { - if (has_tool && tool_count[t] > tool_in_use[t]) - continue; - df::job_type j = df::job_type::NONE; if ((*d)->dwarf->job.current_job) From a55ce5f1d91486982148b9e62baaedb7c8631d01 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 11 Aug 2016 16:58:35 -0500 Subject: [PATCH 0178/1012] labormanager: whitespace --- plugins/devel/labormanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index edae051dc..7d41c7814 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -2594,13 +2594,13 @@ public: /* check for dwarfs assigned no labors and assign them the bucket list if there are */ for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) { - if ((*d)->state == CHILD) + if ((*d)->state == CHILD) continue; bool any = false; FOR_ENUM_ITEMS (unit_labor, l) { - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE) continue; if ((*d)->dwarf->status.labors[l]) { From 1a79e7456c871ca1eecaf5dada85dca0582040b0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Aug 2016 18:43:34 -0400 Subject: [PATCH 0179/1012] Add shell script to build docs Useful for testing docs without changing BUILD_DOCS if it's set to OFF. --- docs/build.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 docs/build.sh diff --git a/docs/build.sh b/docs/build.sh new file mode 100755 index 000000000..d9c0428ad --- /dev/null +++ b/docs/build.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# usage: +# ./build.sh +# ./build.sh sphinx-executable +# JOBS=3 ./build.sh ... + +cd $(dirname "$0") +cd .. + +sphinx=sphinx-build +if [ -n "$1" ]; then + sphinx=$1 +fi + +if [ -z "$JOBS" ]; then + JOBS=2 +fi + +"$sphinx" -a -E -q -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" From 99813038e9e33246d2c9a4ac3cdfc526b9d0e4b1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Aug 2016 23:42:58 -0400 Subject: [PATCH 0180/1012] Update labormanager for unit.relations removal and fix warnings --- plugins/devel/labormanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 7d41c7814..e2c188d68 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -687,7 +688,7 @@ static df::unit_labor construction_build_labor (df::item* i) color_ostream* debug_stream; -void debug (char* fmt, ...) +void debug (const char* fmt, ...) { if (debug_stream) { @@ -1924,7 +1925,7 @@ private: for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2) { - if ((*u2)->relations.mother_id == dwarf->dwarf->id && + if ((*u2)->relationship_ids[df::unit_relationship_type::Mother] == dwarf->dwarf->id && !(*u2)->flags1.bits.dead && ((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY)) { From a1c25570df856d7304cd0628dad32fe4b67cc56c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Aug 2016 23:49:15 -0400 Subject: [PATCH 0181/1012] Move labormanager to plugins/, per request --- plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/{devel => }/labormanager.cpp | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename plugins/{devel => }/labormanager.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f36b0fa0b..a81d0993a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -110,6 +110,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) + DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index a1e5b7f14..e5001fff7 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -9,7 +9,6 @@ DFHACK_PLUGIN(counters counters.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) -DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(kittens kittens.cpp) DFHACK_PLUGIN(memview memview.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) diff --git a/plugins/devel/labormanager.cpp b/plugins/labormanager.cpp similarity index 100% rename from plugins/devel/labormanager.cpp rename to plugins/labormanager.cpp From bb66ec6aee7ba07151134c17773c046d21205cd3 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Fri, 12 Aug 2016 19:04:23 +1000 Subject: [PATCH 0182/1012] Add 'Using Commands' to docs, with pull#767 syntax Plus consequential changes and minor updates to the table of contents. --- docs/Core.rst | 73 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index a65d16e12..9367338e0 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -4,6 +4,12 @@ DFHack Core ########### +.. contents:: + :depth: 2 + + +Command Implementation +====================== DFHack commands can be implemented in three ways, all of which are used in the same way: @@ -21,7 +27,42 @@ are used in the same way: Most third-party DFHack addons are scripts. -.. contents:: +Using DFHack Commands +===================== +DFHack commands can be executed in a number of ways: + +#. Typing the command into the DFHack console (see below) +#. From the OS terminal (see below) +#. Pressing a key combination set up with `keybinding` +#. From one of several `init-files`, automatically +#. Using `script` to run a batch of commands from a file + +The command line has some nice line editing capabilities, including history +that's preserved between different runs of DF - use :kbd:`↑` and :kbd:`↓` +to go through the history. + +To include whitespace in the argument/s to some command, quote it in +double quotes. To include a double quote character, use ``\"``. + +If the first non-whitespace character is ``:``, the command is parsed in +an alternative mode. The non-whitespace characters following the ``:`` are +the command name, and the remaining part of the line is used verbatim as +the first argument. This is very useful for the `lua` and `rb` commands. +As an example, the following two command lines are exactly equivalent:: + + :foo a b "c d" e f + foo "a b \"c d\" e f" + +There are two ways to run DFHack commands from an OS terminal. + +* If DF and DFHack are already running, calling ``dfhack-run my command`` + in an external terminal is equivalent to calling ``my command`` in the + DFHack console. This executable is provided to support external programs, + and is not intended for direct use. + +* If DF/DFHack is started with arguments beginning with ``+``, the remaining + text is treated as a command in the DFHack console. For example, use + `load-save` to skip the main menu with ``./dfhack +load-save region1``. Built-in Commands @@ -29,6 +70,8 @@ Built-in Commands The following commands are provided by the 'core' components of DFhack, rather than plugins or scripts. +.. contents:: + :local: .. _cls: @@ -245,6 +288,10 @@ The following commands are *not* built-in, but offer similarly useful functions. Init Files ========== + +.. contents:: + :local: + DFHack allows users to automatically run commonly-used DFHack commands when DF is first loaded, when a game is loaded, and when a game is unloaded. @@ -329,34 +376,14 @@ Miscellaneous Notes =================== This section is for odd but important notes that don't fit anywhere else. -* The ``dfhack-run`` executable is there for calling DFHack commands in - an already running DF+DFHack instance from external OS scripts and programs, - and is *not* the way how you use DFHack normally. - * If a DF :kbd:`H` hotkey is named with a DFHack command, pressing the corresponding :kbd:`Fx` button will run that command, instead of zooming to the set location. - -* The command line has some nice line editing capabilities, including history - that's preserved between different runs of DF (use up/down keys to go through - the history). + *This feature will be removed in a future version.* (see :issue:`731`) * The binaries for 0.40.15-r1 to 0.34.11-r4 are on DFFD_. Older versions are available here_. + *These files will eventually be migrated to GitHub.* (see :issue:`473`) .. _DFFD: http://dffd.bay12games.com/search.php?string=DFHack&id=15&limit=1000 .. _here: http://dethware.org/dfhack/download - -* To include whitespace in the argument/s to some command, quote it in - double quotes. To include a double quote character, use ``\"``. - -* If the first non-whitespace character is ``:``, the command is parsed in - an alternative mode which is very useful for the `lua` and `rb` commands. - The following two command lines are exactly equivalent:: - - :foo a b "c d" e f - foo "a b \"c d\" e f" - - * non-whitespace characters following the ``:`` are the command name - * the remaining part of the line is used verbatim as the first argument - From 468cca3f09fbc1c682b0542814f0fff4c36cf657 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Sat, 13 Aug 2016 13:42:12 +1000 Subject: [PATCH 0183/1012] Improve docs on use of OS terminal for DFHack cmds --- docs/Core.rst | 47 +++++++++++++++++++++++++++++++++++++++-------- docs/Plugins.rst | 2 ++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index 9367338e0..1c24fd825 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -37,6 +37,8 @@ DFHack commands can be executed in a number of ways: #. From one of several `init-files`, automatically #. Using `script` to run a batch of commands from a file +The DFHack Console +------------------ The command line has some nice line editing capabilities, including history that's preserved between different runs of DF - use :kbd:`↑` and :kbd:`↓` to go through the history. @@ -53,16 +55,45 @@ As an example, the following two command lines are exactly equivalent:: :foo a b "c d" e f foo "a b \"c d\" e f" -There are two ways to run DFHack commands from an OS terminal. +Using an OS terminal +-------------------- +DFHack commands can be run from an OS terminal at startup, using '+ args', +or at any other time using the ``dfhack-run`` executable. -* If DF and DFHack are already running, calling ``dfhack-run my command`` - in an external terminal is equivalent to calling ``my command`` in the - DFHack console. This executable is provided to support external programs, - and is not intended for direct use. +If DF/DFHack is started with arguments beginning with ``+``, the remaining +text is treated as a command in the DFHack console. It is possible to use +multiple such commands, which are split on ``+``. For example:: -* If DF/DFHack is started with arguments beginning with ``+``, the remaining - text is treated as a command in the DFHack console. For example, use - `load-save` to skip the main menu with ``./dfhack +load-save region1``. + ./dfhack +load-save region1 + "Dwarf Fortress.exe" +devel/print-args Hello! +enable workflow + +The first example (\*nix), `load-save`, skips the main menu and loads +``region1`` immediately. The second (Windows) example prints +:guilabel:`Hello!` in the DFHack console, and `enables ` `workflow`. +Note that the ``:foo`` syntax for whitespace in arguments is not compatible \ +with '+ args'. + + +If DF and DFHack are already running, calling ``dfhack-run my command`` +in an external terminal is equivalent to calling ``my command`` in the +DFHack console. Direct use of the DFhack console is generally easier, +but ``dfhack-run`` can be useful in a variety of circumstances: + +- if the console is unavailable + + - with the init setting ``PRINT_MODE:TEXT`` + - while running an interactive command (eg. `liquids` or `tiletypes`) + +- from external programs or scripts +- if DF or DFHack are not responding + +Examples:: + + ./dfhack-run cursecheck + dfhack-run multicmd kill-lua; die + +The first (\*nix) example `checks for vampires `; the +second (Windows) example uses `kill-lua` to cancel a script and exits. Built-in Commands diff --git a/docs/Plugins.rst b/docs/Plugins.rst index efd5da0e6..c1ca5a56d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -57,6 +57,8 @@ An in-development plugin for realtime fortress visualisation. See :forums:`Armok Vision <146473>`. +.. _cursecheck: + cursecheck ========== Checks a single map tile or the whole map/world for cursed creatures (ghosts, From 570ac8d719014baa46d948079e474c302abfd443 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Sat, 13 Aug 2016 15:46:15 +1000 Subject: [PATCH 0184/1012] Add link to createitem wiki page --- docs/Plugins.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c1ca5a56d..7746b8b51 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2210,7 +2210,8 @@ createitem Allows creating new items of arbitrary types and made of arbitrary materials. By default, items created are spawned at the feet of the selected unit. -Specify the item and material information as you would indicate them in custom reaction raws, with the following differences: +Specify the item and material information as you would indicate them in +custom reaction raws, with the following differences: * Separate the item and material with a space rather than a colon * If the item has no subtype, omit the :NONE @@ -2226,7 +2227,10 @@ Examples:: createitem WOOD PLANT_MAT:TOWER_CAP:WOOD Create tower-cap logs. -To change where new items are placed, first run the command with a destination type while an appropriate destination is selected. +For more examples, :wiki:`see this wiki page `. + +To change where new items are placed, first run the command with a +destination type while an appropriate destination is selected. Options: From 18a851224248623878371b530e82b52e624a31de Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 Aug 2016 11:40:30 -0400 Subject: [PATCH 0185/1012] Fix LLONG_MAX/LLONG_MIN in luaconf.h with old glibc versions --- depends/lua/include/luaconf.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index 1c19bbeb4..ce96b4747 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -14,6 +14,15 @@ #define LUA_COMPAT_APIINTCASTS #define LUA_COMPAT_IPAIRS +// Patch for old glibc versions +#if !defined(LLONG_MAX) && defined(__LONG_LONG_MAX__) +#define LLONG_MAX __LONG_LONG_MAX__ +#endif + +#if !defined(LLONG_MIN) && defined(__LONG_LONG_MAX__) +#define LLONG_MIN (-__LONG_LONG_MAX__ - 1LL) +#endif + /* ** =================================================================== ** Search for "@@" to find all configurable definitions. From 576174ea0babdb06d05baecbff0719e9ba9201d1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 Aug 2016 19:52:14 -0400 Subject: [PATCH 0186/1012] Lua: Add lengths of (some) containers to their string representations --- library/LuaWrapper.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index f7b2bbb1c..6e7f81102 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -996,10 +996,23 @@ static int meta_ptr_tostring(lua_State *state) { uint8_t *ptr = get_object_addr(state, 1, 0, "access"); + bool has_length = false; + uint64_t length = 0; + auto *cid = dynamic_cast(get_object_identity(state, 1, "__tostring()", true, true)); + + if (cid && (cid->type() == IDTYPE_CONTAINER || cid->type() == IDTYPE_STL_PTR_VECTOR)) + { + has_length = true; + length = cid->lua_item_count(state, ptr, container_identity::COUNT_LEN); + } + lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); - lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str()); + if (has_length) + lua_pushstring(state, stl_sprintf("<%s[%llu]: %p>", cname, length, (void*)ptr).c_str()); + else + lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str()); return 1; } From e965f5318fb6876b10431882cfbf7fa5ecd10e43 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 Aug 2016 21:44:01 -0400 Subject: [PATCH 0187/1012] Replace many includes with forward declarations in modules/Maps.h --- library/LuaApi.cpp | 1 + library/include/modules/Buildings.h | 59 ++--------- library/include/modules/Maps.h | 39 ++++--- library/modules/Buildings.cpp | 55 ++++++++++ library/modules/Burrows.cpp | 13 +-- library/modules/Constructions.cpp | 16 +-- library/modules/MapCache.cpp | 38 +++---- library/modules/Maps.cpp | 37 +++---- plugins/changelayer.cpp | 23 ++-- plugins/changevein.cpp | 4 + plugins/cleaners.cpp | 15 ++- plugins/deramp.cpp | 3 + plugins/devel/frozen.cpp | 6 +- plugins/devel/kittens.cpp | 21 ++-- plugins/devel/tilesieve.cpp | 1 + plugins/fixveins.cpp | 5 + plugins/follow.cpp | 18 ++-- plugins/lair.cpp | 3 + plugins/liquids.cpp | 21 ++-- plugins/plants.cpp | 2 + plugins/power-meter.cpp | 42 ++++---- plugins/probe.cpp | 38 ++++--- plugins/remotefortressreader.cpp | 151 +++++++++++++-------------- plugins/rendermax/renderer_light.cpp | 23 ++-- plugins/reveal.cpp | 5 +- plugins/steam-engine.cpp | 50 ++++----- plugins/tiletypes.cpp | 16 ++- plugins/tubefill.cpp | 2 + 28 files changed, 390 insertions(+), 317 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index df077ccc1..997d57a38 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -89,6 +89,7 @@ distribution. #include "df/proj_itemst.h" #include "df/itemdef.h" #include "df/enabler.h" +#include "df/feature_init.h" #include #include diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index de78acda6..16df7b212 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -26,19 +26,20 @@ distribution. #include "Export.h" #include "DataDefs.h" #include "Types.h" +#include "modules/Items.h" +#include "modules/Maps.h" + #include "df/building.h" #include "df/building_stockpilest.h" #include "df/building_type.h" #include "df/civzone_type.h" +#include "df/construction_type.h" #include "df/furnace_type.h" #include "df/item.h" -#include "df/workshop_type.h" -#include "df/construction_type.h" #include "df/shop_type.h" #include "df/siegeengine_type.h" #include "df/trap_type.h" -#include "modules/Items.h" -#include "modules/Maps.h" +#include "df/workshop_type.h" namespace df { @@ -220,55 +221,7 @@ public: item = NULL; } - StockpileIterator& operator++() { - while (stockpile) { - if (block) { - // Check the next item in the current block. - ++current; - } else { - // Start with the top-left block covering the stockpile. - block = Maps::getTileBlock(stockpile->x1, stockpile->y1, stockpile->z); - current = 0; - } - - while (current >= block->items.size()) { - // Out of items in this block; find the next block to search. - if (block->map_pos.x + 16 < stockpile->x2) { - block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z); - current = 0; - } else if (block->map_pos.y + 16 < stockpile->y2) { - block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z); - current = 0; - } else { - // All items in all blocks have been checked. - block = NULL; - item = NULL; - return *this; - } - } - - // If the current item isn't properly stored, move on to the next. - item = df::item::find(block->items[current]); - if (!item->flags.bits.on_ground) { - continue; - } - - if (!Buildings::containsTile(stockpile, item->pos, false)) { - continue; - } - - // Ignore empty bins, barrels, and wheelbarrows assigned here. - if (item->isAssignedToThisStockpile(stockpile->id)) { - auto ref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_ITEM); - if (!ref) continue; - } - - // Found a valid item; yield it. - break; - } - - return *this; - } + StockpileIterator& operator++(); void begin(df::building_stockpilest* sp) { stockpile = sp; diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 7ce594522..af3a9f5d5 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -36,22 +36,33 @@ distribution. #include "BitArray.h" #include "modules/Materials.h" -#include "df/world.h" -#include "df/world_data.h" -#include "df/map_block.h" -#include "df/block_square_event.h" -#include "df/block_square_event_mineralst.h" -#include "df/block_square_event_frozen_liquidst.h" -#include "df/block_square_event_world_constructionst.h" -#include "df/block_square_event_material_spatterst.h" -#include "df/block_square_event_grassst.h" -#include "df/block_square_event_spoorst.h" -#include "df/block_square_event_item_spatterst.h" -#include "df/tile_liquid.h" +#include "df/block_flags.h" +#include "df/feature_type.h" +#include "df/flow_type.h" #include "df/tile_dig_designation.h" +#include "df/tile_liquid.h" #include "df/tile_traffic.h" -#include "df/feature_init.h" -#include "df/flow_type.h" +#include "df/tiletype.h" + +namespace df { + struct block_square_event; + struct block_square_event_frozen_liquidst; + struct block_square_event_grassst; + struct block_square_event_item_spatterst; + struct block_square_event_material_spatterst; + struct block_square_event_mineralst; + struct block_square_event_spoorst; + struct block_square_event_world_constructionst; + struct feature_init; + struct map_block; + struct map_block_column; + struct region_map_entry; + struct world; + struct world_data; + struct world_geo_biome; + union tile_designation; + union tile_occupancy; +} /** * \defgroup grp_maps Maps module and its types diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index d964c8a58..b7933f6ef 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -49,6 +49,7 @@ using namespace std; using namespace DFHack; #include "DataDefs.h" + #include "df/building_axle_horizontalst.h" #include "df/building_bars_floorst.h" #include "df/building_bars_verticalst.h" @@ -74,6 +75,8 @@ using namespace DFHack; #include "df/item.h" #include "df/job.h" #include "df/job_item.h" +#include "df/map_block.h" +#include "df/tile_occupancy.h" #include "df/ui.h" #include "df/ui_look_list.h" #include "df/unit.h" @@ -1290,3 +1293,55 @@ df::building* Buildings::findPenPitAt(df::coord coord) } return NULL; } + +using Buildings::StockpileIterator; +StockpileIterator& StockpileIterator::operator++() { + while (stockpile) { + if (block) { + // Check the next item in the current block. + ++current; + } else { + // Start with the top-left block covering the stockpile. + block = Maps::getTileBlock(stockpile->x1, stockpile->y1, stockpile->z); + current = 0; + } + + while (current >= block->items.size()) { + // Out of items in this block; find the next block to search. + if (block->map_pos.x + 16 < stockpile->x2) { + block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z); + current = 0; + } else if (block->map_pos.y + 16 < stockpile->y2) { + block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z); + current = 0; + } else { + // All items in all blocks have been checked. + block = NULL; + item = NULL; + return *this; + } + } + + // If the current item isn't properly stored, move on to the next. + item = df::item::find(block->items[current]); + if (!item->flags.bits.on_ground) { + continue; + } + + if (!Buildings::containsTile(stockpile, item->pos, false)) { + continue; + } + + // Ignore empty bins, barrels, and wheelbarrows assigned here. + if (item->isAssignedToThisStockpile(stockpile->id)) { + auto ref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_ITEM); + if (!ref) continue; + } + + // Found a valid item; yield it. + break; + } + + return *this; +} + diff --git a/library/modules/Burrows.cpp b/library/modules/Burrows.cpp index 48a2ca3a8..f8fbbb23f 100644 --- a/library/modules/Burrows.cpp +++ b/library/modules/Burrows.cpp @@ -29,20 +29,21 @@ distribution. #include using namespace std; -#include "Error.h" #include "Core.h" +#include "DataDefs.h" +#include "Error.h" +#include "MiscUtils.h" #include "modules/Burrows.h" #include "modules/Maps.h" #include "modules/Units.h" -#include "MiscUtils.h" - -#include "DataDefs.h" -#include "df/ui.h" -#include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" +#include "df/burrow.h" +#include "df/map_block.h" +#include "df/ui.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 98410c331..9cec2eab9 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -31,21 +31,21 @@ distribution. using namespace std; -#include "VersionInfo.h" +#include "Core.h" #include "MemAccess.h" +#include "TileTypes.h" #include "Types.h" -#include "Core.h" +#include "VersionInfo.h" -#include "modules/Constructions.h" #include "modules/Buildings.h" +#include "modules/Constructions.h" #include "modules/Maps.h" -#include "TileTypes.h" - -#include "df/world.h" -#include "df/job_item.h" -#include "df/building_type.h" #include "df/building_constructionst.h" +#include "df/building_type.h" +#include "df/job_item.h" +#include "df/map_block.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index 2ebfd9644..fb4aef935 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -33,39 +33,41 @@ distribution. #include using namespace std; -#include "modules/Maps.h" -#include "modules/MapCache.h" #include "ColorText.h" +#include "Core.h" +#include "DataDefs.h" #include "Error.h" -#include "VersionInfo.h" #include "MemAccess.h" -#include "ModuleFactory.h" -#include "Core.h" #include "MiscUtils.h" +#include "ModuleFactory.h" +#include "VersionInfo.h" #include "modules/Buildings.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" #include "modules/Materials.h" -#include "DataDefs.h" -#include "df/world_data.h" -#include "df/world_underground_region.h" -#include "df/world_geo_biome.h" -#include "df/world_geo_layer.h" -#include "df/feature_init.h" -#include "df/world_data.h" -#include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" -#include "df/world_region_details.h" -#include "df/builtin_mats.h" #include "df/block_square_event_grassst.h" -#include "df/z_level_flags.h" -#include "df/region_map_entry.h" +#include "df/block_square_event_frozen_liquidst.h" +#include "df/building_type.h" +#include "df/builtin_mats.h" +#include "df/burrow.h" +#include "df/feature_init.h" #include "df/flow_info.h" #include "df/plant.h" #include "df/plant_tree_info.h" #include "df/plant_tree_tile.h" -#include "df/building_type.h" +#include "df/region_map_entry.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_region_details.h" +#include "df/world_underground_region.h" +#include "df/z_level_flags.h" using namespace DFHack; using namespace MapExtras; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 1114107ec..e56ae3109 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -33,36 +33,37 @@ distribution. #include using namespace std; -#include "modules/Maps.h" -#include "modules/MapCache.h" #include "ColorText.h" +#include "Core.h" +#include "DataDefs.h" #include "Error.h" -#include "VersionInfo.h" #include "MemAccess.h" -#include "ModuleFactory.h" -#include "Core.h" #include "MiscUtils.h" +#include "ModuleFactory.h" +#include "VersionInfo.h" #include "modules/Buildings.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" -#include "DataDefs.h" -#include "df/world_data.h" -#include "df/world_underground_region.h" -#include "df/world_geo_biome.h" -#include "df/world_geo_layer.h" -#include "df/feature_init.h" -#include "df/world_data.h" -#include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" -#include "df/world_region_details.h" -#include "df/builtin_mats.h" #include "df/block_square_event_grassst.h" -#include "df/z_level_flags.h" -#include "df/region_map_entry.h" -#include "df/flow_info.h" #include "df/building_type.h" +#include "df/builtin_mats.h" +#include "df/burrow.h" +#include "df/feature_init.h" +#include "df/flow_info.h" #include "df/plant.h" +#include "df/region_map_entry.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_region_details.h" +#include "df/world_underground_region.h" +#include "df/z_level_flags.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 64e932609..27e4c12e6 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -1,25 +1,24 @@ // changelayer plugin // allows changing the material type of geological layers -// some headers required for a plugin. Nothing special, just the basics. +#include "Console.h" #include "Core.h" -#include -#include -#include - -// DF data structure definition headers #include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "TileTypes.h" + +#include "modules/Gui.h" +#include "modules/MapCache.h" #include "modules/Maps.h" #include "modules/Materials.h" -#include "modules/MapCache.h" -#include "modules/Gui.h" - -#include "TileTypes.h" +// DF data structure definition headers +#include "df/region_map_entry.h" +#include "df/world.h" #include "df/world_data.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" -#include "df/region_map_entry.h" using namespace DFHack; using namespace df::enums; @@ -394,4 +393,4 @@ bool conversionAllowed(color_ostream &out, MaterialInfo mat_new, MaterialInfo ma warned = true; } return allowed; -} \ No newline at end of file +} diff --git a/plugins/changevein.cpp b/plugins/changevein.cpp index 9c17af292..8612603fc 100644 --- a/plugins/changevein.cpp +++ b/plugins/changevein.cpp @@ -10,6 +10,10 @@ #include "modules/Materials.h" #include "TileTypes.h" +#include "df/block_square_event.h" +#include "df/block_square_event_mineralst.h" +#include "df/map_block.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 3bc15e343..24694c6a2 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -1,17 +1,22 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" + #include "modules/Maps.h" -#include "DataDefs.h" +#include "df/block_square_event.h" +#include "df/block_square_event_material_spatterst.h" +#include "df/builtin_mats.h" +#include "df/global_objects.h" #include "df/item_actual.h" -#include "df/unit.h" -#include "df/spatter.h" +#include "df/map_block.h" #include "df/matter_state.h" -#include "df/global_objects.h" -#include "df/builtin_mats.h" #include "df/plant.h" +#include "df/spatter.h" +#include "df/unit.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index f0171dabc..fd7ceb3d7 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -9,6 +9,9 @@ #include "modules/Maps.h" #include "TileTypes.h" +#include "df/map_block.h" +#include "df/world.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/devel/frozen.cpp b/plugins/devel/frozen.cpp index 338cc37e8..ed5192073 100644 --- a/plugins/devel/frozen.cpp +++ b/plugins/devel/frozen.cpp @@ -1,11 +1,15 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" -#include "DataDefs.h" #include "modules/Maps.h" +#include "df/block_square_event_frozen_liquidst.h" +#include "df/map_block.h" +#include "df/world.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index 6317a0a39..77da0851e 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -1,16 +1,19 @@ -#include "Core.h" +#include +#include + #include "Console.h" +#include "Core.h" #include "Export.h" -#include "PluginManager.h" #include "MiscUtils.h" -#include -#include -#include "modules/Maps.h" +#include "PluginManager.h" + +#include "modules/Gui.h" #include "modules/Items.h" -#include -#include -#include -#include +#include "modules/Maps.h" + +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/devel/tilesieve.cpp b/plugins/devel/tilesieve.cpp index 5c82eabe0..4cef423f6 100644 --- a/plugins/devel/tilesieve.cpp +++ b/plugins/devel/tilesieve.cpp @@ -9,6 +9,7 @@ // DF data structure definition headers #include "DataDefs.h" #include "modules/Maps.h" +#include "df/map_block.h" #include "df/world.h" #include "TileTypes.h" diff --git a/plugins/fixveins.cpp b/plugins/fixveins.cpp index 5f612c9ad..88eeb3b61 100644 --- a/plugins/fixveins.cpp +++ b/plugins/fixveins.cpp @@ -14,6 +14,11 @@ #include "modules/Maps.h" #include "TileTypes.h" +#include "df/block_square_event.h" +#include "df/block_square_event_mineralst.h" +#include "df/map_block.h" +#include "df/world.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/follow.cpp b/plugins/follow.cpp index 7b8ad3906..7a17f89d0 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -1,17 +1,19 @@ // Make the camera follow the selected unit +#include "Console.h" #include "Core.h" -#include -#include -#include - -#include "DFHack.h" #include "DataDefs.h" +#include "DFHack.h" +#include "Export.h" +#include "PluginManager.h" + #include "modules/Gui.h" -#include "modules/World.h" #include "modules/Maps.h" -#include -#include +#include "modules/World.h" + +#include "df/creature_raw.h" +#include "df/unit.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/lair.cpp b/plugins/lair.cpp index 96a178485..6fb167988 100644 --- a/plugins/lair.cpp +++ b/plugins/lair.cpp @@ -10,6 +10,9 @@ #include "modules/World.h" #include "modules/MapCache.h" #include "modules/Gui.h" + +#include "df/world.h" + using namespace DFHack; using namespace df::enums; diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 1953728a4..5b97f9ea2 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -20,29 +20,34 @@ // - grab the code from digcircle to get a circle brush - could be nice when painting with obsidian // - maybe store the last parameters in a file to make them persistent after dfhack is closed? +#include #include -#include -#include #include +#include #include -#include #include -#include +#include +#include using std::vector; using std::string; using std::endl; using std::set; -#include "Core.h" #include "Console.h" +#include "Core.h" #include "Export.h" +#include "LuaTools.h" #include "PluginManager.h" -#include "modules/Maps.h" -#include "modules/Gui.h" #include "TileTypes.h" + +#include "modules/Gui.h" #include "modules/MapCache.h" -#include "LuaTools.h" +#include "modules/Maps.h" + +#include "df/world.h" + #include "Brushes.h" + using namespace MapExtras; using namespace DFHack; using namespace df::enums; diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 5d84ca96e..a15377279 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -13,7 +13,9 @@ #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" + #include "df/plant.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp index 039b133f2..c61c3c8ee 100644 --- a/plugins/power-meter.cpp +++ b/plugins/power-meter.cpp @@ -1,13 +1,3 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -15,22 +5,34 @@ #include #include -#include -#include "df/graphic.h" +#include "Console.h" +#include "Core.h" +#include "Error.h" +#include "Export.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "TileTypes.h" +#include "VTableInterpose.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "modules/World.h" + +#include "df/building_drawbuffer.h" #include "df/building_trapst.h" -#include "df/builtin_mats.h" -#include "df/world.h" #include "df/buildings_other_id.h" +#include "df/builtin_mats.h" +#include "df/flow_info.h" +#include "df/graphic.h" #include "df/machine.h" #include "df/machine_info.h" -#include "df/building_drawbuffer.h" +#include "df/report.h" +#include "df/tile_designation.h" #include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" #include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" - -#include "MiscUtils.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/probe.cpp b/plugins/probe.cpp index b1302868d..94ebc1884 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -1,33 +1,37 @@ // Just show some position data -#include -#include #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include using namespace std; -#include "Core.h" #include "Console.h" +#include "Core.h" #include "Export.h" +#include "MiscUtils.h" #include "PluginManager.h" -#include "modules/Units.h" -#include "df/unit_inventory_item.h" -#include "df/building_nest_boxst.h" -#include "modules/Maps.h" + +#include "modules/Buildings.h" #include "modules/Gui.h" -#include "modules/Materials.h" #include "modules/MapCache.h" -#include "modules/Buildings.h" -#include "MiscUtils.h" +#include "modules/Maps.h" +#include "modules/Materials.h" +#include "modules/Units.h" -#include "df/world.h" -#include "df/world_raws.h" +#include "df/block_square_event_grassst.h" +#include "df/block_square_event_world_constructionst.h" #include "df/building_def.h" +#include "df/building_nest_boxst.h" #include "df/region_map_entry.h" +#include "df/unit_inventory_item.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_raws.h" using std::vector; using std::string; @@ -303,7 +307,7 @@ command_result df_probe (color_ostream &out, vector & parameters) out << "salty" << endl; if(des.bits.water_stagnant) out << "stagnant" << endl; - + out.print("%-16s= %s\n", "dig", ENUM_KEY_STR(tile_dig_designation, des.bits.dig).c_str()); out.print("%-16s= %s\n", "traffic", ENUM_KEY_STR(tile_traffic, des.bits.traffic).c_str()); diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index e210b7d50..5edb8e248 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1,101 +1,96 @@ #define DF_VERSION 42004 +#include +#include +#include + // some headers required for a plugin. Nothing special, just the basics. +#include "Console.h" #include "Core.h" -#include -#include -#include +#include "DataDefs.h" +#include "Export.h" +#include "Hooks.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "RemoteServer.h" +#include "TileTypes.h" + +#include "SDL_events.h" +#include "SDL_keyboard.h" + +//DFhack specific headers +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Items.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" +#include "modules/Materials.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "modules/World.h" // DF data structure definition headers -#include "DataDefs.h" -#include "df/world.h" -#include "df/ui.h" -#include "df/item.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" +#include "df/army.h" +#include "df/army_flags.h" +#include "df/body_appearance_modifier.h" +#include "df/body_part_layer_raw.h" #include "df/body_part_raw.h" +#include "df/bp_appearance_modifier.h" +#include "df/building_axle_horizontalst.h" +#include "df/building_bridgest.h" +#include "df/building_def_furnacest.h" +#include "df/building_def_workshopst.h" +#include "df/building_rollersst.h" +#include "df/building_screw_pumpst.h" +#include "df/building_siegeenginest.h" +#include "df/building_water_wheelst.h" +#include "df/building_wellst.h" +#include "df/building_windmillst.h" +#include "df/builtin_mats.h" +#include "df/caste_raw.h" +#include "df/caste_raw.h" +#include "df/color_modifier_raw.h" +#include "df/creature_raw.h" +#include "df/creature_raw.h" +#include "df/descriptor_color.h" +#include "df/descriptor_color.h" +#include "df/descriptor_pattern.h" +#include "df/descriptor_pattern.h" +#include "df/descriptor_shape.h" +#include "df/dfhack_material_category.h" +#include "df/enabler.h" +#include "df/graphic.h" #include "df/historical_figure.h" - +#include "df/item.h" +#include "df/itemdef.h" #include "df/job_item.h" #include "df/job_material_category.h" -#include "df/dfhack_material_category.h" -#include "df/matter_state.h" -#include "df/material_vec_ref.h" -#include "df/builtin_mats.h" #include "df/map_block_column.h" +#include "df/material_vec_ref.h" +#include "df/matter_state.h" +#include "df/mental_attribute_type.h" +#include "df/physical_attribute_type.h" #include "df/plant.h" #include "df/plant_raw_flags.h" +#include "df/region_map_entry.h" +#include "df/tissue.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_region.h" +#include "df/world_region_details.h" + #if DF_VERSION > 40001 #include "df/plant_tree_info.h" #include "df/plant_tree_tile.h" #include "df/plant_growth.h" #include "df/plant_growth_print.h" #endif -#include "df/itemdef.h" -#include "df/building_def_workshopst.h" -#include "df/building_def_furnacest.h" -#include "df/building_wellst.h" -#include "df/building_water_wheelst.h" -#include "df/building_screw_pumpst.h" -#include "df/building_axle_horizontalst.h" -#include "df/building_windmillst.h" -#include "df/building_siegeenginest.h" -#include "df/building_rollersst.h" -#include "df/building_bridgest.h" - -#include "df/descriptor_color.h" -#include "df/descriptor_pattern.h" -#include "df/descriptor_shape.h" - -#include "df/physical_attribute_type.h" -#include "df/mental_attribute_type.h" -#include "df/color_modifier_raw.h" -#include "df/descriptor_color.h" -#include "df/descriptor_pattern.h" - -#include "df/region_map_entry.h" -#include "df/world_region_details.h" -#include "df/world_region.h" -#include "df/army.h" -#include "df/army_flags.h" - -#include "df/unit.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/tissue.h" - -#include "df/enabler.h" -#include "df/graphic.h" - -#include "df/viewscreen_choose_start_sitest.h" - -#include "df/bp_appearance_modifier.h" -#include "df/body_part_layer_raw.h" -#include "df/body_appearance_modifier.h" - -//DFhack specific headers -#include "modules/Maps.h" -#include "modules/MapCache.h" -#include "modules/Materials.h" -#include "modules/Gui.h" -#include "modules/Translation.h" -#include "modules/Items.h" -#include "modules/Buildings.h" -#include "modules/Units.h" -#include "modules/World.h" -#include "TileTypes.h" -#include "MiscUtils.h" -#include "Hooks.h" -#include "SDL_events.h" -#include "SDL_keyboard.h" - -#include -#include -#include #include "RemoteFortressReader.pb.h" -#include "RemoteServer.h" using namespace DFHack; using namespace df::enums; @@ -2301,7 +2296,7 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage if (!orig_part) continue; auto send_part = send_caste->add_body_parts(); - + send_part->set_token(orig_part->token); send_part->set_category(orig_part->category); send_part->set_parent(orig_part->con_part_id); @@ -2410,7 +2405,7 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_caste->set_description(orig_caste->description); send_caste->set_adult_size(orig_caste->misc.adult_size); } - + for (int j = 0; j < orig_creature->tissue.size(); j++) { auto orig_tissue = orig_creature->tissue[j]; diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 716f3d0b6..48dcb8223 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -1,34 +1,33 @@ #include "renderer_light.hpp" #include -#include #include +#include +#include #include "tinythread.h" #include "LuaTools.h" #include "modules/Gui.h" -#include "modules/Screen.h" #include "modules/Maps.h" - +#include "modules/Screen.h" #include "modules/Units.h" -#include "df/graphic.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/viewscreen_dungeonmodest.h" -#include "df/flow_info.h" -#include "df/world.h" +#include "df/block_square_event_material_spatterst.h" #include "df/building.h" #include "df/building_doorst.h" #include "df/building_floodgatest.h" -#include "df/plant.h" -#include "df/plant_raw.h" +#include "df/flow_info.h" +#include "df/graphic.h" #include "df/item.h" #include "df/items_other_id.h" +#include "df/plant.h" +#include "df/plant_raw.h" #include "df/unit.h" - -#include +#include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" using df::global::gps; using namespace DFHack; diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 01c007d05..20b36467e 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -10,8 +10,11 @@ #include "modules/World.h" #include "modules/MapCache.h" #include "modules/Gui.h" -#include "df/construction.h" + #include "df/block_square_event_frozen_liquidst.h" +#include "df/construction.h" +#include "df/world.h" + using MapExtras::MapCache; using std::string; diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index cc9455914..fbc575e36 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -1,12 +1,3 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -14,26 +5,37 @@ #include #include -#include -#include "df/graphic.h" -#include "df/building_workshopst.h" +#include "Console.h" +#include "Core.h" +#include "Export.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "TileTypes.h" +#include "VTableInterpose.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "modules/World.h" + #include "df/building_def_workshopst.h" -#include "df/item_liquid_miscst.h" -#include "df/power_info.h" -#include "df/workshop_type.h" -#include "df/builtin_mats.h" -#include "df/world.h" +#include "df/building_drawbuffer.h" +#include "df/building_workshopst.h" #include "df/buildings_other_id.h" -#include "df/machine.h" +#include "df/builtin_mats.h" +#include "df/flow_info.h" +#include "df/graphic.h" +#include "df/item_liquid_miscst.h" #include "df/job.h" -#include "df/building_drawbuffer.h" +#include "df/machine.h" +#include "df/power_info.h" +#include "df/report.h" +#include "df/tile_designation.h" #include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" #include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" - -#include "MiscUtils.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/workshop_type.h" +#include "df/world.h" /* * This plugin implements a steam engine workshop. It activates diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index ae5362e15..76da1df3e 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -19,27 +19,33 @@ // Options (tiletypes-command): // (anything) - run the given command +#include #include -#include #include #include -#include #include +#include + using std::vector; using std::string; using std::endl; using std::set; -#include "Core.h" #include "Console.h" +#include "Core.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Maps.h" -#include "modules/Gui.h" #include "TileTypes.h" + +#include "modules/Gui.h" #include "modules/MapCache.h" +#include "modules/Maps.h" + #include "df/tile_dig_designation.h" +#include "df/world.h" + #include "Brushes.h" + using namespace MapExtras; using namespace DFHack; using namespace df::enums; diff --git a/plugins/tubefill.cpp b/plugins/tubefill.cpp index 73c615c9b..1b77d466c 100644 --- a/plugins/tubefill.cpp +++ b/plugins/tubefill.cpp @@ -14,6 +14,8 @@ #include "TileTypes.h" #include "df/deep_vein_hollow.h" +#include "df/map_block.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; From de731b02994765e9fad7e236d44839c7d2dca0b7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 14 Aug 2016 12:41:09 -0400 Subject: [PATCH 0188/1012] Move save_dir back into cur_savegame and update submodules --- library/Core.cpp | 8 ++++---- library/RemoteTools.cpp | 2 +- library/modules/World.cpp | 2 +- library/xml | 2 +- plugins/isoworldremote.cpp | 4 ++-- plugins/remotefortressreader.cpp | 2 +- plugins/rendermax/renderer_light.cpp | 4 ++-- scripts | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 975290e52..fb1dd80bb 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2107,7 +2107,7 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve if (!df::global::world) return; - std::string rawFolder = "data/save/" + (df::global::world->save_dir) + "/raw/"; + std::string rawFolder = "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; auto i = table.find(event); if ( i != table.end() ) { @@ -2167,9 +2167,9 @@ void Core::onStateChange(color_ostream &out, state_change_event event) case SC_WORLD_UNLOADED: case SC_MAP_LOADED: case SC_MAP_UNLOADED: - if (world && world->save_dir.size()) + if (world && world->cur_savegame.save_dir.size()) { - std::string save_dir = "data/save/" + world->save_dir; + std::string save_dir = "data/save/" + world->cur_savegame.save_dir; std::string evtlogpath = save_dir + "/events-dfhack.log"; std::ofstream evtlog; evtlog.open(evtlogpath, std::ios_base::app); // append @@ -2187,7 +2187,7 @@ void Core::onStateChange(color_ostream &out, state_change_event event) evtlog << timebuf; evtlog << "DFHack " << Version::git_description() << " on " << ostype << "; "; evtlog << "cwd md5: " << md5w.getHashFromString(getHackPath()).substr(0, 10) << "; "; - evtlog << "save: " << world->save_dir << "; "; + evtlog << "save: " << world->cur_savegame.save_dir << "; "; evtlog << sc_event_name(event) << "; "; if (gametype) evtlog << "game type " << ENUM_KEY_STR(game_type, *gametype) << " (" << *gametype << ")"; diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 909ad9133..77374ea0c 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -386,7 +386,7 @@ static command_result GetWorldInfo(color_ostream &stream, if (df::global::gametype) gt = *df::global::gametype; - out->set_save_dir(world->save_dir); + out->set_save_dir(world->cur_savegame.save_dir); if (world->world_data->name.has_name) describeName(out->mutable_world_name(), &world->world_data->name); diff --git a/library/modules/World.cpp b/library/modules/World.cpp index fcf516f4e..3be400515 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -151,7 +151,7 @@ void World::SetCurrentWeather(uint8_t weather) string World::ReadWorldFolder() { - return world->save_dir; + return world->cur_savegame.save_dir; } bool World::isFortressMode(df::game_type t) diff --git a/library/xml b/library/xml index d5036d833..a8b3349ee 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d5036d8336d266bcb13e27e702a0acada01227a4 +Subproject commit a8b3349ee1e03cc979497eeb95e59f7f59552691 diff --git a/plugins/isoworldremote.cpp b/plugins/isoworldremote.cpp index bd3433b27..017fb45f6 100644 --- a/plugins/isoworldremote.cpp +++ b/plugins/isoworldremote.cpp @@ -198,7 +198,7 @@ static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, return CR_OK; } if (in->has_save_folder()) { //If no save folder is given, it means we don't care. - if (!(in->save_folder() == world->save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. + if (!(in->save_folder() == world->cur_savegame.save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. out->set_available(false); return CR_OK; } @@ -345,7 +345,7 @@ static command_result GetRawNames(color_ostream &stream, const MapRequest *in, R return CR_OK; } if (in->has_save_folder()) { //If no save folder is given, it means we don't care. - if (!(in->save_folder() == world->save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. + if (!(in->save_folder() == world->cur_savegame.save_dir || in->save_folder() == "ANY")) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them. out->set_available(false); return CR_OK; } diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 5edb8e248..8a2428adb 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1571,7 +1571,7 @@ static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, out->set_block_pos_z(pos_z); out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); - out->set_save_name(df::global::world->save_dir); + out->set_save_name(df::global::world->cur_savegame.save_dir); return CR_OK; } diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 48dcb8223..f0f3c92cf 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -1171,9 +1171,9 @@ void lightingEngineViewscreen::defaultSettings() void lightingEngineViewscreen::loadSettings() { std::string rawFolder; - if(df::global::world->save_dir!="") + if(df::global::world->cur_savegame.save_dir!="") { - rawFolder= "data/save/" + (df::global::world->save_dir) + "/raw/"; + rawFolder= "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; } else { diff --git a/scripts b/scripts index 274e7c372..955db4654 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 274e7c372c83611d8fad53345a0cb9ad2a1f10df +Subproject commit 955db46549779a411da563252dc98c4c242de489 From c2997b9c79bdff4c7a0dcc2f9c10da2c0f92a1c3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Aug 2016 15:47:40 -0400 Subject: [PATCH 0189/1012] Fix save_dir --- library/lua/dfhack.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index f535d25ad..900607c08 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -606,7 +606,7 @@ end function dfhack.getSavePath() if dfhack.isWorldLoaded() then - return dfhack.getDFPath() .. '/data/save/' .. df.global.world.save_dir + return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir end end From 0b6597ddb21c8b75fdbef476cd26dc94706afe9a Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Aug 2016 15:59:46 -0400 Subject: [PATCH 0190/1012] Stop field_offset from crashing due to missing vtables Now, a pointer to NULL is cast to the type in question, avoiding the need to call new() or delete() with potentially-misaligned types. Also, virtual_identity::find has been tweaked to prevent it from crashing on NULL vtable pointers. This was suggested by Angavrilov. --- library/DataDefs.cpp | 3 +++ library/lua/memscan.lua | 14 ++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index be381d860..06f1ac6c9 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -255,6 +255,9 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) virtual_identity *virtual_identity::find(void *vtable) { + if (!vtable) + return NULL; + // Actually, a reader/writer lock would be sufficient, // since the table is only written once per class. tthread::lock_guard lock(*known_mutex); diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 534830724..e24d8a8c9 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -307,14 +307,12 @@ function field_ref(handle,...) end function field_offset(type,...) - local handle = df.new(type) - local _,haddr = df.sizeof(handle) - local _,addr = df.sizeof(field_ref(handle,...)) - -- to aid in diagnosis of bad virtual dtors - io.stderr:write('memscan: deleting instance of '..tostring(type) .. '\n'):flush() - df.delete(handle) - io.stderr:write('successfully deleted\n'):flush() - return addr-haddr + local tmp = df.new('intptr_t') -- pointer to nullptr + local _, haddr = df.sizeof(tmp) + local handle = df.reinterpret_cast(type, tmp) + local _, addr = df.sizeof(field_ref(handle,...)) + df.delete(tmp) + return addr - haddr end function MemoryArea:object_by_field(addr,type,...) From 9e4a6d75911334996eaf6b412cde60a13e8f1fd6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Aug 2016 17:28:18 -0400 Subject: [PATCH 0191/1012] Update default base address on 64-bit Linux --- library/include/Memory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/Memory.h b/library/include/Memory.h index 33d40c3cc..0f465c834 100644 --- a/library/include/Memory.h +++ b/library/include/Memory.h @@ -13,7 +13,7 @@ #endif #elif defined(_LINUX) #ifdef DFHACK64 - #define DEFAULT_BASE_ADDR 0x8048000 + #define DEFAULT_BASE_ADDR 0x400000 #else #define DEFAULT_BASE_ADDR 0x8048000 #endif From 98a187ca43133a21ea05bc91526356424ed6c3d4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Aug 2016 17:28:44 -0400 Subject: [PATCH 0192/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index a8b3349ee..87aa5e2ea 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a8b3349ee1e03cc979497eeb95e59f7f59552691 +Subproject commit 87aa5e2ea8c1153b2b12b3189fe5a593f52de792 From 9171149afbb79e50b5721a19024f4bad7b03a96b Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Aug 2016 23:42:17 -0400 Subject: [PATCH 0193/1012] Update submodule refs --- plugins/stonesense | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stonesense b/plugins/stonesense index 9270ceb54..5db5f9151 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 9270ceb5463657a6d039bdc3389fe69c78a0784b +Subproject commit 5db5f9151d4140b53169fcb7bed3383991b33326 diff --git a/scripts b/scripts index 955db4654..8b532b757 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 955db46549779a411da563252dc98c4c242de489 +Subproject commit 8b532b757600d63855f9babee4c1c32af5e2d496 From 70ac99cbfa7cd9e8c65db10d82a1e007d7e39892 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 21 Aug 2016 20:58:40 -0400 Subject: [PATCH 0194/1012] Fix Buildings::setOwner() persistence Needed to set bld->owner_id for changes to persist across save/load Fixes #983, thanks to Quietust --- NEWS.rst | 4 ++++ library/modules/Buildings.cpp | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 97daeee32..0715ccae4 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -41,6 +41,10 @@ New Scripts ----------- - `load-save`: loads a save non-interactively +Fixes +----- +- Buildings::setOwner() changes now persist properly when saved + DFHack 0.43.03-r1 ================= diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index b7933f6ef..9d672cd58 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -239,6 +239,7 @@ bool Buildings::setOwner(df::building *bld, df::unit *unit) if (unit) { + bld->owner_id = unit->id; unit->owned_buildings.push_back(bld); if (auto spouse = df::unit::find(unit->relationship_ids[df::unit_relationship_type::Spouse])) @@ -248,6 +249,10 @@ bool Buildings::setOwner(df::building *bld, df::unit *unit) blist.push_back(bld); } } + else + { + bld->owner_id = -1; + } return true; } From e983e66a1ae2061e26b0cf59793a9f7907237224 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 21 Aug 2016 20:59:24 -0400 Subject: [PATCH 0195/1012] Update xml and scripts --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 87aa5e2ea..bb582c8b5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 87aa5e2ea8c1153b2b12b3189fe5a593f52de792 +Subproject commit bb582c8b51fddfa9d8c83a3e8d27c205b587180c diff --git a/scripts b/scripts index 8b532b757..042fcffd0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8b532b757600d63855f9babee4c1c32af5e2d496 +Subproject commit 042fcffd051c1f1ceab268e54bed1ed197b1f91e From 93ac937cb9b53f7c89587bb6e06f29beba212f33 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 21 Aug 2016 22:24:41 -0400 Subject: [PATCH 0196/1012] Add new job types to autohauler --- plugins/autohauler.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index d7b233195..7a3f8a3d8 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -397,7 +397,15 @@ static const dwarf_state dwarf_states[] = { BUSY /* PushTrackVehicle */, BUSY /* PlaceTrackVehicle */, BUSY /* StoreItemInVehicle */, - BUSY /* GeldAnimal */ + BUSY /* GeldAnimal */, + BUSY /* MakeFigurine */, + BUSY /* MakeAmulet */, + BUSY /* MakeScepter */, + BUSY /* MakeCrown */, + BUSY /* MakeRing */, + BUSY /* MakeEarring */, + BUSY /* MakeBracelet */, + BUSY /* MakeGem */ }; // Mode assigned to labors. Either it's a hauling job, or it's not. From 3d2f7d426d00628b684a975b0ba5fc7e63591241 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Mon, 1 Aug 2016 15:48:21 +1000 Subject: [PATCH 0197/1012] Update the example init file After updating the extra init file in my pack, I realised that many of the changes could be merged into the standard init. This enables a few more UI improvements, adds neglected keybindings, and removes the roses-init and binpatches sections (third-party only and obsolete respectively). --- dfhack.init-example | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 4d2a1b2f3..f8cadb215 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -48,6 +48,10 @@ keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" # export a Dwarf's preferences screen in BBCode to post to a forum keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves +# an in-game init file editor +keybinding add Alt-S@title gui/settings-manager +keybinding add Alt-S@dwarfmode/Default gui/settings-manager + ############################## # Generic adv mode bindings # ############################## @@ -177,6 +181,7 @@ 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 eggs-fertile tweak fps-min tweak hide-priority tweak kitchen-keys @@ -223,7 +228,7 @@ enable \ # You can comment out the extension of a line. # enable mouse controls and sand indicator in embark screen -embark-tools enable sand mouse +embark-tools enable sticky sand mouse ########### # Scripts # @@ -232,14 +237,11 @@ embark-tools enable sand mouse # write extra information to the gamelog modtools/extra-gamelog enable +# extended status screen (bedrooms page) +enable gui/extended-status + # add information to item viewscreens view-item-info enable +# a replacement for the "load game" screen gui/load-screen enable - -#roses-init must be called on world load if you are using his persist-delay, class system, etc -#base/roses-init -all - -####################################################### -# Apply binary patches at runtime # -####################################################### From 1dab45ffeda58fe7a47b387caabbc7d53a5daa27 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 22 Aug 2016 23:18:08 -0400 Subject: [PATCH 0198/1012] Replace lua_pushnumber with lua_pushinteger in LuaApi.cpp --- library/LuaApi.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 997d57a38..18b4ab270 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2347,7 +2347,7 @@ static int internal_getAddress(lua_State *L) const char *name = luaL_checkstring(L, 1); uintptr_t addr = Core::getInstance().vinfo->getAddress(name); if (addr) - lua_pushnumber(L, addr); + lua_pushinteger(L, addr); else lua_pushnil(L); return 1; @@ -2413,9 +2413,9 @@ static int internal_getMemRanges(lua_State *L) for(size_t i = 0; i < ranges.size(); i++) { lua_newtable(L); - lua_pushnumber(L, (uintptr_t)ranges[i].start); + lua_pushinteger(L, (uintptr_t)ranges[i].start); lua_setfield(L, -2, "start_addr"); - lua_pushnumber(L, (uintptr_t)ranges[i].end); + lua_pushinteger(L, (uintptr_t)ranges[i].end); lua_setfield(L, -2, "end_addr"); lua_pushstring(L, ranges[i].name); lua_setfield(L, -2, "name"); From 2dccd1d0ffb7f87a92a326e6a0a34768d7697864 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 22 Aug 2016 23:18:30 -0400 Subject: [PATCH 0199/1012] Add CheckedArray:__tostring() method --- library/lua/memscan.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index e24d8a8c9..78a9e7b8c 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -39,6 +39,9 @@ function CheckedArray:__newindex(idx, val) end self.data[idx] = val end +function CheckedArray:__tostring() + return (''):format(self.type, self.count) +end function CheckedArray:addr2idx(addr, round) local off = addr - self.start if off >= 0 and off < self.size and (round or (off % self.esize) == 0) then From f4b0c2fcfc0570569569c57f0f83e7c69df23ce1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 22 Aug 2016 23:18:58 -0400 Subject: [PATCH 0200/1012] color-dfhack-text: Fix potential overflow issue --- plugins/devel/color-dfhack-text.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/color-dfhack-text.cpp b/plugins/devel/color-dfhack-text.cpp index eb5e9c659..d0db8e710 100644 --- a/plugins/devel/color-dfhack-text.cpp +++ b/plugins/devel/color-dfhack-text.cpp @@ -16,7 +16,7 @@ REQUIRE_GLOBAL(gps); struct { bool flicker; uint8_t color; - short tick; + uint8_t tick; } config; void color_text_tile(const Screen::Pen &pen, int x, int y, bool map); From 7097b1f8177061ae666eec201b38471cb6860a77 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 23 Aug 2016 14:34:33 -0400 Subject: [PATCH 0201/1012] Add TC01, Qartar, milochristiansen to Authors.rst --- docs/Authors.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index accaaa630..8592dac0e 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -19,7 +19,9 @@ Anuradha Dissanayake falconne AtomicChicken AtomicChicken belal jimhester Ben Lubar BenLubar +Ben Rosser TC01 Caldfir caldfir +Carter Bray Qartar Chris Dombroski cdombroski Clayton Hughes David Corbett dscorbett @@ -62,6 +64,7 @@ Michon van Dooren MaienM miffedmap miffedmap Mike Stewart thewonderidiot Mikko Juola Noeda Adeon +Milo Christiansen milochristiansen MithrilTuxedo MithrilTuxedo mizipzor mizipzor moversti moversti From 30601dc3fd6535a58abe33b2bf2643cf981f4079 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 23 Aug 2016 15:43:32 -0400 Subject: [PATCH 0202/1012] Update Compile.rst for GCC 4.8 and x64 changes, and general cleanup Closes #980 --- docs/Compile.rst | 162 ++++++++++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 65 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index 650abbfca..b693f4cc3 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -90,31 +90,15 @@ Dependencies DFHack is meant to be installed into an existing DF folder, so get one ready. We assume that any Linux platform will have ``git`` available (though it may -require installing from your package manager.) +need to be installed with your package manager.) -To build DFHack you need GCC version 4.5 or later, capable of compiling for 32-bit -(i386) targets. GCC 4.5 is easiest to work with due to avoiding libstdc++ issues -(see below), but any version from 4.5 onwards (including 5.x) will work. +To build DFHack you need GCC version 4.8 or later. GCC 4.8 is easiest to work +with due to avoiding libstdc++ issues (see below), but any version from 4.8 +onwards (including 5.x) will work. -On 64-bit distributions, you'll need the multilib development tools and libraries: - -* ``gcc-multilib`` and ``g++-multilib`` -* If you have installed a non-default version of GCC - for example, GCC 4.5 on a - distribution that defaults to 5.x - you may need to add the version number to - the multilib packages. - - * For example, ``gcc-4.5-multilib`` and ``g++-4.5-multilib`` if installing for GCC 4.5 - on a system that uses a later GCC version. - * This is definitely required on Ubuntu/Debian, check if using a different distribution. - -Note that installing a 32-bit GCC on 64-bit systems (e.g. ``gcc:i386`` on Debian) will -typically *not* work, as it depends on several other 32-bit libraries that -conflict with system libraries. Alternatively, you might be able to use ``lxc`` -to -:forums:`create a virtual 32-bit environment <139553.msg5435310#msg5435310>`. - -Before you can build anything, you'll also need ``cmake``. It is advisable to also get -``ccmake`` on distributions that split the cmake package into multiple parts. +Before you can build anything, you'll also need ``cmake``. It is advisable to +also get ``ccmake`` on distributions that split the cmake package into multiple +parts. You also need perl and the XML::LibXML and XML::LibXSLT perl packages (for the code generation parts). You should be able to find them in your distro repositories. @@ -127,16 +111,34 @@ Here are some package install commands for various platforms: * For the required Perl modules: ``perl-xml-libxml`` and ``perl-xml-libxslt`` (or through ``cpan``) -* On 64-bit Ubuntu:: +* On Ubuntu:: - apt-get install gcc cmake git gcc-multilib g++-multilib zlib1g-dev:i386 libxml-libxml-perl libxml-libxslt-perl + apt-get install gcc cmake git zlib1g-dev libxml-libxml-perl libxml-libxslt-perl -* On 32-bit Ubuntu:: +* Debian and derived distros should have similar requirements to Ubuntu. - apt-get install gcc cmake git gcc-multilib g++-multilib zlib1g-dev libxml-libxml-perl libxml-libxslt-perl -* Debian and derived distros should have similar requirements to Ubuntu. +Multilib dependencies +--------------------- +If you want to compile 32-bit DFHack on 64-bit distributions, you'll need the +multilib development tools and libraries: + +* ``gcc-multilib`` and ``g++-multilib`` +* If you have installed a non-default version of GCC - for example, GCC 4.8 on a + distribution that defaults to 5.x - you may need to add the version number to + the multilib packages. + + * For example, ``gcc-4.8-multilib`` and ``g++-4.8-multilib`` if installing for GCC 4.8 + on a system that uses a later GCC version. + * This is definitely required on Ubuntu/Debian, check if using a different distribution. +* ``zlib1g-dev:i386`` (or a similar i386 zlib-dev package) + +Note that installing a 32-bit GCC on 64-bit systems (e.g. ``gcc:i386`` on +Debian) will typically *not* work, as it depends on several other 32-bit +libraries that conflict with system libraries. Alternatively, you might be able +to use ``lxc`` to +:forums:`create a virtual 32-bit environment <139553.msg5435310#msg5435310>`. Build ----- @@ -163,15 +165,16 @@ or the cmake-gui program. Incompatible libstdc++ ~~~~~~~~~~~~~~~~~~~~~~ -When compiling dfhack yourself, it builds against your system libstdc++. -When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which -comes from GCC 4.5 and is incompatible with code compiled with newer GCC versions. -This manifests itself with an error message such as:: +When compiling dfhack yourself, it builds against your system libstdc++. When +Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which comes +from GCC 4.8 and is incompatible with code compiled with newer GCC versions. If +you compile DFHack with a GCC version newer than 4.8, you will see an error +message such as:: ./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version - `GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so) + `GLIBCXX_3.4.18' not found (required by ./hack/libdfhack.so) -To fix this you can compile with GCC 4.5 or remove the libstdc++ shipped with +To fix this you can compile with GCC 4.8 or remove the libstdc++ shipped with DF, which causes DF to use your system libstdc++ instead:: cd /path/to/DF/ @@ -180,9 +183,9 @@ DF, which causes DF to use your system libstdc++ instead:: Note that distributing binaries compiled with newer GCC versions requires end- users to delete libstdc++ themselves and have a libstdc++ on their system from the same GCC version or newer. For this reason, distributing anything compiled -with GCC versions newer than 4.5 is discouraged. In the future we may start +with GCC versions newer than 4.8 is discouraged. In the future we may start bundling a later libstdc++ as part of the DFHack package, so as to enable -compilation-for-distribution with a GCC newer than 4.5. +compilation-for-distribution with a GCC newer than 4.8. Mac OS X ======== @@ -190,38 +193,67 @@ DFHack functions similarly on OS X and Linux, and the majority of the information above regarding the build process (cmake and make) applies here as well. +DFHack can officially be built on OS X with anything from GCC 4.5 to 4.8, so 4.8 +is recommended, as 4.5 has issues on newer systems, but 4.5-4.7 should also +work. Anything newer than 4.8 will require you to perform extra steps to get +DFHack to run (see `osx-new-gcc-notes`), and your build will likely not be +redistributable. + +.. _osx-new-gcc-notes: + +Notes for GCC 4.9+, OS X 10.10+, or XCode 7 users +------------------------------------------------- + +If none of these situations apply to you, skip to `osx-setup`. + If you have issues building on OS X 10.10 (Yosemite) or above, try definining the following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 -Note for El Capitan (OSX 10.11) and XCode 7.x users ---------------------------------------------------- -* You will probably find when following the instructions below that GCC 4.5 will - fail to install on OSX 10.11, or any older OSX that is using XCode 7. -* There are two workarounds: +If you try to build with GCC 4.5, you will probably find that GCC 4.5 will fail +to install on OS X 10.11 and newer, or any older OS X that is using XCode 7 or +newer. There are two workarounds: + +* Install a newer version of GCC instead (e.g. ``brew install gcc48`` or ``brew + install gcc5``) and follow the instructions for linking libstdc++ below. - * Install GCC 5.x instead (``brew install gcc5``), and then after compile - replace ``hack/libstdc++.6.dylib`` with a symlink to GCC 5's i386 - version of this file:: +* Install XCode 6, which is available as a free download from the Apple + Developer Center. - cd /hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig && - ln -s /usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/i386/libstdc++.6.dylib . + * Either install this as your only XCode, or install it additionally + to XCode 7 and then switch between them using ``xcode-select`` + * Ensure XCode 6 is active before attempting to install GCC 4.5 and + whenever you are compiling DFHack with GCC 4.5. - * Install XCode 6, which is available as a free download from the Apple - Developer Center. - * Either install this as your only XCode, or install it additionally - to XCode 7 and then switch between them using ``xcode-select`` - * Ensure XCode 6 is active before attempting to install GCC 4.5 and - whenever you are compiling DFHack with GCC 4.5. +If you build with a GCC version newer than 4.8, DFHack will probably crash +immediately on startup, or soon after. To fix this, you will need to replace +``hack/libstdc++.6.dylib`` with a symlink to the ``libstdc++.6.dylib`` included +in your version of GCC:: + + cd /hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig && + ln -s [PATH_TO_LIBSTDC++] . + +For example, with GCC 5.2.0, ``PATH_TO_LIBSTDC++`` would be:: + + /usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/libstdc++.6.dylib # for 64-bit DFHack + /usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/i386/libstdc++.6.dylib # for 32-bit DFHack + +**Note:** If you build with a version of GCC that requires this, your DFHack +build will *not* be redistributable. (Even if you copy the ``libstdc++.6.dylib`` +from your GCC version and distribute that too, it will fail on older OS X +versions.) For this reason, if you plan on distributing DFHack, it is highly +recommended to use GCC 4.5-4.8. + +.. _osx-setup: Dependencies and system set-up ------------------------------ #. Download and unpack a copy of the latest DF -#. Install Xcode from Mac App Store +#. Install Xcode from the Mac App Store #. Install the XCode Command Line Tools by running the following command:: @@ -229,26 +261,26 @@ Dependencies and system set-up #. Install dependencies + It is recommended to use Homebrew instead of MacPorts, as it is generally + cleaner, quicker, and smarter. For example, installing MacPort's GCC will + install more than twice as many dependencies as Homebrew's will, and all in + both 32-bit and 64-bit variants. Homebrew also doesn't require constant use + of sudo. + Using `Homebrew `_ (recommended):: brew tap homebrew/versions brew install git brew install cmake - brew install gcc45 + brew install gcc48 Using `MacPorts `_:: - sudo port install gcc45 +universal cmake +universal git-core +universal + sudo port install gcc48 +universal cmake +universal git-core +universal Macports will take some time - maybe hours. At some point it may ask you to install a Java environment; let it do so. - It is recommended to use Homebrew instead of MacPorts, as it is generally - cleaner, quicker, and smarter. For example, installing - MacPort's GCC 4.5 will install more than twice as many dependencies - as Homebrew's will, and all in both 32bit and 64bit variants. - Homebrew also doesn't require constant use of sudo. - #. Install Perl dependencies * Using system Perl @@ -286,13 +318,13 @@ Building Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: - export CC=/usr/local/bin/gcc-4.5 - export CXX=/usr/local/bin/g++-4.5 + export CC=/usr/local/bin/gcc-4.8 + export CXX=/usr/local/bin/g++-4.8 Macports:: - export CC=/opt/local/bin/gcc-mp-4.5 - export CXX=/opt/local/bin/g++-mp-4.5 + export CC=/opt/local/bin/gcc-mp-4.8 + export CXX=/opt/local/bin/g++-mp-4.8 Change the version numbers appropriately if you installed a different version of GCC. From 64e3b12f4fcaa789c447ee1150151e81dd3c648b Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 23 Aug 2016 15:54:11 -0400 Subject: [PATCH 0203/1012] Make docs/build.sh a bit more configurable --- docs/build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/build.sh b/docs/build.sh index d9c0428ad..6745db4d5 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -9,12 +9,12 @@ cd $(dirname "$0") cd .. sphinx=sphinx-build -if [ -n "$1" ]; then - sphinx=$1 +if [ -n "$SPHINX" ]; then + sphinx=$SPHINX fi if [ -z "$JOBS" ]; then JOBS=2 fi -"$sphinx" -a -E -q -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" +"$sphinx" -a -E -q -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" From 7487f44fc8cb801cbf6d283833e7eed7f115dfdf Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 23 Aug 2016 21:47:41 -0400 Subject: [PATCH 0204/1012] Implement a helper to create lua environments with shortcuts (e.g. scr, unit) Used in gui/gm-editor and lua Closes #977 --- library/lua/utils.lua | 42 ++++++++++++++++++++++++++++++++++++------ scripts | 2 +- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index a677af3bf..24c24c074 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -611,15 +611,45 @@ function processArgs(args, validArgs) end function fillTable(table1,table2) - for k,v in pairs(table2) do - table1[k] = v - end + for k,v in pairs(table2) do + table1[k] = v + end end function unfillTable(table1,table2) - for k,v in pairs(table2) do - table1[k] = nil - end + for k,v in pairs(table2) do + table1[k] = nil + end +end + +function df_shortcut_var(k) + if k == 'scr' or k == 'screen' then + return dfhack.gui.getCurViewscreen() + elseif k == 'bld' or k == 'building' then + return dfhack.gui.getSelectedBuilding() + elseif k == 'item' then + return dfhack.gui.getSelectedItem() + elseif k == 'job' then + return dfhack.gui.getSelectedJob() + elseif k == 'wsjob' or k == 'workshop_job' then + return dfhack.gui.getSelectedWorkshopJob() + elseif k == 'unit' then + return dfhack.gui.getSelectedUnit() + else + for g in pairs(df.global) do + if g == k then + return df.global[k] + end + end + + return _G[k] + end +end + +function df_shortcut_env() + local env = {} + setmetatable(env, {__index = function(self, k) return df_shortcut_var(k) end}) + return env end return _ENV diff --git a/scripts b/scripts index 042fcffd0..3e3fb7bff 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 042fcffd051c1f1ceab268e54bed1ed197b1f91e +Subproject commit 3e3fb7bff6dce41e7ea85ee3addf44291b53f800 From 6ce470ad57fe198fca15644d17e6749ea07fa592 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 24 Aug 2016 16:20:30 -0400 Subject: [PATCH 0205/1012] Add basic lua expression support to memview Currently just supports basic field accesses (world.x, screen.y.z). No support for world.x - 4, etc. Closes #976 --- library/lua/utils.lua | 21 +++++++++++ plugins/devel/CMakeLists.txt | 2 +- plugins/devel/memutils.cpp | 71 ++++++++++++++++++++++++++++++++++++ plugins/devel/memutils.h | 3 ++ plugins/devel/memview.cpp | 16 +++++++- 5 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 plugins/devel/memutils.cpp create mode 100644 plugins/devel/memutils.h diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 24c24c074..408ceaba8 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -652,4 +652,25 @@ function df_shortcut_env() return env end +df_env = df_shortcut_env() + +function df_expr_to_ref(expr) + expr = expr:gsub('%["(.-)"%]', function(field) return '.' .. field end) + :gsub('%[\'(.-)\'%]', function(field) return '.' .. field end) + local parts = split_string(expr, '%.') + local obj = df_env[parts[1]] + for i = 2, #parts do + if i == #parts and type(obj[parts[i]]) ~= 'userdata' then + obj = obj:_field(parts[i]) + else + obj = obj[parts[i]] + end + end + return obj +end + +function addressof(obj) + return select(2, obj:sizeof()) +end + return _ENV diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index e5001fff7..4fb4b5cf5 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -10,7 +10,7 @@ DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) DFHACK_PLUGIN(kittens kittens.cpp) -DFHACK_PLUGIN(memview memview.cpp) +DFHACK_PLUGIN(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(notes notes.cpp) DFHACK_PLUGIN(onceExample onceExample.cpp) diff --git a/plugins/devel/memutils.cpp b/plugins/devel/memutils.cpp new file mode 100644 index 000000000..5674f2c9c --- /dev/null +++ b/plugins/devel/memutils.cpp @@ -0,0 +1,71 @@ +#include + +#include "Core.h" +#include "LuaTools.h" +#include "LuaWrapper.h" + +using namespace DFHack; +using namespace DFHack::LuaWrapper; +using namespace std; + +namespace memutils { + static lua_State *state; + static color_ostream_proxy *out; + + struct initializer { + Lua::StackUnwinder *unwinder; + initializer() + { + if (!out) + out = new color_ostream_proxy(Core::getInstance().getConsole()); + if (!state) + state = Lua::Open(*out); + unwinder = new Lua::StackUnwinder(state); + } + ~initializer() + { + delete unwinder; + } + }; + + struct cleaner { + ~cleaner() + { + if (state) + { + lua_close(state); + state = NULL; + } + if (out) + { + delete out; + out = NULL; + } + } + }; + + static cleaner g_cleaner; + + void *lua_expr_to_addr(const char *expr) + { + initializer init; + Lua::PushModulePublic(*out, state, "utils", "df_expr_to_ref"); + lua_pushstring(state, expr); + if (!Lua::SafeCall(*out, state, 1, 1)) + { + out->printerr("Failed to evaluate %s\n", expr); + return NULL; + } + + Lua::PushModulePublic(*out, state, "utils", "addressof"); + lua_swap(state); + if (!Lua::SafeCall(*out, state, 1, 1) || !lua_isinteger(state, -1)) + { + out->printerr("Failed to get address: %s\n", expr); + return NULL; + } + + auto addr = uintptr_t(lua_tointeger(state, -1)); + return (void*)addr; + } +} diff --git a/plugins/devel/memutils.h b/plugins/devel/memutils.h new file mode 100644 index 000000000..b94d02a9a --- /dev/null +++ b/plugins/devel/memutils.h @@ -0,0 +1,3 @@ +namespace memutils { + void *lua_expr_to_addr(const char *expr); +} diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index 53ee86755..27dc72861 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -8,6 +8,8 @@ #include #include +#include "memutils.h" + using std::vector; using std::string; using namespace DFHack; @@ -138,7 +140,19 @@ command_result memview (color_ostream &out, vector & parameters) { mymutex->lock(); Core::getInstance().p->getMemRanges(memdata.ranges); - memdata.addr=(void *)convert(parameters[0],true); + if (parameters.empty()) + { + memdata.addr = 0; + } + else if (toLower(parameters[0].substr(0, 2)) == "0x") + { + memdata.addr = (void *)convert(parameters[0],true); + } + else + { + memdata.addr = memutils::lua_expr_to_addr(parameters[0].c_str()); + } + if(memdata.addr==0) { Deinit(); From 85ebbf0e9c1e23cd289cc4828a93932c1db08a34 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 24 Aug 2016 17:18:55 -0400 Subject: [PATCH 0206/1012] Fix df_expr_to_ref for raw userdata --- library/lua/utils.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 408ceaba8..4800042f0 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -660,7 +660,9 @@ function df_expr_to_ref(expr) local parts = split_string(expr, '%.') local obj = df_env[parts[1]] for i = 2, #parts do - if i == #parts and type(obj[parts[i]]) ~= 'userdata' then + local cur = obj[parts[i]] + if i == #parts and ((type(cur) ~= 'userdata') or + type(cur) == 'userdata' and getmetatable(cur) == nil) then obj = obj:_field(parts[i]) else obj = obj[parts[i]] From a3ab757642c37fcc8a4394547e4e184cf8882182 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 25 Aug 2016 21:14:23 +0530 Subject: [PATCH 0207/1012] Add spatters to remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 24 ++++++++ plugins/remotefortressreader.cpp | 78 ++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 996e01a30..36c2442ce 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -176,6 +176,29 @@ message RiverTile optional RiverEdge west = 4; } +enum MatterState +{ + Solid = 0; + Liquid = 1; + Gas = 2; + Powder = 3; + Paste = 4; + Pressed = 5; +} + +message Spatter +{ + optional MatPair material = 1; + optional int32 amount = 2; + optional MatterState state = 3; + optional MatPair item = 4; +} + +message SpatterPile +{ + repeated Spatter spatters = 1; +} + message MapBlock { required int32 map_x = 1; @@ -202,6 +225,7 @@ message MapBlock repeated int32 tree_y = 22; repeated int32 tree_z = 23; repeated TileDigDesignation tile_dig_designation = 24; + repeated SpatterPile spatterPile = 25; } message MatPair { diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6933826c7..dbe8a5f12 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -913,11 +913,42 @@ bool IsBuildingChanged(DFCoord pos) return changed; } +map spatterHashes; + +//check if map spatters have changed +bool IsspatterChanged(DFCoord pos) +{ + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + std::vector materials; + std::vector items; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return false; + + uint16_t hash = 0; + + for each (auto mat in materials) + { + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); + } + for each (auto mat in items) + { + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_item_spatterst)); + } + if (spatterHashes[pos] != hash) + { + spatterHashes[pos] = hash; + return true; + } + return false; +} + static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { hashes.clear(); waterHashes.clear(); buildingHashes.clear(); + spatterHashes.clear(); return CR_OK; } @@ -1310,6 +1341,43 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net } } +void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + std::vector materials; + std::vector items; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return; + + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + auto send_pile = NetBlock->add_spatterpile(); + for each (auto mat in materials) + { + auto send_spat = send_pile->add_spatters(); + send_spat->set_state((MatterState)mat->mat_state); + auto send_mat = send_spat->mutable_material(); + send_mat->set_mat_index(mat->mat_index); + send_mat->set_mat_type(mat->mat_type); + send_spat->set_amount(mat->amount[xx][yy]); + } + for each (auto item in items) + { + auto send_spat = send_pile->add_spatters(); + auto send_mat = send_spat->mutable_material(); + send_mat->set_mat_index(item->matindex); + send_mat->set_mat_type(item->mattype); + send_spat->set_amount(item->amount[xx][yy]); + auto send_item = send_spat->mutable_item(); + send_item->set_mat_type(item->item_type); + send_item->set_mat_index(item->item_subtype); + } + } +} + static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) { int x, y, z; @@ -1367,9 +1435,11 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in { bool tileChanged = IsTiletypeChanged(pos); bool desChanged = IsDesignationChanged(pos); + bool spatterChanged = IsspatterChanged(pos); + bool buildingChanged = IsBuildingChanged(pos); //bool bldChanged = IsBuildingChanged(pos); RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged) + if (tileChanged || desChanged || spatterChanged || buildingChanged) net_block = out->add_map_blocks(); if (tileChanged) { @@ -1378,10 +1448,10 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in } if (desChanged) CopyDesignation(block, net_block, &MC, pos); - if (tileChanged) - { + if (buildingChanged) CopyBuildings(block, net_block, &MC, pos); - } + if (spatterChanged) + Copyspatters(block, net_block, &MC, pos); } } } From 012e5e2a3b6acae039fa8e3c8135639ad7c09ffd Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 25 Aug 2016 12:14:44 -0400 Subject: [PATCH 0208/1012] Add gui/manager-quantity keybinding --- dfhack.init-example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index f8cadb215..169d40046 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -52,6 +52,9 @@ keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves keybinding add Alt-S@title gui/settings-manager keybinding add Alt-S@dwarfmode/Default gui/settings-manager +# change quantity of manager orders +keybinding add Alt-Q@jobmanagement gui/manager-quantity + ############################## # Generic adv mode bindings # ############################## From db7be082ef3bf4dd117dd1595295ad222d67e769 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 25 Aug 2016 23:56:30 +0530 Subject: [PATCH 0209/1012] Only copy spatters if they actually have any amount. --- plugins/remotefortressreader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index dbe8a5f12..ee78a2a77 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1357,6 +1357,8 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB auto send_pile = NetBlock->add_spatterpile(); for each (auto mat in materials) { + if (mat->amount == 0) + continue; auto send_spat = send_pile->add_spatters(); send_spat->set_state((MatterState)mat->mat_state); auto send_mat = send_spat->mutable_material(); @@ -1366,6 +1368,8 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB } for each (auto item in items) { + if (item->amount == 0) + continue; auto send_spat = send_pile->add_spatters(); auto send_mat = send_spat->mutable_material(); send_mat->set_mat_index(item->matindex); From 5c3260c382aba267458ce1001230f65b32639357 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 26 Aug 2016 08:49:54 +0530 Subject: [PATCH 0210/1012] Actually check spatter amounts properly --- plugins/remotefortressreader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index ee78a2a77..a093f2dfc 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1357,7 +1357,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB auto send_pile = NetBlock->add_spatterpile(); for each (auto mat in materials) { - if (mat->amount == 0) + if (mat->amount[xx][yy] == 0) continue; auto send_spat = send_pile->add_spatters(); send_spat->set_state((MatterState)mat->mat_state); @@ -1368,7 +1368,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB } for each (auto item in items) { - if (item->amount == 0) + if (item->amount[xx][yy] == 0) continue; auto send_spat = send_pile->add_spatters(); auto send_mat = send_spat->mutable_material(); From 91cb734a25f65a40c7aa85692db3c7fdcc9ba28c Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 26 Aug 2016 12:23:50 +0530 Subject: [PATCH 0211/1012] Send histfig materials over remotefortressreader.cpp, using unique ids. --- plugins/remotefortressreader.cpp | 141 ++++++++++++++++++------------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index a093f2dfc..b9683aa83 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -15,6 +15,7 @@ #include "df/caste_raw.h" #include "df/body_part_raw.h" #include "df/historical_figure.h" +#include "df/world_history.h" #include "df/job_item.h" #include "df/job_material_category.h" @@ -945,78 +946,79 @@ bool IsspatterChanged(DFCoord pos) static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { - hashes.clear(); - waterHashes.clear(); - buildingHashes.clear(); + hashes.clear(); + waterHashes.clear(); + buildingHashes.clear(); spatterHashes.clear(); - return CR_OK; + return CR_OK; } df::matter_state GetState(df::material * mat, uint16_t temp = 10015) { - df::matter_state state = matter_state::Solid; - if (temp >= mat->heat.melting_point) - state = df::matter_state::Liquid; - if (temp >= mat->heat.boiling_point) - state = matter_state::Gas; - return state; + df::matter_state state = matter_state::Solid; + if (temp >= mat->heat.melting_point) + state = df::matter_state::Liquid; + if (temp >= mat->heat.boiling_point) + state = matter_state::Gas; + return state; } static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } - df::world_raws *raws = &world->raws; - MaterialInfo mat; - for (int i = 0; i < raws->inorganics.size(); i++) - { - mat.decode(0, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(0); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - for (int i = 0; i < 19; i++) - { - int k = -1; - if (i == 7) - k = 1;// for coal. - for (int j = -1; j <= k; j++) - { - mat.decode(i, j); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(i); - mat_def->mutable_mat_pair()->set_mat_index(j); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - } - for (int i = 0; i < raws->creatures.all.size(); i++) - { + df::world_raws *raws = &world->raws; + df::world_history *history = &world->history; + MaterialInfo mat; + for (int i = 0; i < raws->inorganics.size(); i++) + { + mat.decode(0, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(0); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) + { + df::descriptor_color *color = raws->language.colors[raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]]; + mat_def->mutable_state_color()->set_red(color->red * 255); + mat_def->mutable_state_color()->set_green(color->green * 255); + mat_def->mutable_state_color()->set_blue(color->blue * 255); + } + } + for (int i = 0; i < 19; i++) + { + int k = -1; + if (i == 7) + k = 1;// for coal. + for (int j = -1; j <= k; j++) + { + mat.decode(i, j); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(i); + mat_def->mutable_mat_pair()->set_mat_index(j); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) + { + df::descriptor_color *color = raws->language.colors[raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]]; + mat_def->mutable_state_color()->set_red(color->red * 255); + mat_def->mutable_state_color()->set_green(color->green * 255); + mat_def->mutable_state_color()->set_blue(color->blue * 255); + } + } + } + for (int i = 0; i < raws->creatures.all.size(); i++) + { df::creature_raw * creature = raws->creatures.all[i]; for (int j = 0; j < creature->material.size(); j++) { - mat.decode(j + 19, i); + mat.decode(j + MaterialInfo::CREATURE_BASE, i); MaterialDefinition *mat_def = out->add_material_list(); mat_def->mutable_mat_pair()->set_mat_type(j + 19); mat_def->mutable_mat_pair()->set_mat_index(i); @@ -1031,6 +1033,31 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage } } } + for (int i = 0; i < history->figures.size(); i++) + { + df::historical_figure * figure = history->figures[i]; + if (figure->race < 0) + continue; + df::creature_raw * creature = raws->creatures.all[figure->race]; + for (int j = 0; j < creature->material.size(); j++) + { + mat.decode(j + MaterialInfo::FIGURE_BASE, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); + mat_def->mutable_mat_pair()->set_mat_index(i); + stringstream id; + id << "HF" << i << mat.getToken(); + mat_def->set_id(id.str()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + { + df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; + mat_def->mutable_state_color()->set_red(color->red * 255); + mat_def->mutable_state_color()->set_green(color->green * 255); + mat_def->mutable_state_color()->set_blue(color->blue * 255); + } + } + } for (int i = 0; i < raws->plants.all.size(); i++) { df::plant_raw * plant = raws->plants.all[i]; From 7d145de4a7490cbf8328d0ac9e71446374dd1dc0 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 26 Aug 2016 23:48:32 +0530 Subject: [PATCH 0212/1012] Translate histfig mats into creature mats, rather than sending the entire histfig population. --- plugins/remotefortressreader.cpp | 737 +++++++++++++++---------------- 1 file changed, 365 insertions(+), 372 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index b9683aa83..81dc690a1 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -240,9 +240,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("ResetMapHashes", ResetMapHashes); svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); - svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetWorldMapNew", GetWorldMapNew); - svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetWorldMap", GetWorldMap); + svc->addFunction("GetWorldMapNew", GetWorldMapNew); + svc->addFunction("GetRegionMaps", GetRegionMaps); svc->addFunction("GetRegionMapsNew", GetRegionMapsNew); svc->addFunction("GetCreatureRaws", GetCreatureRaws); svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); @@ -855,6 +855,22 @@ static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) return CR_OK; } +void CopyMat(RemoteFortressReader::MatPair * mat, int type, int index) +{ + if (type >= MaterialInfo::FIGURE_BASE && type < MaterialInfo::PLANT_BASE) + { + df::historical_figure * figure = df::historical_figure::find(index); + if (figure) + { + type -= MaterialInfo::GROUP_SIZE; + index = figure->race; + } + } + mat->set_mat_type(type); + mat->set_mat_index(index); + +} + map hashes; //check if the tiletypes have changed @@ -919,102 +935,102 @@ map spatterHashes; //check if map spatters have changed bool IsspatterChanged(DFCoord pos) { - df::map_block * block = Maps::getBlock(pos); - bool changed = false; - std::vector materials; - std::vector items; - if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) - return false; - - uint16_t hash = 0; - - for each (auto mat in materials) - { - hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); - } - for each (auto mat in items) - { - hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_item_spatterst)); - } - if (spatterHashes[pos] != hash) - { - spatterHashes[pos] = hash; - return true; - } - return false; + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + std::vector materials; + std::vector items; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return false; + + uint16_t hash = 0; + + for each (auto mat in materials) + { + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); + } + for each (auto mat in items) + { + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_item_spatterst)); + } + if (spatterHashes[pos] != hash) + { + spatterHashes[pos] = hash; + return true; + } + return false; } static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { - hashes.clear(); - waterHashes.clear(); - buildingHashes.clear(); - spatterHashes.clear(); - return CR_OK; + hashes.clear(); + waterHashes.clear(); + buildingHashes.clear(); + spatterHashes.clear(); + return CR_OK; } df::matter_state GetState(df::material * mat, uint16_t temp = 10015) { - df::matter_state state = matter_state::Solid; - if (temp >= mat->heat.melting_point) - state = df::matter_state::Liquid; - if (temp >= mat->heat.boiling_point) - state = matter_state::Gas; - return state; + df::matter_state state = matter_state::Solid; + if (temp >= mat->heat.melting_point) + state = df::matter_state::Liquid; + if (temp >= mat->heat.boiling_point) + state = matter_state::Gas; + return state; } static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - - - - df::world_raws *raws = &world->raws; - df::world_history *history = &world->history; - MaterialInfo mat; - for (int i = 0; i < raws->inorganics.size(); i++) - { - mat.decode(0, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(0); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - for (int i = 0; i < 19; i++) - { - int k = -1; - if (i == 7) - k = 1;// for coal. - for (int j = -1; j <= k; j++) - { - mat.decode(i, j); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(i); - mat_def->mutable_mat_pair()->set_mat_index(j); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - } - for (int i = 0; i < raws->creatures.all.size(); i++) - { + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + + + + df::world_raws *raws = &world->raws; + df::world_history *history = &world->history; + MaterialInfo mat; + for (int i = 0; i < raws->inorganics.size(); i++) + { + mat.decode(0, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(0); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) + { + df::descriptor_color *color = raws->language.colors[raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]]; + mat_def->mutable_state_color()->set_red(color->red * 255); + mat_def->mutable_state_color()->set_green(color->green * 255); + mat_def->mutable_state_color()->set_blue(color->blue * 255); + } + } + for (int i = 0; i < 19; i++) + { + int k = -1; + if (i == 7) + k = 1;// for coal. + for (int j = -1; j <= k; j++) + { + mat.decode(i, j); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(i); + mat_def->mutable_mat_pair()->set_mat_index(j); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) + { + df::descriptor_color *color = raws->language.colors[raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]]; + mat_def->mutable_state_color()->set_red(color->red * 255); + mat_def->mutable_state_color()->set_green(color->green * 255); + mat_def->mutable_state_color()->set_blue(color->blue * 255); + } + } + } + for (int i = 0; i < raws->creatures.all.size(); i++) + { df::creature_raw * creature = raws->creatures.all[i]; for (int j = 0; j < creature->material.size(); j++) { @@ -1033,31 +1049,31 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage } } } - for (int i = 0; i < history->figures.size(); i++) - { - df::historical_figure * figure = history->figures[i]; - if (figure->race < 0) - continue; - df::creature_raw * creature = raws->creatures.all[figure->race]; - for (int j = 0; j < creature->material.size(); j++) - { - mat.decode(j + MaterialInfo::FIGURE_BASE, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); - mat_def->mutable_mat_pair()->set_mat_index(i); - stringstream id; - id << "HF" << i << mat.getToken(); - mat_def->set_id(id.str()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - } + //for (int i = 0; i < history->figures.size(); i++) + //{ + // df::historical_figure * figure = history->figures[i]; + // if (figure->race < 0) + // continue; + // df::creature_raw * creature = raws->creatures.all[figure->race]; + // for (int j = 0; j < creature->material.size(); j++) + // { + // mat.decode(j + MaterialInfo::FIGURE_BASE, i); + // MaterialDefinition *mat_def = out->add_material_list(); + // mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); + // mat_def->mutable_mat_pair()->set_mat_index(i); + // stringstream id; + // id << "HF" << i << mat.getToken(); + // mat_def->set_id(id.str()); + // mat_def->set_name(mat.toString()); //find the name at cave temperature; + // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + // { + // df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; + // mat_def->mutable_state_color()->set_red(color->red * 255); + // mat_def->mutable_state_color()->set_green(color->green * 255); + // mat_def->mutable_state_color()->set_blue(color->blue * 255); + // } + // } + //} for (int i = 0; i < raws->plants.all.size(); i++) { df::plant_raw * plant = raws->plants.all[i]; @@ -1241,28 +1257,18 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc default: break; } - RemoteFortressReader::MatPair * material = NetBlock->add_materials(); - material->set_mat_type(staticMat.mat_type); - material->set_mat_index(staticMat.mat_index); - RemoteFortressReader::MatPair * layerMaterial = NetBlock->add_layer_materials(); - layerMaterial->set_mat_type(0); - layerMaterial->set_mat_index(block->layerMaterialAt(p)); - RemoteFortressReader::MatPair * veinMaterial = NetBlock->add_vein_materials(); - veinMaterial->set_mat_type(0); - veinMaterial->set_mat_index(block->veinMaterialAt(p)); - RemoteFortressReader::MatPair * baseMaterial = NetBlock->add_base_materials(); - baseMaterial->set_mat_type(baseMat.mat_type); - baseMaterial->set_mat_index(baseMat.mat_index); + CopyMat(NetBlock->add_materials(), staticMat.mat_type, staticMat.mat_index); + CopyMat(NetBlock->add_layer_materials(), 0, block->layerMaterialAt(p)); + CopyMat(NetBlock->add_vein_materials(), 0, block->veinMaterialAt(p)); + CopyMat(NetBlock->add_base_materials(), baseMat.mat_type, baseMat.mat_index); RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); - constructionItem->set_mat_type(-1); - constructionItem->set_mat_index(-1); + CopyMat(constructionItem, -1, -1); if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) { df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); if (con) { - constructionItem->set_mat_type(con->item_type); - constructionItem->set_mat_index(con->item_subtype); + CopyMat(constructionItem, con->item_type, con->item_subtype); } } NetBlock->add_tree_percent(trunk_percent[xx][yy]); @@ -1370,43 +1376,39 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - std::vector materials; - std::vector items; - if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL, NULL, &items)) - return; - - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - auto send_pile = NetBlock->add_spatterpile(); - for each (auto mat in materials) - { - if (mat->amount[xx][yy] == 0) - continue; - auto send_spat = send_pile->add_spatters(); - send_spat->set_state((MatterState)mat->mat_state); - auto send_mat = send_spat->mutable_material(); - send_mat->set_mat_index(mat->mat_index); - send_mat->set_mat_type(mat->mat_type); - send_spat->set_amount(mat->amount[xx][yy]); - } - for each (auto item in items) - { - if (item->amount[xx][yy] == 0) - continue; - auto send_spat = send_pile->add_spatters(); - auto send_mat = send_spat->mutable_material(); - send_mat->set_mat_index(item->matindex); - send_mat->set_mat_type(item->mattype); - send_spat->set_amount(item->amount[xx][yy]); - auto send_item = send_spat->mutable_item(); - send_item->set_mat_type(item->item_type); - send_item->set_mat_index(item->item_subtype); - } - } + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + std::vector materials; + std::vector items; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return; + + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + auto send_pile = NetBlock->add_spatterpile(); + for each (auto mat in materials) + { + if (mat->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + send_spat->set_state((MatterState)mat->mat_state); + CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); + send_spat->set_amount(mat->amount[xx][yy]); + } + for each (auto item in items) + { + if (item->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + CopyMat(send_spat->mutable_material(), item->mattype, item->matindex); + send_spat->set_amount(item->amount[xx][yy]); + auto send_item = send_spat->mutable_item(); + send_item->set_mat_type(item->item_type); + send_item->set_mat_index(item->item_subtype); + } + } } static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) @@ -1466,8 +1468,8 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in { bool tileChanged = IsTiletypeChanged(pos); bool desChanged = IsDesignationChanged(pos); - bool spatterChanged = IsspatterChanged(pos); - bool buildingChanged = IsBuildingChanged(pos); + bool spatterChanged = IsspatterChanged(pos); + bool buildingChanged = IsBuildingChanged(pos); //bool bldChanged = IsBuildingChanged(pos); RemoteFortressReader::MapBlock *net_block; if (tileChanged || desChanged || spatterChanged || buildingChanged) @@ -1481,8 +1483,8 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in CopyDesignation(block, net_block, &MC, pos); if (buildingChanged) CopyBuildings(block, net_block, &MC, pos); - if (spatterChanged) - Copyspatters(block, net_block, &MC, pos); + if (spatterChanged) + Copyspatters(block, net_block, &MC, pos); } } } @@ -2045,126 +2047,126 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) { - df::world_region * region = df::world_region::find(e1->region_id); - df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); - out->set_rainfall(e1->rainfall); - out->set_vegetation(e1->vegetation); - out->set_temperature(e1->temperature); - out->set_evilness(e1->evilness); - out->set_drainage(e1->drainage); - out->set_volcanism(e1->volcanism); - out->set_savagery(e1->savagery); - out->set_salinity(e1->salinity); - if (region->type == world_region_type::Lake) - out->set_water_elevation(region->lake_surface); - else - out->set_water_elevation(99); - - int topLayer = 0; - for (int i = 0; i < geoBiome->layers.size(); i++) - { - auto layer = geoBiome->layers[i]; - if (layer->top_height == 0) - { - topLayer = layer->mat_index; - } - if (layer->type != geo_layer_type::SOIL - && layer->type != geo_layer_type::SOIL_OCEAN - && layer->type != geo_layer_type::SOIL_SAND) - { - auto mat = out->add_stone_materials(); - mat->set_mat_index(layer->mat_index); - mat->set_mat_type(0); - } - } - auto surfaceMat = out->mutable_surface_material(); - surfaceMat->set_mat_index(topLayer); - surfaceMat->set_mat_type(0); - - for (int i = 0; i < region->population.size(); i++) - { - auto pop = region->population[i]; - if (pop->type == world_population_type::Grass) - { - auto plantMat = out->add_plant_materials(); - - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); - } - else if (pop->type == world_population_type::Tree) - { - auto plantMat = out->add_tree_materials(); - - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); - } - } + df::world_region * region = df::world_region::find(e1->region_id); + df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); + out->set_rainfall(e1->rainfall); + out->set_vegetation(e1->vegetation); + out->set_temperature(e1->temperature); + out->set_evilness(e1->evilness); + out->set_drainage(e1->drainage); + out->set_volcanism(e1->volcanism); + out->set_savagery(e1->savagery); + out->set_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->set_water_elevation(region->lake_surface); + else + out->set_water_elevation(99); + + int topLayer = 0; + for (int i = 0; i < geoBiome->layers.size(); i++) + { + auto layer = geoBiome->layers[i]; + if (layer->top_height == 0) + { + topLayer = layer->mat_index; + } + if (layer->type != geo_layer_type::SOIL + && layer->type != geo_layer_type::SOIL_OCEAN + && layer->type != geo_layer_type::SOIL_SAND) + { + auto mat = out->add_stone_materials(); + mat->set_mat_index(layer->mat_index); + mat->set_mat_type(0); + } + } + auto surfaceMat = out->mutable_surface_material(); + surfaceMat->set_mat_index(topLayer); + surfaceMat->set_mat_type(0); + + for (int i = 0; i < region->population.size(); i++) + { + auto pop = region->population[i]; + if (pop->type == world_population_type::Grass) + { + auto plantMat = out->add_plant_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + else if (pop->type == world_population_type::Tree) + { + auto plantMat = out->add_tree_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + } } static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - if (!data->region_map) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); - auto poles = data->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } - for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx++) - { - df::region_map_entry * map_entry = &data->region_map[xx][yy]; - df::world_region * region = data->regions[map_entry->region_id]; - - auto regionTile = out->add_region_tiles(); - regionTile->set_elevation(map_entry->elevation); - SetRegionTile(regionTile, map_entry); - auto clouds = out->add_clouds(); - clouds->set_cirrus(map_entry->clouds.bits.cirrus); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); - } - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - - - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + auto poles = data->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + + auto regionTile = out->add_region_tiles(); + regionTile->set_elevation(map_entry->elevation); + SetRegionTile(regionTile, map_entry); + auto clouds = out->add_clouds(); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + + + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_data * worldData) @@ -2208,7 +2210,7 @@ static void AddRegionTiles(RegionTile * out, df::coord2d pos, df::world_data * w pos.x = worldData->world_width - 1; if (pos.y >= worldData->world_height) pos.y = worldData->world_height - 1; - SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); + SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); } static df::coord2d ShiftCoords(df::coord2d source, int direction) @@ -2371,13 +2373,13 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w south = region; } - RegionTile* outputTiles[17][17]; + RegionTile* outputTiles[17][17]; for (int yy = 0; yy < 17; yy++) for (int xx = 0; xx < 17; xx++) { auto tile = out->add_tiles(); - outputTiles[xx][yy] = tile; + outputTiles[xx][yy] = tile; //This is because the bottom row doesn't line up. if (xx == 16 && yy == 16 && southEast != NULL) { @@ -2427,82 +2429,78 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); } - auto regionMap = worldData->region_map[pos_x][pos_y]; - - for (int i = 0; i < worldData->sites.size(); i++) - { - df::world_site* site = worldData->sites[i]; - if (!site) - continue; - - int region_min_x = pos_x * 16; - int region_min_y = pos_y * 16; - - if ((site->global_min_x > (region_min_x + 16)) || - (site->global_min_y > (region_min_y + 16)) || - (site->global_max_x < (region_min_x)) || - (site->global_max_y < (region_min_y))) - continue; - - if (site->realization == NULL) - continue; - - auto realization = site->realization; - for (int site_x = 0; site_x < 17; site_x++) - for (int site_y = 0; site_y < 17; site_y++) - { - int region_x = site->global_min_x - region_min_x + site_x; - int region_y = site->global_min_y - region_min_y + site_y; - - if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) - continue; - - for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) - { - auto in_building = realization->building_map[site_x][site_y].buildings[j]; - auto out_building = outputTiles[region_x][region_y]->add_buildings(); - - out_building->set_id(in_building->id); - out_building->set_type((SiteRealizationBuildingType)in_building->type); - out_building->set_min_x(in_building->min_x - (site_x * 48)); - out_building->set_min_y(in_building->min_y - (site_y * 48)); - out_building->set_max_x(in_building->max_x - (site_x * 48)); - out_building->set_max_y(in_building->max_y - (site_y * 48)); - - auto mat = out_building->mutable_material(); - mat->set_mat_type(in_building->item.mat_type); - mat->set_mat_index(in_building->item.mat_index); - - STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); - if (tower_info) - { - mat->set_mat_index(tower_info->wall_item.mat_index); - mat->set_mat_type(tower_info->wall_item.mat_type); - - auto out_tower = out_building->mutable_tower_info(); - out_tower->set_roof_z(tower_info->roof_z); - out_tower->set_round(tower_info->shape.bits.round); - out_tower->set_goblin(tower_info->shape.bits.goblin); - } - STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); - if (wall_info) - { - mat->set_mat_index(wall_info->wall_item.mat_index); - mat->set_mat_type(wall_info->wall_item.mat_type); - - auto out_wall = out_building->mutable_wall_info(); - - out_wall->set_start_x(wall_info->start_x - (site_x * 48)); - out_wall->set_start_y(wall_info->start_y - (site_y * 48)); - out_wall->set_start_z(wall_info->start_z); - out_wall->set_end_x(wall_info->end_x - (site_x * 48)); - out_wall->set_end_y(wall_info->end_y - (site_y * 48)); - out_wall->set_end_z(wall_info->end_z); - } - } - - } - } + auto regionMap = worldData->region_map[pos_x][pos_y]; + + for (int i = 0; i < worldData->sites.size(); i++) + { + df::world_site* site = worldData->sites[i]; + if (!site) + continue; + + int region_min_x = pos_x * 16; + int region_min_y = pos_y * 16; + + if ((site->global_min_x > (region_min_x + 16)) || + (site->global_min_y > (region_min_y + 16)) || + (site->global_max_x < (region_min_x)) || + (site->global_max_y < (region_min_y))) + continue; + + if (site->realization == NULL) + continue; + + auto realization = site->realization; + for (int site_x = 0; site_x < 17; site_x++) + for (int site_y = 0; site_y < 17; site_y++) + { + int region_x = site->global_min_x - region_min_x + site_x; + int region_y = site->global_min_y - region_min_y + site_y; + + if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) + continue; + + for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) + { + auto in_building = realization->building_map[site_x][site_y].buildings[j]; + auto out_building = outputTiles[region_x][region_y]->add_buildings(); + + out_building->set_id(in_building->id); + out_building->set_type((SiteRealizationBuildingType)in_building->type); + out_building->set_min_x(in_building->min_x - (site_x * 48)); + out_building->set_min_y(in_building->min_y - (site_y * 48)); + out_building->set_max_x(in_building->max_x - (site_x * 48)); + out_building->set_max_y(in_building->max_y - (site_y * 48)); + + CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); + + STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); + if (tower_info) + { + CopyMat(out_building->mutable_material(), tower_info->wall_item.mat_type, tower_info->wall_item.mat_index); + + auto out_tower = out_building->mutable_tower_info(); + out_tower->set_roof_z(tower_info->roof_z); + out_tower->set_round(tower_info->shape.bits.round); + out_tower->set_goblin(tower_info->shape.bits.goblin); + } + STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); + if (wall_info) + { + CopyMat(out_building->mutable_material(), wall_info->wall_item.mat_type, wall_info->wall_item.mat_index); + + auto out_wall = out_building->mutable_wall_info(); + + out_wall->set_start_x(wall_info->start_x - (site_x * 48)); + out_wall->set_start_y(wall_info->start_y - (site_y * 48)); + out_wall->set_start_z(wall_info->start_z); + out_wall->set_end_x(wall_info->end_x - (site_x * 48)); + out_wall->set_end_y(wall_info->end_y - (site_y * 48)); + out_wall->set_end_z(wall_info->end_z); + } + } + + } + } } static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) @@ -2535,7 +2533,7 @@ static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage df::world_region_details * region = data->region_details[i]; if (!region) continue; - RegionMap * regionMap = out->add_region_maps(); + RegionMap * regionMap = out->add_region_maps(); CopyLocalMap(data, region, regionMap); } return CR_OK; @@ -2720,10 +2718,7 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_tissue->set_name(orig_tissue->tissue_name_singular); send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); - auto send_mat = send_tissue->mutable_material(); - - send_mat->set_mat_index(orig_tissue->mat_index); - send_mat->set_mat_type(orig_tissue->mat_type); + CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); } } @@ -2779,9 +2774,7 @@ static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in growth_remote->set_timing_end(growth_local->timing_2); growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); - auto growthMat = growth_remote->mutable_mat(); - growthMat->set_mat_index(growth_local->mat_index); - growthMat->set_mat_type(growth_local->mat_type); + CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); } } return CR_OK; From bbaf129adfae4f081468c8bb4e48e2c1bdb9009f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 27 Aug 2016 15:24:53 -0400 Subject: [PATCH 0213/1012] Fix ruby codegen for bitfields/enums with long base-type --- plugins/ruby/codegen.pl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 3e41f93c9..eb9828490 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -657,6 +657,9 @@ sub get_compound_align { if ($st eq 'bitfield' or $st eq 'enum') { my $base = $field->getAttribute('base-type') || 'uint32_t'; + if ($base eq 'long') { + return $SIZEOF_LONG; + } print "$st type $base\n" if $base !~ /int(\d+)_t/; return $1/8; } @@ -793,9 +796,15 @@ sub sizeof_compound { if ($st eq 'bitfield' or $st eq 'enum') { my $base = $field->getAttribute('base-type') || 'uint32_t'; - print "$st type $base\n" if $base !~ /int(\d+)_t/; - $sizeof_cache{$typename} = $1/8 if $typename; - return $1/8; + if ($base eq 'long') { + $sizeof_cache{$typename} = $SIZEOF_LONG if $typename; + return $SIZEOF_LONG; + } + else { + print "$st type $base\n" if $base !~ /int(\d+)_t/; + $sizeof_cache{$typename} = $1/8 if $typename; + return $1/8; + } } if ($field->getAttribute('is-union')) From 41e298bdfce4c56c5c1a82d45f93dcd0bce66fd0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 27 Aug 2016 15:27:57 -0400 Subject: [PATCH 0214/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index bb582c8b5..761f2ca80 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit bb582c8b51fddfa9d8c83a3e8d27c205b587180c +Subproject commit 761f2ca80e9eb13aad7f104b8ab218739ea44212 diff --git a/scripts b/scripts index 3e3fb7bff..0a022e862 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3e3fb7bff6dce41e7ea85ee3addf44291b53f800 +Subproject commit 0a022e862e009b37d06cd0f4e1d4097ae3395ecf From af5d4ec5a838630f80d9879f03a8de5a923fbb1e Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 29 Aug 2016 21:43:20 +0530 Subject: [PATCH 0215/1012] removed foreach. --- plugins/remotefortressreader.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 81dc690a1..1ff5cd0e5 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1388,8 +1388,9 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB for (int xx = 0; xx < 16; xx++) { auto send_pile = NetBlock->add_spatterpile(); - for each (auto mat in materials) + for (int i = 0; i < materials.size(); i++) { + auto mat = materials[i]; if (mat->amount[xx][yy] == 0) continue; auto send_spat = send_pile->add_spatters(); @@ -1397,8 +1398,9 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); send_spat->set_amount(mat->amount[xx][yy]); } - for each (auto item in items) + for (int i = 0; i < items.size(); i++) { + auto item = items[i]; if (item->amount[xx][yy] == 0) continue; auto send_spat = send_pile->add_spatters(); From 1862106fd10ff44260013ef48448586d7831cc1d Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 29 Aug 2016 22:29:40 +0530 Subject: [PATCH 0216/1012] Removed the other foreach. --- plugins/remotefortressreader.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 1ff5cd0e5..6bc7c8173 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -944,13 +944,15 @@ bool IsspatterChanged(DFCoord pos) uint16_t hash = 0; - for each (auto mat in materials) + for (int i = 0; i < materials.size(); i++) { + auto mat = materials[i]; hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); } - for each (auto mat in items) + for (int i = 0; i < items.size(); i++) { - hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_item_spatterst)); + auto item = items[i]; + hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); } if (spatterHashes[pos] != hash) { From 8f2cc5bc4df1a68c1ea291b8132e95dfff5fa7f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Sep 2016 16:06:26 -0400 Subject: [PATCH 0217/1012] Fix and reorganize remotefortressreader includes --- plugins/remotefortressreader.cpp | 176 +++++++++++++++---------------- 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6bc7c8173..6ad2f3eda 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1,111 +1,102 @@ #define DF_VERSION 42004 -// some headers required for a plugin. Nothing special, just the basics. -#include "Core.h" -#include -#include -#include +#include +#include +#include -// DF data structure definition headers +#include "Console.h" +#include "Core.h" #include "DataDefs.h" -#include "df/world.h" -#include "df/ui.h" -#include "df/item.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/body_part_raw.h" -#include "df/historical_figure.h" -#include "df/world_history.h" +#include "Export.h" +#include "Hooks.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "RemoteFortressReader.pb.h" +#include "RemoteServer.h" +#include "SDL_events.h" +#include "SDL_keyboard.h" +#include "TileTypes.h" -#include "df/job_item.h" -#include "df/job_material_category.h" -#include "df/dfhack_material_category.h" -#include "df/matter_state.h" -#include "df/material_vec_ref.h" -#include "df/builtin_mats.h" -#include "df/map_block_column.h" -#include "df/plant.h" -#include "df/plant_raw_flags.h" -#if DF_VERSION > 40001 -#include "df/plant_tree_info.h" -#include "df/plant_tree_tile.h" -#include "df/plant_growth.h" -#include "df/plant_growth_print.h" -#endif -#include "df/itemdef.h" -#include "df/building_def_workshopst.h" +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Items.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" +#include "modules/Materials.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "modules/World.h" + +#include "df/army.h" +#include "df/army_flags.h" +#include "df/block_square_event_item_spatterst.h" +#include "df/block_square_event_material_spatterst.h" +#include "df/body_appearance_modifier.h" +#include "df/body_part_layer_raw.h" +#include "df/body_part_raw.h" +#include "df/bp_appearance_modifier.h" +#include "df/building_axle_horizontalst.h" +#include "df/building_bridgest.h" #include "df/building_def_furnacest.h" -#include "df/building_wellst.h" -#include "df/building_water_wheelst.h" +#include "df/building_def_workshopst.h" +#include "df/building_rollersst.h" #include "df/building_screw_pumpst.h" -#include "df/building_axle_horizontalst.h" -#include "df/building_windmillst.h" #include "df/building_siegeenginest.h" -#include "df/building_rollersst.h" -#include "df/building_bridgest.h" - +#include "df/building_water_wheelst.h" +#include "df/building_wellst.h" +#include "df/building_windmillst.h" +#include "df/builtin_mats.h" +#include "df/caste_raw.h" +#include "df/caste_raw.h" +#include "df/color_modifier_raw.h" +#include "df/creature_raw.h" +#include "df/creature_raw.h" #include "df/descriptor_color.h" +#include "df/descriptor_color.h" +#include "df/descriptor_pattern.h" #include "df/descriptor_pattern.h" #include "df/descriptor_shape.h" - -#include "df/physical_attribute_type.h" +#include "df/dfhack_material_category.h" +#include "df/enabler.h" +#include "df/graphic.h" +#include "df/historical_figure.h" +#include "df/item.h" +#include "df/itemdef.h" +#include "df/job_item.h" +#include "df/job_material_category.h" +#include "df/map_block_column.h" +#include "df/material_vec_ref.h" +#include "df/matter_state.h" #include "df/mental_attribute_type.h" -#include "df/color_modifier_raw.h" -#include "df/descriptor_color.h" -#include "df/descriptor_pattern.h" - +#include "df/physical_attribute_type.h" +#include "df/plant.h" +#include "df/plant_raw_flags.h" #include "df/region_map_entry.h" -#include "df/world_region_details.h" -#include "df/world_region.h" -#include "df/army.h" -#include "df/army_flags.h" -#include "df/world_geo_biome.h" -#include "df/world_geo_layer.h" -#include "df/world_population.h" -#include "df/world_site.h" -#include "df/world_site_realization.h" #include "df/site_realization_building.h" #include "df/site_realization_building_info_castle_towerst.h" #include "df/site_realization_building_info_castle_wallst.h" #include "df/site_realization_building_info_trenchesst.h" - -#include "df/unit.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" #include "df/tissue.h" - -#include "df/enabler.h" -#include "df/graphic.h" - +#include "df/ui.h" +#include "df/unit.h" #include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_history.h" +#include "df/world_population.h" +#include "df/world_region.h" +#include "df/world_region_details.h" +#include "df/world_site.h" +#include "df/world_site_realization.h" -#include "df/bp_appearance_modifier.h" -#include "df/body_part_layer_raw.h" -#include "df/body_appearance_modifier.h" - -//DFhack specific headers -#include "modules/Maps.h" -#include "modules/MapCache.h" -#include "modules/Materials.h" -#include "modules/Gui.h" -#include "modules/Translation.h" -#include "modules/Items.h" -#include "modules/Buildings.h" -#include "modules/Units.h" -#include "modules/World.h" -#include "TileTypes.h" -#include "MiscUtils.h" -#include "Hooks.h" -#include "SDL_events.h" -#include "SDL_keyboard.h" - -#include -#include -#include - -#include "RemoteFortressReader.pb.h" - -#include "RemoteServer.h" +#if DF_VERSION > 40001 +#include "df/plant_growth.h" +#include "df/plant_growth_print.h" +#include "df/plant_tree_info.h" +#include "df/plant_tree_tile.h" +#endif using namespace DFHack; using namespace df::enums; @@ -113,7 +104,8 @@ using namespace RemoteFortressReader; using namespace std; DFHACK_PLUGIN("RemoteFortressReader"); -#if DF_VERSION < 40024 + +#ifndef REQUIRE_GLOBAL using namespace df::global; #else REQUIRE_GLOBAL(world); @@ -2502,7 +2494,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w out_wall->set_end_z(wall_info->end_z); } } - + } } } @@ -2603,7 +2595,7 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage if (!orig_part) continue; auto send_part = send_caste->add_body_parts(); - + send_part->set_token(orig_part->token); send_part->set_category(orig_part->category); send_part->set_parent(orig_part->con_part_id); @@ -2712,7 +2704,7 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage send_caste->set_description(orig_caste->description); send_caste->set_adult_size(orig_caste->misc.adult_size); } - + for (int j = 0; j < orig_creature->tissue.size(); j++) { auto orig_tissue = orig_creature->tissue[j]; From 36b9ee5cf4f03d09f167a26e787319b59de7c746 Mon Sep 17 00:00:00 2001 From: David Seguin Date: Wed, 7 Sep 2016 23:28:50 -0400 Subject: [PATCH 0218/1012] Added check to rescan zlib and pthread on linux unset ZLIB_LIBRARY and CMAKE_HAVE_PTHREAD_H to rescan those libs when arch changes from 32 to 64 (and vis-versa). There may be a better way to do this. --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a55940eff..3eb6a1927 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,6 +274,16 @@ endif() #### expose depends #### +# check for change in arch (32<->64) +if(UNIX) + GET_PROPERTY(DFHACK_CACHED_ARCH CACHE STRING PROPERTY DFHACK_BUILD_ARCH) + if("${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_CACHED_ARCH}") + # no change + else() + unset(ZLIB_LIBRARY CACHE) + unset(CMAKE_HAVE_PTHREAD_H CACHE) + endif() +endif() # find and make available libz if(NOT UNIX) # Windows From 860e5840060491a73bbf1dcea4201c34b723cef1 Mon Sep 17 00:00:00 2001 From: David Seguin Date: Thu, 8 Sep 2016 11:28:15 -0400 Subject: [PATCH 0219/1012] Added @lethosor 's change --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3eb6a1927..abb188577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,9 +277,7 @@ endif() # check for change in arch (32<->64) if(UNIX) GET_PROPERTY(DFHACK_CACHED_ARCH CACHE STRING PROPERTY DFHACK_BUILD_ARCH) - if("${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_CACHED_ARCH}") - # no change - else() + if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_CACHED_ARCH}") unset(ZLIB_LIBRARY CACHE) unset(CMAKE_HAVE_PTHREAD_H CACHE) endif() From 01a97f6487ea72aaeab94234d28ca627a7ba5f39 Mon Sep 17 00:00:00 2001 From: David Seguin Date: Thu, 8 Sep 2016 17:12:42 -0400 Subject: [PATCH 0220/1012] Made pthread required for building protobuf --- depends/protobuf/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 5b54c8335..9846fd4e5 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -68,7 +68,7 @@ IF (HAVE_HASH_MAP EQUAL 0) ENDIF() IF(UNIX) - FIND_PACKAGE(Threads) + FIND_PACKAGE(Threads REQUIRED) ENDIF() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h") @@ -247,4 +247,4 @@ IF(NOT CMAKE_CROSSCOMPILING) TARGET_LINK_LIBRARIES(protoc-bin protoc) EXPORT(TARGETS protoc-bin FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake ) -ENDIF() \ No newline at end of file +ENDIF() From 1029e0f02a53ab1254298189fd5181c96c9a30cc Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 15 Sep 2016 01:37:07 -0400 Subject: [PATCH 0221/1012] Fix a few whitespace issues From #992 --- CMakeLists.txt | 2 +- depends/protobuf/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index abb188577..1b77161d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,7 +277,7 @@ endif() # check for change in arch (32<->64) if(UNIX) GET_PROPERTY(DFHACK_CACHED_ARCH CACHE STRING PROPERTY DFHACK_BUILD_ARCH) - if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_CACHED_ARCH}") + if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_CACHED_ARCH}") unset(ZLIB_LIBRARY CACHE) unset(CMAKE_HAVE_PTHREAD_H CACHE) endif() diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 9846fd4e5..59fe3f0d8 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -68,7 +68,7 @@ IF (HAVE_HASH_MAP EQUAL 0) ENDIF() IF(UNIX) - FIND_PACKAGE(Threads REQUIRED) + FIND_PACKAGE(Threads REQUIRED) ENDIF() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h") From 38cd830abfbed1469d22c8a9cc70bf730871823c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 15 Sep 2016 01:52:54 -0400 Subject: [PATCH 0222/1012] Revert "Added check to rescan zlib and pthread on linux" This reverts commit 36b9ee5cf4f03d09f167a26e787319b59de7c746. Ref #992, #982 --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b77161d2..a55940eff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,14 +274,6 @@ endif() #### expose depends #### -# check for change in arch (32<->64) -if(UNIX) - GET_PROPERTY(DFHACK_CACHED_ARCH CACHE STRING PROPERTY DFHACK_BUILD_ARCH) - if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_CACHED_ARCH}") - unset(ZLIB_LIBRARY CACHE) - unset(CMAKE_HAVE_PTHREAD_H CACHE) - endif() -endif() # find and make available libz if(NOT UNIX) # Windows From 869e705549ffbe062a885a96586ab774fc86bfdd Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 15 Sep 2016 10:24:57 -0400 Subject: [PATCH 0223/1012] Re-run zlib and pthread scans on Linux/OS X when the build architecture changes Fixes #982, ref #992 --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a55940eff..d25404e27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,6 +274,13 @@ endif() #### expose depends #### +if(UNIX) + # Rescan for pthread and zlib if the build arch changed + if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_BUILD_ARCH_PREV}") + unset(ZLIB_LIBRARY CACHE) + unset(CMAKE_HAVE_PTHREAD_H CACHE) + endif() +endif() # find and make available libz if(NOT UNIX) # Windows @@ -407,3 +414,6 @@ set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_ INCLUDE(CPack) #INCLUDE(FindSphinx.cmake) + +# Store old build arch +SET(DFHACK_BUILD_ARCH_PREV "${DFHACK_BUILD_ARCH}" CACHE STRING "Previous build architecture" FORCE) From 27d222966504223578684db02eb466abddc85679 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 25 Sep 2016 18:29:57 -0500 Subject: [PATCH 0224/1012] Fix follow_jmp for 64-bit code - skip REX prefix if found --- library/VTableInterpose.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index c547800c2..520d91061 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -96,12 +96,13 @@ static uint32_t *follow_jmp(void *ptr) { switch (*p) { - case 0xE9: // jmp near rel32 #ifdef DFHACK64 - p += 5 + *(int32_t*)(p+1) + 1; -#else - p += 5 + *(int32_t*)(p+1); + case 0x48: // REX prefix + p++; + break; #endif + case 0xE9: // jmp near rel32 + p += 5 + *(int32_t*)(p+1); break; case 0xEB: // jmp short rel8 p += 2 + *(int8_t*)(p+1); From 888a09bed5c63fb9b5023ac7e3122727b4a4f035 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 26 Sep 2016 17:23:55 -0400 Subject: [PATCH 0225/1012] Switch to downloading OS X libruby --- plugins/ruby/CMakeLists.txt | 23 +++++++++++++++++------ plugins/ruby/libruby187.osx.tar.gz | Bin 340284 -> 0 bytes plugins/ruby/linux32/.gitignore | 1 + plugins/ruby/linux64/.gitignore | 1 + plugins/ruby/osx32/.gitignore | 1 + plugins/ruby/osx64/.gitignore | 1 + plugins/ruby/win32/.gitignore | 1 + plugins/ruby/win64/.gitignore | 1 + 8 files changed, 23 insertions(+), 6 deletions(-) delete mode 100644 plugins/ruby/libruby187.osx.tar.gz create mode 100644 plugins/ruby/linux32/.gitignore create mode 100644 plugins/ruby/linux64/.gitignore create mode 100644 plugins/ruby/osx32/.gitignore create mode 100644 plugins/ruby/osx64/.gitignore create mode 100644 plugins/ruby/win32/.gitignore create mode 100644 plugins/ruby/win64/.gitignore diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index dfb0dc3c4..ac5b4a959 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,17 +1,28 @@ IF (APPLE) - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.osx.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - SET(RUBYLIB ${CMAKE_CURRENT_BINARY_DIR}/libruby.dylib) + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib) + SET(RUBYLIB_INSTALL_NAME "libruby.dylib") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + MESSAGE("No ruby lib for 64-bit OS X yet") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libruby187.dylib.gz" + "gz" + ${RUBYLIB}.gz + "e9bc4263557e652121b055a46abb4f97" + ${RUBYLIB} + "3ee5356759f764a440be5b5b44649826") + ENDIF() ELSEIF(UNIX) + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) + SET(RUBYLIB_INSTALL_NAME "libruby.so") EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.linux.tar.gz WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) FILE(RENAME ${CMAKE_CURRENT_BINARY_DIR}/libruby1.8.so.1.8.7 ${CMAKE_CURRENT_BINARY_DIR}/libruby.so) - SET(RUBYLIB ${CMAKE_CURRENT_BINARY_DIR}/libruby.so) ELSE() + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) + SET(RUBYLIB_INSTALL_NAME "libruby.dll") EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) FILE(RENAME ${CMAKE_CURRENT_BINARY_DIR}/msvcrt-ruby18.dll ${CMAKE_CURRENT_BINARY_DIR}/libruby.dll) - SET(RUBYLIB ${CMAKE_CURRENT_BINARY_DIR}/libruby.dll) ENDIF() IF (APPLE OR UNIX) @@ -35,7 +46,7 @@ INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread") DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) ADD_DEPENDENCIES(ruby ruby-autogen-rb) -INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) +INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) INSTALL(DIRECTORY . DESTINATION hack/ruby diff --git a/plugins/ruby/libruby187.osx.tar.gz b/plugins/ruby/libruby187.osx.tar.gz deleted file mode 100644 index 42153395778ff0256aaa65bd297e1724c1236146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 340284 zcmV(|K+(S+iwFRHL+Mli1MIyEe3Qk}F#aT@kd{CK6hTF(T9rE$3WA_kB#H=vK)rDj z5D*3Bk_c!o;6v3Iqa5*iJbF;k;{^o;6cMqll@>${ShZ?}fEAvYs1bXlBGPYX_WGnx z3+H|R@B9CK-_L4k@;tM? zK>wcKHw*t}^~)ZRef|Ym8Rz%SzMya4?DP9)UyzY?e)a`f=W7{R|3mnge*6o4Qx_0a zrp}pqJNbCaoJQcL-aL2q?M>8^g+CeM@Bbw~yN>>NyaoB&jQlnyzpcq{ZHlHjG>>K@ zpTp#zDO1LKuOFXOaEwd*h4Lw^Jp4?V;+wJ9Cqa|TcREGWjz5uxf9-0W4sEF{vvA4W zX?M(-ve4(BW>Lp!Z=`AC+tR-_r3z;Xt)n@ugA|@}%ZxemW-PF%V_tEZ_GD-J*Mvuk zipTVYz6G;ypVdfTocq$WFCU?Q)jHJAq>eiXZ#7Df$o!UC)wlF-ie~*YWs3jyMYC_e zdCKhDXWn5^$EMLPZ8-be*bmU@dOMUIcIo&_l@~CU88-G;(>OmkMccQ};lnY{x4^Qz z$4>vM{YHLj#_O0fwW&HhV_n)n7yYZ&0k}LO>PV)exsIDAyR@Y#^l#&Iym_i`s`}kr zM`XH7%Sxkv8?R&9!iD`CuOs7jmsb2U{jAoZ^68W*^ZZav-^TEm)Jk(OLa6jG*Fh@i z*Ek-=lvZRG)1PKd)zMe|Zmy$_)X}k7(|Ew=fmn9rl~-To9d;$>%XzJ6sd+AKah(mv zQY{B>Zz8`P+4B}o@-O-AbL|~-{Bvh4>@#ZW^x3!j?pS!s@L_#&7tUC)u+Ol|`;c{N zVINqt@Sk4i_a4~$0(c0&=B4Z`Oi$iww8UCXYe}AsVPzh0(%*{+P0GoOU0UNm&SzU`tC}}_emZMs&aJ(F*FzUa z?p?F*j=asI@-oRY9Iwz& zSsg3tTv`qh?%mk;aPwAJXAkB3kS6-_*Uw)+fBpRR^ViQ`KY#uF_4C)yUq65S{PpwK z&tE@({rtcAlN^G3i6nq-d?hrZj>?>}qVN#U`*2u{K(4 zIupKRWA!aIWx$v7$d~5i3m7qCr^#koYyz`N%n2=MdgKz?#wcen*A;6Ss0a;rlxq$G z5eSFI)E5=|;|Opqg$VjRu@(pv$l?Hr$p|q{kEa`5XEEwDBQMUyPk#{1J7HtzK)vK! z>yW;!JWUHYGL1P;rAhc~0pFd$dl0@;NZ&P(5+<~A7nB^I>44I=s--)M(ydu(8;5OtRH_SP1`P*2O94p8gFkJj24@~ zau(~|1fWL+OxXlT$q^qgL(_~7PqUuZmTReqb1msnV2mTA9~;1FZ!b8TJg1GEil3kn zH^6F3ciIA_&@T_vk>jKtC!ajxlZ$;4V{|QPKf`$YW+V}GdNv^)!9ve>X-mj3-#>s4 z^Qw>JFb{Zw3^Rc!=oR$3Lg|deu0f@XOhvXSMVhf9euwUL*@Ax3PZt~F;eRJ!mwT|k zE;4+GU&aJwMw~+M3+WGD)(R>nG+}iPA-F2s*0J1?30*bztz-iyIdJDnAZ{Y$bp^cf za&IkQ9LorL6ND|u;Flb*hg<}Mcxa3xkQ)zb0h{ivwGm7Pd!2bz00%=s=6fQk;)Pgo zEl7}pzR*EmPtJaV_i7d1gDx`kA6=Cu2AR}cyIu76-06x0=p=ZltvXNKMdi@f{ZE>? zCZzvkC?OM26Imyo{hzXqGl;Yc~L0QpD;if~ZCPFdYf=`1N&GZjeD;zLq+Sf(qax2B(*oBu+>skof^v&bg^$@v?Q$q0H4 zz0TnyE0sYTL3BFxLCm84Z16YE7DDoAcU`O{tDpt)Q9{p8;Jm-AAD_hUmdZ(d@k*G) z39aBfG7-q^9qdgMnt+!nz-=hjWLybYemAn}(Lylp9y%isZuH}9`jg>J5T?Rb2!c1^ zLQKf;DI>!k!U}xq4r21#{x|y0{urljt%y-+=vPu>x6+WlC5sLPbSbp*$znMi_xvr5 zGw)4xY{Ld1_M>c+rRX47llJp_DIEZH$OhksdxREXtKfCadWbI8Sgw$YAr~E4+8Fst zB)JV3EMS*wh?LgBIvh2agXCN2D9`~`o#0px$~gL&$z%M?7bGs`YK54qg;5h?j*A}B z(@7d((*z7Ul>ub(kN(r1~D#zi;hhdhcYd#k((u2$ivh?ZuhS2p>jLH7vK#e$&070_mjr9aaB}(?&Z99+@ zaU3Tn$cwT}vBZk80tQpM^8AB1G+cuMUD<_esVk@{6LJy-BRX#&*GUNa6?wj$&8&1* z)xT@G@D-gC7+bH)DjU;~vTU+Wp1?LMT(Y6PM)lb>R zWx=kS|0dX#u6@Iudejye&UrE%1o1D}3pWt0SSJ?*)3n%d_T@a%8?tt|=nCa&$OIq# z$ykzbuO~PHrUyZ=+wQ;)MJu3!2jRc?wIO-d3l%qLQkEN|)NQl^z0O^DrJ^kBhAot> z=*wh>qA1QjGTWHEz*O9Jd2+@Rt2y15aevW6Y zEll`|44xA=#8MXAaetb|u&Pa>tcRc!%y*U?vfF-7#_Q(CL@~LxdaT#hzSsw{fM-me z!p#75;hu|4;Ca$!1i<4=Dvs6{==jB+k=wI zlnvDT2y4aWF~C*7!VF0;<^ZXWFiU=c$DlZp&W2o%Ve#BJn+^u77BKb()y;6VVk@B` zRzd?bDZUaKd?m~_rd*5qs@+|*mg*5fjoDAg+SR@*U%OnJL#U{d?|QL=!@2lZ0_x$P0XhqzNoNy9#YKBhi9IS!~w z;C8|pB6v$9#?K*Cn9n1sKWFSKEIT)qtTnNoD+Gcq5JAv6u%2cQrl}Kj>2lcPWGidG zOB8d_VyHDw<~iYu3k-FHWn2dC3+$XVBe&2uewW|Knw)!)Q?qBZqMu4yoisE0mI zA=B>?wm8$88(nnA@S2fN-x}6W+sFVK&)zGxO1^ZyNol{4ZbW+Bo7U(a z>xBo{T&+bJ;z1dLUl+F&ieBNRSaB;>rW=_Mv5RrncXmjhdJbh3(9KZ8zi6Z6m{mon zPR2ZpylB>-no73V;bt1!$0BoU?xK5PyBRLR9cUlp8iILA#Hm-b=7R!4TQdvo9b|B$ zTj3qsi1kZu44{03cd2ynFxI+JC*`0$x&`BXEv3UakMc7i`@0{oaPrB)1t2urt&J}grF71VMqel7x z2$nlC!5D8u3b`~%p#7IrpqS!5AdgjC(v72|#aftd)Yt2Ess0r5JvNEV;WsHh{q8pj zs8euQecc!UQZ(~TOEb@Pgpp(I;L9+H;k|{5?dLk!oewd5qhV5!<(W*<&S&yT>YU|X!Ms-Zmu)ZNy!&UY)o` zAe38;ZIaRB)N9X;c*eo8GgXcvnw*U3PV3{)zPQUCX#qQ}0hzR#BqkIH=%u zp{*XQ3%u45cTu^^S5dRo_9ZmSc@(o#SEGAAF2y~K*YcZQicykf<`J>ok1sl%_-0OB zj&~REb;ZdvgRi9hs;jX%kF_`WWZK(6=pF0L+v~vA*O_3v zE1{_?XjNbf>}l1x+I+c2;JWT?xUQEg<~jyIO1`y3`aHR&o$Rut_UL2-+TM7{@sSiooSWNf>^bbV!}qv}M&sO$`XpU6`^mm?_JScAU0sab`5X zmdv(yElnL`nBxd%o=?i#)Lj7+$HSab#I!!}fl<3juc z^-kqma-uxIA7ch6eZSe7G-pzu`&qc(V%}tz@Fu$igs%l@&8`V+bF4q-g@nQjcjEjs z^nAnN!;O57lME&VLBQUFY=t?aBnpe6ojQjvBSbfLE$*R{pBaB(2D{gmQfC0J4On#z zYd(5Nu6q&#G%cV(doL(wEqphFhBlf#eE3cjj_pGFMPJa#LhCeQ7XyU18kfsR$XiW? zoRcb=pMjH_08hM=0=Hq+tyoIG+g6~L>NM7$wn9&?K~Kn1+$Skd<}GE0ct&kQP}^uo zZ4=Gh52t|>IzO)Y2s76RX08#;OcbaZ!OS)0t=Pq0*m%}b71Z%BE2aMTJCQwMYu_rQ z$4_DI9XbY5W=4EznprPCFic^?Q;^7x0jW zT8EFDLf%+Nioshs%l~#6k`KMoj(XLPG<^v(NvTGe;R}(q)9Dt+K|u*E>%*}T4IzQ{ zxT52P0sCACRd3WOR+d9^Oj`))KOLvsfAgIt`;6$%tmC3TM0%rSIE{_%yNmfw2wPsg zK#H+UW8F|iDxD&vl1oY@whG|bUHT#&3D6KQrh>{Kzm*K?dYAA{pJm==dU}}B;Zxf@PpKNR6kp=*p`F(?VOIQX(|wSRfY+sObA7bYHN?tWMy{Gfx@<*`qV(Q36ev zCmAo@xFCmxfH7ZNj$=@%zb$*>?*%4H*qL*aw-Gy(a5Td?k8L9Y^G;$LVZVdmGoD++zo@V0nDn*6WcB={ z=o&2c#++n?K9uC({_k;f*z$nYM=wOpSK0v6(LRl{WNZbc5Qobnwi3m(>gW(84n z#h$k*x+1X7CvzF=Tz&gVuueIld~A+n<@vXoSb3Ng15Lk!8^6yq^Uy5^a1wGJzv$Lv zmU@%(IJnH&KmjmW^2Rr43FMSZA>FG~MA>8K&{YaNa&gMY2?dGoXVXD7xBQ$?D*R^{ zug>A7bJX}&5<viXLlE!joRrmTaSA=TM_{UMYUu|=FT{-MGR zED-iqC#p)k0BF%_TjK&bXV?#JbxLg`nCr zwFV#;UT!XT`ptX+P5?R|vB&n=X=c~5`{6l6oud?6v( zhFCnnga95RzlV+eThqw=>0lEYPV^X?Ig(cneNC0#55r(Ih@jnmG{H$2jN#=&(@=Uc z<0i}|M0U|ME|251Mx^W$)1V7+(uFt~hrG{X18H^S3wS{yMD&F8bq?mE8CA<&A)%F9 z9+P{z&yYW8dl!iYkb>EO@-}m$8Su|=Dpz{j

      {LRJPyTp? zGdX&8`3{`!;`k5MH8Kj$sTk>Pulki&e1J zb%wuuiK`+@TwC%7fcFxT2|F0keZr^|u!h2*7qEvd1WJ`)y>$y1Pyj&gSBm;8=@MFr zauSBbyK@DTIvXlrPfe=ai7JPlZ(!v296L9J+_h3pnrEl-m@y(e@@MeV7e*mHePLLf zzSw=UJbhtM=QjGYjcy1yoW)j|T=x4Wv*1<(UDOH9HmTJvYHLO0)x=63WeAQ8*!>!Y2~5nqi=`ug#fj7fJs zESdCo!aZODD}3rmesw?uYUG;=yr&eXn9RMIWUSq5OVKTa7#x@mj)Q6r*s~nyRYfK$ zJRIZXaW9uO9{gN|F%&hHF%-48@OEvet`GKB=WRr>05Z8hegc`?L&)hfg`DnyNK^&W zm^%~<+^IW&QLFT_o)g4ScjfBXXsd1WtrK{pp#zTdCMX+Zuw#pw66st#jfybxibuS0 zJ@zG3g0?5Mm)lTrPB10@lTsQ+)tj~mX0@|vlNfHGv^t{kR#(YZX>`9w^D$?{jU0zd zg@T1VHgOMK*H!z#@IAP$)7apts06cGlG@vVZfdbXMl0dIZaC1ecr!0{A#3V~66#SJ z64LEKDymvf<69Sm_+^sX*cCx;bjO!tr>3FOCxywNQr^2xbj6Zp#ih)w_|-)9$lyuR zWccoeG|ApAX;OOAe@K&$yC&0Q1Z(PxVoDQM*MHi*qZ7gl$S{)MxZ1_4(!3Xga`MIMn(fjo-p;v2afx52cnjNQd*J4vM$_w>) zyj!tTPMz4a(HuH!7b_ylGS9>YMsqx*eThqM1&Jb_o@vaq}}+T8OY9#Imj;c#>j z$BmJ>CZv<)E}pc*7=OB$GnU#gg;_3>?4<)gG1*I7)D?yF0& zo$(Yqac~4nJV*})a-7GSaoDgE-olDVEout-)QE&VST{M1#pf6b!V||QBm6=`gfA$y zQr5_e=HvO<4+Wile>R0|i<3gOnI*Do$2CBv=&*Bd`3mG^n=QuD~xPzuV6m-`>Kne2uvn0cypusADiWwI36c}FAi*`!n@gx)bPdD1fd0@%_;3hL?6;{keSzm z#`NKixfUl)EpD2F8k=sU#NS6VyCRu(4w{sw@E5-2slG>Uc|N}eVphmL?KC_x*n?`~ z;y@@_ee*hq1s|24W~A!qzv!5jW8!Pr;lWO++PqOj)N^m3C&t*A?fEYt&m_m0AuxFU8RtLSW~$RB46I%^{t@5>z+ z|C1#%&tw|^I77sAGJ~r)(?6}yiq)?iO+yLEgzeJU+5V@;i*sObR>c#$B*fhks=@if zKN)w%<$<^M!g`A!Lu2MpX~fy+)F{qx6j*wrV-`Ovr|oTpjheD5)aGdq4gNF2ZJ z!;W9mNUt-|gT!o`31QnzV2BZm#Y(8kA6JlhJn=ush{a|Z1|j9QlOl)R9xwT)`>5K( z?i)%DyL%RkErdRotWjq7kfMMI_hv%4H~ISndmj(jvDYgbu4-eiQsw(NvwRmAV`(;i zy^H}T#8}48Ne6%W={TkioDl4Y;@5drgIO_>$l5qhHbDnyJE)L9KUM;w#nx4b1MzbIpdI(P>>RcQGrQP>;ppJT?#JC@9iT1*{=0BCMy=M-m z+Cd+}!_heEF>#yU%Qf$w4Gvc-NAqG=(6YadOmYVnZjU}kRU4M2rxnUGv=!#ipj-at zS5#FigXDjBQ^QDd(*|^qaWqVkJT;sRlmtWjsD(A1F4G=)(AhA#%y4x&^-o}{Drgdf z`FKT`pWXo#ddjITTQM2t0z7<^%XZKh3L~$xB$Brh%E~jHa{&lbQ(#R1Q z12DXPZt$gl>|Ju{AAKb*woe_&hXgXHam{to{<#X?QUx@a+H)GS9k0Q)H*Uc|fhkT5Z-facyxJ-M z_EzPk;uAl76+-6%( z7;G_MR#$3t)=TAsXndZ=d;a$;xL*rkQb){~p$5f`Tsy@Ve= zG|}U?p^P4jE;|W&BrZK6J)Rl%AJOBmqKu^*W8H8<9xCdlp*hc@@KaX(H62@(pwm_k1hgUrSF7g;vJDkz`$-V+C9J zI)_axkg&azU_t7IRzC5qfL$YD?Mbj2!rr`t!(RWkg#F?YE7(^);jqUP9A34AZL^ER zIuw>Eu!Nl{v_K4X<$6RHfc?FR`M-tnWFj?p z3N|L{?V{AX%@Vf1sP`NN_U{*2;d|Br4%=OUz1dX2Pv>W46%Z3zmdaUq`+>qgblyVVJ}l)AF+hJt4!dljPcANR(#g)BSD8F681t% z*cSxsRSFKREn!=4Dg5QLtnXz}-zEii=3pyY zL4cmmkJEA_UvgnjsZ4!cP0|Akht#alV-Dy81HPXPO}0{eg^tW(I{0tJW3 z7h2J*R>;S73eWVlgxxP-Hz}~qEn(O1=CI2Z9KIf81$(K04Jfd0Si*iOU>{as@3Dl< z-NRv@P++e;LA|RK*zPB&ceMh0e4rKVib@V!tBl*8fmY*ojeuRF)cevvD>*jka}N8q z0(+MwY|R%O_I(BRYD;|ke$8QjQ1Cs=685XFIP7+%-rp{;g8ie4!-f^u9T!;6H-STy z0{g6Gy_ZyT*lGoK!39=ykh;r31@>~wdUN-3*q;>G(=73QO~BSFus;p3!r^tXzE>)9 zsMH$P;IOV^687-{Ryasq;HZMbt(LGIzT~i)ve~%U5{Fhb9JYl5+tv~`U%<9fU=Q}U zg1v3O(1{e-&6cpU#rl4eqDMYt3Hxc3!**2aon~F{4;=P>MHd)g342~}wM*lr5!y_T?T1nl_=?2Xx0{4n`2hrLjN?QIGB z>MtC2r~+%Z#G%DcLT^xDzvyRG?*sumR)KxBpH;p6e---*1-8Hv_CoFA342Ju z&Q@U0wZyk?Er*?_z#i>u1v~3E4tu8pyQ{AipM6osVee63pSP^{#y>dhLkjGjmi7Md zCx?ApfgRD;iVoKu=de#Ju$?XItr4&Wc@fPB*7 z9H)D09Uwf<#f&9`fQj;C0+<|{VMRvdyG|mP)6l^*S&oRMhXZf4v^c4QXQaT19ZiFb zCYeco9D=pTf?Q+U5o~*8Pr#$*&H9wbl6VbM!Y6Yr+GhA{^BQe(<02kJfhYxMfgKNo z#slH=_XNn-+&CY?+QPN;Rce@!e+G*`8bj|oa9QX1b`lxI4h&-_XG`5T6Uk<#3 zUfvXB7MN4n$>B@y9T)j`J)DZicms8M{xOZF+U%pxJ!XTiaOnM*XswPh>3*0fz@WLu z=%J=Y>m);TjC4(}b1a%!?yaM|tyWn}efV|o3fb3YiW%+f`?ZUGF z2QbRTPA8v^R9!^sCyCF45$1`T2Qd+_v(qEU zJPE>Gn1m|K+!y-XxD6k)hYC!bNk;^@i?I`&gUe11cn0ycB<;u~Cv$eNF23D^S#E1J zJo~#X$EnF^#g5DJ!3ccGqBH_dDcq73%J!oEd&7Pq(vN8>#D zP(NCq*NOa7*a9cQ0uOzRgvXTUyzG+8=qG_HRL`AWqB3m}sZ3kI+Avl2t|qTQ(U(^x zbGw~!`^4Yt>(y8Z2(oAKrZPeTy?{`4z{dU}#{){>H>|VWxr3cNTp>J-H2sDeWv1~y0Mj@|7fao#+I-TqFjaqU96V7 z9y#0|8y>ry(9l-UocxLTqy0?O?oE5f$)Y#eVl`=8676-h=1Tf3D8^C=ZCq367g0fi5-&5!p z^cnPHfy;Bvm8syv$rs(o((6(eIq63@QH#Yt=u5M0;p6LKXS2uK)W;XZTwJHab%kQI z7+ym37@zkSHRJPsTr;t-w?~b=5|39_;*sSH_k9t%m2ZvJ`SZosUAULV%}4A0!nm0}DJ^ zXSy!UKZLg_`tWZ05HUR)Fx?RJZg9VydkA+c8&F*)8}jHl zgsTjV=~7he9$s2fmx8EPVn^Iu%)(ZI0J^v49t!F0zGN<0C!rSSvz1^y@vaM!|6w6p z14p^NJ0VzC9$a3-qYvOAtWrBkFZV{z#~>xbcxNunKdAXUY>b{FkMSKDyZgNdaj$Zc zobp(fC8zu&>x7)LB!#8QKz2!qzDd5&^=>CcyqIZOv1v&?Q*WYI0-KRd-S!WXMkhc>9n( z|;N!YqL4%m6obV0NDYLnI8l4~>_F!#y+>pmo#Re5MlER=!>;yMC zkR1dnq==qdqaB%4 zBX&Qic{(y-#vAWyUK1 zfJoqIg@Y#}5vBCtz>hFy=719c49YRBh5l%nz5DA4o@$`HXNI0&73(C z3hq9twi3$>=~rw`QqSIdM2^wVozk=wI@wFZCV4He`c5MC$)}z8iDnx-jcFQNP*_y3 z&MIfM+MUB9IcWfAv1;M!q2Wp5UeMj#;`{AU-A2O{A2GL7fLb3g=?fw2N_oQ(BLDzuCIqnFr3Q6J6wVHf4_phX#;yhD_5kt{PU z?;YjYKvn3)JE)npuRR23uviwuPFJQ`oTO-TDotpP$8T^a*`={0dlNfzOTKbq!{5Q2 z06yE;e~??w=@Mdt?b6emlIG)UBjtRYaw?0sq~}#wvL~8@s}HkPbAuQ)SD(P>%}nNH zs)-Q^;w89+^nN4L)CkPWsL5XpvLCLeJ5t3Gx2q@Inq%?Wt4zfm$;F9UQ47hLLVEE# ze2H)<-JRDFy#F_@{WS};gM(yDmAj-ZGCZbSB4b{zf2i;WrVD>|Bn!#5y zg@DyXL~rcR~*tO;M1;7@}?w~f)5Y7+jtG}DGZ;&A^kQ1 zIKCvLuf%V?HTM&sRgoU_Pi}yW?$x9tvF4$b*N*_7=t|Z>-nA|5(=^da1@7YEYXuaq zxT}r)?5DX@Xgi%`MWK}o@0XOC+a6L-LMemDhA**h=VABc66_9yIEVBX=wra#Yq(by z>j@j7>c?0J&Mgvd*~;j`IN2lvc$|NPlZcR`pQeFmG) z(!=iV6(RjKQSJ2=4EKq&e)F-4QaH(CSLTAdXnH+M!Z_ZP5S**$P+Nl31k>?P>LPme zdwFxEF@8Vc^kuz2=k2%g)-~`+CLnLJL?Z!HQzF0Y>d#Hc=hlUmT`V5mP)Z(`uzHn? z`{Wep2q=;=$15e@B0N4vB77?^Zp}1aiqPpmdNPpoT^?6$10AKkrQA*iXJVzwIizK9-%zYts%`dUd5Fgsd&{Em7>u7mY)a2R(3FHldJsWCyWD8K{sET|=`X;IT{d zkzGV9->rIGb6+}~c5)G=n7j>p&~XI0J6xo~;e^rKjEb;^&c=qSpdt8Y6m)iErjxe0 zlL!=MjWJ~-gvY1ZxHS=Tl>o&DLgIh4iT=rLo60+W0l4LELO2u1OE zK%66T@jC(_ia|s$j5#AsiR(x~G?bj$0CLZ_=$7U%v;Rl%sOZ!v9z0QMuU1?Q4T2$V ztaJGu_oJmW9t7Tl5J@U>)Rji__i(|)RoZLzvJ?;=en8(P56&Yy%```9#LbZ!amz>z znoq!t8s}2p@|WLI0~z`F?|lqmc3a}AZ@|-9LmQ+hRhns5HpcY!S*<8=B)C!-3Z55hqDku&i{j~RR^;Jgp&^M-~ zH_SG`N&fGtNr+HA5$?0R(i8MomK?MzTjFksQ;%}ntp?%2R{aGWctywgNhILlgTlO@8q+$Ue3`S4d5gh0cW zis2>(Y97OTMdedpHg0D-n5D*Qq8db>Yp{ld;PC@2cZ}p3Nb-p0FfhMV;fPoA5wC<1 zXOQ%LNKbti^TE;^XM?#_e6)Xu_-x9yz`s>&e9OHN`iCZ#o5bl~%}=`tYrg66W^Mqn zc_VGW|6VCym%@2>d$NmO_f`|KR>Q&5r+HXLRm^K$v8WVIBUj_7SEGr_W?47=j)U!F zZTt(Qgc&J1f2x&%78q4pcA!!jc$wbKRi&sF9bnZO0I9XulI3^elEtmv<{^HMDTR-b znFqP7@!|RwQn8Wx2&!?Q$dFYk*4A9jqSk4O2u7q9ec2RJkro0etpQT!GNd#Wsm_*2 zO=L*zY1I&^1Fg)4;5m4ej4DcmSr{e>MeGVh%*i_O))z`ACR3#?M|9$J+6hQrfDPW= z98g>bD6W$z-oQ}oL(qxtXfVl!z~l8yQ4_Pw#*CZE3nZ5n6yldaJow& zEh~8%D6LjsupIRc?75skOK=|69KT~lpufwmjM!tQ=c!|_1oua2G7ph&gY z_H5$GzJzDOzuUwh7#zHXnvv8NV2-mvbO}P^90${VF>Y2CB$uJ2;Fj2JfiJ-}Wnu&g zW!u)~OKUzcO~XKTCM5MW3UE(F10a^4VOMm!3i@Yf!?9AQxWXm=Vw}Vnw-FfiAjWYt z8}%TM-=M=57E1*zSD^X_ysHAatMR7WYPdBZqR94jrp0AdFn2W-G!=o|o%A~{3Y{A( zO&(OhC_!67)2D^aTtXL}2xcicgL!!xK?ghA!y7)Gl*gff$|YLS3at?$>T`>24M=Aq z2d(AFK<+E9&*!o%K?wa2It&vLwxjVzLCyWg2mf zJLLY8IpYdgoYWP6rqYE9)AyOgVUi&2!Q84yILNCUF}GAhmL6M$m^ACSI?fQU&ijNm zL3Aw$&P^xmOq0DK9v>V_RhIz714J%*65Fex+3R?aJLnTsj;^zq0X48Z-kUTTl(|~o zGCv&1Ul9nmgbmlD;0N@t2bu&tU$1s2Y50&-b#wFYumi!G5%m;M?8k)e!b#5&VN=rHnd z(+Hd`A_bjzS!#N~leFKtZwK8fw1BNbSKN!xUBbVQrs6K4y}AFx$f6nRwL%;}qj_}9 zFmI!pi2y)AzrUTW{Y14`VTYf-muagT;AvO%%ldJU5;-qWzf7q&OjVjJrgZ#sz< z57delJ+ehOSw@dA)p@W;9s0)uC7&ID_>Ee2f(@thFt8hfq3_Hvz1FDC2FG^l2=H7m z_#FwK2^+dPB?_gEy@q;hi%NptV`^m4l5{hQf3zm8oB?kNlHPpmp_g~7>7tQRODYQ zQf&4KqS2cA7D9T@_f&t$`1j2I68G{~Y{FQ*3@&rZf&7&i$Ap1X%vwuNc0npOmYk>? z;VPTAh6uf-LMAFbyXQDNw1lL)xLn%deVxgk+r1oAx@a$(VuxRKB$XYs!6~-ggiDxP zj$IbGJo6`BTEs6y(v2K`D-xg;R;*$%%yH|IGqkedpkyA#%vvA|zv#A&U6Zt`M$#Gf z8+8Bwc?wtu1og?2GsyZC{Y;adgb=pR;9qe_c$hliU8Zk>4QdMSucuk*Nq_p2{zR{+ z7iW@w`Y72D(-RHtyXy|SyCWYYz{rBI@BTYgt(m+zTlI1zVoCg8NLIdBI%A{B+zsYE z23#nwIwE{H()g)N?t8&Rq0Gjxo?n?)C~cBh_5B2uXvEI1_Wada_HnD;0&s=#>-oP~$>#ty^J;O;yJu57Z zdR}LPy*Z|dJI2oCjl=7IAf+FCJBSQlOq5!vFkPeZVZf3Y-cFM_f&!$Rp)C7770ysX z=uL5Oo(KLN;|h(i1%|r|Egk1 zYHXB3>CIV{7zQ>3=4RPQSzoe)FI*!enxkQx{6t_asj zNC5=D=NHOt?Fcw7w4}`Le7tJT|1Spf6}-Dqww^4cz9wr0oIs#A@+MRwct#~uU;1jz zW0bUX@CtS+iN91tX#!!Pr$bC_kIT_7)3&1kw3VK!&h?n zDh4kxYAHJ4GhK!hTKUg^qIn9J#Tu??pIHn;3u`J*D@$X(2B|Udkh?NBE}W#bwjFRE zSv-`@9wcmsgKf{;L6mNJ|K101@snCb>C*h~G&sB(7hAUkY~A=Bi1PBj8o10fEc=6| zyG1yX`0+8AS>iTeI?zM5CJppezcn_{ZGLWKpjZE-4s`QktASqnlRD4`KKTD_pyBqH zMh5!rXDkMK;MV`I20Ha*2HIyi(6}u)!9YK>RSfi+;)Vk~ZVMaeN;c5bYE#(-nqr{C zEdJw1{zeWW*85(ZzkqLNV1vob@I+i?hTq>u7#!%Z80c_=feSYnxUe$Nf-%Ak7$cm_ z81OE)lEoZ6Mq>`h;DpUjD(RLyY<4((^0ag~iK}=nfXs7fg`|E6O9rSq83s5%nE@^} zSJ5Z~G<V=2Kqt1y(tNrumizq3q}tg0*d2`LuGma7I*b$z`Oh1@}F7K-F^KE9E6i+_GP7w(O^RjZ_FDW_7}yQJ&-!C!&d}ytM=XHrpOH zS^ZmpPlhQm!Vp|`^5eWPHlK~fludM;ua*pTmU)~P{+Tq+)8J?@8|h0sn@0Mzuz93k z{+W*S>_$d9i|jm_9BH;&%XvqRbtNC`usJkCgrwwmk+DncRz*nfO@set@>BcB(p`A$ zhXsu2Ytr6A>>6;&yVoL;`EPV%e zbP}u9vSW0+gu6O#S?o^LCcRS{U`tw#iUf9n{k0}gPBu|sXm!6H{_F?JZ%PyfTG) z$@?wI7}JWAM!7`aZv#fqBU+$5P?wr0Qi5MG5r-PE<6((|g#6-Mp5TeJI!HlAdQx|O z7?K%b0}etbQSF{OHEVIV840W-KTNxc0X*#lj}H-hUaFQl90&LaTR zi+z~{yN}Y4KC(yz`9p%Z(`0=9C*P;4w~=;~W7M8a=zo33C;7GbRk_>ByL2-S=M7^zog|-u*mtLa5|1;g!?IBNh)7by6#!PqV0f_&P&vZ}xe5QLr zEsy`N#Q1+ZM8=x&NN&Ix(hmv@lA8L4JpGD%PLSU^($ufe)N#_(KGM{0(9}1islBAB zD${8J{{!m1D3rcnIRb`~xA1!3gu^dgbTt|LB^~KD|G5LHWHa!=3Yz{7{+bd}Zx;y_ zua|sM1Z5Ohnwakek?`+|juQ5UBv`unST7RK*5^vtUY7MfCz8IlI7PzREMW(TM6N9r z*w6P{!S)f!S;r}Td&Ls=T#-a|w9>bHOV}p`>~%`L*PNi_d#NSt>A!Q>pOt%2 z+FQcDFOqTY%a^dxy;iUniDc9DO2V4=EnyFdWYe>TOW40z!uAk!XwhE6&e&^3v#~-x z1fQ0$gDhb?$^_jC&01N)_7(|2KfO-E?*G;bcE3mn`hwE8jo&Kkp3!cNIko3k`N^q0 zf8{J)*r6Wf49n`!vz58M(f$4=yG(c!iFdZR9qD)M(W z!);0%iaDHlz%-uU=KW`BJb4CC_)wn1+{WY+j*Q{_K9#2Mbcw36MOEDe@);a?P#I96 za?iJMX2xImC+Gz?!hv7TF8e8WWKM_ScxpdZ z85@1V`o29;ebb=6D`|ZzFw(+BZ-K^vN4tTNULgYjOYtH-7i~!>ys1R@L{=604s3}s zj9D+!Es@;*UK0^??*UmgarF49Ak4GLS%_ZR4Q_Zon*NS7|EZ!yk0$Je*R?{_XmG42 zz#P#|QpY@mcCo9DGY^1Ak0^S-xv6$_9-l@|)6LgHjr|>0`c{uq=->A>4gK-{R852x zcPA|yyMbJnvj}r`v0`ZDYg8g)nHTm`4lc`(7e%ET{r<%_=9GVTPy3sSEsep&hlrF0=Jt&B^cDtQvT_7cOx!ERK-raFq5-x{X_9~ub#h@L}6!?=NS z9m)VS>Ll2;X(4_7Vv}S_2+5QXk|`l16PBfNlFTx)5zj?l9^-&K5;(n%dd z4L!sOGy+s9b_h>J4skZ*B&8XW5kj&iBt&DZRMQTK5Byo{*%?Q_1yh`V`f#ofHM zK$W&jcRA_!7@dbL{ip)mbm)YDi^PQj{56@MoZZCA|}ex^VJ?Y z+fBJF?tD(z0L#DPQ%D()$G-)#VyvY)O5duqy!YRNmOU3HW&F55Rks(_Xr zw2D(&{yo)1%R&l{k*=7oCzHp9R&x$wq^(M&q`eJGc$7NY?_r3t9CGTF?=ODKDdaTw zZdhza-3>^WZVX;UblXl}NZmFCS?{6+X5H4d;6K)F%X2(JrA=0Ddwi`bx6;7Y z)yUt?SA2y4Z&1dOrnRDfis|&lmuxznJ-@+hdGZ?^_sNzoL*J;we;?!xtV^M5Pyu^5 zq?!tu!Shui)6S{nf7wl08urCuHUSpSZ;YMOz5;eXyV3V;zcG&xRp*|3R&aOp3k&Yv z^fmVKHuKXBU#s1F`7rGs+~XKsM%QR~ej^ZJ8!K|%*R;D=&tu*7WwHq{sDSV*Vo)(X2ivJ{HKh_*F%Rt zl`B?G^UD3qm$-6cxEuCj49oYApI!WZ1rO`l#9x4x4oI?v`M(jfSSsJnIsW_mwT7i# zqO{9g+GVlZGZXfPe}EH@doG~^KYpt0;W`S0T1#BWa~Iiwzz`U7!y>wBS^~Mq*ABF& z6ixV4uHU6p*k4rGL*RX;z`FzLE>3WdyMk?(9YEZ7;5ZRZ!b}m&+scQ49pfjfcaPio z>fI(q@?$sJICVC_(dJsN0Qt^?Vd{=bs;mevnMKVR(!N|4yENt`EwXHuwX_<{e6TU- zPcTU@*PoGKv$@<8yqx-R*fB7kENd z(*;+aCb-h0?nHd{*Z{S;4h=eTV0)_Sf5&ILwkLP)2_=swr0?eO?~{VwbTZ|Asc_Z3 zgWv(T4h;6D^9%s#vDO6K?tYAYU4iEq&SDJz(ry}IcRSHqlm&70cD8@sE;j`(>KwH= z%rUE8<`e=zx_htyyf_Ka-WXu}BtWe(z_`2!q$@=8bE-^`#|@lHzmDvI1;Ia1gnB?u zI}GHYvw%#`=3*6bn#<1GqtXNy+m25%Wf-bK*2xZe4Vp}E@9(J=$@I!_Tn*$CJ|wlq z0IJ3iW`Zpow`YV_F0YrH^X91Dc?2KX;m&xEU_FmuJ&($IPUIbX%#GC&X2LYi{wIvl z<WBC-Zq|&>d;)w#(Ld=05tiffHKa~7Z5-l{L!ob*D)eH5 zLhoRq-Axsm*PzfnSm>js3Jp#wlxd8DLVWDn)me?5vm%uyLTtgLB4dnSqZox@%%f@! zCf^+)#6pe`3j<7Ip({Z!9CY?7r zf{T;1KUy+p`@>rJ_!gG;ii6~CJdUv)r4!0 zrONy9wh0qx{UP+Rk4LA1JEY&h3PdYx>{b!-@ErbdJ$-10hoktzXKh00hV;3|=<;&O zN1)y>=FeSB--i2+HqhzO;@qY5%8$Q zs+Yu)k3kI%>9ggxb3^(J@r@{0-M0bB=cy1|ytkROtR*q0Fc_d@;|Y zyFWC|Bra1*D{v-xm_$)Jtdza#Ly2g%(Qk!vtr6(9A<{<(wM{@C;O_pE=%vW=2_;d# zyU1l%pW%H7?)#2NK)A9di%MF;z6ISwdWs-LoTA0dRsDaL+z!-WxIp-C4F(IFEvr5G4Ad&2*kR~PpUPTcRP+qP>zU^`T39vr^A>!K^gnE@9=hGUI=|RrV?=?^g5o8(O!pXTNTN zo)uX44BOuR0JJ^j1Z|IQyzK|JG}87WYtQP49^VY%g7@A+!v*258V)pFg!pvNkS42Zp-cnVcLeO>!*Wh`w}kB{LVWI0V2{6R z1-n+jcFK}C?6HLHA;N(=E3hvm!P*h_Ujnv3!Qn21E%j4p#=v5r&a=15TZ8|X)&Y9+ ztVfT~T%8_lkXwr);zm$*wgE4iI#b-yH`(uDhY{eOqUd9!zwDwbCmvdeXt2eM1L#D! zPz?{_;M(bD8E=aaI*tkMoUGPf?xN@Ewt-!$Hp#vGZ{h(eH?(3yRBYJjH(7B{tk|Fx zV~|YIa2vfh$2XW_AvB&I;yVn*>DzGqKZQNqiVy3^!r^{90h#~>P4JprNj14-O}jth zHMwL>Z(vQuP!j{~wk?n#G=KPIP2$%?t#@11dcDNYWya4)s>zTwoiA!KWKFFleuj!) zp^M!y6nz8!c?FRLvi?&$DYBN8S&;R)ZT|(b-c@Enfa|s;lXcKGFwO8wVJROKP5Tow zj12ZBAQva_laPbiV*5S}=Yd1c13EG6c+|-}L#p}QscJA)g4A^4V){7g*5!<4d;5pz z`PCX6v1D_@N#_6rb>NfUK0JRwbboYRzyT$CP~tSa36D`wue-#({16~tH-RQ$)9bps zi{S1UIoF1C_Z@D!wdHu;noOx1Pk>b)|G*|7SVm+M&Nvlk(y2&W@?cAuj1qnPjr6lG zl?&A11rCo+|Ak6^WP||u5dFsXpI5L3%fOvqVhd~V;FRK{cvcM(8iQ4!5(FN!vyhaD z4nk4WG^tFecm*nhlTi_a)Bz#W_+XfiBV+BIB3EtZYI~N?#Ma)63E4md5tKy?q1VIwNIQ%ir|z8{!!q2d5YZSBx=V=jgWM!VhHb z+`6OEYXiE9uQv)sYTRHu}LqUCgxdUyU#-plH3W0P&YnmyNFHQGq;hN-YZtG>5}=W&6m--W9&y5pSXm$#It4 zv(8_qxEImH&MkV_X~!03n|E*%Jni%*`n(|q80ri5*!O8Ph@I8xzrr|7v|e!Xcl5c8 zgY0t$e@CP9`c$Rhw&a4X<-3mSRLGIDoImW#&37ikaC4r zyl*7&3UCF_RD%%vHmLzB?cb-CPR=wb2QUMpF;BT4;*Z;>Atqri}Fp>Br8Z`<46GOZSb158_?ydzSR) zC-r*t>lVG{77=Aie7`X@`8o&h@dh-sCD#TcmSX%$bUkDDI|NZaqbR=^o0(@Wfu3(d zJevOeJ#-rBr6T^cgtQ*(Y&`T%DmAJz{9K&C->#9^8CkERsiu$dCizejaq1PVn;=kk zQZ)L4?7|&?Ht1E4HL_P2UqO3y#-H>G3hYY%%{0gkzj!uNB(f(x)Fc z(Czq=H&{umwBndjX~nzTw4nJna4{eUpzK%!RF}SHIRFc=Rz~$_H>+%X_!X0bk8X3F zcw+S_aZtGZIxIr3?1BeZHf%aYX&NQ~Yx>9^kl8JP4}>2Tg|>6MrL|)|O_C->-iXB@ zK0G7jq`&xtQi6zujw85OcvLyh@%pPmEEN6GpbwY6YDvC+e<jbmunF#o!zj) z#4DB+9yqF0Sg=u9ZIH2*D_z-YFkmAZt6Iw^KfI!PZO&aJg7CrPLl0+{=iohqA-(u| z7kBT$NAOquV`57rro-RLnDrW%Jm3i$>RBcGUrUZLF1eF60`c(!L;3_(Z9V-6)zU{0 zj1|%^AQe(uz0fkwA&6lQ)rzx?TkgbwFL=Q{v#*em2%!`uHTABt^80&UCJ5Hq{2fCy z;xTErzaLk3nImcWWkqgP)FN^5G2u_1| zNVl_Y7IZaAU&i&Zjd3PmymIdZQb*fCVPETzel=_6Dgt8sox)vfOn6hBem`QB5%%qp z4YUj5X1-8vB;uxVO1lpHPdm%?W{i};#1YaQ zde2&VEZ)u;pDA~~dU3vrd>i%_jCZ}NGJXG2D!rIqx&=G=AYcR-$0IJ#yvJ#EfoAwc zsLu|eesPf=Ax_gHAT`fji!ZhM$+?r;G>h z+!M#u&i%2TPjLPuHMwu;M$SSSM2rk@?YES~hh9t$-9L3<@@c9|HV8&vl4D%4j;g12 zU$!xreNG3Cu(mJ zYj1v|?G0KWVyW+x4f%77+m(irQIn zoA%E58RyM9#HkgkMr?=I&Q!rY%vhEol8fkZPcp)Zpx?>;(h~x8i0%YHcLJc>$CnZF z0;cEcb(4H8V}qEHE|$g1oQH%>30+lHfmX}rG{7X{eM`%^H* zj<85fRV@6Gh*HapN^#Jf+3mbwNh=B-$E`0CefS%a=%*`N^$1Ek(4&xl z2eA3$&_t>ns2%pyak&>^?TJGl5xxSVOIV!dWZ0Qa2%$AXJGf^Ji$mNYb=QaH3Q_g_ z8*JspIGHcs!2N)&6oix6fN0~xHvM(6J>v3W0m~d)+hCY*Xq}xI8SR=9X2qJNA z6G({g^oI45PIkkf<+Fp{uCmskm+%2s2l2Eo7>p2|=@8=XpA^#1K47KK!`2tCFWRK} z*^Q@TQebph?VFU^={LjuN~$IsItv@}*nG`NLpJVzbfr+Qm>+JB*WAlrvvUbe#8C?Q z6l)&RKY*i`xfxuQdj#@*cvtKBt2JKDM-C{q8s47&DrS|8UP1Fuj?hS{=I4KHz>OJ==JyrRJ^1eiQzl^^sR zPq(ACp*#Zz_%;g$SOxC~|Ay~}{kPd~C+NsKX?~|pWX{4OQBr3wi8O;|o_<*bFMHSM z`D+wXw8rHU=6EcRP4Eg#^oA4s3_X9R2Ksay!@|f?QW2opy`2J%Uff7a*!=x~8pECN zfQ_d}X?O?5(1Jx?))pM=geW{Yn-}A328JWbUncp}iPEkkwdkb>y=!1+0a>?`QYBD9 zgk=5mD7*C?zuoqKnEMj&sEVc0nUKIt7|0L;gngAo5fD(>36cS1k%$Cv1r-qx6d@5* zwlG8)h69lhL`6l#D_23eii!#v5DhA5P*AQ$jS%&W6E$L#$dbISs@~4b3Bmu~_rK@o zkaOx(S66pecUMrnv{aMsOIX$$SDAPr&ar1uRms{1 zv$2@cC&?*Y9FYK{FDa?9=oJb!zcPySSjx@Tj99^DxA1Nif|oCqNndG{LT#eX`4o(@ zu;Xe~9#MT-2eF&L5&}0MCh`>B7|-NwcpJGew;=_EpI&Bb>0)SaR)3(NS5tpNyJYlk zImz#`MMbeO-gbi@-g_D8sw_}$`&6DoSo-b!m=alJ7d{S=2x zX)n%S^e3%1H#}@}ApEvlY4Z;1I0>BunKXK`K4h#fB_qZT8oPBC`r&aIT|M&>uUPl0 z=;Vxsfof@9Zj9G>$C;mOklJhhdfl6xVz4|R4xgbx;?uS2xJGK^O0!s#qav=+%aHZD z-UkQcP`>Vm5aA}S&P>+gwMhi|$3>Zm{@JDz#A^v1d@KdW>T+5+HpUHg;XbXc{j~Q- zKy6M~z7!R*QucHYju;qU__}B0AXfF1TZF(Aa(uTO!p1OmimZ#GBB~G!3Lf_)5AMMO zp&rH^+49iu4(U*h;aRcxa8olcH+Pfb1&xJ*%AzHauYmH$W(8}Z-sX8ZX4lN^*!P}) zQ)uwMLf|x*c^KmQ{-!uaLmONlG2sc`mmtpbNGHV4NH6U2dEi9g^I-a6C;R89^8`v5 zsq^NQPgHww0L%a0TDh!b!;XhIOjzc`ApkM%Vl@I>yElY}*+X-<$Eba$-Ab*(Tm-4uC70VWoM*nRC0 z|CFJ;ikqMn zrA(+T2hUP;>CQO5K6_<)(cm*sCwM2GIWc z29V~ur)aTzQ6B7ROdbH&3*PQGx~i)0UI>DN z-#0!&j{Uja^!OA_3eKND;aAOVoiQ zJkfh=yM7;C2NE%(tBP4?_`AJF^3iD($j6v|t838V4Mxw;VzJ_;yvF}xQA?UPfFZ=8t${UxJHq{g|kWmcWeYMR(bAw z-wIrxMz_5!?*4BCZl*@}&}#B~o z->c!iQ3G!GcNDHs1Ma`V3isv}cKo{J@sKQyhf8g7QGcQc#+{V>x)Jx^eC$!!gXKYv zfJXVN2W!H9NWzOo10GOSwx;^d3Eih7DY?JiZ=uvpU>F0;|9v-#v$W zH0C(p;3_`t*`rI@^8D&u%Eqk~+xu)9Y0Os_-7wnB`(XLDEUJpp8o-cob4hb6bD5vZ z)gcxxZ@hLomv*da&3zUp&@w8Ik(RI%Y|pF?oP}?|6I!KC{&q*cx63|*wV*#gVUHQR zr#M#Z_IUZ)J&9?0W&*2KZgMDA+9B4;s)yWSKPa@8dsP&RHssT5=e5d+QP1o}T0$t> zK|4vxZc$q;bTX%yb(J>3zSYk|RoPOKw^d30j4p{5KI@%zTzSH5490SpsnjJ^h2Z2T zqTNZ=;-z;;mcGbla(-Kd9t^bOH9Ul21H(V!Z9?mZTw{ozOt;}r!}EDkSBg+?9gYZR zpTpxgMV7SoZkaS%Gp?;CojBo|tkR9Jj9E91$6$dis!*vkx-$Vs$d@t7NF#k3%-zKL zJGrQ^wgrS*IzpHuJb?&ADJO;k)n&AX=IySUH=U>;(jCA+re|kA_8I)&L3{1p!R*OU z0@>ha!l-!xGAI|^I0+WagMR~H{mPpIL&;ksuc!-#j7r8-_NNJ}zW{2l)gG`obH0B! zU^p}4jbTnI=2XLf-7aGHB~+4&acw`8OXU%49=K+s3yIdfaiOP_39U%2;87F<13H-N zR)ZiHISc&nJN?@*Kg!mzxKA(}9t5eF@U=t-p@I}%NXzJq20Hk~SrEjMGO?u%XBRpd zyzl7F>#%zimx~Pl^Btrzh(gg(GgKP>6TFR{X3F&YbsP%a$c5^ok+`X*{an7rtzpUY z+81glNel&}z=_&{!O*$|3I&@7`egP(zRO|wF+Y}k@IxprLvg>#Mey+T39tK>TlYNL zsZxlf@!2G4JW)rgFN;rqq90+-;H&T_w1G`~S^J<9h&x7t|2xMAJE-m1IT!OEG zA*)Z}#S~|P?0@=xM*ohh+UG~}k$#5x>Jnt2zTtnGPo^8NGmY=&aR^S){b3)~LRaz0 zUP34~7^BI_944aSVFv6&#L~(+=L5Ty^Y5(o&5n|tueZ^dl)o5;n%jvEw@(}CrEJpd z##gLKG(CII!BKdTG&1d2=(hOw@!X$Xs?{t;pzxa4EO9$5HP^wMGYXZY+I-=T#kp*o zU2!@d$(TB9Vb?-o@{3kCPLE4knfHP0;MX9gB6I`^3YtlU3Y78q;T z@NZEfX_6xVtL-n|w`;>GER~tNP>5uQ7pQtKUI-$2jSxu_y`Y6;nudSPMdvS>SKm)z z?4l*I;bKu)iPB{V4O(XwM2Y3UN3h}R{RKFDnaA$On(1oZy6`+sQxx6j#tRy`*u5*o zjt^^nPm$P-URYhoqnV31qnQzmUK7n|4`8$3or6%;Rr6{A|1U9|ECqinfv}ILuqfb z70|3pG!6f*&K?;DCyA}XNfq=~J7^@{%SrZ*?#AFtTN}c<;O?du!)-}rEs&~Ws=Xxo zpKZ^LqI0q6p+wJqI{y9MD4JN@?!#a@pG18}P72B<-}hU)j`+oVJUEb%2%f@yn9SVD z`WpMY95MGiv8~13oQNae%|lSL19CtUC*NfI-r6XbaO(CS#Lfa5_Qmm!i~>|D#G> zvt19%%pku4RD9*eMLBI;V2&T$OI_x+xztafcg*e}dWVra_ndWx9b{?-_oJ}(2+i|B z91r}4&pPeq>L-{_43WOMf7~N4F(e zA~CcHYBO-Mhg{0cE9cp$3ea;i+zu_i<6BkQ4KHoXp@VU;fG7Gg%gvEoepc3XG+GOuhZM`G&msZ34^>eGQ_a0{Vffkv#EQM?L;_=eY{k zeKyAM`|s4_FL#|Qt9`RhVmN>aq8o=dMS}qfbArB9jFtnwk`P z!9n3VdhojarujMQHg7g=kLlrUzqvylV0392fF9L@|E-DaplGrWDzb~~YW(u{YGb`% z>m22Kk(Vz;7<^&Ua?uTqDPBZ_){EhFfGy{}Gm4*+Gvx^*y_ojoQeu%FuL2~wJGzLQ z6-m1yDtmKp^X@))ULNOcP}%hJ-`7lcQ;>1oUovmF1H~wX+fl(TlF&?Pi-|RB*q1TB z!hNDrcCD;2<(U~|RH>+)(NxrpX{no1jOQY!eG<~#%cXj`ewOM{(4>v9s>>}+8lI@j zYu~2!f`zsI{Y+8oQkUyC14p-pHiRQ&3v2%5WlYg?&E#vGz|8Yd^kP^J;VYS<1nt2( zI9_&>&K9_N$*MLd*4z#+PPeXfsIsPACM#>k9e=m7Jjv&%terEWD$6+?hp_XltPd|` zmG#whURgKHIFHIYa(gwEHNDB-s;sQr?JBG9rLwXH%(PZkx5zSKs38v?5OVQH4-Fx& zM+}kQHa0Kcw3}#Hwiozi}={COc@$GFjZhTw^*^RahbpORq z!u5paO;|>h;c#9K%e_LfpM<87X$0r~Zx|tQ1e}@;-OBMsgHkT>F~$gIARc#3OJSW# zZaS-OMxT)giv&L?{p`&uUVKkXrZEAt3zwYkHwzmxCQG|nxHOk?2kv*xE2fG)KSa4` z8X37tG$U8jn{`p5mBSxB4Cg65$O`SXO+&qbQ7j>!;e~1CG&xT zwMA7R9%Jkrzm0je3vVJ5CKxqd^Qsc;s<{M5rbL=BlZD}tu@PmOUe0ntt0isE{S>`DH?CXt zEwl1AOUra{h?e>EA=NT_rpT75xKXuCS%NCp&YH`0cCt;&$a1yfSg+h7TFIji;|dc3 zMOtfM*^N;RH~C`LaC4^chWl;Gc{JQi#$I@&+2lt3Oa#64Et}vjK#OghG=4w0R@FdpqW^ghV@?z67rN$xsL6#s?LZc_%iYN|L zb_akEj}j6O90l*VRoTGfk*uFVkk0HB;OUd3eTLSOn}XI4I_tCGT>zCROuSx#}X=;EH*ejO9f) zfV=(ScyhOgzq*@i2kFl2Sfe_@Mo4oG+}(K!cTNN@)==&RA%VM8qnm1rd&^G(x8B1F zH#q{AT#5fF-TWJAxTV+I@w-8~&0m$HaPw_(Kb7wI%{02}YQSx+;ZC>3y;{1gPt)pQ z!1Z?ZFhjbmchu;5Yrrkrt8h!Mv%^i4?(OesJnXc^EtI%H4R=)yxEnRxDc9N6LqqA_ z-d!tO?;3C~({Nq3JWQ7E?Hd{@7x1EM?Qn0GZs-Fwy4$a{<6)jDaGPsoTW(9YGAwY1 zX>=#q;yzj`a7SslU2SpGe-^lD8m_|@H}MyF+(E-VJjo9C#$N^Q^&0LLTij>M1ny)F zccCrrp%Vi4Rt-1J7I##+z@4e#cCf`=@Q1*?Tf?oGXouVCq`+OE;U1W1HCIJseh9lsaKBNq>AxU+2OmPy>FHQW?iy2s_QkLNVpB-^q*A&+Fu(s0dm!0q-c z&3ga9S+C4Pv)(Em+aw2}r5x%?Cg^<#E$7g!dx^qSgjRSgcY0XE83Ecu*Yf>Bx{>Hw zeY5zz_EE7tp+rFy+Y_#0HvAt|iSv$#vlW&$dkz2nf5dC&9h+-qZ83oYH$VW&hdGv! zJ6^*$m2Pt)^3wKrFHS6~c%bRH9E%hEvmyAI2ZLGQTD6=sFOPMrn(Hw=(j$E?Xno6V zA3}BIr<&Dr^22cyuohOJrJw*K^yP`%5`|tftH*JNf}pwty|=lvUG*CM&TEy*#;Rgm zxu}ZHn$Z6`o~e#$>HHk%q4b*1fsPsvevsVIRB^SN$6>|;)~1o$4Jt5k&reXkB*VXs zmYnC>BKzeUI!i6=2-*2y<|dz?338Xe_j%Q-s6#q;dMNFe9d+IBWvvNcwWcr(zuF^hI&@b zxgW+@>iB`0>v-k3$ciNUR~rlfmjs%;kBV-IeV*iqihX|PjczsC_=lgVs3%`2I2QUm~0lIeeVrMl_7eP)7S8X(+FB&>#)LY5W8@K0`rORo>SM3K?4|&Plg~ z5H)z`qqo}YCNuR&Cva1e_38+7z||FXUN=$DcDmeR5jl0g&sE*lQZ-& zfy~Z@oupw8LF{)lY*Lo8{u0EMduiCO*N8n(!ww+!3xw@e z=?HVUhItKO=01G7E~-=S$P@rk=lf?MJ%Smz`J{s4mm_1;7gd{2OJ(wfB`UAqM_F;H z1U;oP`NA6tw;%$S6wiYacT}uW>>snm-5?duAuF`@uK{%tx^>Vl{$>h3P-40G$wLo~kTbL0Fjjhn zN)#~=odLD;N=d4R4VjD%$wca|bCdelc(>Ixq_@?a+D;rt_Q3tO14V$?;200ZgMvGG zogW9sz;u=r9OJdi3J$)`8?Vi2S*6Lb;-IfsJ)mmEphkAZY1)XH1>IS`ZMZ<+DO}eW zVx4$&?`>rFNz}wT{)vMH+(-bA0k53gvq}I=K(oI)Lmpbn=={rhj_tt4Am5Tj{zRfW z<^?)jGO6B%i{(o>MkFhLe|a2yiWi5kCE}N(g<6S~~9<|7b#SHUS#$brvhJi7sy-Aa)i`Vp!OUtd;ThCOP^@ZHLY z3}O2GI0jU#_@ESv$?=Q*Qo8#&ouv@MT6aI%djt0Pd3k}&=>2`3gyn?}e)l7qF94JcC(>|mt!w!E@523{@bz-|Wx$Da^9#8CiQ$w% zdt?!`hp<_kg46IB4aM#(8!PdKhcdVi2tl8Z_&4)1?nu>EVK7vC&8ri|g=S(4>RQH2;C;j2`VC4H9EqMIc8k({ z$aSCBvO|n3>TB%wq^6{*=!g7%hW=w{uQd%d_ms%cbKO$)<>5aXz-Y&P8)nV3%i`vt zZtm|6;;_W6Zz)}Q6{@x93CHwL>&LEFK|(b`vXvRmnryM-4G+dxW(l~s=rF(Pt zpmrrsMq4}z0vJv3NuE1SFApXz{n&{sNx$U`c^a}b;dNr^i4o`2{XZERc@IVduUuC- zPwH#fM@RA%8QoIvO?0aY_wndO{y*)fqh&*cNqqK?@wwfD8x@ANQrYvWR z3dE1Tk&&jIi#a+CFj@Cn&>N48iV>P*4oQIOI9XPgbd$!0|ItOnoqss!h42ORLc-A3 zSfe7V=Fj?guqQTQ`37as9Q!AZv;IY8b*PcGgH`ViVvRhPeR5|sGyGeAAzj~7OX=Cy z3{gHU{mCwBb*w4-1CEB&sV;rPA;P&uL??VR!akB@4z4c)v8_yT^PzRaLW`)L9T{0z zcYXAAp|IsH$KqB&b%3f`ONq4irD)yu;ZH3+bL~iL&qN*{s|T&rMJ9e zn}z774iWmBR`khNs&2;>+C=u^tmYniF{dU2O*lbh(lx1;h_E=dntbjnzRIn=IYw~X zor^Cg-~i*7rFF$DCU3Pvy9SJzDh^JY6g8P`@St=#7dN5$bXmQLq&HBxwW|GtwW=<^ z&r;Q$uC%G@9v-N>RrgWdEl#!6UHcJ{RqaIdbdEmOie5fkmIebMt8wlBuD*77a4w590N-*fTmYK|9DL`8D*XgPXtdT}oRJw%f*Sl~}qqrew@F|0X(emywy zfZE{rs9l)V1Jo`IT{}l!8L^q%@#A`Dq#=!t)zJh1BqrjSwKf@z{1t68nnM`*wuZkE zU9K6v+3?q*6XxLwY?wYkkE6r{B>fj>7sYdX=FWj`aXuBooR{I*wIPdz^3-4mpGnH- z-mDyq$UHnqk+<4IymIVN^RRod4*2W>%hb>`RVss54YPcy8p^jOiiCSgF}d3r6t zSV7vQEoiRo%d;n+-acfq$KLYTJ#HENq6ahvT%RJd)AKqo1g@>b6N0vxoEU1evv++R z&VD^R_74t0_WQ~e-?2=!;sc4~VLykA5clVaTK`%`E|vLB1U-AS8LCZsHbdGw$c=Zb zq#ri?pZoy%_uZS2{Vviy8o;CVMU?N@$Ly5cg?;c29E`48L^EtXh-C%0vKP_**HN~L zOj!O3e-_(RM`@c1qegEVh*mDBu|#9cVJtqc?~hZ^+jRkF_wk>h=f;3a@z@WfQrw%V zq$0l$4rHt}fwb80P|5pC)9rZAzZ{lrGc!s4dJKXlKP)nCa0OJ%QDQ%%OLeTD7(l5@ zp=bI^!fwOACeuxhz(rW_mmCy^+*fG$@Vqr7Z8bcozrMAgLcTbxTZqrzO?l__hQ9O3FFD(*v z=Bi=rDM&@w+LT=Jo3KwKE|-Z>wzv;U+%Kjn+}07eWcMB^or?xPui5{5+2MXIaqs<6 zv;WtC+fd{8F<{I6h5xBHp@r!hZx($BAe7OSCcmZSDhdhx1u2O#o>tT;k)G3cEhpD+1RUf%}|B zw~j6D8o67!O~Z}T>VjI~72XQ}><%TW;;m2>(F&(d+O>kw9d~#mYW887^P^fT+}u5~ zKkO0hk2yEwb=mN^!Z^q=>LErG8;pN*jBq!=Xl{dXoMU`|73OXd|hy ze7ej=j9SN|dtD;OxD_$#B8HiIId-s;Xo77xdT&H;%?Y+XZmsky&e&CYGNjRPsnU1I zZPk-nrI%i8#U;@=E^(V`gWFGt`zD|I-iEi__Q;tstWExfZ*=&3@DeP)Sko}Xc9U1% zZi+CLh^?Pj2FmRwHM@81MpWB$rxge{!Q`E;tUA9U=qScK~p9Ddt6CDh&Tq zUq4-TEg;A?ig0+MwvYuRg`F_jj-(NYJkg`%NGKF@z7F);}F)pNu?sbMD1B z-Tu)tF;g$o&Q-AG?898(pe;9F#`m9!asR1U?&1leXOC4?nbR(Vbwo+Po3#%LZc)C^ zRIp0$UFPP-ilP17dJs3p!wjmuxfdy++g|}fci&MwWF^A$ww<9m;zUB`h9Wc>TqKPO zZMjh)7L5vGTWRg3luz-6`aSkHVNM^@`b@THyE0H-4=X6zA3X^DNFfY8kf3XU!2EeQRmf`a|h9F5TJ3R)MSmmzegg2HXaW*daQ zr=Vj18jsKe3VIbl%S!?JnSx#c&~Fg>wSo@9u@a%jBV_Vrg#N0a*8_ATLjP3I>j3%y zLeD7ZtpJ^a&{{JkbP7PPLug$Ey$ztL2yLLCw*a&oLYpY)aDXNww6%iX4A2;ac2ZD` z#8w&xXg39&4A9RJ+DAce0%$%$QxtSOK%YnGNCmwCpz9ENwSuMsbTL9FDJTR*H>V@? zW(Dm8&~XU8Q$afebO1uL-**1?>vZ5`?~}pwrN9 zgU~k=v>QNoBJ_O)g>d@j(+K@cL3;pn6+*vJ&`SV%4?;^7v?oBPAoLFf?FG=02zA~e zp}hgx8=-X-^iqJfLTIvrUItJXLfb3oZ-I8y#E@efC4(MBBofG&h+Ge}y*f{Ry{eE?K|^GJHYjhKwQn zTMrG4nqMcX@rr!jj2RvH#!AL5O0!$k7OEc!cr{!)rm5kjw~cbQ;fi2#Pz!gbc9$|e zLN|N6ovs1i@C%e?+Q;PNkg$3gY2`#~fuwb(Wv6P2;h+1lD)Ob;D=;^yBNg>R3;Us) z75nSkKrzL4BiE@F?LPKR@S4~z(!qS|&y`~9>Vr0NXYRAJO!j zMg$QaZ_TxJulZ9ctL>~^ytXrA!WS7?n~T8-4MP6MIr3q>h0$fti2IPwum(NT-&Zj= zEUpMJHzSpM6qON1)=kk=F2q{|`ujX^9)(_W0}Pe%%Y*n;!-2) z^tVx2G~rpe?CE_c>HQW4dhZ^T^oAH&FGSO8kBqt)K``3`&(+dC@`2WJ>lt|*80xXk zvs<_#vZmPdQC%EnYb_q5zOY^kUIaCCm==bA>TV&RFHTmX^Y50Lhe32|7pC^%6*+@p zJIt}itc$}>7YOs;dn^sDgV_+z+9snR198{!t^~?-`^O~3;PopqoB1-E*?Kk^lQElX zE!o6mUTOH-JxVl>wj-LC8UE?l2(hGid7TJjSw~?GI53$Y@h!o%$m7%*=qMRzVOd|+ zH~d|TC|Pz3*(Y9uo7L03qW3 zz7_2m9%sVxr*RgY%Ec*R`C6UT>m;jbmb$uvF~=Qk3Jm{>gEUA-)Jo@$*qfD$V+L_i z%nAorB72nQ|MG)kkKD!|a(fFHU%CogzyvmSfo((Ft_0<3j@4!7Vkef|zP4cS;P~sC zkw0DxaV0%#(e~0DM>9DaL}c~vW^$xV<5ZklmO3TiN(7PTvn!y4uY}9W$nC?W!!MyW zGf=hAn=Q8;ZXJJ!0_wtTVTy354q_GmSXsr-Ytl7mcy1GqV$rpo>f|6gTuejP`w~Vq z9`9fEfMWssw#Tg1%U6CuRUKA@y@5h!zO!&oaV=<~^`t+1@i{zPFWAX zVpjHMXJ#5_)hn)^b*aZvEJ+ED?4?>^JRHS^U@wJ7VWjD{wRDq+z~9p9>H@kWqz$M4 z4PM(u^5~JgfP+j4)4W9Ha0#_K{Ah6JDDJ1MrgeRof9d0jVjsPw7=L}&5v7$xiTA8A#kxbvfY#Wp`h%wezYAP-2DiBMy@lwN#i;pc0gYS1 zU|QnHyp)dz%iGWpgRDH+f{u|(O=k9WZfV#4ys_I65lHMLKiqWwqwd`WH0oxJv5dMe zx3M2}XEuo%b!QixW7O@}#4_qCH}pfzE&2UZ2o{q(;Y8atDhwO0fMh~+v6us@po+V8 zK*Xz43ToZspiuufk(%NFv3@67M%%3V}36V`}PEB zrR^0lLj;|d?cGZy%!sF83v|_sPW@Cl9bp2+%bPKtjcdSFU34k*vXLu1B$yd>eq5aV z7KW2Y*uq-9N7+fV{TTY^AvDxy@xZ&j9k-}@&I?T-a3r}FQLUIkj4_v3^vGojrwoEZw zeFu7HvIApeA@+5K|L9&EeQ?#2*Hm&NcmXM!63eovH_ zt2_#e&HNae%xb(tG+Dbt)Wm=RbWA15$X!qL!9P~J-IBZHm?*C;$)^&^iCmUoqohQ; z1eNIat`^B|hvo7fi|=eAjCo0pjFHt-xfZB=-b5P}7*v}N=n?gMT7ZDo~t zGRZBjTW}0S>g6)*S(|sL^H_1U2YkN@{eb z{yGax?Lel&Jq13{h~od_vv}Y`;(%Icar?HZ2Im? zqhTKog@K#1gZgxh=<1cfinlsAPu#WV~gp8+(#yE*K5_tn`4>@q-^cyQv)3^PIpAi>YT?<_-Vl!_v2h~Scrx!$1 z!fl(szaSb*z*S@2!VN(K*{BenEOB|WAK6S_Tf^5Oq@7`W--!U|!2{!ikF-+r{)qJpuX&aUEv3+}Fh&txaBp=XTs!S6aVl;aA~(gF8KX{p z&__&1MsKFMbF821+4KBWJ>WUWu?p(Z>Mdg|qv+3f_V<-8N@b71^isSb-gHtn=q@*EM4K>Y9f6VR*z4So zlfAAM)CIg3IH+FkWY0!!y8wmtfH&yB%U-4T#rXAy*Wt~)9R@lEe40K|^XA069cFes zM!a~PhNDvRt9Ydw4vNqX?|Joa>V^d=TuWe29{J=YY7e|hd7AtY3`fL?@F^naZM zv{#s2UZ(eLst$N$;?cIUk}6o_Nzi~H2kme=ZrY>(K|*;1@e`HN%eEUiiB8*sgJU2~@u$4Mjl zc{I1A3C!vOfOaddY*3cV=XDe(MlSwM436{qKa7FJNjjH0FdvqZ>;wcUulOWL8@c!w zlOH?Fk{kYhQrNQgt#fFhF*=y99Da z0>@$=U*}nDMUCr=u4{X<;$1Fx1Qqw8N^$<#e1uI`V6Vpg>d;3{zD<$fuW%(i_#_yW z$>L=A!S4u0N$1h3;v8`5xr_atWWNX54}2xz0+DGf#gF_iU+;wOWY6Z1KhWW07|H6B ztdT+!0Nw@R@xuxJAixI!yaB>na`;^U?+Ea}$^m`}hff4}eSm+5@ZlUj7~p>*+pS;W zqJ5Ki4yKG?uVb~>U`h)H8EA#@Gnu5$)kt3jeTS!l>C+(eHd?9z`bWw?3&s261!hHt0GA_nD;Y<)YHBbclPs2rvAhp*59)kx2Q;_y^3{@10L zg64O1FhC4_W3z03R^=9+V8yZdU}cqNf~3fQsNkJ?C7u`4Y*GsV@4uw#;6wSWSy5aj zTxexJCDz%j&=|fxtBg?a1Z8@+ax6#b2rB@X*g0@rJ)tG#RgT=gl&8=V))1L z_T9yx{ISsRU&j&l7x?n2eh$!|!^AA08hLa0YDLf}D)1ep?*K84rh3gh|PPz-g?f5pKy7qmfH;KQXSH`n_soCy#3@bIQ9Yv1N5#qgSsK*Enw)6IB6 zsx`N5lkGKwKeGCo{eoQutVJn^HVEc~*)USXeUm zw_y{~<6^W5J#LNj^lb%NSBWHV${F@7vQfm_lJ0W5PE(~le@2v`x7iL$(D|Qrg*pO{q9{d!sVKizy3ist`Z3ZHc_Y-0 zB}389$E4Egu)7Ls0kH897O$uxGw!U5kF{b|XUbLGU0bwOohes!rd-vTa#e@7 z8=7)eXUbKbDcN4KMNYZ+2sa`UCt^M-)@J{cf4OLw;#E(jB^vtj;V3O1@F;!r1gFD- z!icP@XH1WE49uRZCa^()^y|Ea%a0z2YFH>S9R2gzBeV?HO)Vvwc}Iv z`VRZeV?XeCdW8KRK*T@$)1VTIVpkq!Dt8ul9sBJJfkqkrvmEOi4(en0Yw<7%A92tH zhTp|OJ2@!P@F#H4vjoavgCgH{$MPf(u#_jxDfb80opi~wOP+l=W%RQwp+0j&8jQbx z7tjt{(Y8jRz5Onjtvwi>?=V$(%J_f}R2f#0`B;%IS&@CTikz179eYb-zgg_Jg8jg{ zwb`NFBhRf-ulA#2V_fWrZw36D)8^Yz{_Xp-@*60mq(GSb<9@r375w81>|;*YnIp>v z;(7QP5>EkQ;#v5t1*F|Y=1@oOVBKJ@-m9tU8=mH4hV;>BEqyc^S^RmKs^pDzNO!U9 zPtk&xu>5lLdfK#^uU_OB3=ku|5LCill-FjS*LH=zgF!dT5q-KusLvS*`3sCx{QNR*fjWa~E5 zn7Bm{qfgmsthfc)1DvmrdP*)V%5CWb7G zyaBvEDMLAGi&wAq%CI`*DRbJ6r^VE`Wiwyl$BXAMd+NGQZEV?1nJX(`$^rJu)3CJO)S|#avnFLNC(61Ip#^q&>o`TTrWXag)ksqwHQqYwYP!!)Q`QOC z_lsezR8bpm4AH(COrwLXHNGv|mp1l$(uR`I=IaapnS|{7X~~3J!IH=nM&tu4r*}$D zF%s7Va6iZVTT06W?S4T1xL}M4UlNXENZ~oMOl_(wldl5Gl)2T)X1sLsSw<{E!Rm2W ztU;oRcMN+$QH_Su+$GH*@E)V76h8a$pO+Byo=CkatE;2Nxh_4x!QL&+E z_VGs?YFmky({$BrIqtCw@9;-tDsaGQxJEeOl=fB*ICFn>IXIfdy!iYsV7YK3XW3j; zih4)5i{hjaZwpoH*`=JAP2Ck3{(VpIKHgUPN;DBrjmlu8QfB3+8S;}N_z_p~bye&n z^Wvw=D|$;SxUQsel=-os!jrYs-nF!^iL}Vlsgte<^XtmDfS=2HapM?ok0o;I%d|Lf z-YzlPON^!#2S&D4FBET35gAk3+ORNoXYxH zn`>{ndtmIZpgjNbC@m~%EgoB=bL++2nd$u#v^aZt zm3|)P*`7+;Vt;mt>HpWCA)7C;>ihaU5bf(5`BKmDzb0O!3+wGb9+jfKJ^4;0lN7^u zINAs1=|f;!D)KKX!cnFOeMAzn-^Ve%v?rYe@KG$iVk+Due1gJV1$&*cVNw_??JNr9 z9e=ub)G&B^`vYpFI;?>*VqiQvin~+y^}t-<$Qus9>pvhzO1I(vT`+(jri0ob{K5Rc zxxl{>Y%Idf%R>N&SVtgpKh)aNILCf>@fc*n9RdbAyp5(Do%qUhr}^e0IHgy3pOuJ9v&*<--S#1<5o1>B zVdy7XoV>LuF8Mc904_%#aT6U1V4Uwnt_AFi=(jKZBKmE=9@K9egds4|&U7YRlLk@dw&-KhIgJI_2Biv{h6P^l= z92q?_9R7Ugp5hpw{L4=+x|_=NMwQaD0Kw?Z zJDw{@ZlFvWM)QRYufRW8Gl%0X{s3&25odrp4f=vk$kpq~Ey3NG>; zU^njA)$Ir!uVdxcO!ildO?a?PoocpAr{#=g75{gC{kpMVU-nClC3wFY zXb!^R_t#vmX(=bs8*1wxyz0YH>j&JyaWs|qN<;e`;*mrb2GqFFi#X*J(pr;v3;6aK z{&1dLVE&pX*XO_IN(%?yfjdfnib>c2)yP}O*P1*b<899`aswnYnX=b7UzXs&NdQcl zkOmfjY_4f+_`5#}Ta>^YRp^P>hz@#z@VP4EL*0^myr>L1YsO5`cT4ywIhrA3c_`W^ z_QPogRtFCwRg+E=#D!LfS6I__;B*_9E`5ZYVB#{Xc1{SS9(<%dyh-t9GCA}ll^|YR zs8=NCx5M#<%e&I?hAX<#L8ftD^2POv>49f=4O{$%{y+~bKn?v=3t|Tyb8)r%N`QUN zV4(wuGZxz)A78qGJ-&sHkGExy+wrmaJ?!yWd;}IkkK*G*cwB>z)8KJ=E#f%^2mcF7 zMRrTzk=k{0+9ptTu#UFKFjUrW#aJWnnbAVYq|d!c101 z>KUAp<8PDzcW7~+gmFTePcw0GL7B9ZIWo6lvs-iD|2!51S3urx!Q|wLg&6ritx_Az zJ|~^mKJbl;Zh8tY5lVd}j{hwT|Kf$huC%?qTIo0Y8uV_Ug%<`G{vTEe>sL!WX-C9a zUB@ws;m*RYM@{}U$YYg-g}PtkVosL9jD4P>#8kL4#;||pH-MPoy}+b%8JnFm+J+}X z`2Q=|Kl9nz+?&xOe3icx&!j7hVw_ajwUo!(ni&5|&KKAY#A24x>RVoYYFjyCjms{n(>i zmM6<)d9r0$p4vbx%aP2e5@?NNxd|Z}V!Nnf+=N&Wl;h>9z_~Xe099;4n16M0($LBY zunae0hHyl~k&yi@CfTUO%cI^vTY97&t-(VmGl%&=_yXR>R){Cr68I#r>JQfQ-VevU z9gB6Hgs1b9gvlrz_YM^=hZCopa! zYSoW18hd{ZyE)i*ET=(gJJe~Alc#}~B1cA?`3#!`aZdzlLhXqx2FdDTp>x-%X_+hqc&*Z#ZsPPhc z1ynvDj#jJ;(pmZKuw*5}Yhj=~oE$-DJ`k#&p>gneIA`dQ!__m?9S~dLNy7&A2M+P5~qp^92wV7u00G0;7#PgH55qOfiMVwp3H-7e~+_ z6jjspKI7G(AL_&tbdAOCzd$|qsY@NGd-sUGC-uS8+#XWj@F%sPlE-0=9x^1|9HpZ- zaq_E&aKGWbmr42V1%~f#9M}kEX{eAT8TuYTrxvPM4web*S%e2#mUQDFs?x8^xP2hp z#_%6suC|2f8FoEx=Y*}dC!Br?rd{VU<$LVH`B%AC&uz|~y$`$StG{@#zhW>2vQ2jd z6aB{qi-n`%U&*n5{|tsO3gkwdD!;d9InF1axl~AESXlwS9g1%}HbmGW-3)elr`gc4cW|8#x>gG17yZI2^l^ z8P%o^hmZZn_HsCK*{_`adiG{%+3!X6`MZg3+tR z%DkDSNgd*FoE^$|NoDx#?=JTD;o%O)lq*@87~X?@h|aR6^gF|T$D0wX7OT?+?AM0< zy0hQ9kq*bt?00PpeUJPBPZ=#B+H+TI>x?$koLZOK2hQ;*0HGOI0wy*$TWMhu@#-Q0 z3KcMH^Ow&h9*RmZG3!`Woru5xTN0(!`JKaMJS6%=pYYP6R$ryi2Yz8MZ$@20aKTB#e;prS61H{EWOWz6v)Nv3d&mLr%C@JO%T% z$`XJsRD!w?@qxO~@+?w(1ZXssT?65E7mn(U8Pl5NZ5&4SQmW6@!yNj@CGr*T>6s&6 z(Y|x@FncL^1kQ0%P{R$2O2%h7NSapu1<6if7#A#aHs_A0*l{IKnF* z+47pYCcjcii=NKs(jZc=Dnyr`HIcpQ;!}8OA?K(FI4S~;Oyr^nIVus)F1YOpU;jgn zOwLh}#*vqE^w?jrJIOh=4qI#b|I+)v?`UX>Q?Ja!;puSHm9bu2$xp#D){Dfm3ziJ{ zO04%+{3_N{Tq696^kzJ2;k%ggeb|w?7Eh78pjDs1$?_=fO-YUuvL94ni#!A>aLK~g z8kav$+Eji1-s(f>M1_0}1wIvpcue>JKPF5|xunQ;ysZKvFCB0qx~sqMz>@bdZ!fE< za9?6W;~}ns%VadA=5T02B&DUYwM}U$mbI(3Q~wq@<^oReG8Y`QM(yROb3@R_m}fnj z5cA^O;^AO0Zwplh7wFD?_hvSehspVbmolEFwD5m<`uu_|)PhKvPnp*b{{N%-Qa}AK zH{Yix#E@A{^Zo0Dee*pc6aN3#e94C*ns4JkQ59MV9{b%PG|Ytqp&|N2HE&l-^Y*Yb zZ>ptv2VnE|md%?Yn|DM+^Ok@D?NwEpJ^HL=>PX|K2eQZO73dA*O@a?;v4OlB@WB3t7ikaf+LWKF8XESX+4o z$~vMm-uoYn7q)MB48-&Op#XL=9l3!Nkv#YmNSjJ*+G+d8KXwU2e?oQu=Vj6S$vC1X zRSKS$A_;hpG7MDIg|(2F8I;IP{Z)!&OH>Y^2K>d9V(1W+S6FGOJm1csZzt@c)Pe5Z zHr!EQ!d*Y&IVg$+4);494xM!`Di1pY*&a#Q$n)r&TiDzU_jzR>sFW<_6%uZqSEh$I z-85qON6?gu>3~Z9lX%H4*qhlzo5an>7KeQBYzpQbs8sh{%?*l98O%%8G7Ar9O%jvY zJ(!mgJ+4FzC}J2&WQk%kJ^VG)1Ia2mg)*dS!%h(64FAu^olOT^j+{{UzV?dZ?LSk! z^cxLLGi#n6I3z!dniCGw?c~oHapukM!nWO{d1`aYe0jgimb#l$|K|Lu*NUb-e!t7{ z_lgfTPPpg^%m0z8d}LoQzlFIy;V0SkR$p%4K<{(ZyLU^| z!>!ezKE_dNg#=ZaZ{X~MZbAgxS*Vpx1cKe!QM@uNDOvheIG)JWRwH1Vmrrg?#jkEJ z5=b5!B`3JYE(599heKu{+l3=pY^9i@{yTb&<8PZ*?)g9twraf8E2Xz;w4bs3v8Y{V zj2BM{D~^ED1HHVBjOkSpz^}nP7uF+`n$_cz46Am+@@MT7kkA^UQaf{jnu&cosE**j z@FYG5rtg4ZVu#+XC*#IrH{tV)X((Ld4y!6PO$tjm0Y9>(+z$Vu9gfVW!wsT%iqeQ` zvz3*lNdKXhbfUl|Ycw-CVj+VY;XjzHYNqu5e<>o@6etmCWhWvHOx17n3ez>;d(0=| zAtX=cY)dWgmvsqT?z61sR0i9BR3!`ML7G6?c$OZ!x*Um8##8MewY+^eUH*sdGwywt zBXq2%{Q%VL$B2Q4hvRs;XS9vE^^f){{O3Q|&|uAerwxs-ey}$Oul+%!OVEslU3*$g ziY==tL^?8%UujSJa{RhWeU;kP1nwybb`3-z({VmmJnqkz7*Awi8Ob z8~()&w@dlf(YiOFN9@GBs6yx1vHisNqRdy=@x4TT=HAb7=)M%p1HyqAtzP0Tv6x!_ z?4j4YBG^q5^bmKnsiqOy)gz*q#cx6}cVIDhL>6<~PAKLMt(d8H#eBU)l|%Et5}l3Y z1}~WPhWb~-`n(d2^&NJsJA2s6M&A-@%@|#zi6u?VfD%gib3DjJ;gDWVB+tnVB#l5F!2^zF!3z~z75!k zdIx!k1o-YL{NT ziouO`JER`IRxZ4P>6;-6doX`m{$aSEB=-+udoVp1=>4S1hUIDVJ^1{v(`v$i=p3dvP{;#<^Dy>QC4%Pg;!w< zFF;vcUW2UW_!s3w%j)u=FX!(z@2UddQX#8LWlgR^SzT@|#r5BktS;y5`<60UUE1H5 z!(??27Qs@I7m?C(ef|dm*$Z(Gcp=~0-J3%ao1{Sh*bYOILPOEb`Ja@Hi*Ak(T~01S zdRIZFSMOHRyO~RG#o3yr7h4qwJg_Fpau7^kMG~ANB{*-BW;f_}zg+9Q)I}H9jaG5Z zo`ZD(|7t?pr6!@p1Xj5%J1YnUvP(75EwzX)Q>SM^Uc2F6M`qK4>;=+~GlM^ccB3r_ zFf6kAL!_)?TK{gOkg)nii^NiL#Q2+Si+mJhPv_i(hXwIj2rzyI8KxX8V!W;h#h8B) zWyR!9qS7BZTDUdzZ!EG+;qx7$@EYO%Kp1^lgZM9+<_sy`0#vXK#9N`EJ$1ob}He76n2cT^rF~CF zDFu?~Fe_2evurJIis0$_lHi<#4{kIRVFh_9F*mkt$6-? z7boyYj6No9h!~S5p~y}8yJOO%s4>YrD+mR$OSCbmByvm|kJUL|j7c^{p)qNE&0~_* zGErj^CP$3F+ZD;@Jxv>v()2Ou1~Dd$j~bI4M`=tNU!|87lRJqDiGduGYzj|f(s=8b z^xPLvt)sC>qj~2a8<1m?0QLGOj7bU&$D}F$QAO}EX^K83jkX(;Zs0t8@~Rq>Ht;dY zJja-HgD5HHfeOkCaxF8t9d2VuVN*L&u$6^7QYJpQ0(qtQ;0ffF;RC}d$A_dqUIjiR z2lCF~LyJJ3L+nV!iXADJ*pc#RMt-);IV0Z0_L^hf3i(TxDWpK6)0x z`4%8G2+r4?zR*d6cH)Ka`y&+|r4L`Af`||~5%U+1{=$PQ35!>evVY|f31V2Re*ZVE zY5GoBTeYt@!s0%-mp*7JH@Jgr)_Su%!v43U7o*AIzXr$&zM|}*2R{iCLgBh2l@stI zM^Q04S3awa&g5%34(>||w-Qhpuu#0ZRCSO!G4>T3bE0nT+xwZlIZ^S$14JbUv6V6U z3O+&Vk9O0+_J{S|^Qt0@+!cJ{74a1Fl<+}EFP8hR z@TPiF!0X?QeHyMaM4`wVQT1Q2T5kN8%Mt3@%HFav^;4+bRr=x$hsPCG2%A;<;H=U| zOi-zEj26eBLLcA+)hBX-`ZZ#LLTafw0jA}c{De$r*Mron3qaV9D5aC8%}x_m>!^y zUSOdF#VI9xUl6~D09^ROXR$|`10yRcEfsYb!oeU`h}Qoyb!MWTIwy3UUGhAcJbYEu z#|8NDsP2nk~dHtBBz-} z{8^JP*Lt5WpRaukiI!I4p=@h>BTPdXE#(=#^9(S5_^h=j!+8;Cn_;1)nC|ddt$E;u zb9Jhzy#cu=@&JgTTq^-+Aw?i3w6hutWd{X?@OZoLh4{VD!Z%DtkiswkZs(eK=xLkM zdX2opwhb)mDDP>P!&2;8P3;HD{s-;1cRovw-kM1{s4;Thz{X3$BpCzC3r z!OMm!=pnc-9us<@i9M6YKT^|s2aDMAcAcKX*qdEV>St?8JsMJXuO{{F)ufiesUBl1 zhtO}0?NfI-N8WlPW&n|`)etH9P>Ge)^D+kqcU$;DTU@kiboB=;@j{jv_m;hH3(M&W z$jQ)>!&&%s(g|6MiZKe%8#cy}r{X)0h1fx^#*4Jo__QbN<)F1HgwFRIm)ZP95xm6BB3l%ym_hI>@lmzYf+7pZFQs*WVfX_Vg@+83_Nel&P%a*D>Mzn;P@=LGQ+ zKz7}$MIq^AZE=b8^0R`$oX`@?QG7Y}3_mg{gp!U-9%!Zn^WbBeVA@?hrKmlzQC45F zDj1OW%(KQVX(Krb!686P+b2Gb5lduIc#^oMWsiZkWgU0;!7=Oi^bQGB**Pu>X1^ zMy2txtwQ6y_G9R(eSDFzNuqY~>Qa{7rb=&0Sj{8qbU;0VP1>eP+8tii0cv&EA2bpH z1%c=Qns;jn)64%ZmWRP}g7}A8Y&vhTTBu@7uM{?*;o=1p(|(kKzFaI`PSb(W=~$uo zZ#+iXV-VxR{UKOS+Ei%;_U~>(LeWZ%evl(9aIHX0V}y z)X%~HP9NxSY#M0&_W#50;z7D_?BR6|noFPKn9e*QToJ_})L>{^=bSf(r zDoS7>O5;tRsuXi@l$_#lh~T7u$POBAujHh^oRGuyIFwHsBkM)!c3E`jJ(^Gn~o(zOz%ofK|Sg5hu$ z9}W}pT>8v3RT!NbX~5|sUHhOu{oeAZ2qwCj6WhB^a)Iybt;FerVH}`!m2B4~_u_^h zbi&>6uOnlM;omh+4}CgT6fTJM!gv`N#1ynk)kD0%n$ybVu15ryg`CTF$R*j3$x7GA zu97?j*)6KbQ3>wP5cm*VkAK4W0rB(-Y^w;pD2Bwl%}+ z0mMpgngz3H3Vm8v=Acx&n;)UA-ZomOj#Ia_AK0pe3 zxhCji)`Cb;N@%+J^n!Cl{Y;(%Agq?!Ql3d**6T;ik9B9NzdLstuHN|5XZ6exk!Xv24&R~-W4m41GdevF# zW0=P6z=xpGt)0c%0p40KI>49VAY{2GqYM(2hH69$e-*H}36*3s4hQ5J-cHdv64*M@1p zQ)N8?Wr@`I2FOa9t|_BlIl5Av;<8(5KdF`d%xah3yVU^&efba0Yk}+})MS#lx|(jd zaP&)(433uCaT=F^?i)a?6PEv3&G)t&K-j!U*URZv+(B!&9y|enaw9q}cG{(GZT?#g zYB;izF_~!bbw?Wd1WbtV2lU~iLz2y9xH8ta1+b<3h4E#><{<6{Tj$* zYLxJ&AS6X*pUSJ^nLLt3GWJJ*&x1~$?ZV1;ah7Mzz_{`tEh!vr)NI=YuZG zw6{=~kCR%-vaq5A;X78U`~|EkM>Kw_f4?1O?)pcrhX5{TW zTcx(TsP;a28fvc(&cS_9rED6+SFHVqT)m zaH{H|y}AfHh`|3a5DH{dRp{Rxi^Ja12)jdQ%?{CDp)C6$qTBf$8XUJW>d@dbIBoNQ zPR;2_#P<>*ze25c2pKePD;L25AwC$~T4P?74q@mhy7vA8UpYBuOue)&dAX!-1Q(a| zP2k^>z8Rcb(zk$TOZrxDYf0Y@J}v1xz@a65CwQ}@?*dnr^nCDRfk?bW2!l+*qmjN5 z_RZ&TsL3B8V6EYIT~5#q=94SML4R?}WQlZSnRIDghrDI7MBXx4B5#>2k+)2i;GtG| zS3rrpWwJ#2tW4*etmBUmiIF%F0UBpBmKkX7={)fhpdSq3^DqD$a@>Gs*3G#_<$fsM9b=&rGPAPumX#Hn1)3EV6%`c~)uuy*#x+cZ|E!Ps*oTAK z@ArS+_kEuCdBn5#%$nIVYu2n;vu4d&DY9N*44kg@pTAgMj|MN-)*zIm%q`?An_J|Z z$WvCuFw-9WFpSw-zzs_P3~uWzY>~D)Jd$YhYg;pEtjIjpn#CixL?blMFn0|W(9cq8 z0zliOmh-E!qDQlV*HfSzU*Us_%m9&r)AlIwKxCVWPe8WGkm=8RKOG{gF{K&QU<5E-M&a4;QpP2Z=m9njZVpZX z?mB_VV2b$Kl#^KZp}q`X`SearXm$zyngL9C=0L+AqM4&+ID~8sXYxf`p_S;G^%O~AXv-rTB9kT% zB^ycE)pE2eYz!b~&u-EVyEB}lvXtA-uPz62Xt1rc;c6Kq57@uM+$_n0z10UMdduD9WPS#d?#;JbUvcV25p=b|Zv4tm1vqVmed6*?r$(uZw&Yt`^tFLq_r7!)9;kVZ;L>Gx;ATI{v-z;;m+8pexO0E^POryhOTdTicbKvG z5c07*u>sU#FU0qT4L@H=oP+CQ7mVB99-Xur-;Jth>Om^{8&%YmDiTZ3L2K%Kkecde zyK72_Q4^Be^Ai=htzG1m(RLwt^`vwaO5RJ~kgUuZSm?!>`)i(#QafiNI3-n7`oMC-{yd%F!?sG9iqfehabyrtDXVc@@)>FI>0Iy6d&Gsml8}u zm)qpiZ&4wPcm-~_$?JRr;cJw_+%VNh0iPi12-~w-c<*3D?6s{|%V`GoyIx4@;6&UR zLS7H5y$}QeEAcaZfHJ+4fz-%&T;|SjuG=BIaJV#MtaY`5S2&ci$7Jc>X4bT)?9;E>Z`6WCAuhpx-~Xx|FDw0l1WJ~)lx>b|9=npC%m zbsx!BsJrrF>2y}4h;G|G+y)f~ZbhD1w-V`_;*$#Y*+9;l{__$M+_Q^0XkSVg1Iu&a zCAWK7ZLqS3?D#L+ysGjYw^I0v(gcbm!*Q${$S=97=3lpA8zQ2fS5!Tn-q!WpaVs(E zMHkuDDLoZ)OW+=^x(ZLZR?H>CEx8zlan2QI!D!ynpZM%m z18F|_3SLF5U^?(J<*DuJ{%W6k69S{~%0a;yt+w@d%tBvW@K}_L>oe45XYT}>q>3Q| z#pKlgZ4inmB3=g`RNYAr@73fxKL{PZbAXyTclM)cB9p|-xnqDUNza@Q?m+r>20$hG zr}rcII~`WuBT3+lH$HNZv8SrBd+Uy-zQ`oey*0;} zNzlD^+`WevQAh=|S0V0IJF{2oH&L%1yj}CF(o7){e|Qt}{i93~l_b3BZ%~n0w?hD_ zROamY^+0$|K_)D*r386*Iw3{~E2&~rA@)tqyd02fALoobdX!qe`}?xM2(GC>%UOV0 z&LybjT#8!GV$^ajLoMe+sO4NC@P@k4!KfP@2D;JAkww9Ew)F^q<{=>?6$MvOf}LFe zI9tnkoAFzjlmHq3FX;6AB6l^4i7S*ny+9r+WO2=dPc?^$ANl)I!ISutw;EcbdZb{ylHP< zWW82B$!qph(q8Uo(Vpu89mee_0fEBESLngmgAQ@@#<9tsAH zTL2W<<0HMqZ(OKTm_yx&IwO-KoscP=%S?&qBt)J=r|ua1iha5(jVwj=v@YU#e$zj^ zs$3<-must?CwK55igHfHsSrPjw3CKExLp->I7S_NykOTcUy&MY z!RkFZiFJkXPJx{6Q7gndY|npe3W4_{k(T+7aApr$`uf3|2&K<6gew04)gu; z?#Xf_waC!OU?ic=oPHstBdK(iUx?31VyfXI5IEmR2~SkUkg!OlX0%j@y?otH0yD;O zACh$`3Id>#Y-*Qt8LWZHQ1eY_5zgZ%q~EUU=qcI0^p0@`gI@qRG?QlB~Hgv;(zGHv0CNg6`5@P>Nhe0 z)vpN5K$ypB%-SX%@Sx;+5bd_H&LrnZiZr37d?F4U>+&?|0(NM2PJv(pd?Qn zl;o*{l00=#k|$RqJY=|^F~mrQ2z`%GZg4Z4psi)lP0{PB`f^8hAr+qwE_c>PEwkAb zP*pvkcQ0<(<9&r2kRyskqVQ>B;2}Q4VvM(t1rz1(@CdFe?&tB&62pHH=9A=A1(H{G zDLzb5WC4=bBisO)G(i&rC7FU{9~7vokmYfdhg*;vm72=owYxQgIlONeTl&p{>Wf_* zxio(pNMG`dVPLeYm1sR9Py2CuT3T3NJ_>cULl0F@Zf}!CNr(|aCjoO<#}0AbG2AE~ zrZ%`#0k_Y=jh&scN<<=Tq6-vs}&C3P;Kl>j!NqDwhO5VwRWVdCR7`uXVx=^vJ1FV%~ez z!J(G}v>)4>%q@tIoEkX+`osvxPL&sE6Fja%uW23=3DAk{qVbW_gqS5utDIkKtE@c- z0YtT23e905RwixGVrBds<9zU9IQ|>!oH_z1VcZcNNxdvS0j^$;w5K6|Rz`+X2c@!=?J|HnVTLpa;daj$vs{w~(oy7op+^j+Aw0Pq9KsZ)=jg@>;#7CcKs`S?{{oeAb9 zDe8V=&|ef^wn5HeV|MJ7Uk4CYpBNV(+7ws`2H!U@(Q47vw~g1LH0S0y92MVqzscEbzKoQhaK3OTrT?&7(4-73Yao zz|F}GJYrwW1U@!a+O|oB&`m>O1#*Z}sfCR2E%z+5fo)fy6wzQ?9paLek{@?fv{NhU zO#81XD2W&P&3#)Dzmb4%GvqAXk-=V$0;0@Sdo%4y5lY(2Q+Qt}_iV7oB`1EtdfH9O z?}Ig79$MoSpf%nS)Ke`*JykL4sg|Lh>LJuqtw251D%4Z0Mr*t^wza%7>yd2hVSW^X z^R4{2LbV?>Sowan7m$xtvh%2tT{}5-Evt_Y)Fc9 zK|2@#Wn_lzq!GjWgUit(POz9-JpWA9cEKF=+!HFND#!5m(JC-aAq>eN6u=;wP3AUHsk z#~d?O%>k&Ly#)GH=Vc8_D;E|*Fqj1`c=ng7rm(H%yZmk^}bNj{6^QY_=RV+}SqL!U&2Vn>4> zjY9oYA%Ouim8o{hJdGj(q@S>~ks6L4)bX(XV*Kr!FJwx$pm#*`MXAz2Fhl3k_bzi_D9J}VBT z#}r(IS^CHjtAA0MZPgdkt}JVsv?s<5-U*kAahbSH$PKAs+GE1bPFe+kTJdNP>9Mb# z0tNpA(j^+aSA$BeYtfX#a*gcK!P-zivn67&1p6Q-PNMVHELlq}8SIuD8`Jhvy);t? z#?$Dh(j#6N!p--EUXL8;8P1?|80f1I5dol(Be2-$V2ZEsZXEE_JS-9`W76Y2iZhIf zmlAiSvOKLKs1cZjyCHZ4+p%h@&8A>meAJmrjYos}+rjT%b_IQc#k|w-F*3r+O^FK) z;Z^mK>9l-(vx~>I(OZ{*>c0>TenIX-^+jC8 zVcv8yrP$g{D8**`?UdzMe|D|4H31|7kYc%qaAVvr=!hj=h?s7Vl+ruKP4-(`;_R_D zCQYo3+gmh6ThbP#WH=v9_Y$l(F6fV31^Uaw^W^zIYU?cgTFRChuT|bzT{-ikcS@oW zQd=03I*!cUlf0$`jAXXZ+1k6g1x91RaPf&TL4VG%wzcdx8atXZDXhJ)OSavqNAct= zXocuNyw%jjch}WkLkByeinakxA%BngbVyaj#c8O3Ja5T`P6383&x`ACM3A(^MSs(>sWXCEPWut5s9wH0-!=nyf$-<1( zgH=@9o-0Zfuv-sNCEbQ31O&{Q!=YM9{buEN$;tyK(x_K~aBdCsRj~Pk zA@b2g{(L%rK23i%p!=E%EvV+kh!#{uVW3q@GN={l;ZTkENuyQ-!l5ehL-=T;LCLZ~ z8C-d~Zcv)WLm)h?C{)dDQw>7vnaV>^*1&LXt@z0x45f#2s}XIvRpLjWt@2RB2gfv6 z=Rie#8M=|_x{+zRk%-GGJ+PIBrr8QfvxA8Z4kXoX2HiYJ2dQF;Xm)mGE3%$gp*f~* z_WE|*?7^zp19h`AbhFcSv(t351G?Er4Hbv(!A{wOVbp`c)PsT4gAD3HI`tq8Oa!7k zlDiX+0-`$dQ#^NMnCi%2-I0O1BN@6Q>AE9nx+4MIkyL=)dFVcs%RY^yJ`JNj4W>Q~ zq&{U(pVFyMX`oPQ#d^wPv7Qv!wUMf8!*tgM>j^nfPe|-#m7Xh=dgLl}FL&x*mLIy8 z+hi|CQ7=bQFNaYt2U9NxQZF;8m+92YG%)FEMUdNK5oDh@?(8Vl*^#=l!*pi{>&_0; zoz2jlP1l`G)13|I&Zf#v9irc*vfpE=-=nDCBdOoRsNaLB-vg=N8PxA|>USC_-dmAz zX$&%s91|^{2*KpCm0B=)>}*i$EHuQN{Q7I*Xr?Mv?7{Io1=SiDn1n>Wj4e$GAhUkg z2wJVtTNjO!&xNI-QIrpt|A(+J{PhHqai9FrlyR3X(~U?w8&sG-@8N^OrZ{kp+AgG2 zZC+Lp72DR8eK#Z3XxznV`y#epotqzNSAaV!dDh@}6H9ZTgVi`8D z<-2&4T;Xy~FpY+A722p~DM5R+DQK@oK|7$N>{YF$>}yftT#Zt80ICb7NfdMR7l=bV z9*;vH+2-&Hw)JZx0c}Pn6{XGcH0L3Xo#ny=< z$HOOVjvOdI2;>R+IQf=Z1TN+9O)RPuNGX&ks9K&zonaJBG3yBwWhKbBvdj!ZS-L*c z815^8KuF?YaT;9eDrioCw?V!F5UIfev0vTUo#5$8UqL1&UF9pd4j-oZ3f>fD>jre9 zO5sEVB2hNyp2+0>+d1fni*^yOg5;ee&yH7+?d;lRwEcejOd!HNdx$E11rFYtq<7F0 zOd>TtSg6;aUG_5CHsMBZnN^c6=yr%47dn~st4)Y`eJ>p`@DNm{S^tSQjYH)dBw8d`LLryJ~n^iYM9$7zVN$ zaaRrTAZs!Y7k8P6yQP4;YJodwI;ifbPpGE)l|L?%CJ-KtBn%3Oy*WzIL*1Tarkz7} z-r6NQU$-ThExQFrU;Crv=mBD;9~sQ_gVViSyJ2tJ^E@w~j@K=qisE8c^runLomf#C z%)WG&?F!boPS@BU`@?)I0GY3!t};8w&iajhox0G&*SXD7UW}25r9SSUiPJ8@->%`w z8P<-tqO1&a&fM*h)q{SjFYBjrGrjVo7{5|eMpYym&o_&Ld3d0hAnn2N%u)Q0ucUE1 z4&z@Y9E>`=`1w4bjw(;+33q=UJ_Yog>1b=_B@snw89+60$yFX@vLJhuGhes05`1t} z!ki;(12n`(GX#7F?IVTxvueOA-mt?VXIPRbdLXp9(i2))2g(ROkF%}&dCo%w59if0 zysn6pGHew-OK(54^mEuMj9V$gR?!QGt-@Ml*eZ-l>6aC8$PRi*F<;mPkYqiXnh`CZ z&#M(QSbF?PC+DiYY^#|{fFPaD@uQk~n*Pa^ktIMgGZ8Ok6fv;_Y2D#b)AWDhH%N$w zJMnyQr=S5@uT-Gy1p0Vyj8}CI>BNf*piI~)7C<Qk73+(@`wPNqtcr0(_R(O1cb@7HTfX%H|^fSQjDqRTQ zWkFc$Ae6M3odEY1kcWYI&bYF2w|RU8`*C})&8P~@Tly@g75bR5XeEyYgw$R-CKyKlAqFBd)0uJ>w5t?s2nBBsmJR~xa2whF zFP6>R7#YKyegX_XFcB^=lF+TAb!)sGuNn9v~(jkDK4$f1gS`{aw zxlmwjz<1YqLjMAS)h+vZwp{7qgrEXW2$m0PFGY_e|0&T!QEOTmJ^EQB+3UBCzF>qMo=EMYk-Vp1#3l5Gb~mo zhTRf-YfrEL^#u6SxGy6*Y?fSy;NDb9gyM6J_ok7F@6?esNc^ zG0yD{E7Mb=iFL5`18bNw>MX@a4__j!kbGpRSmT|zQE1!pm*rYj%UNf5T>(kautgT`l#YV@l_0{?=J9Jr#AdY{gtrN5T>)I6Tx6IafscVR6Tqy_kJ=lsULGmW+s2ZL z;q_0MaiXBbj(K`q81;>@a?`-O6dH!`jucryfLK5($+v!Ej-UU%kZj+@1*aa@ zaj$f(R*Mg*cuBOLFNs2cbsH}~dTqXSNqfr4z0k)Uo~vIkTG1{v1g@lzDBAYdE4~w7 zpBM+i|6gtRawysrcc(*sMCuRveFbp;UeqO4=hyJl4jpu3W8x0cm@}cqQdtb?mE*?^PLUhY_Vh3H_-*!->F zw`0Q-op3f#6dkHG(xFNK&%yDM2s1u^)b4C&J=}30M+OHMS@k33EgCRXI~;M*3}D~D zdY(pQLg;U0LWtchIaN9QYWAZ!djPH*sp#BjvF0b!2+6Gtmmx1eblcE=!=2o(9yrtV zdcef6^do8aupnR0nuR0q3>BqWG$?4auJgv(mgjp~SsI+u5oltv93AL^#Rz2C+4X2- z3swAk6lq0px}FuY-6_;=uhe7_!pQ;nK6dNaKs95v+8y1+0{O$>Sa#91;tPKW9y~U7>ta4z~h>j z#aZ3U@Z75s*VM{E~P|2A2gIt2N#*0#=J zfVPaF0F;fOxRhwX6=Sts-7(~bFm_9?YUV+!rQ&!yoMK_N&q<1iLXDAeoA65BDe?oj z@IbruH*w)P{aaU(w#tM(lxYZs&i(N7-*$>M9;pW%k7aw+eL`8yBT?0O4x^fhd-a{c zan3yl4v1xK^;axVz_O)Zi4C0y&L}ZSN2njR$g3jM|8_h`7hDFfQ|kJN5mLp&jf&Np zfXy@gAoG-bvsy5}MYgqyslbtsVPh)SQld>>ce!%7h}1wT7ljve9D>WP)OHY3l!ex) zCLo7;G_+jx4Ut2=VxNMVM<#%;!u^1(TdJu^jn0{a*`;D5U$E(bkE!HP#p5e#4~G>O zpTLf4KpLOSP85IyN0MBy_ zW)Wo1!_lBG+_mV#Ep!^D;g!~|!@espZBJap20 z6m`;FT@S|yv8|TtNt1Dn5Rxp>RC38YCfK-mr_1jw*L&lB=3Gu?lR!iMuT{iNyyPqF zO-%fp6H|2C)}g z21WaoH`)xhccK1GxDe6`R!&Rc;3~!2#&WCE(X-Gn${=lTu0O$7@Hz_SD4)blz_@er z05if|qJ zjFDeZMk!{@r3A`bSLVEMyKr=^sb%GdZ5w zjp51Tin7up1N9Jt6T2Sr)h@x`uHe~ka=L)T5A<=4ISwc@Ylk$rO$9%@@+DwfiLof8)I)1^ZqR32J(E;EX- ziYg|N$8Y4xorfFs*W|2*G^+E}ll1CNCGF*@tS#YsM5|T~SBAvC!g~>PSXHvJ!Cv@v z2hyI?qr5;9a2dL$Xu-T%xDF!Q(bLVL*Ax zQzV?dIcieiJ|=5Cx-p?i0Vh5=x2o|FjlXWJ#5rz?BHfYMPQ^X@SiCoijz36r{$5YR z`p95(%gu2T>Q$VCdN?pi$2Ae9C1fUa{osALT`Q+Ro}g;xb>Pt589xwY12MJ*m*RfE zroFGQ8*bmDX5uL$z3Q5P8g`L>NdShS(zb4!L(4~hm@Fj-pdwklGn5tdl?kMh3X?O& zFXP#d7KB0-qYuG0oF03iMmQ5eF=NqfR>?&Mejxt%)aJse%<%UPoVCFvRPMfuP-T zV2R~JfW20dFRQk}uGe7MJK-R%D~zb_==J@)YRHh`jpad?NfT%?8cArYL-#+Ahsw*% z>z`sV%yCxkg2nLSxw!62kFIzjF<>LLr`qV-%1?S7A!fXMV=OUa#4*sgr#__fwRVdp z*<|_t1WE}tFq5XhFA`1wpZ1~d6U+u=WErs~`1@OOQ~%nHhW|6DohqDy72;!u4EEUC z(Iz|mQG+p|-6A32R4DBnr~{s}r{JOoP4pE8kn%QssCculAkj!d zb%A^=h~3bW9{1RlXSzmjURv)93(@n=CK{a-tF zHEJu$N(E=J!P@Y|2HNs8x(zu|2DtnWAbJP69rTBJ%m#W4I*|c~QAy7~KoHDPvJT+K zIr%re>9?{zV2)#3Gd@r##YZxXVF14KK36RdnID)(p%Xr2G}GZLDqv|r(ca3&eW>sa zk6_V5F4W)?HfpW)2gNp>=9_-yBTgeB1TzC=^t{*EFsnyxNHsz7J;NhppD=<3w%gC`#@HNuVLkH$QjyP6$ObSP;(Dm6 z7OhgK3CPGKj7*w9oiviPw}vIh@`bW(N{>RS!H?^Klhy5H=^Z7ZLGEo;Pxos_1}7(i zUa){9Ybf&myak`oOxhb(R{YAB4!37OBar+sGpNTo*pdr(N?zE>rrGiE0I_}S#xF|; zc%}G#dLL9-qDI}1>iksXQ2|u1IYp+ELiTog4IdaSZ?*H<8G(=g`YxUj8Qz}~JkpHC zygAbG8F%@S&Qehx)!SnK^BmLy(j=pyk*bV0t27*`ys;K48)-D=G*Ou#o%W&rv|2<* z39_xLxg$G;a26Sc{g*K*XkXi^#8X`2k$pzfi3BQsKG5QP+Z)e^#-l56v59?rv)sgb zX*0BAo?cka?@b{=+T$0@Yf+=Yv}Z8#{=2U*S4XLSmi5{tM_#KdJdg(K;1@g8*t77t zQY_vP8Hl8~9}YN8z%zUEqIJG){kVg-uqcQ11vPY3;wgP;e<#1LW3C=+wBnXDUKxi< zU*@zLrpQ;ZB>I(`b2!pXXGN(uZc!I<#tzB9XYgJ2lyH=?-X&fm!kYJ+69JWyep>F6 z@o69VHN$lP{J&zM_+I=0K8$W`Em<7ZZ0b1=UFGvVWt)vkUJXd0#cya8vk$1M1F8uz zw8M|O;LMyWqwB>c9N4e*-mu4aPI6#Up7^TVvI)LAX$SLb+j>u>SJ>92<%)r&2##f2 zrsboB~5qS2op9t#D)dg#48&Y{hxCCjVuQsO&*7oiRu(L! z-a?9^zQ~M-m*LK#8IGb@EiS@&gA?_Sy^d<=iL)*lDfo1;S9IVkEWOD#u* zxN}qkCNUvw;zwBdsU?zB=$S5UuSzgljdAfLvL6zu{J2iny1Fd5g2a|J^s}D34FiIQ z?ueotQhLej%QtTsFK8F31_w@)=#l)fUh%53p&xJ%kBFB8i6rBk;R>j0=uD=NcJFd7 zqbh}7pk+6dEB(q1Out3kmW}kN^@((z($pIWSC(th<+doUP=h=9B2L#|ZZW@n;;W|8 zzex5v!?yDIIVaJ%6lR)rv_?as^BjKvZC$!6IE2v~$Je_^5)I?M?JTd@of1}X^1rIp zsLn#Hei9D_9P*4}&f!-~03y!x)j(eI4y0b;lXXXfihdo9Gy6o+9mP3L%2xFy70+%H z`CZ)v)!coY$K!%wW12}kqf$(QR`bnWC*W@qciRZ6>2h*z8`a|h=vbMY+mSf)-3#`;sqD`I zpk4k`1Vy?0rGPNJxJI6@_*HnXA97;qKf2N}3x=YC^T-O2F?bI36d%PvBuY9ac{RNT z#AC6{_XeK?LGPfgMHMXnic+(VMh_m5Bl#0k@V$Y3&+zF%T{?=Wmu+Rb(8nquWsq8A zh+G`1@i?VnVQ+GMuJ-~Co&uz!;G9^u-GinXA{nfUz{iyDB~L+V=tyj9VjVaq-V}n5 z_r%+IkUe&&$SC%(AEEXr1!UXShAUiN8*Zgf47aV9-5E0}qlaxhd&mUf7kvfi!*NQsS0*WmLB@RQ zfsH#EznM%uC$#IYQz)}O9EcDa8%d-_>_Mam#EF@if{4sX#zP?t)C4qB>ir)%@M_R< zxaqd5PcI!1C+b1DrG(6Y|0RXk3|$^R5osLr8xP*~Z(K$$`V~@Sai7<#u;Qd2dOVue&pz z#|$Cps_m)y@t*W?jy`lM`PV;*Gne)Te0$?6WW$Yu-|jODMjswROq2RAVJmU$8_8-* zi=*^IN4OiAst!rHzOTCxuHI-dJwva-I(~kg<$}IQ`}As~UQboqxf^zKl7BO-JGuC6WvH+A6_?NWd`&X4}@2&zgeFpRZ{_ zX7_7KkXhz~Rrmg94MFAyrs21?rN{oDNJR$=DnYQrSQbOQ>%_*10VoJ zWR`8MclYBZTns|z#qPt=u0DLLIbC5JUwMN;K<(R0$X{eTiw&FF3D^>JufVnh)(yK%`M*p2vzMAADc+tj(MwU;F za->G%&kkN`$#HyFr3rDmFlYK`lhTzR^|F#WhrVp?8~bubO<8awwPq-%tPwo-6}4R* z%8#YCHJkx9spFo_^uuRE0BSFv$vamr2pBy=&*{RWQJTQ7lV@YEjhE-WHeRJioNdM9 zYcdEbXh6i2<8?JSrbI5&`%iQoDo~XorNeTMtXOiGdaLP12wiehPVc&hs1`G5>puh% z>+-PFQAVk9{>03Vq<%c(X}KNy+tcQzfzYKkQ-Jg6DkddKRRz2q(;n86%b=J#tnHDA zzH0H_S>j%}H$pKG;5-Tt$eS2wUy zBwu+;{NR70s(Xw0K`+6GS}LTA?j$wGUTiW0s6n&N!e(V{kgCg5)hrIES=`y2#h_77 zeL`x~_fOHh;+aI3WFu`|@5c*8B<6y3jxvV!OQ#3b$zIbvH<$a*Z_O=aT-G=yn6mI~ zPW~X#tKwv*I(L0^poMk0$D+}d*8`sJPv5AtmPhN*Pi`3;3r$7=-S5W9a9!yq&AQV1 z4N@sYl_s=R>4D}Lm45Z1sx;52^uY~M2S=60w^iwys7hHAE^;P*+pPT0w>BG|Pvy3| z%kH|XO>3vMQ-^ZH4=^@UZ7cL5U^9`(3lD`3iqk0v6lvBGqxMrsHASN- zl14{)X^j`lf3{QP4tTrir{)%IylzdCEp7#PuLi_GAapOWH(27p{>@ABi`d&tCW&xw zoRiPNPj1=xNj>Dm%QP$rF&#LsNYL zA^5Vy!$v9#xt!q@LR>t@n&?WZF%^+0nr@wXI zKk+Wm+P)X0wl;NFbJV9Tg!udgR_?+_veifC8y`t-hF%k(!R?QZs&S-qf~e6sYF`p* zcF%i8N!|{ITD%dsAY9vWSo}w+FLI$tK#pXb)2Qe1U^5USyIVUNk*^cw<_&q_n|~VQ zR=e;C+q&Qxnr#h{bIIIXjOa=91O;}-<94>6PT3q!H5a&Rp8aPM@l?NL+3WGjh-nRR z!VV-NQ13Y>df0aSymd@R9c}A&@eJXtI@s1N^auhW2CAfMETk=HfO!k1xntJSDQH6x z-FZMC;4YT(#&#IQgmuhPA{5~h$LB&|037d=ROR2>mNypUpjeP8u^?NTTS1!bnc@t^ z_GB{B8KS#s3Xfo3S5LKJED2e$U4p-8 zV%=3Imu03=HS*Q#ekD5^HV>ZJl;m>s-rnO<;tf5@Lkd-BID5$-$B&KF2N#ah@r>|9Aher+>54CZ~c&b>P2+P-W4B_{Z zAYBp=s+-BQ=DU4KhYpB)Z4Cd!9FAy^L_+g6l`Ax`0zi)y|3Gk?apFC5S^Ryv^oWZRFcZ;-bJib($+og zDP_qJcv_tYr)gqJe@amr3N)e#+Lq!dyhz}sh8B^56dL&_U^r34 zTiVlf&Lm_rdCMmCzi>0W5#>IfD@KQyq0}beCe!09JdW(>p61hv!bS)aN(h+{6Co4+ zA4SN4`2RIR+BF^qLOyQiM#uygLXcePtL`h7e&?laa1p3tH7z%(MZx`6?tV{>5OemE z51!8SL~fTk@POW=+z!R99s`N(Z{IH#yON1dlI6(sC6u}WQzvoiE9)qAC8lO`>XG+R z>Rp&RjZ>#RL#a1l>ZP2zft{XD$J8r0)jpe2gP1y-Q&({6@tArIr#{W8Juvk;`L+3! z+744kbLv|R`^!E^J%v-NIrTG4J)Kj3;nXdd8st>(cuIW+Q*Yo@AEz$I)DWlU!>>sF zqY1=@_a2ZE$WQp5_@I3M4BsDKP0-u%t-cAJHV6XSK~I8}1o$gAq40GBr7No++xl=V zYgMgY40%!uvTtt-($PmB$(oMh{39BZwDR97SS<68;r!o!kDWh+^FRLGtbh9BGGC70 z23Pq3_saZ}xqQj*(39&PP(e>pu%?m|LIM(Xsi%$C{Pph$eHxb?^e5o@R>t+SjO*|p z00XID$k5{frI*DTO8yIF5H?=7d+tKnu4SiC-TQW%{nGj8@wdNp6z~J0SBf<^1WKPl3D^J>}tXpUwGWT=k#(w9NlI=l62e ze}E_xA#PxZy}L|^Q}@ai+`;*uyYgRZk@@#?{wup;k8Ivsa%5K;BYWQ=p?wDQRc$~Y z|D}X}+<-o@4d^=*^m+q2388ZhXvA^Jxc^G%XU{ThU>jmXJ9{MbF9!4nt)QtTOI6)( z7%h3M4QOYVgnq|>zNro9Q+7+}_YCM@8_@SF=+6x3?rlJC_+3JOV?fva78@t8s5$+c z0sUYb(BCWQ76ba4HlPRJETOxdqoK#N0e!QA9%?{$Y6VTb{7K5<1s_&OT(_MF#YML)N{_fZqEHK!+ZX(ylqqXSPvO_ivlI zrhgeVef&#o=z%F}UK!9&w1SSuk?Z1<(61WMx3p1r;A9V<$dwt;=d^-G9#-;plC1j^ z1N!JTptHRa`YQwa_j-Wt@?dmJ_WluThQHkg^aa1in&A(&0iFMQtT}s4eQd6ms9N%q z(e5tw=6ccTLA@UQdJggPx`+w!iLqidF+^F!T$7&aAy)DaAC?@5X}4M8Pc!>%N0M=##?)RWpLlam1OQ= zpZaf-Ap_s?3dq1KA7+gWtAmDw)eVT#G}iV}k0fksS43yFL~FSMSpBJH^=uE)(?~X| zIHOe+pKO9Ex(iss*Mp}Lz9xZeW28UA>7%34|H#+g)63dqL%Y!1=qNkJfLG5Y-MrZqVytdg~+J zoKZgk5-=$~>hKPXSIZN?+T05kvXp?gQxfY6J!}L6YPj3!iM3$t))e_~eG*;bY(RA( zsKvsQQ^J$?7x|@QDnXReH5J>9M7ySH`aOxfI*@LSniVITB_~JSom7Waij$;YJr&9v zCHVv04HGX3G7AyBC&F)JgCO6gz}pw{Efd~$%C{Nt_J(|$32*D=+uS121aKjP*B!T; z$Q1ioZ=@$#E~x}QE?y<*5jFKSvPfj0?##=GDphW-E;m<}`^flIS!O^XG!<2^*OVn> z3cuj05g7GYh`y?oh>x-F!IyB5B>`)4Er9&+pfU-!6H>Em#y zJMCuOERNbu2k=CTko#fzXLRGI2pWB~JfYhd6E37HwpN7%gi^lGS7ayDw=zfNUm3 z2#@sSHS8tcc9J~orBKY&t^z%wdM@4h2a$D_;4Gyj@{-EY%D+H&#B~mP@UBIw^^1W<>$v`ww1w; zt8FV#qHM?HV})&{l}P3MdfVD|Ki~K%3vQwnbl`o;UMzzPm8*N&n@L*$wb|?~R+5qL zM;P(ygOCyVJ-`Kce=(hJOx`DH=#D=n4OL6TkA?{crf$@D$a!Z|FW{G-6>a(b38y1a zvq(H+DmN9Ko@C8%a_5P>9)N`v!8=|BMu-5J!pt0FI^&W=n~0! zBfT-se}DZZ$s@0Td+n^aGQMk0#qhX#+-CP3Yrpg5vmp8YU=vw2S-DNf3!0KVhV5iu z#S{a$7UF&l5JgbFVV_O(S?D= zE5QAGb92j>pcewSs`R=QaavL+|L(A+(Jqvsm;FINVjk}yZL{0$&mZ6N4B~G^&!Fu! zdIrt8SI;0Mp}En<{RlZO4B^@Y&`dqE3iiNi6g3_8?D_xNbm-TJ0Vz-sjVnkmx|5WATx!kZ;_s`>0`gc6J2G|yBQcQyQHwAQ?tWBbie`~ubgc-lsO!oG5=!`u z?x$ujXlJ5v3VTS15#j$# zQQra9QHEpLi8;cXFEzKwkLFOY=b~0FvQL=kZYdf~7E2qnV?=!pPt zG|yA-X2)#_zPs+j$x)@E@D5JUQF!MR!?t9R_?yCiVC4ZbIDz)c$PU_F;hLh@cW`Zd zBs~%ik&Aym6Ch=NPc`oGkqHbdfW}^-sRhmF(|MB0qzMvg&q%Qt{&ZeMvx^;hQJfSdGRT3SYLl{u9f??;O=;5i8^NEI() znM>C9Eu%&${E4Y~P2sCw#dyio?tJoOmud6eTr}P98yC zLrKG*coS6K^*h;;DF2B z+m)5Nw-w-90XCf^M;(E$3H<6ScpOXBvJP<;R$D2n1&@W}(1i)K)oI41x(2|>-{>}Y zT#!(dTgtVTy+N9>N~<&ucIP#?&}6VVgrwx zk59!S7?~Z_kdSN$e)OGJjE`f^m86Q0sWkg zGfYz02>!q)4X^{of?Y>f*rARI)|wba;T^?- zb_PZ##K!2{{TicLtucCcyvcL1!QCs8h_(2^*DNjdXTupOZ!4%-$Y!cyNY@%*Ck2xd zo#|#<33o!L(eM-=!SHkwfogG8c2J9}aYhHFxLUfH(2_2Y?~|`WdeyPmh$u@Jo6@VS zo21v$d{cVewpdHAWhke%^O(`xOj)%w-;`Ck9$44DK;DLX;PH&f`d|0tVWF79N3*@sDX5ScARn~@xetJsVL-?E+&aiCQXo^Q%Txw(6?2FkT=*Z z$Pb!z;}Q@d$C#Gmjx#< z=Jn=tp9Oo7ax)kvclqn4`~p0_x1CGoK52@%rL>T=m^+L3&Ea zd7TITHhPCp5ZLLUSF9soYVM4vKyAK@>!-Qa5xgKf<1^qO=U&IA1%e^|r$wrz9O@Ig zN1pisZrwq($8Nk(K@UoINPwl^L1(aZ7|>V*f|Y~EQV<;MHW)u}F9K_J2XqAn$8XFI zPgLyiIJF2Ltrp?#z*w?t!^x;u%>NE}C7mwRx*Xo5JI6naOAoa7ilL3ImdbSHJI=~nbQnkKo~MW}+Gigitfx`uIGmqT3~OdHzOUbpLSScRWSh9*Y0 z2U^k0?D7~i)BNsX(aeC4)w$xGPl$d9QE0(jO*r5G*@|$Eez(mt+IeMsMw@=~|Kg1H zsItF)Mmz6AI=Ih19E*a<0VHPQ{Fd+kE^CF_5)LO{ndCwMOld zy{%CD&UQxaU+W0p@*TQLguJ<@qDX&5h;OnuAHAJqT%xU{EIYdrqd9PsCZ2U3HMM*NwKB~v`DRa&>c@z8N%%%_5%}8C2n1Q3-$^n* z`jc4I>sHA5>;)x2Ki*i@zU_j1y&UiL>_yYM$hF1|WH;P$gj?Fp)wiW6JYco98+;Ce z=TCEiU+Vf+PUJ>ComX#n6(3PfNTY=b6tHKt`nZo_=#V$|HTTJ(%FSwF_UpFie|rG;bYp`B@Wv}XmcpufS+ zG!*5OF~Q(k7vGI;8sfm|3cK~cY%BKD+4K!YzoQV?x!(BDECn)VE1S)xxMBofL#K3? z^K1IosM^fpSCCy{dF?dAWU8Nc(MznaBH##xp*_rV$r({>fH#g|8Kk8 z7bxWjiUs>hSg?0*L2g_3FcSq-@xjMf#dTc8ou5#^KQ)Y)y8JcmA)yvE24vw&4*`7e z7iFb|-Ekj`?_UMqzmdKlNz*8>OFDGbeY^d&B<&O+cODx*Ky>V(w$%maHXZ7B5KuUx z-E=xyZHV*)7CmqZ)w^y{AV<24I%9I1RA;5KhvqTO`Z)=)oN`@aI_otMw0D+Tat z-kTY{P2LVV#c$mIgEFmPXDZn72JCEvy?q|p;6M60yzRS0;^zVb^0-%_B?uWG-P)_T zwJnmM(p)sFkoc8ErTt zW*c_=AYm^yaMmp*?Ao6s?020t?9VU7f_+iJK5X>ujhD;|vhRH*FP1#Ug!fPz`A>5G z0{oVnR-Wa&b0M$fY^Ig76|HQ>siEIl;{N}*VX3!6cgLcp ziYQp3(#FPE6temdu;!>Qh}nk9KVnhSgqX0S_DWc|t|ej94*_dZNaMy>u)FuiqLA{K zuwB)a-Hk?jSHy%Jt6-ls#P0bqVQ*HjZy5ECiwV0=jqx@EHh{2aUJ`wo>R+nnS3L&( zZ)i=E?=nrk1bHRrF-@MQXmB9TFa2I~Uo=hrxj{o5iYDhq!5)kz$Hs(>O*{UWu(4^U z_Qiw4nzXYiCT#36UK$fN_88B6F&55J)wT1_4cL(gdts|Fu2(gGZ9unwG0Ee$37wS( zi+%iLbJXKH(5RQb0Hc1J7TS!<9XbM5=^w`=%*fXaJ6zNi=;P@JZ^Qn0R5Th}G8gJ#HF`@U1nx_&&sb;YT= zN*6(0yDu~AqD?%i0?qvv`C^qZ-;7{q=6>tz7m_^sqRM_Sq_|?y0cmfGcT>{v0uiZb z*g^){~P-5cn{sPp@iK&f8HpOwA6iBT6+2t^HMa4_8MwvDk(~4bb!!T`` zD{UmEJ>*Iog=s5XX=5>Ml`Ab1(^k9E#$nnTSK4??TjxrffNATEGj?vRmCIY_1DA*1}nwz;V^2b>Z0OIbU_bR3e#=NH_3EbtcP`2 zXL!G5rZP>q` z8Mgs%8h!~Hj|9Do2IuE`;l2&r+rbCU1b2US{zaE0^FAI2TQ5Q1-U>@lWSYdvXKRz9 zq71xwE45gL!u3)pT)M}CRNZJ>+ow@G8gq~57l`9?9;**@oL9RfH(sw2sq2zF`MHg< z+4#W4$qU&p)2P{v)NHr1sQ;~`O2>gE{eZOzx753R$CeA`D}KauUwWMvNJacj+q&B~ zbqh@Rny=tw#jNb}1>|!+GYj|s&ygxAZ%RDf(wHX`GHG4vu3j|ESqzi85Z*?~5_ulV z9v~A6IH8+_p9^nZ`F1TzZqG9CG6t@b2@i2Xl}uQ{31u>26(_tT6SBC0AIO9WoUlwL zOyz{-&jMK|@nCvGiFHrL(RFR6@pdAqgp)F-h($HAu0X!d5O_HT^<6V4drM7x90gsN zAxC^De=&2GAe*|K9xgg_OtA5$Ay{^%E?Xf|Z|=@DPNW;=GY4%cDvzs(w8LxNjjZ1L za2>1n0-_D`=?N4(lV$DoF>9~awbw`2UQdcbsQo6ay&lg6(H1DDlWl#;Gp|1MXxJ4I zwu{bd^cB+Ga8dPq@DG|T+tU!UJq@}&4bkmsAmtvLZq_`*im<)1f2)8%sl z7DXSe-Hn7`)_1u`&ie2(Fz!9@9S8MAWR|@zBK{_8nq`3L)blZwVC69o|1#CY-5dZm z7au_jb`%HjHbA>b*J4bORkbs!66gzUu&p6esZlw7<+GA2OFnHu*^?yM=aZuNLwoGx zsBG1#JnR%DL^l*#K-#7Z;~msZsp=gRP?^~9!A9re(PLp5zIi-!9;P}pd~K5YBy=cj zxm*HYmBUyEq#=a^^0$U`>r_Q&V5??f+)+Jf*8ogqdA@~Qw3kh9UC@$_L?lFWJ8kjC7I}a zyGFa{K(+*m;;Hgs3R{}P9JEKO%J@4oaMjAu^1F9Ey8gK^882jt_RO-a8}!>;egpS~ z7beRXehFwCcdG_lD8RDiTQL!3uF5Vhz=V*7zt*;9EBLJWINr1u5!*F#Hd4HVTaFL5 zm3S7Re@l(GXp5NWNPiv5J7{3rx<=QTBRZwu7KrBP_L2PgENCVufrQU3G{BSuYm(Ci zNWJ8TY>w!p7pCT?0c$_#D)EN?BTq+x%Vq1XXmFBPuh1AGarIK2Kyq{Th0H1Z@)_hE z@EPhdu1j@(nGcMi*CcaW7pJy$LS%UKOp176<$9BRcU?&foVq!6wnyKbx>`KnC-QF) zzt4-mSH$0s;_pb2HXu$$IqHw0S=wEwM9p@SNYs=`Ley-TY$kzR<7I?=A=XT%-RdMy z-RJoI3jjh)z4I<4G3#tBVeT^@+11igN5XnL?4@!=d?uIqJ&V3}+J$ln`1>H(mz<~G zmxvEW{>~5r^301nV2S7&?mbh+I2yjCzg_islBXE=-7cQs zch39=jMT2vO$k1DfgWahmcxuwS4_!i7to=ocoS3dQvD`o-fZ>8Gw;$#DuHI+H5y*b zyestEeA}9&fRSvhnj~O$2eK55m`T|R24>PUT|LdDYjgt5q=&OR$OPKMJ$pJ)A0`p4 zPt>g@T0iFk!u}_dq=;{Qj*lrte&lJK<#q|>_uyfXvFgL%NM7k%2 zRr7f$!Oi4}$>_1jOxDkx|6vroh-NYndsPQ~1=)>f}H#Hki+MbJZjeZa4UMgL7{ zc45Fcx$YaW6tw&uA31>=^bcCPaI(o&%(dRD zWq!MK$slW?m|a>_`tt8E&pJ$qB^j+lLXVTy>ZrIGBjkl!j9j?LU9dt5KMdi!=Wa?# z1g(I9$<7P$H2QCl1sVY(>-XC+s~47XXH{Vq1TpyG6nQ4u+^7YM2UoI3uST-tZ!kH| z!Vy4Z)IpTXd%%Sxmn`<#JjB6y#NHCmqX{->qMBe|ol99mCQ=rHn(;3lQ{*+pDZ<)b zKGzVH!FPxC>hEZAf=AQ6J^E|g0_ck=>l;dpnu9ZU@kcegvGt#(5*INXIX zEDxA1q3e4B!iiiBTIVWi(?1yris+lh_UXB`crCdw`f zRT!r~kx@ zPhxvmquIV$mu8%k@f|hZ!FA!_IvB}i*!%Frb*_oEqlnXGfib~Ni&Bi@o5XlUeF2&8 z>oN_%!r3+XTbb$N%Fd`x0RQYpv-7V9-o;kjYOXv8821{=aIG*~JmakSe2u3RgxR8EvkZZI)e@3tr5?8rP zuez~YtdWYG18U@Qgt++#cwU86#4V2;YgA2yCO|#69ig8?NxGL~g4MplV)S9N(c>$K zO9XqZDo;+IvciOBd1cTm&kiuHC|DV}5!+x?-!m#9)S)a`iLB#n%-QW5Tv~+1!}vca zJUL^=mN~wGqUbeh5=`4d;P74b&2Vs&ofSvdu>CxoV0A|X01GIQ3`=2Gu7>4jvz5Eq z<12z_)Xtt&NjS|{2#IdA5kmLzmEdq`MS@@psr$*hiJpGZRJ#fX;vvsr67O?j%5?}n zbU5^D#R82@!cz&<%6-TKmq%*C*_-L0LEuRASq|$cyu!R*467d_hzm>YkFIomQW|8JUcJW3Jb)gj+ z*EQjswTnl_ML|gH-7O(DMng!X%_RuL%pk;!z{fq`BR&qof8C7HSCD6pyuCcdQ0O@~ z;Hr3q!B3WjgUd|bxh!fKG`MFJveui(l9+4FtdS_Y#vKZQ-7u6Tm{T4Mi4A)Y>ez!L za>pL>8MNY1gM5(od>J-C+@ZzV8XAN@(Nl(lOPb|URuE`tkd`N7rUw;%Vi;Ii=Z6F2);-l}uL10bz ze%?6HI9Vfi(H`~QJDOXfM2uc~n&7$fcm|#21_j$xvDrPJbI)?;!s6V=wU&0l%CZ=| zyBi#2q7#)&M9S$J^HfYaTf#~%;z~ZscU9u27G2J{8_itWnZK};vL2RMIH2gTWQ%?W zWp;0pQiC-iuWdmri%5Ih%Ks|KBaa`cCG+@OH>M?TXKu==l5Fa^TrOVdFM^2V$gk!o| z!BB8oz1~P5gyQt9x;IIi&+~2EZrHB5W4K-Hm1qPmN#j&|#>w_@{t;%rY!~PK;fc-z z4Gd>~VPu+(rxen8#MrBFHcBn6vS9}ZR}T>g##O9#b{O9}*z+fZb6!XYPplQI-kZ4U z{ZzEK!!E0ILeYdf67+{-)~vdb?X|YXNxY6T@QQKV^6ufYLgRii>N8%M2FUGrGPAnD zw)N9!iBftRy%oH9a4I9xSNIa5m&%FVv~yaGkc}=aMd?QLmIT+S?-ddRzi)T4;`c!t`2D|+Gx+@j_X~di-g5Hg>9=w#Jz~NKPfOa;Kg9xa?43fWBfFFtD(8%`@(%LK7cMmL#J7}eotsL_-hP}uyO;Qkk5HP%=BE> zI|2Aw04|*YnqfgTa>1_)6v-Lmto#bPy&jso9twDL#{=Qa1nPU?c`?cKhLiOCm?+!M zPc%O`Rn3ooqAV5=WQLzlRZR?*7G-a<#sBCjj7a{yL6YPvPH#d_R6U$M;+H9N(ggf=RP zyM!r#g;6merfq{grfm9r7{ImS(UU@XI&cP7&nNRZ9_ z6ik@BP6bA&3DL<>XD|={h#R>=8)>F-I0|(UFPUN_M%Ap$>=qccKUzD=HzxZgQ?Q zx@9tVt`=y=Y%I}^SAz_5##I5;Q~}k9|E9t=LsG=Zvx!#li#PX?mDnZt+loZKC^R|* z#({>XpRPZkRL-q_$>Y&>bb=T83jQTi4AXF5L6HfEdxM+t@~rdT#h@;$cjc{IqGBk3 zXg(86Vmh!71Tl1r`bN3?xHQjo%lNsx|HIpt2S!mek8Z+dv%r#FAadXLA%}u;Lj-~# zAOVS>BA_5%fE>FV$~_>4WdR9tqoAmWx1t<^2ILk51r;^=MT|OOqeO`i&b+RwzGin4 z_`UbN-yg}&OjlP|S9e!eS65e~I~je!$OP>t!9oNdj~Z-BqJTgxmCr(Jvt0`4U$1vp zmX$nyjPTw}q)>*1FML1VCM^7Sv!PHE#Bn8F)TeBq{q!I#_OK~na;6ztk(ze}nHi6l zJ%JaRPo7pZU+D)k^fxWk4i|k7;`iN=-*KODB1%mv5|I-es`lauN_`*C+fRBcU1H2cCoYJJ*0%+9seewXk&!-|0?Jr#7s6esA9?if ze5LqvHYD)fwX?tA*jGWsU$|SA{Imf*^pECz7L79-8rG8N{KwtUs7sr3QQ@zG-E1Qj zH`5~m?>;|A^YRiK#@dGP2;A+3J=roLW4XW<@CZ#eI9rCB=|D77?=PL~vAPfG>)ekO z?N82$ye|xK{q()O;)6Ona-~+G;);TOUxRf^*!*icthhxid$xXUSW;i$(b#6%Mk<5J zm~A1+Z627dUr9#(e|=$-i_7f6&D6G5>t;eTqbG`**S#6_kAiPi|Cl&4UcpC8PD)VH z(7a=|1}_8?l4XgW>ocF|1GQRtvmk_?n2I=1JnJ$RzqB+ETjy4h zS*q0_ZB(QvWXzwv(f>&nUX>T_owo=VSiE*?Y6xvJPrL|i6WjF2!gvJ*_pY0#pCffL zKV*cf>*{2eo0U4*=4Ls2mYanaj#byx$q`Jno2vm2cz{EYT{oSDVFi|)A1_t+^0Tb& zo{45ftkvU9O9>K^n~Dn36Di>Qt5m>W!n#5>vA*ONqVKCNs-G)fxNYz8yUD@>qKtU(P*cf+1ngolIuC}i`Py7QpMoU$!rcVXq4arOu+3xDGKx}E zd#f+9Q@%CBGFB?_=Bx278+>R^g}47at;Im&a;NePDL4N9MqH*K&jEPo8~_g4v$Lqd z<^Vdg1LpInkvOfF6pWh)QOMZbScvd)A>-M`!hkl2(Ln)(=yVbL)ct9#sm+^v!Su3R zFwhcEL3M^w3`$gEz;q;sBC>D~zi~}@h)o;D#C-#d=`IeSY50Yp5||67Nd_H12^L$A zar*MSue~qq@qYYu+LG>Qv-U)Y!q)(jT<}F`95&?i037@llpZ~?4nu&Ycg^4dlC$JB z2M3az;>ogSkdz`nKgXpxejXy92Fj(r0C}P0^DMeYWvWi$*z;;7=wT{+Ki{&N7 zYNU;nPmjr`X_B;Aa!wlEh?Y57!b^6f{E#vlbB&Yv{qkvw#I!`?2WoRp@v>4+S*yRS z*TK4LRO8Sgy54Id>FOgYn4*IYl%W1bTnczG0S;cEQc+PP1(ysa6lwUt#N!EZm&`-Q zgi~9VrrEvuOgsinoimVcktB_VO_dZGc(aNOyy@d{h-;}!T(adw6WQ{jiEMd^3pXxf zxDAXpz8OQ3$y-;<7n5N$=GW(y@`PsR)-T27q;~%qKpAHm@-cfG53O+^IoH<^V`PBn zWC~ttzVz*<&`ZL7EB@I?=l;fsa)k{_V)}JaZkeZ}oSh5O#yrveYN5D1$;Y*gWK+7f z#MGy`qmMi+&@=KQu&|Z&V^EmlAxLi7`XtG(eyOyKGLvBnxP#Xv(2$Oo^BDpzG6d*uJ25zGd*nY_cc1PT-aS#3aiZUji`x(tvZVg?MJ7C4{$^ zK!$G?;pP*i!Sp6zvO~t7^+bu|7UEGk6oa2eWfoVva2D}OvC2~9qJAo(Co>nEvyaO*LEhVbS$pn+!u zjc-=Ge%}MGu?}*%2;j^VZ*cVIDGD!f~Sdm;l>!fiAbkl z%@rv{5P-J~Fpi~Q48WWQG)MXPz96U+arJ4|^hvkB0h8&zFZTH5H@NS_8n!gw!Rfsa zFZOeI=Z-?Fa~^vdf(!Y^Xs5;%ac?w1`ota)MrZQTnt;%LXs3@Gk39snc!KoNHaqb)kFM_XaW(<*U*@!^7(`=~m6I9E4Mjk;E- zBaP=scYjkTzGK@HUpfZjG70>&VgBGo_9N6Ei^O6BZrtk|FZ^F z?(RcMpm!WE^}=n36`6xsXBvuF7t(mfGfEncCo~)d<9Gl(zR#V9JKO}R%zX2-9Q((K z2zbKw6Edc9&yGqLBi)_NPOKUi$BAk*;3UjHs1OFm$!d)H*WynfGCsIUh(9Z^cFlU` z;!aZ8|Kt!>eo4rom-ak=Wqh-S zj6j8|7DaogEG;-_mf1s8m!6_lJG?BfrK}^z`9)QTghF&jWcc}@o?(s1fXjs{mh*wp zYY1$CQ5~-qh6l^_=CW!L)-dr2g2z|X5kl0lJoHX1qrn6YV0VfQS2cHximq-fDXF_@Sq%agOa4#@?9bQK(=0}wg;`Km=O_{_>3&`_^0@Mb?%l`rsAZO^xd+V-rf3_*>m z3b|Vs$*%|RKJlILE6xm#1yOt?k z9^)rbJ-$4^>(MJ@yjWG%<7IsGz~gIGO9>@*5~m9=rTGL#F&zTBxnD%K?Wnd;yKR+J z?Uq#{EGsMV+EwG<5W`G{LFg6Ro>V$Uy~nvdz57Q*d9#W9tjJ24x7!y`PlYS@3&9KZ zZNgp!!+G&YfH3aa&w0TzxJ&CjN}RGmOH^` za*vvQE<7$3+s;U>zZ6RLoV^b1;0Kb~`0O<@Dy^>w#COot<7IiEy3u!dKX(`_-d_JU z_47gr!z(`8PZdA34@)i$^Dnuy_2M-BeMn}aF0i+v-5by#DDDlY7NmOvXmOqp zGUjJct0z5bt!RQ->nU#FgfGT)pA)uqIe;`UsA_*dmhVP{>%fs60U7M;(~s$%9ZT`) zqdD@(Xj;&4mqoFM_gIwHa$knprjo6KI*YiFq6(tQ4=&Y76>9FpvGGM3Iavw2v)snP z1TAFg=Ody*Nvv-r+vmt93E9CyU(} z*5AuT>523ShJX-zGjU#%-vcN33rtPrNGMTaC>?*x*?dWH8rsEgMs7!Q%Pq`hLH$UJ zbf#{R&Xg8u?T&0|kd zk0s#Bm<>&A>jn@o2;vo{rKCWDu26jbAWeN;QX3_T_4bwuxn|}<6 zr`_D{Wg(ZK@z}ID3-#J2I~4lBqiMxSw^|~A_i+l)^Pj73hYHYB{v*{4tq?&h+L!c%BI)g|p&H0@fb$#m9=F~djOlKt-^cmzQG0L`D=kb) z51KKovbutWP_u@M?FY<>b5hZi1{Wr5XEBdZ2S zev8={i*_>w0czUPOoqmw!2fA}s_(tfytG#xzDZt(NEaRqiza+eJHlPtJUWc4Z+yX` z=S&|)pIwvw34yqtAz^8s%GR)Q6WkM8t-}dgYpd2T4-z#&3Uszyi>2z6%NTKY0oavO;Yz@ z$nce-$0O`(DX{@*RN?@q4kRCk&dfLK*XgCmV^)X-qdAf$C}si6j%R zQOTl=c#nrh=*xIu+jRIl3B(wr3FuX2VUQ+YDa$9d9JqZX_KDehf#bURKDz-hzxs#N z=*Nb_ro%Zm#m6~U;1-q?XUJ&5VGaxen9p1UQ_}*|fy2ClFvmCy{1P*6dH^OUdRHNu zyUH;7{f?^svtf`GmaYicFJWsgP_VsiVMC^X?fRjD?R6d4o*HcR2v}_A^ryZTuzfYy zzwfoeQmE}Z682oKg57to9p2j{?66l9>?&K>2PN!18VzG?VZZ)Xzz)`6@34hEBVivn zreJTfg^m4Az&@nm%}KR`Z66k}qcqrEsdhXYB4MA>V4t_e+e==DF;#*qd_%tXB)hKjKzt6x&EuiN5%*|z^aGr+F@eqq~x``O}6%M`GuG}yYf zcsEPfUo_YY{q68xl(1n9_6u9sn4bmgc@6fZ{&qB!msb*0J*W9MN5G=Hae94u>BF!x zDp>QKws`NBup>TIc&pmd`;COXs?mEk#SXT`IRV>L>t~-MtaVO|-9mHXVn|E>j?anT z$vH6w$DMlL-W)DW&}YIEDGEaMviJf)L2#DhyD1DPG$ii;Y#J>{o@tSjA8uV*pPwqg#ugHPY3%|g4Nk# z0ei#(R{oLzYhnfK%)!V#t&aq2Z3U}{wF~pw^}Q!vto!@GgpeNQN>G@TKE%-0is8V0 zs%OCaFDHT5AMzId&_xR}{4Ey>;%~{cQfRqEXNh+KQkmD8K8*f3e^rBX0sP84RDI76 zREr4@(>h6CbzVhRCO9+cD5S@e>??mXLx!6JWZ@~XQ(RBwE{FsfN_`IWMV^TjRsMeE zU)B=*U>sx^u|2BH$=m!Ro<#~bWx3)5^#!_M(vS|xp1@7&eITma=)TW%Q~C~PGV@Wj zJm0&$2p*?Sg{19sOKH8M@gp396 z+MR?t+6U_ckJkoh%i{1DSjwls9eM5c1QOx$Kd^@l!PvtPD;T1QqoYbeY@lt(7;w2H zUe%qq+azlb$o}+)FQbPXzqLDL{Ju#!71i%6{E$Qs2;u)G3dzd@(CdHq88>Aw^@hs+ z10&q*hf;#7j7{18GPRrd#G3NLw4~X45q2}k2>odz;2u$v8M~?VV5kS_>tWA ze_JUq0Nf3$ZgH*a9qa|}c&!aDt?`b+csywtZpdC-?4_fF{9@dAJ!}H|0vj@6H=31< ztS-v+$D?|2_)fa*k9Ei0r(+@LE}nG5#roob@xn2!dmo6|iH|b}ik&m3So%RUKXFrP z{>*piF=8L>OV+u_X^zXD5&d{uC8~|i1o{BUO$Nq0bYK0|mGBxDpc&7u)?w@$4(xmW zZd~@mUJt9-{%w^U>h$TAGts8cY^|q$)famF&6@Ez(Y&P+boulG8l-Y7aYA9FcY(W7 zlr?nFU7CFndtSAW@oA2mTZ>~~Q&ZQ|2&(S4RGeDYPsnJLO>gwWovAx{>I~RcOR7aj zMR3I=KIEZiPoC&7Rq1tlJX(pv9=v~;ZxL%dkjQI5K{k_~Q5&FTC=uIjw^<4MbTrf- z*A!HJmV;bE6`UyQGk~KzYYXm-O5MOxy<3yq-mMw%`fW7COoAxIA+|(AJcuPAYnDf+ zcEMEK8kiEDS{qZb|J{FQR2@t3RGk&AqQNq+YG9HVrJ_sw5CBtJ2a{T+@iSKkdPD&& zLTpJmTSupkLlhmkgX(M10R1p~B{AQ@ryBjEpc*4oEgh;LB?_tpLN(H%zD`k|1Ov_U ziD(B~OB2?Pe%$zlnG5>I>&Hx=N8QEqisj?9e(~aPTuxndr_OiGV6$(66)ctV{_DnV zcjs{}sXZoLJ7s~dZ-MWA+$q9YqgQTzY@jEXa?5+ib8qSLRARKZbn+1ZoC?}EdsJoY zW>P3T78L;(`|pxCyGopuipN<$3g;NWS%u>n-P=;9RxFLgZoX0A@jm+%Mpqg&!vb{k z-L&a1w#)O1-7e3=d)TA4%kyN&5R`i5%A;1n)~Op%n0Z3!UFiw05l-M8WBy&rTHQm; zbWQG*D(Yvw_;eud)4jWwb{+Mg{_%R)-{tHz+%iNR>0)2$Gy6_*yn}zu>1)Zc@t%0y z4`vE?2H(|J?&C~I#>Y_VN|C1u-sn5EJcnJ;m%8zH-Jy_`@c2E`>#?`b&V5b^{rsL0 zGn>(qW;V>e5SBBk%j7yU1vV231KLbC2@6<;B1sgWNG_#F{tN-h7Xs#EMS5C)iPghp zUoAy;9V^+QHP$9eM&1%xg^W9+C+Al5L90x;{!x{AqnoVEn+38mkC|RpZ#X|)$~@N( zpM=kD`%v;0t_^{vZvI0Hv2c07*?ry?4b*Fl$&Y~{HuYDvS4VY%BGt71T7Q@_r}CL^ zDz47NgxpM=uoU|__j|V{uVvGTk=0B2BUAj`aOYs^dPWD+R$aRv@wkAfjZi>C>?(dI z`&fc!ubb?FwSwFvg8Zr}S85ab2YBRvDu=Ae@~>z4>pSusj!upwhr`C3ISR+v;&Awi z#(|l)-X*)#=&rI$y?&RaOWod&^U$t@^*2W4!OO)^nQ>z{<`#!z_>JL6Dh@}j8^dws zl8(dTuU?6Ba9A(N*Tg#{U;Fh^r#9f-+f8SzEusnv(HBV)kt+hx?Y*yy=x9%gXpTa( zt7p87O$YDOyIvR3T!pBeLgbG?wDGR%BI=+J<#myWnsP)j98vg=>mvH+PKoGsh3NF1 zRz&5BrYNF)t6eOnkIcHUK60u+$HCO5gTd4zC6A-5xo+k)A~Oox`j*W2sT_S0y5gFS z@iKC|--;?Ff{wsOGmSB#-F_<_#$LY+pdNuCAsWNt;xI(j=I8|-0b7e~g0E*U>Ss72 zxq-5~N)I_J9qS~g_fkD9!fBtS>sR58RTl!U6>*+ihQ&bY7AWCNNluycRAf@DOd41y z=h0I*=javAJg--{ZwR9qYmS{eE$PjIsVjpB(r%BKb1DZ@*9PZxw0yJnSgXGB#CeOp z@<(^lR~mK%2I@U!e0$y9w|_}g;P^62>);6?Bd`f4%A9XVyRpI~5zM?51oOTfOf7<0 z%wa-yFd(!`hm7?E6CND};3I&yIY93yfb|6M0S9Oj1&~1iTRA{UwkVk+{UboL?q;hV zzSvF5lzTcznX;&xrSDbm8KrA&X)krH<=51-Z*8tm@-5BPwi`9ql=e{Od-|ekdbfB- zw3->}9HFCC>#V7n^?O*<%#l5!G&3(2^p;*wVz>Argi!(!tJU#%JIKCG&;CtUJ34>s z6hWuIlSb$CWDA`;lcVT-6bss-7c{zSQFM+v7fEL$%$}iVZ)Qtpr${=Fbkyklrn`mC z#ArG{YYX5z^@8@^p|6=eK?hhVG`lFx{Nft88Ew_DY;Ep{Lw7UO%wyY=wM{p*He6~6|4Cb^qrXdY+iSXLK)MOD3_n!CQ0iDSE4 znAon3&cuLIF>zPhcpj0!N4#WAJiXM8iPdjMJEyE=F4Yp3-IF^|EmQNs&0R9_AZOy{ zGr~{a%0yvirDLfW6gZz;0d>F6J1kWhd%LPicXM+IRh56*h^iz;c8e;uHR^pEb&CQg z+-s19Ynhrw^^;}o<7tZ}m&0cZczc$NY)=N)WYcmF^Hy;Wb52CR>MlIojoezIMzz{t z2tud7Mr8LY$Gg`x*7f)@3YTs^g!M^SAHm|>`n3)#3{DTW<2_PbGMWnmc6wegkPlal z(*7XRWz(i9_q&ghWUtKSz4BCR*(+;tGw}^I?h$L_KHpkz+@mq7ac{c4$j0ro#IAA2 zw)!75?$);dv&Nm@w%Eqq@rQlm>MYSn{i3Ud2@6{Zm6dTJ#XR7^@L?c51vA{Y0W-3R z8P>7eX0aVJ9=esrE~}iIN8f7^NL9;dY4t0^hFMp31+bItW)z50U9dy50iW_Gicg%4kBaR}Qhf=)Tz?xy0F)_zKgQp!( z%L^Bt%nIcakiR>%aDD{dX&H7Kh8$_VV1{X!Z5=_T(Um(5u@kQJv5Q8?jt46;At# z84)7Oi3&^;O6a1>&A&x{ZK0Q2M=Q5^l3lseqRIs~@8rCZsF5R*@7l@I z7v3VbA|G!}%3!6h9?6sbu{iOkw&pTEkE;=Q&O&a_zSvo~ZFWe`^Y4Z@4pS@htl7wY zp?kd5Qj0r@=|kJ`IT+tQMrbp_tiHO7Wsp4DQrFgBxq3xPitsJU+d+m2_?q|DTGX@PCQl%4y(P! z^t}!U5A@FwYGN+NL+LN3sg^F=3|e|GJZ?i}?np2dl;XyF(vD+H>mwbKvu1Ss>X9sZ zocXVe{zb8gP?r)FPK6UGP(u?di;t(oIUF~XqYkqSYa>hU4BBuiWrS^;1Id{%NS5Su zg|O(fkE3+wYAv`xQgog~4K%MY(=|@QMdn%4dH}@{AE$?{w6AMzq5Y%{?KI)^MLfIH z4#F&BIYEGP<@np_h%1zpc02~peD0S~Yet}WI1Z7?1C7i(Kv~tAB$t}xnARjSngUtJ zgGtq-%(u46!Rxll=dCO?J6L4Rs2Hi6A;J4^)60LkooXA?U^UHoJ4PO%+Fg@^Si(gW zsIYeB;Shu+mP^!QS19_?b%sR_`bWLf3TnKQcLKJE1PoQ%MX_Pqb=k1YZSu=D+n0b89@6LWh%TD>jF`N_c}x8!-B&46cccj3%_ zOXvJ1)iikh+$NG1y)|iJNtyn;wOyWQX=#^DHtiBjeTzS4YyDui!TLCc1QsigaroRS`O} zc%iV6b-E78Cd{E`j4?ga;jfKLRha)O;?rfizSsxKQn^`Q#-#bQS}8)$HRtgP{4!nv z?yor2r7CYVw_66K6sQ@ywK?|Ol|odTDx!L%ZyK#@#B{2Tw8HdJxEi*$Iyv7CX=*AE9mlR$U&!k(T>K8HaY+`xK> z>((LJJ(*j)M;T*$F$CuQ^tkuqr2@DN7Gj!8TSQR(nL}%PAs%C1QBF*s5N{mq#0FjB4 zSQa|;<$@BK6&_?g(N+IuZYQt{#*ihwOR}PZF=RyrW9Y7Vmu$(qA_DhSyji!{56u_B zCs=1GjAu;1?|#nI1XZ+IwmYBlz-gnOFurJ5;g zWXc+OWQ{zsMmXV>f{tvdk!iE^>fKbYOPl)9tyzkw;v@PnpNI2|eD$AKBVJ$PgCW?+a0dB;kISQ2%SfL57c4Y+cv?4(#X#k# z`Zs0@-hKQiF;=OjT%p{gRw#6dZZ7F(9tr4?fSzbT9txkwn+ta$Pf&0+=AN~T;+syg z*`gpmP$@DI3`lIj3gUtH1vd`AyqVij@2t-i&66hx0g{m7CNS^2d%)Brdsw?hX!YZi zT=K!5tg@_{Zth(d)E)$mlekh7RG(ge54&Y5v zT%6Xk?;MdOhscXu6{gB&u@kn8*Q`fa&>Op zR@RzZ!^+_FbWLsh$Bp*+RdWAlkR*mrkGZuY4DQd=#&rezhSy?(FLcLU!WeUHs#*_@ ztc(AeHfiUo2sHV1^w3(>M znJb0n^EFU?NI)jwrP(hzwEG)Q?c_&^AT}JmhzJzA-mn?{fm`55=01-HK9qw)P^*F;#%&qW|=__UYaYjMi zKsnPE}Pd0iUZ*;y{cy~jT81lV4j_AAy+w9%faR0ZNd8odqt%ubXBCt69d%|3&EUz{h zOID5Q@ku7rmdW_lA=$$eC8OL9VZ3l_hh;+PUHhv(yQ50H5cuI#SSTjWqn_v!qQ1$O zAXgLow??EwTI;%KevHoaV->76R}z_CJna?zcom#~Fc<%U>OCGVcxbyAP#=v{#8t0C zW@$wE@==;Gs^J&9K*`6SJ+z_?H?p&@eyZD7$FknTz6du;uJnH%C!@)PjEOw1W2;6K zlX`IpF%rZCA!h&Cw8S&aJB@-?-ua^-XgaYXWaO~_z>Szwg<0O3jz?LiYvU~X??)~D zR)%ME32XZ2?EPwq;&H$qgiGGei2IiPor|IOX6)~-i1O#M_YK;&PuX(;f4_;9(~p;x z#(!U9zghgb3b`499wW|fp-$rbuCOp?N)d@oA@PAC64!pi5<3@>cpK!bT|{ERDVFFg zBJuPOEb)&L_K9U6@o*7|pF`r?MI=rFYL^s|cI5&;rH#8 z{4u}yuao=eULtoxRXcv3yBbC7EJrc?ta~k*Kwt3$#$JcO{ht$oJ*yVM&zUz!qu^)Y zbIH$@hyUy3R^;UNuY%-SQ1^;LJ(cGQUWq0osYovGvVnFNPj<|8$R19Sx6RX)k?cCf zlASYGaX;pUC>OlHxLfkR+M)kiC4?kdvpZ6@&AN^>e0;Y!NnGS<6B&;{f(qjem`Ebd znV{8#pB`vcD(WEcr3{TgchR@_2`@k$+<9H_wK{lg5#VTBd-fFA)^fMeQT@nEY0e~S z;Vh(|3qKvNUuVdpmb5)3{an_RN4107Plfju3faIQ^K5zH5C)%Xmltl<@OGjCm}t6o z5qbq0qjr|l;u+$X&E?2al}rX)iV87T+l}}YRT1wz&=S2{lNVZN_yGSzW|T+4km|31 zVG(>1m;!wEruXBR=nffQ_KkOhT_L02qjY+gv|O$U-}@+O!g(d*@hU67i*<$ljuzBc zlQ?NpJgCj+01!_YY&hAl-NHFs7Ph<5^s}E0wHl0hdVmwv+`p8Kcj%h_6Y2y-qy5Qe z+oPqU6ynTzM-ABAiWm-s_HmQJjo$K0^>#(eevp|)2b`n!eI6)lJ;ZkAD!-8# zE8PVgYcpt@DZfLqImx|*s@FgjM=^UUS~hi{xbWg|NST?w|At#_IOf|z#)+pyH$jT0 zCaPnq97qKEtdnSCq-<@@`v$dKT6~G^MsTJgzYVu_?{`C~cnSmA`*qT8je?jN$Z)NMSN!n`ps;;NyJ8-`w=Y9uQyr^7Gyag(9Uxx3Q_^?v=xX*=;XuI>y-G@YF zVw;z#c|d^`o)9kx-l z5%hJp?CWmX*WH%B{^-VtzV3cl_4U2Zc(rj@Q6edWi#WIB8d7<$q!Ohj9eo^RA?Q|~ z7``NKI*ckk_aRk!52uWu@Y-b&4c(n{0(+A{3zv4-A!?{q<7x?*rTV=p2=UJS!r{=q z=>67Uux3IGPI6*vIz@qE=gpO!H=9S;y~h(T{GHrLbv7BE%gF#dI1iq5Kp2bM7#$z@ z9L#(%WE3XRK+Py^nM|iU#4a`M@Rk%3qa1#M*hb-a{tczE#VDhDFh3frpa$=kMTl3dTrL3Fl73Jo78NQKDork^1HD+sVA93|D&~*ylC0$1k;-#rP z6=R@X^NR;66%!dLtTW=y8%q2>;(#z)YZ(#<0++Wz50`hfBf-o?n3V`4qLDHE;WH6{ z?-Iw=nT>Ll?%6HUp2*XGwxrLL={0!zrTgM( zE7M;T^}k|iKbgKs)ZdbRO49$nXn#xkIhnp))ZdbxAo;gj)Sstg*iNIdOg|**e+9zs ztbDhmC1mHRog!G}$ze)1EWPS-M8gJm3E1y7*ip8y7xoL-|7fu7ZDIQ!5U{^#unD%X zmn3XhgFSu44)*1P0=C{x1^aOXECua+;!7DSSi`%(7Iw-Z0eew{9cl~v+!q42P=meI z7IxQR0UI}5!Mbc=A2}*uJsRu}m#wfA!u7y00Snh12-tUQVY?g`u;n$pb1vKQcdLZ0 zs=?kH0ZSpj7k?$&LW6B`9oUy!DcEb5?CAYQ!Zz3NetpS~X9K<#u(xTjui4_A`l}45 z8&I%M+v45(qk!$G;l0}y_HP-|x2FbM=aL(Mo;?6~v95xD=12Uk}z1^YI&fpB)~rZuG@GLSK+!Zqu9W zvXh4Q7g*^f*k5d=VRKv9=WQiitSxN2gLV?`+Y7cd*h;v~7wme7>#&{FnsLFd_YXZ{ zC$$FH;&o)(jg$tquvLE-urFvWcx1%;UUtSzSLTLq?qPc*3>0ZV*4D`8LQEu3!$>)9q?ztg3< zE$pol_7`2M+rr*2VZ$2i)3&fvB<<$5nl9CC zVGAT|thUN~(H6GCb^#l&!A`J+?IdB#>Fax2*ijM|wR#b)&=xi*VXJF+|N6%c_6-SJ zUxVFa3wwCGovdAM3!AsyPS!qQ3tM4_oviI_3)@k`n%ZbAXA2uTYo{sy{TU1SSeLI&fnp}{_E3;ThDZKlEAZVP*l4BX#FgDqtX8=Eg+duXsf z=h?w-m#_mg*bi)BXZ$N*M`*Cm+QPmkVISA$e_PleCG1oB{BH|;QeILqNuU31VOz^~ zo379QXKm$!><{xb*v+=E9!Wz;pZ{%PJ4@K*`uuMTJ5IvBsKGX{g?(AVZqi^c{bdJR z^Spq4UxWS97WQrlyIq5Q*%o%Xgx#&dPGYcRY5HG_Z!V3qrG#;`S_CVj-kR$jtwZZPUO zgff&8Lyr(Mo=ODpZmc;OHRdOuFiK`OUdt&dg5Fi1D98L?SWa;m#rr~r_v7Osqr}>h zc<+|S8m0W`j;vvv#7Gia=BC;tPmJZQa$_0X*~|5A%Y8hxhhT zJbWvc^U%j#$u6@N%GvLrVD-RRewjzcTH>q+IGeGSIQ!gc-t=+i@LB+MWTD1IdJ#;} zef3KTUT}EH%)Vx&2t2vP;n{OB0*~TpiL+LoB0mzgQ=js;3K=_tqKJI)Vrj2Es9AF; zIHRC7kI&5523>#b8oFQN3O$PaGVF!93c>DFV0R)|tOkaJ1-4z0ccf1Ji&v_}8e(}q zUTKPVN&5I#3q6RT6_jNBxtd>ifsvgc1aUWbf-6rT3G)B4Kx-m;k=Li9K8b4+4h0Pp zehN>D2a}wHu`iDvg@+p9D^iyRWhmTcByMyv7;C|8GTh|_G9HtPjnZ_(a_c!oI%7j! z24HGw_Sg!QO3zW^w#@y_cp`GD7O&?vR!_L5(Zh@9*@5G{OsG6=^2C|<MR~c5Rlf@y+l6iOo9k!;AxQ+GO7t#RYp8_NY0;Bn!mt+3K^C7_pFJf z`AcdRUh247&W4G+NvW`ZyiE>Tu@4nstcyB%~m?5C%Wz0;laK8~;tDG;| z*ST7abN+_oJ2^iQeK)cue68;8#KGQU3-tf|<-A4=`Pv>dlTZ5{8v4Cue8%%Id@J~K zoVl|K7Pq3L5PS59;sGAhe0-9D4MJ2G5*_Ny8lZ3Xw~bWY_TiO6b5)Lu_9U5fEiddZ z!t3le%yqy)H;$krgED8#6S_+5jJkkU+`XMI@7~UbySHPo9ntFob_G-DAu%n5j>r4b z;5ey1eS!uMO1+ZiQ!ZEKKZITQ0wCvm*ue)n-NDoX-1H9_^U|qz1(OTF&1QE&taYdH z|Zp>hzEq@_7ar@azt{&9kBoi8pSHgdB1zS{0Zy$JI-)=EoBKQ@E>O71H z0zOT8BWs5R_*u8kFmd+HWdcnUt(WW0g{I7dxF6srp?D7bED5Ts8MEIJqesCMVMRQw zXz3h8amSfvqx=Z5)}!BNed!z6T;v`n&*Bid6*{yVgsd3-F$ZbdC@RfuD+hAJ&9G0A zk7RXvE=Ym{==pj-&!^}{H2!iYd_fK$?QvMOOV0Byd>n7pQRmy>4gmAr_h4A2@tzSf zUL9G|fl*6d$Y)D4C_g`x4j)7F;KO!sVb__vit)odb+xcCT-g|tk8{3d^bLjMW|$SO z7|)!&7fO9VogQ$S%@_s2>8#U~KqA^5n7|}J;Zk~@#C-+Q0o}#oM7xOMJo;1o85lCo z0S;YgaJ&)*_NEO;7?rC-EghqOqT`^D@!3f7*KPHsE|fo`o=SYXt-D;B*h0 zP+ZCxr5yQ-myb|qbB-g5fd~T&+j`=^J z06%?lHIl9PSq@F;Tt4&o+pKd1+&r=8->}lL?fqDp_k>VBa*lZOfMmrLg0~U z9SMYp4l#!-!WHBm5QB;-b+Cu|9vE5#S1JgukcOH47S=V~MpbxF9-cjI_7~v4^ZB!Qhrgx# z5Awj@cUszD4Sv3cBl2&Z_6&Kx)AGUl6CA(z6*MljDPN~Ue_yKM6nO?~zoKkG^LP9& zvW#_{9=p2f_#?lGzeqX-ZK7^F*l)0hM~m<9E`(d_;nu#a!!N$HWOvSztC5MbFwp~v z6DbiMAHzqV1~*v2^@hZIeE`Z6-@GIfn{y=W>jCoXD`zdx%Mrn*i3HFq@YiJ`mp#rS zfJ#drxrDw;4R&f9r1$!@VHxzl$^SPtd%9mGfy_WPh1+F|tdHyD0n1a0qs1 zD`;wQK|`(H_A>7}nV3;tp;!a8LMPJ-#W4gdq4Xv2aOLOtGpGHKGXYMWT8J2!h!zyS2fhtMA-URL(jAvZnG?7~@HhpQ_LW(Oed`smH z?JP5B7Hg$m3sYe&^ZQpJI#C{l2Z1cs`L~S}PcnoCV?XLdMtgsRVP{3DzCJvtSn4 z11_x;_JG0cZS^W?9bj*p+1n^@Blb5an|=L-J?F8%f>GW*`LUzC1Fwzp4*6r0cf$Fa zqrAUokMf@VeUx`b7VC4fr)PmOG&vp!^+5wXFVk^Y2GCexrEyHFG>+wLYFf)=L6!V?DPlEDc6TJKS4*@GhZ&Gjs5ebnQg!PEEw@-3_NEN8a0 zt)w9d-^h@Z856dyb%DCixK+hS&W=$H<(<<7+ogy~vk4(^KSpcInl!*3>Oc|?ZNxZx z3NDj61vVTvly(XP%_-#TDfZAI`A8s35PF=+09TNanw%TeRl4IPk+01sd+)S zdj_jb??sM3Yc^O_2+-peP}Uc+Man5l%I9q8?!n8Rvty1&gZLQ8iGRF9_fUD-2>+9% z=4d1QUopbZiV^-N8{vPk5q^f~OOteVDlGf7W3q={Sp%1Q+Cf#326Zq~#+jxGtocAD zpi#OE9}8%djx%q830(yDGUL|c*(oRSW@8$Hz>nhkHt5H-;J{w01^i?37? z*}uv0Y^CBa6|P{Iwb;{Sw*LC=R+_smedSU!Qe8PCRpv92bqUls)8$aOyLjh+6{o3$ zhPI+v(wXT<@Mo}b{3`o<$fbU>*zXSvbDvm(0V~5LhG{61MlL6P`)~bqWP0j5CH}iO zBmW-Ke!H@IY;vl|fG~dH3H^DPo{+PLCFbmM%9ZR0Y5*R#;+az}+pHrX>wL&MM<54% z>A_-@2~;Wr9YV(Z3EI{fy~y6gi=CSZ!kGht5Xz|vWmOPm`Tpl+<=K>l>yS#8vRIeT zdA9__buD4fw8y_=H4g#Ab2>zVf_Uj$m!i@_LvB$tEc;QXA(zq66iR4NLaXlxVh_X> zX{#eOi$dDPg4Co9Cf-mL=m`#Q4d7bzcGl~2w!~_!*-JIZSKny3XqU;pMq0)b9OWFN4in;dbFRdd`zYINc{0tnB&Sx4Y7_6= zO%7FK@7&dr`M4rEPAPa4KhOnYy~8-TT`&1aQ6>8)U*KwIpuXuFOsb|U zlT*U_#SV9_g3XxqkWl7#nEw8LM(ua~`;2moiJQj1mohVcVT(pMaG3KYo;s9ZaXGuR z54gt%k7+*hZp2-3v^rWS$jLbaDN~=6VjS!x(F~XJQ%Lse>=9$4?7Ct-VsDSqQ}iJEG>l z-{8jG;2_+LaSB#8pil~jv@{;OBIg9`-DS_7&X%S=$E0{%qxu|_DHzE941gQwRlekX z>v-($Gr0HI4{KuV?f8HmQL3X^c{%J#1t+QPZ+h;K?1pV_d_lXqmC!ynL}=3g1)(#i zMG%TR8xvjtGP46>B#XFbS%+DFiTzoNI*aCzZZBLBdxYGam2)1I#*$O)%o0wVyY17W zR&o?%KW91V*|fl(Ctgrfb^0f>f0IiyevXBS#uILCn4OG**vSpT58@SNpw?8ur^{&V zf~+FuNNl*hh#DJ+WjSH*v;urlXI)1h!3~mf;-->AE0nT1IfWf6ahL8DM z;k!eIuQAo63LBfCar1*zSNDll!ny9#FR7JIjnG?ZS$)kvdM|Kdn_qDvUveT}asn^0 zdOMaEY`Cmx|e6;E+qC5cA?| zID_@!=KTD<@R8k{0X`D>gp-bxF-*vVKMvyrub+XhXD(j??bT-@rzkc&UbcB9+2#op zo1H8^45qKhK?6)TjDLF=Nv(mEA)qwtS^0Mk(-H<_I{@b*oa!*dBgC0@^WgMutDnc+ zpX+lbnoPf143*gWkX(=PF7oO=7p;saiM$)7rEP_T&k}+rm4Qyc;RPWW@?{8yQ^Dj+ zx_B#BXZE1aTohd7RERA{Y2b6P7DGGT-~uMX~7-(D79Ptc8l8dE*`zzdY+CCwHOb zEc(J)?v@z52pC^wymZ@wM`7*Bclbp#ny>$0noqK66|?os%9#C&ae^8DA~Sv*C(*&9 z#RW9&59NRJF+%@mT+#kF_Gv}=-#jSCct1JDlP$K+HWH$eguv^I9rb1^N3a7)x&z4I ztD3PncfuO-*ZE_d+yZD@GghLIxrQSal-^3F+;5p6Vv z{dg_1AAJ>6+C$|GoKr)7j8AA8Ywe0W1|a-% zreGxWdZ19Ub&I7R8W}z|3x*rooF6H}bJzHUO<^whd@x3N+Qlvav9P5E&E!iyfhls_ z6LiuPe)Ur_|BKChsil!QD{u&v}7{OvWglajWu_e$!tmU9W?_p#md{C&_b2 zg&LS666~4;D^pW}eZM`T(6$%k2|f+%gY6O6hD)%<8dy+)QBCtCSX&KjWF)rtBv>yE ztWzXd{c8ejhz3@HfpP!ZTXq5K&Cw;1^(}VEgc|J8?6eg+DyGJo@9l<{?9tR~H}Nc2 zb^?MUL&SNPky<$mp`7$hJV*Wmk!Swb6v~JUewAy~HgwT126}NB1>N?#&@(iON^Ntg z;CTNVC(UpL;e3`hb#TAFTFBTjNN{UGRqcG}Hf=Abfi%)r<3@T4iJ1^G?i$BqO6c&> zMtuxb7HAqW4i8cxzgXd4%fesO3NO3O>c+^WS<)_-9(YqzUwOW>J6)PXan*S;o;II{ zesOS@3E*>8c_TI_}_)ToHHjEJyslAl)hhzB@*NOZ3OBSZ6`}UAT&4E3sf-4_{LP@p&RUT$2X( z5y_HAYbF1@g%+O$G5*0uL9Cx?9pQU{&n}0$b0{R-{gKW6cDk^1hrD14pD&Yk>BDer z!EzAe&Bs*Fl3^K!7T6e}ssvL!#+evAmO+~af%~L1=qIH?GM5HEq3wedg2s1|UD4e9 zi6%wtVio>=Hq95|7u|X`o|XtwOi?$9*`?Q%x5$`JM5FyMK(OwNrp&zZAz$!X2SVm7 zl9Q}vdw9*FdkMd8CzM``OL!j3jE~DJA6olEqWLVtYCih0R4N>$9hQSJeFprG55^ow z@;SK$MO1*?x2nvNLwPz7#Bv7M5@+rxH!^!Fs5U+5{EX%ym1v%x2{L2w@RHnu|7M|P zf+f_S&BShvE82@}?YZv*>vT6u8`ZpD> zksn7Qrih#NKEk{ZrPLXSh0FMO1j^F%1breF&W~lo-X6_<*=TYQJ&(tmOx@z*0bz6b zg?!Hgopbf+k2TVn6wydkZc@Wx*?TTQ^9NTs&14^@P*?CYwMkQi9DX z)8$w=cfOD-K+u#pUZ;$pAu(FcJS6@PTE)NjL*Y>YZ&kN)Lt8$cLUUUM|GH^H&5AbS zmEDS~)J7p=#V}=Tjb=USTy)4=YVOLpHpVzl39 zXZPLnHq*8Lea9u@jQSH%-(lQ@YyV&Z0VgN@1n}{A6iRJeX5erfEW4U zs^+pckuzm@D^6z}m^btxeyCFFw+%@#D{%R_237&5Japa0;}`Y{5xFPbos}LL#BFnQkzk7Gv)OASl_y`KUJ3f z$!qCP;Tqv;vfOlQxr^VH)~RXi6&@*EDXs)xR6X1`Q7KeeAQ6SqJ^BslI=m^Y8vmH^9FWuQC2LG-qwJY8H1`+v%gtRvD)T zG-x$gUvW&#re?Y$hH^l@%Nu3q1fY`KgoPiTaI9I;Z|D3$anaT zBNd7vx^=CiGfa;4IJy(48o1d)oTgH@*dRVkGxVYBD{m{`a77+{?LYB~p!WdSUXwjR zc~6=<=S8I>9=WCs1*N=U&H-<5bOaS<{ipV( zsQwc{3+aomMbWZhJfr1uM|tm3$GVTOHF$L(kz-Z7m!v_khXH;4Ukqg zg0u`PX_sER5z++ok@X}B7rq)r+LPlLY13Zeq+NaG21uLqaP*bV3YsXSXg5RoTMC|*ko^5EfU9Vo=@zVw8exwz3e16Pi?u?ey#QoDo4*{jSsYP_M=PX$ zbRn@&$k{c;7E&*|kRmmNuCKX@ydX`dtR?hjanCOw16^MsKC>ipABtMMBFX)pls=#O z76_yJT`S$AH*hDch`jzvv6^}f^G)%GLc>COEt>@hJ!qvbs&Ko|V?RlXuMg=lXLZ0Qn zg05HE-!){^bWo?h=VgntxCovdumzNSP^<=-#Kd6;gNOXq9*3K~bMeGbwe!q!kMTRa)WCYvVn?L9(5_6|PeX6R}I$L#yx%Jzsa}3g)Ilb^3C8eY$ZO*yZvxQ{TdR>)`abj!?wekHp2efQQI4;jyW;If{iN3 zVsE}IzY8_L+=%E;OV)s<5bW>TTB(m4Z(=D8Iv zeq0qRmbofI*W5Ky@gi=OQr zbIelJIW%Q8H2bf{hZ>w)NvNX>r#SaoGOd^6sc=J6Bsb8{H)n*Z(=98ZPT90!V3P5@ zUxA(R{rMMM4s-8vn+=0I_t9eKp5=fGw}bvdQH77MEXPN>|BRD%Xe02)l>GVRHo>1E zmlS{gegP+WcpUn&*lu&EO$S8ZgjFE?kl1ucmx#O?qD_b>JKR2Gls;xL12v*0Vg>WU zN_;58dHMS~I@o|OA6?N-SECGg{b8v=7Oapy8OK9Luq7U(ROUXA6_t0vz?qNQuu zkTJZcTb^3*g%dbA7a_n>vhHo)NQ|Z57{|;ROK>EcM?eVUfx13H5$3OxgEpVv+8}$g zzX^sN$cMNE_#-NG(p&I>Rawe6OV$kFD!MXnF+ixBb78;-+P+xhzPKGuc}E|L&7)&3vFjofv{S{`s>6`)fM!zyET<{~dPx zZ@!wucM}8ycgveXMz=pKoLUI80W{F$km1a;B#j46BdXQ(0L#77fhw0ZTYFScL~}ly zP9(QPZNcR2$w5n`M>JDA)DP3oS`g+eB@>viujfhoI^HHP?CZ)#b}!?eU|f6Ffz1>1 ziC?u}5cAk$QOt^2r8;X5MgPliYt8;5I`93E=%?q;=Ua%LG2c3kEhYlVGNL=c$Wgz9 z4O(;NGNMq9dnX^qa_r~P?cIStgn=Y-=I#l2+HgGiqWoxMCibxd_o_bT4a!wty`}mx z+qu%J$*yW76w~&9D#i5XG`YxI^+L1()lQFGw?Hl~f586XVRO7ww{4nZ(~DL;?p`6n zf>S_nR%8-qfBzMH!RVpOtOE_P2~kzI_TgqLTpEAMSyT_pCcGfpOp|Q-n-OguSt^;apnW40S<2rPTZ=KczH_pOQY<`!eFp9(;0Y{k-To!=oA{@dY*5JFCI| z(VxW(_A!6rVAq>vSDJN@uh0iMXmdljWpaI*E22{MkrkKnnIczAulf{qB^PExQ`5TRj0k@%M*K_Hhm$+)ILQlDuR!EZ(Vg%DPc& z9;MAVbH&swmOftxH9uETzd`2yxkz~Hh#DRTz7=U+UHq(#c~xW`c{a*Vs&{`Sd(-Ek zX;fCcIj`BK6IgMzBfh{|^S<+~CRIfIPYNg&Z9CAGNKzV}ENeyQ#IF279is_;HTl;V zlfEY6*IfQ}Nga9P7Lx`qe@ZZ(IRNw6E&T3Mg7Jk6_@a7bbAaOCLg`-|SH~At8!APe z%sVR2PcE#k-5NUE4z_`WU9v^ZKMNyZ>5SeBKS|iX6zuQ_SmbT`sS^UWw5ecQ+rsvi zx5J+KUBS9-VXOZlU^i;?X3nyMy((`DF1Vmz-?N1sBVpHSc<0Wth~ zz}C?4rr6Su{*8c*(O~P%w5wZ-Zv|{U4Ypv09nYSUu+22sBQxyqKJlG^ZLh(upJ7Mu zCJCFQ!TPTQ+f#$>X-n@2d0%&m23zequq8fHuzAz%U?)k~!5ZGZwy@908@F3(w-c|l zh21Xk4%hIGwuMcW_lrNN!6wxJJWswy@cfhH)C~5L?*gl7>YZY)f0%dvc`w)#!CTW5=@&X9Vmk z8tkd3i=VTe)o6Ij7FOZitl^zy3%fzWzOTU!w1utyo9ydt)p%=k9oTIe-YZj!ht1Gn zkK4kokg$g|*p1i0ds2g)avi+iYp{J7tXaquKfH>)72S;{RUig16$F2JR;x(G2fir9 zrzAE%eN#m?&b6CTOH8%G(y+ZMVfCr+yD3&!n$8O3mFN1@_vUqA^{H=W1T1y!htAoF z+k0$b-#=$378}~a9?P>6x0ff|!S49mPK+O$Y{%cTVLOrZiY?xO|JsS9$+obSF4&2K z-nOtauiA;Enzpbp`F0}d+$1~La_8;D;sIOOWmoOQ_!?W-j@RtO*He@1`1{&5JCW4Q z7FGp^&_!KETN*x*{MA+1aKH|B^+h}N{4-nFiv@Nnce*X?8cBn$az7fdt6QJTcIsG% zfZaH*aK%m?^Iiv5SI2(!+tIsG@4PKxtpnP=I!wGF3V~D0RIe_ z66dnYn76T6&HNd@Jq52Bli^J5IQH*2e7gmnRTDL=aOKm z+c;hGQ#{E0)rsHJ{;7RjJM8^z7qGQBYmuG9|DCoznt)e}Bh>INXl}i3G?@lvE&eLa z%L+sL!y4kx$4f8&k>hR5Kj%90i}k96j6h8ytg@Eh81IS*>bbkKv<9aH_%#NMpnB-5 z3Gu!drfwY5m2VZMcH^U*(cx@D76goIATslFzs_(R;?*3K;Vv>Wo%I+QdRrOCnk$k( zvI)#FM9h0Nh?vP5F>j4iV2pr3f_Q0S6qE||#KLhMI1O#-ZDi$|iQ_}CM<~aj*rb=6 zq98Y?$s!Kcq7dLI5zL>|Nzz?7Q4thNbQe@7y1O1xbmu>%z-lpeDN6S|Qv{`pu3eAP zU!LJeAzN92{y$MP3mET95NgNLj_m79v15JLGbB2ujdy7$urcY-(=OpCH?^0!Z>kHs zZzX>vb31>o==Yd!^Jh<>y!o5zzYDd@GE8#LSc_|7qFJZol|r+`1hFM`z>(mv#27Kl z{9nWv;V#7a);-s)9c1&}`!vdr$Cx-&_C4xA(;xe@MfIpxrP2%d4_h? zR3iS*@kl%dWKBxMK0LG}xV4a=&9<;=T$<%62xPG9nzhcfp zRkiU?@f&Z$Geh(Ipz`fU`}PhHxM>&CzB>0NKw^tAX&PLHK*7HG#xEpHnJE3cSo~2EuLI@<5XJl!DW7d&aAvnYbr}YC~hI5GckCED+Y71UR znKZ^cvOXSO9}f?bOXmPi29r-xm>9=Q29%wX9Lux0@%9Ms++d966?zH@hT9$K@$E^} z*XND44AfaDMzZO?F`#covyhQmN)EA_=IxZB0?+XDHFT-RTghS}?(cCLVPX)&V*IAhB{3HX8hm0|$bQz!#$YenO zQrdt&^d!iD`}9v$*zh*{11`99Dwuq*Lvl78B0h6}2!ATU7^; zse0X@`*;_RzyZlHoP<4cEi%86+ZK<|WXjv|vMIW=JcOn83BMQz!zoh?r~3bgxpx7N zs%9R>cT2ZjV4(|?`$dqu+!480gbFMoP(-8xa=(L!BDN?>3-wT-tSe|iKv7Up@qR-@ zL@bDah*+>MR4G`o%2~Hcfdb{)FEf+m9!~m?PplJ_4R|YTe~0Rt>tp0n9Gr39=*F*Ih5~E zqIzfw?1vt-U>|Q_?|BZ?DfrrkN`{`1h^_&1YvUdV=bA#gyQh$!eF+gD7G%5=Fp?o}x+OUc&+8Cj5L4Lh!ih4Q&tkLE!QSZXt~QBUn4v z5Hi%AYU=})h8FUMuAhwfsB3>P0OTVhhSI){Af!o^v4m1;zbDG(X2j#HpG?Vu(0YY@ z8^*p4mdFbP`=@gy4I3y{uwSsI=)8Le-dVA=DewB%!pkKq#^3hEpnc zmZTJAmd`d5>8VKyk+M)1M;v@;w@grP6|C+c)x)%D7KZXplMMCG8KIv$%Fxg6M5R*Y zs1iPyW?jw)Ur~mex*~ zDqA~os#5cyN3dc1vgOTA?$LxZFO@rHc}%JE8_L3sA0hdlI9VdUG(|x^{^2Mga0A-v za>`R=^g}+KK;ZF@L{T_#4hoNv=@Vy0358paP*=|E2zBM$AE7Lh%O(LibJV1K##4Gt zz=cujQ*_jOiiNufI+dnA${kfOPC2BsR(q2Z7&y#!p*FO zKf}Tv8OxF+wOdH)kPzPa7YyO;u^>G-*@AT3kiq@@C{^>srgc!^>h!PVS~%krIsZsDuvG|7rtk)dIY6R=^SN zo5T_3X(@RQ2x*?jnorlH5nuqTF?h(>D|~}uJD2M(7a0e`-Pb(cXV|Mci?l2kQ!^pe zK`yF!wDGPK&=PiI?V2yYJU+A>vjt|WraPV4U&9w(3che*MwYkbu0WqA4YjP89eU)B z{76)b^Zfl{x0a$xlkZP1U}|N(jDbyR`4e*4pV)wZ7+@dR#4DCzhg^nppmq1)8i$|P z*dz~3E*9F5UtZ{MLD$FmE_^<}ZQ2ff?L4Vb^7=Lf$}uTooI;~#L@A3(a%&z2tvL9S zlP;(8m84i%NzzJ%R^piJ3jX1s*FIChyVnWNR28-4?QsRiN-V70jt7bR&W^)7TkyGh zLX?qTIvpfTAxW6VrO^)gBmw=9*PICaIIYPN6doneoztU)SbFSQv?5_f8o!u?876-* zanfcSNSLu|QYvF4{j+INB)3ridP-LO_jDAq)ASY7#5Oe=wy9M@eI?AT3?}pdqh*}0 zIJJ^Yt_PB9^fYQ?K|njTi)-+sS-KDFE5N9H6vGOKvjtwtMF&)P3u1{^n_27x`o=69 zi6MsA@OnsLmJ765T@Lv2!u0{SylIkF2*Eh@CvKt3CXwTvRe7{x{2WJsU30{>eCY~k=4qlu`wzwoj<2eK)Y0d40WBpQBM6vp>mM63%Y9f zGE+a{a#AweI(ne*T8cL^Stz~2tTks6G}8;WR@DpUDU&28_nX9RuX0`G1p0m|4e`~{ z(sK6NSdxQG1wYtw2QUY0LyMjAT|)kd&*UrIsS;~w3kn=uaPeUT>7K&U8xfiyD;qc! z2Pz_Tk4)-`Nerqx?%h!G^&W}TaH4Qvq9P?C)F6$zZ|>k)rq~4QpbP^z@CH@m!=y~4 z+n2(--z&ZQMc`cY?)NGS12t;%6tf9q$rNErEakReZ0Ac4Afd>h5vL`9w=Tz!G=+?3 zJYsGnN7Qo)Xt^dN_dj5mQdi&SIq5f>CvS*XZj!$LQ9s^Cz*HZEwa~lV=V06hEQrYz&`e3iRbj|VjSLoJ|14D|4L1jStgeG*2*lR+tR%=gPNXHff% zF~|0e77I384k2M9PDA{Bdlay9z5`4i`-8b?@`!aLx5kxNPNF;-?M0Jl5^Z@vmLENe zS_amLe7M>0G4FjK^P1A_AxOSF(7yL`-+ZMrn@jM*5}6-5;$kH*Mugsj2EnC8tnW^! zFC`xUW_e`8{$?{EV;-Bb{3gj8lTl>|et_nQTw2S%xG2`L9Pxv%WnuVUw%|)7d=dw| zUO(900xqSBR4FxNG)@JOQrQ0Xqsc}L(iLSv z0bwNXi&3(3D~4M-*Lb+>T$AC-ZYE|N*K#mwK%A5G+GMn9w?Wx}m5mt{Wxs>|Z7fZe zYWGY0TZU1?@B%UjsmF?ikjhee{r-I%%;Oi4pkz`5I~NFHFYE4NvOGBC#N=R{?m&uMBn2M2V2rtbEKNr(T@S>HRY!wc-J z;YUZqh_YrwFN0Z5K02jIBSnLYmiT@YcG`gNN2S5(Y-WXS6;Ju^&U=!K3+Zj03`% zhW^;dD6#C-I-@RBjx*{)b>+0ODyzbBE31_g5?i)V|{tG<3}N2on&1|#$VBzirA+y@kLw`I~I)Iba# z7p6z(nb<3yWK1KKXw+$oh$O8-%q;_<=2C();rq`isY1w35tqVs?zwdG|wRO6=q(6F% zU+zlV1!JX{V5inaYseBNM14gdHkZe2W=*7JOIc%UT^6cGX;OIrfz5jk#s-h1XJ+EP zOfcY?D-S?NZ^rIqD!bF|Ag1}(H>B|>=leSJlux`T&gV{QPGJF|yKIelv{0-uxt}Wf ze?pY7Gb^vBwAa(>;AwB9Lx$#?crRZtbv|Zi2FtNNI~X70jZE$gQzTNckc&V{;9Q9M zKAwNYq>pEH(h~I+K6u&qvRfF&p=br8=t4FU!Ka-LVFVIK%=AEg>wq@(OcY->==85p zc+ep?wroFce9oq5oWTxT{-CSXNjG*Lqpw85YQ zu;D6AdWfwtaAbR!`pOpgdEevGT;DH((W#MsdeI5Bb2$!d9-WcGBT$!vMh8o_LN z#;YiXId|Ya7Cq|dAgSl%4T@3*0+TjhQ4e10*5ZC`l>*vlC2l$e2z8ccR?d}n+UqxW zqn(0ltrVf)K7Pt0gE)4?hhapcIu54odbnBtnL(EJ*EQN-ZxGk$QDaQ%7v?E-kG1ge z1X=jk1a9Hyy03?HUz|&i4l(&z?>bZfnCp1U&qr-W@ zwp}ILraW)g56@kaZ7XrM)j;Q{BwwMg)8ER)4_z_{`imO63k~ymv8?HT-^HW9JlyYJ zM1MKiH_8!w$|)`VfdLi{t~Wq(aQy*d8-#~Bw6PKK`zqW=i+}gKCH!yHeE94?L^0Yg zJ*gP&>4#YS5xCWT+?v4w(PTg9UTL!b%GJ01xKXE@E4BFDNzLqrvZ0vu8z z4YXl27N-yMKjMjl?vhI3#eNcXO!^>luQTf{)vCg=UYmFMzN7yQ=U~G=R)^>8DuAN+ z9YWEd|GyyP(cWexopgT{dAttN-ZU}F^^=Sq+3z|NM$2H)H6E4(B$}KIW_EAk8LRD3hGVX@k z5d&$#hM8fbMAYmn4hdl0`^;xq_d3Oty&6B%mv0mi(KR3EIuUI&;G-phyOM*)`Q}q@ zRO2UXG}QQ~@04RSm6uRMM=l;rM-L5gH=^K}rQqmsr+j2GtbZRzMu_-#5V-x(! zrHJ@YGyOG=rI_p9{YC2<8>ex#tf!~n48XSSlnE;ah^E1_^m>AU)J-H}8Re5>SRD{p zC|W(G?0{vWFrZ9Ry&5Z-u@fWB;!|GbfHNYDUH!)232EbhKg6*muv5`;|lC_~g-_I6)?bUd3M5f} zv|HdbDB2T`Ym5;*xtuOmE z=T0dowL5LzhK<#;DqtwO4Ky<=_l^p4!B>X0XAkRgnpOd$vYQI{MRfMhcnqXhdYF7d zu^UN@*UySt>%yYUv$b$h&VPLteR z_*q4OWjvrg_ZJDqWCdB%?n;45LYrq;;5?m+z$15YpC`h0&G6bP_76reOpbk#+&Jw} z9R>HrStT!0?7ZEE4t?}<(TY=R9vyGx(@tyTTDzUrYu%(% zYo1(m^JvjUbKiv?e9_I9{;K)hUlq?14)!qoPv~jf9-L$a+>U(i4snD-81Ttk?o-K# z_P9<&d-YX=Xve!5MIy*Czd$Y{l>^$VJfikq4u?NphfVGdGXHqEi?ZSio8Q-HXrWeA z3Y68^UvP;9^{jwXcAARw{XS&XHN2hQ=mHbDkGLy@_dVuE8BXzgZkOky{db$sM=!;z z=c7IDu+Zs;?$Y$0)1CL9pN|&s^HFi8Vpvj`KHm20Zp| zAralV*QD_FC{WK)-|1=g_Rvu#;Acip>v{f5J^6V)dTIRPlut=KMjk%P)sj zZaI8dVU2IP0Jj`hlVb0Ho6>*_c#zYhcMm)hM8?VIjI-=EJ~r}dOX(eSa+q8ZMkv*# zH|r)rU_IJTx10Ep?F~J4R6h;jVu~8V*a&W{j3-$J3lm$TlHCCfUWW=j6wrpj-$^1X zBB?6NPpYn!IKgZ`0y6I+5f1Uz-Axxb=uzQ-HX71e1+;PSw{bx0kRzM}+~gcEajh|) zi#l1u?Cx^P1q0@V5~`Ez_*%*AB#&0|cv2;;WPDPUP%SN?*crMlEur{n&`eshkDc(r zYJTA-OU)-_a6+`b@<+w4i?gU~*1F)=-e@zYKwEJMQr+P7;+X%p0}0@MUo@mc|G59Hk%(7szkymw#?@!q;GV-P!Uy49i$`WBo)#u+%;C(A`PHRV_Ik&1bS{rwL8ok%?{|g!+NxF44 zNP1dD;w10nMEcI2Me7tvsJ2(|7mgeyt%Oc^y%Rs_ke=tb++}A)tFWHZ+ zl-#CLlACkr_6^*C=CfI_>%@*xMxf=v`jpEcX0RKrI&#al$B|q)K+vCcjxu7#GWped z9LcY_-SOO(`y}nfBYh{n-$>UvCdQ$S4g^;b-ym>R>~6}D*Pvf&1H-zPiLK57ZSVo3 z%d=4JT%r(R45rLeQh-iE{Mwp?5}iE z0{a|gp{1=W9d2R6+jEVxKPK*{=f(D`K4%Ygm0DiYuDmx%k24O11uPW9_oWrO(xV4y zDaGLam8x?Byx9?YwUBzn#QSpk@)|w$CwrKh2G2WtNNDd}X$bAdx?=F_`7j0Aq3$*9 zINl?pS(DTrk{(i%#<%Ai)8IjPJmZtjB;6V(R}o%THQPoxf*TlD=_@;v2B=F)*5H(k z>mte3zKcSx8k`b!F%c=}?E?BAU;>*z@Am7h$jfi8V1(yeVL+gxZa%iJY%AptEl8<= zTcmOA#Lg4-C9I}EN`-uq3I#$cywKK=3ZTYIsgRFSAs^b4FQr1hlnVJ$D&$M4kT0b| zJ{aXe|I9b$T0TzzXF`u8S5rtNRpuohnbJM?T?9RqmXCUHWXR1w$t!X2$T7w#Q2GTXOVB;YE03l_e!J~fx0Wz7oyipg+WhCp z_;|bYcdd&4t^hU?Ur#9xHQ+CdJ>QYr%iGZ3{cW%;Kiqz}BOYMGXX9J=S+=}PASTem z%9-{+J=hXYY`Tm6`axujx4tu`ed2A9@{*F$`!<{fxc0W}BwRpy^7H4xk@^ZkdW&Bi zYnK#K^b0PN1KKJ?hbuaRMLV;4*waUkI}elulT9p{Y`o;3%#ih*XGc)Id6a`Y@=om_ zEeG{l%G6;UxKW8LH(*=_AD5Vge&RABeFe1V0LhyNXm41%gRu{7K@33_+;hd;PXt|M z8dxnxRk_9J+twIOeKVWv^34kmnPm)*9$XW~mz0*m(?t3Fpdag8&jd&)DxlrATnOIq zf{_>a>s)=5t<1$@T5Q57^=xb88U3H;Vw8%Bun+wuMyZ%asp1|nO5PSUO2uaT+REZG zM`_KIcB9m*nH-ND?c^xUX^wOS<3f26x;^eXL$auy9Fn-^>X1x0s|*R?Dgv^H%CzRS zY{hw^fFo$eSX;|-nyzMkVO-5TEv{zPQm$qeZk0~Q%2N$E3;%9UhimGUM|!nTymW)J zdT0{{Hd@(P-EsCZy1T?Y`?jGNt!>LqkHu^o(|uk{dVpJ}V67o{GdQmok%-&!u5nMm zIOz%40n1QoQ~u3gPeKpD3h=x~RrA}{9y}NgXrKRtD@NG3UcSUww_!jV@GTb;VGxtA zg5G3$a2dM+rdBsPn4xbeG+)S;UFR86%Q4cr7V4YU)z5(L)^h=}8e=*ieWARo?2zX| z8(UfrDe_K3!6;eKV_)!IwV?j`>C2_!01vGamB1?D4WzxwM0}`nAnkQO&egPy?5{CN zX0c-O@UmFZbUWj{S5|xHZwy2IIBBTAT^j1!tA={;;PyyUzDJt!Jtk9rk8gwltH^Jx zZz~&1Q~rVGQRX-bqnk@h?I$8nFzB6_R~YBh!EWac{#}m0*bvazbC*mIF{B}_? zZi7yS+nV%6j>Hs#a}yijMgkEm$+76etbokgghy)GBjI^8uuRYAu`n+wsIu~42qiU1HB+LD1A(`(Qdrne$@hhDK@aw(Kr3d2?lvDM;K z%(==HDIP?kK2F1Ya?NJHD+vuFKhBpIv+ym{n+rh!eP9=jShtu-)SvEe8@dA*+xMe>K&j|Q@@HI~Nh#tlH%wT^NK#*10F*PtS-KrAoLmmjn6 zV^eaI)^gzStgES!^+zkYeC0Mm#GKNhN4d*9%PDBZ86^)c4-Pj6**=%v3b9`~~I2#phVa?#p z4K<^pGnvO1D^|Zfsmnd5 zZb);v=j3ZlcluIDn*TnnN7oIwISaJqe(JJS!@5YVW_UtPZ-}W`izq37brgJsxR&X- zmKEY!rmJgNtg)6AH6bHFFHD0KO2>6dHxw8zC@?hwS_VD)6}ly$`T6Hfk_Z)z{PF>9 zIejjdi$)xuCTHK}y0_|5{Z|8OLxcpM0#hPE_m>yaR+Y0|nyYd)jSmIcHDOBaHpJjA z_Pg6|P+CRGCw$U9NDbj#G1@YQw}lHmER)-eT61@#U2`@zGu9VXK(=mxJZZc;78cd) zlSZOy6jXB?M44TD*jzYCD!y=j7lbUa*H;#EU~-9w)I_^LZk*nQo_L zU)9Le)$$E&-9rBSlh;{&j}N_Z^+7k$Eju=7K96|P6|Q~;w0T%J2*mmzT2pI^B&A=k zk~j%}_ifCZ&ot`Y?#)##3)G&rHJyAs5Pa)f7LJ zLNWDidLFA6{sH>_oZa@jmv`z}9ijMv1*|-8&}dW_5%^Lo(yM%I_X2@b?0qw{8O9~P z3x?B?WKn4EDS>;G|CoV@_g(y&9X7SJB&&nrJjoEREq%TG_lN=I<8w0KR%q$jvj*<=P3nrd7A z*#=@atTgubI=DNh)dyYk(3S|(9rpdOJ*@yt7S_vm>ZniZ#yrzdJgR7By2pQq;dJh@ zXS%pLw%&KWe!nan$;;Oz(j2T0*KYQI!|+rUM?%f(fLd1oX>3=3d2->dSjT#-{r3rj zU+UWu=vZyjE5S$Ezl zjaxXX7Aprh18UpO;T9)I%}{(wkuaNgs)VUrJ1h*j&}o&DmujiuQ(Ei$f4givSBNv% zT?1Qr5C8ccFwh=xdRd#+B4mc&;K$B@_VlOR^f9lAHzP@$9!!~MC{#6Lp=qs!PXu=A zzTbdH#_uwwyn5I)5)DgUNU>bgs{30R*1gwJNdC-YbZ@R^wS1h&FQedtSa3ftn4%V> z14Ha>@&<7^PAYA<;&iFW6-$Z&b`#f9xza|?)yc&|X`5NYP}+*n3s$((qS97`7gVLv zRwR|SBB`_$Nu{kwDs4qbC;aOn2TJv%uZe#0S2?w^YebnH8OPS(?T;ldvP(SRce$$4 zDzd(=Q066H(r#$kuf}fZNVV|Y(7DI#ra_k~s9e}axOlDx7kBQ6*H}`r;71)Hp<6P(|#eXjI7}U0rUl*ssN; z{>xFNOSQ&^YgMcyP$996@xQYc3ihbN*s->|yH?bqX_>Lce* z$4V1{vOU33KYi?Ssq9$2fVS#GVHvAG*O;46dGQ{yBXuMYeLiVpfz0^}5yTV7{GGns z;#Mlsyt*J{B*r)9_dIMSSN2t=?$@);s&JAU?^Q9{oi$od@CpMx4#wcx!v1SA(x*N_ zqy2qM8D1c-px9|*@-$(|pU0ZfsYM!L_yQ&B{(E^KrjCEW&qxQm1KPf?QD!B0;HcD} z071s2+`7$c6+1KX)Z)#YluSt1z#-vXwClW-a34hG9?aQf&NV$rz-ZlA}?%(2H zmYP~aSc>4+qvLJ}=^6TN#+}EKP;3 zt*i%XL;5`S|86_kG+Wsa)rbXb2%7u9!XKX@2$g@brrA3+9;(%98I*M!*KDxBUl-tY zCKyU!LmE=WjWkdK;ROmD=J776Y=@N`tdT4$5Niw0+%3MgVC_-@^yetbq1o!3q{||cj7e9TJS2Q7WXID0{Z&<+Tgn7!5YQZ4#7?UKeL#NZearCTU6iz4 zAcGXej#2dpli&|rh8PXv_R&R9X<_HoGN8q7Atd#-(Bw$29?({A6`hH$OfCTv#a(d( zw}dztH`Gq8$aj-}h!Wyp7<7%WgLWh++JBpIL%eN3+pv{WDXEd#l(4Yic})36)XNGdJgl|G@G#bu*30Bb&Lvsx3`& zTuHhEgyTw7moBO!`!q6RXp3n6T^^|zz)MaSwg}QTP@(dXr6H3qB5(6HZm)!w$dJUR zwn!4gCy@Is(mKvx|i40DWK(WkS!h6G%sOfi4S$73M+4fOGHjP|)XWq0{~yC7i%3eQ~x zHNgwqk>&Sg!LvV!1^(El{Rvs7SAQ@Do9AgPLU&WTX}MlT0JU;PQ& z!d?!-bW!N97&uVOPW8%^Tv7`(Uyc*IBvm3fL+P{l}^R#~C< zYC&%o9D7F#uSeesg_tW;VxDj!e_j)Dt8PTh6=B4ze~gHEOO%C}U$_)v?yLl@{K)V7 z2s@jHo&Ct#**uk)%PLuknNq1tVot!U_x!&16#Va%g`di_trSMgx|I}SuCNjlZ{KOY zY$s@eOvaGn?4eU_BUl#dokt@8(?%_2qw+x9UI(m|`yI(%NEZw8UFA@ig3&|Quew;V z4XBbv>pARKNS59Yd04{++%2-75JQ;3y=Q{kV1|1i;qm}(p$TrD87>{+_BvwHKaFtU zy|NMElpn&u`gJi_{s8QYW8vBxH01{7Ga_c9kI_?)rs#GPInf zC%;E1Jt?dZQ0w`U0%b#$IQkUvzqf(oDCxnJq7u)#m2e*&_Aey>RO5(ncPSgj>f`Z4@RIg2OW+*2=-wgw0uX2w`u83 z4jw5K7mY|^tBvy&&nqweVq{uMb?zZ*9As7EW$3wp?A?wk6a$jNngGOGr7rWe?9PxN z?t}M4TE@~%nIen#$$_gPwxMILa$&jVWtZrC*Clke+E!@;*sQ96op$w0!cH4I#jw-< zbcw1*o*yV1vN?)QWC}$5mnifOXl10Vh`LTw8-sHyL@M`bDTR^n6aqbZnAC(WKkzpW z3wY#WmQZk2B*23^lyuR6L91d)31br(_z*7$Oxgomg(A|X`6=1Hk#^d2t&1*Tem{oa zLjMp;AgjJtl_#_id9)4knYPHkEK!j=!jT8BD9G`QY@WVxH(FT1z{!*#9z#jfoe(;i zPmG^Iih#+2jo*{2#rRd9XpCPq=oTwh;+|uI>8!wH}$Ucb7Ct1etc`mDq% zHP(B0<#BLnMq(b^etHr~!taIOzec$iPCtY^bh~2~e@+qf_z7%4gE+jfSU7V_^RF@@ zLcG$I?B9p%UpD(!!u~a9>66*NchQw<6V6Lucwa$q8lX$u0;$6M#&ZBnK(oIFK~BOp zo~an;AL0NQf;>6PfPq6x1+EW=a{*k+E)%FY<5a+_bMV8+7<(CUWf|WVrhicrW7VSm zlnvrx%p)!;y&6g{NXo8^l=c3UEPwJgwof1owfu=20Fgg&J^SNj2s{`-ejp)w zmJo%8_!G1J$!0_eSs4jy{V4{ndaPtEfAR+QC)S_59xi_QlhFv zq=VL^+<^AxYIRL|cD1@DEnTfFK-EfMO?qBUn#6cZe+h8OM-!wrS$NJRj`unggDjb6 zynBH0mmvDDmK7-`ub77Kv1w>L!c?@AE+sK6-OAB@Kv~05tig@mzzFf(S(sUQe1Zp9 z?@+50H>$a(#h2$@wDJ^?Vu3|paLC(7euq4l9CY3#QfErs<*tK!EaEOA+|7B>+a25@ zCc-N1DUqZhDi!fV$|B5A%{iUV0O^dk24&F9Kd@&DSlm7Y+ zBqk7D6|?2w-Y~W7fyAK~Jg>NfrDsSnidH9BgMF`w z2&gr9c+<+fO$=~4E5nOVg3rvCM;fAa!2xa<0t2zIm^EWd0Wb)jbFTXg_XvDYzTku6 zEjH1xmLyxCUnE{mz#@FU-1UwTskf8?N0> zZ!;b+`nKVR8})7GCbe(h^S*UD`(O0!yR}B&`sgda7AtOSz*k`H;u@b_7ki$TUEF?2 zcJZ+@v~r4Dz6F8`m!O-=Mi|}vYQJha&45(ay>!esd(s+_S37v<~kvjY_4He%@2QLdw#D9 zI%TQZvn^Kd4K+5FHCC53MkqBND1sWtOEg6(wpQMrP+tQH7&a14zqR&nzhbq&@tdf9 z)iP203kshN#@c^hFKVC7Yp))VZ+xLUT~A69Py)Z0eZdy}lUt5R?7 zDX4dbtoPEqGU$9)KGE3#y*sSNquW@GU*?M%-+xxr_>017Q?SO1>jau5W!3nfU#Ug| zH2PUm-#qfQAz{)Y$<(N+lO4}AQus=gm#HSb8y&-TN!r&ZsSny1*AmFmqpF{ z<`N~k1hgUd(=k*BSZ*>Aq|9Te>3^fH6RM`-8dv0UC}*d)y{z=rfGW*{zCtGN>jU)i zo%#4^@E5G3nYXDOdRU=HFXb71u&s5t(0j zLq+7$UoNsz=&SNnnv!dHMcKAFPf*(i>W>hd3$CP0=OA7|(|}gGG@3IR@JvAa(J!tR zH!lPsQ**661>GbSv_3d@OukI{WkfY+dYxXMI@nr&CFHts4Ck7xi=hUxu3Xj({e!Cp z`jdYCZaB{ScRyzo-1e7?Vr}2?Zz|XvWHf}`P;eV=iHac*_Q`;jzEsBAK531$U1WHS zp9CBqpiC$XXy3g^DxV%2t-F<0ih$d-3sFS^(i3Q$Pfrq=S6Fj{PZ957BaCR=C#k^* zwvH|2+RP#+1XrBDh9_pRvib|gV`wqy9Yq{&WBx_z{>_X&GkyVne`5`a>_{d1A;=zx z*-OK++4{E`B>Gcp1P|zq5Ol^G!Dp4{lraZJ{>z+#%yYJ^xo+@WO7JOq9 zkCSzo6=E`8lPHp^8o!|I$g?mCltP^k4d-EQ_MS4b?20xFE6NM5WCd~Ng7DobK&+n8 zPVC%ciwwuGfEIHvkz({8An&ym5_5r+`-IYdLAS{`^RGiDW~em;!fcnU#k zHJ;jUFr^;D)S8$I^Nbt<2mDR`ssXJz$N77K%VFei$rAbPc>Xu0{FnBN{7yXobyI%% z10p|;=W9HlnD9fHe<#nMXeytYE%N*F{NASgHOEALP2SJOru-|%Mg9O@z8udd#-H}9 z$QN|_{dcu|-QPt1>jQZIP5Hmc^20>`e^>iAC12!6b7w8hRNj41?soPCh?MuE)9_Zq5j973;4SGlSK4h|&KqMfr zYv4<=fy;OU8)5^m5BofaE&i)4*mV;21rECxVS{UpTiXQr_>0jTat()k1tCugtZdWx zllW#(YdO@?zs!S$Vk2|%cXF_lfgSX#-M|jpBVa#NU~jgCjrl>q#woDpkK47O#{mI* zrvkh0xEvP zz*eDwY(yuZ98 zU{RIhun*Y6u97qxpx|x$i`}?ADCb!}1#iR+V7n=FIQ+96?8_UZd{kiHxdCi{1vd3( zJDTl~u=gpjLu_GJ&6o1At^wQ3whfyk>}Un=rCdAMstW|{L6qBOl` zh9z}4bGm|U_U~p0JbcTXtw0R6K)4X%DFveK?{=*p{j_M&#fAo~_Xa&(thB!1h#j@x zl|A(^$P4qwy>#h2-x=&*o8NM{ZxS+ZVUVD#{zbX0^9nq9fvyJ6R=qdtiu-e zxk3TESAos>j~(nk683-syYWAE?fp)!U^xoxJX_fEiv;YuimE;sVHZn{G?&{^dgV(T zOgjz<1H(||`E3UE!%k@NVy=|GcF3x~`X%09Io{*Hs(2e)@J4XF$%xlqCf;Qd?@5k# zkqK|jGVs<%yvzPYdrxt^Z4s}Z<1M#}wj0a%?`ZxT{=)A}z8(4HI+)LJ%sHq-R{N>! zzFp#w?;ICnB=r0U{;K5CjZUC$97HZhG;gp0`cME?Vw1e$`#csE-{owW6pzao)x5aC; zLESA(xj`G$-NNm*Z5UEwr_EKig+*gmn5KFn`}(k-Dl%-dEi4-G!Zg37wy3)B3@ zWZTi@>e$e!M=cs`-6j3mbpVPV0L5M>{%vB4Jgn>wa6j z8_(P6L$}-DvT zO_#eTbthZr2fH!8Rl=$}*?;x}>`kl4{C5dwNnN>4wd3p5Yp7}Nsl_dFpl@Z?RgfPh1AhJbs|qKj~FKK)C2+RhSV86wTnn~ zM6%RJd1@n(3K1r?CwOYbAH{js^5x4R;O{4EE*r0^dje@s%pf9#?$c7-k;yFO8If`) zx;?X$;Uc9SMsHy$3q(qNjCq^|;SpGJl1Pps??dsRgov^VK}JG@u%hTKcrbn^j~CYn zesn_g(OVkAkLK)06#LN~e$+9liQWQ-h9UHCbZGfKjXKKF!;1VU&*bo8ydYEpKN%2Q z*6L}A8RfeYa_^XV#jE*BoLb5iNAel+(A$w8aZm@(>Jn$R5kKUi)f6-=sf%B{*4jqg zlg*~nys8fx3MyMBx)?n7>ptZil0r__TP?$h_;Oq#gcQHH0MGn%Z=CGo05D_ed8y0_ z9t_Z&Sq`orYlo)ucpC{t#&ZuFm5QhAbBUNaKSC|LImRK3QM&C;YOJ83D+PhfZ=GX1lPCJc(#@9>xckZgB=e0cr(>ZMSvBR zSawUvd0Vn>SGjcowerzO5ns2=-OiqDZ= z$c5h?9xzQSiJbE-xAZv{Ri6L>FC1$%?V+L@e2((s|dc<%h?YEM68g64UM$X~5 z&ct|q*#YfZMwDYpObhpvn4vq!^UQrFrV2xN^(U9(*yz0gwimWcO|cB*{F-Cr(CdG^ zNS*%}87#$@&Fg4@bqxD%N=(}wF^d{FDQX|Qyj2V{+KVs0AuLqXP*ya@R8hWB(Z@OA z6(!^cw8~4dGf>$fmUNJP=dyqKQ(|`h62W@-`;?ebIa6Y0pS@{H%%6v*#GLwRN=#}t zJqfC|hyAZ;hhA$*gl)RFjC8kc`jQAcDl7(Xvrr^FQjcXykqj7-Ch0Gn+O@K{fx+EB*5P2oa0N!A_tZg^AF zl+7Aje((Q#c49H3RJr+Vvc>MmFUQs*;IEcDGqNeDEGQ8R&cxiZH=sSmh{gKE{tf5v zoAPe`qfi0~3lP9}`D`E}aUhmOqX5!1y(!XF;Z;*%D3em?NUFuDKT=YE7-7V8_{u%z z%QbwV(h4_$n40$-F+%z*tj?2>p2R*_8BBm;Iv~&kTE|7ouxbya!>_gu;sGYvZx3ig z(j{+T={eN{7~5x$qs{b(r|gc8bOav&J%%T;G)M4mVl7!)K57uyW`@R>|6~>8JDHY! zs;`Pb^YHkG9l;3HkE{)Na!hzya(||YEuwK@-C?tROkfszUMP?|=EM^EDXi`7cgIIK zg6kckjQYIvU}v5cj?q$_rX7hJeuXp+MSm`vqCxIPN1wfr^WpwJ8gDyBW!P(PII5&7I>x88{7Zwb^CUn=TPNl#>xVxxurbO?n+k4@+Hkl6)-V0sdHq8z0LUi25c) z?oF;GV~yWJUl8!E#F<(zpyiAe4)9mo82y`^MK^@vy%|1t=uUiWl;0=a`JEV7J|L4} zK;AuHYQ!kT5!it7!Z-?{hl7LnCe0L+5Bs`Uyk8SbYL!<(REmha3gLR3m!qGxi(KQ5 z7PBO-26IqUUmILA)GJ=PF4YB-DMG4pqO#|&Nz{{fiCGlq^T1OiUf$u}(BZA;B;Z)8 zTM6U}HUmX@1j-q$KYWE^o0c)`GaQy-kJXz$!#5F~fsgTB0^n+1S=Kp0dbZd7l<#$U zT?2Ny9B^04!KNiwANP%n}c^rvkxz?%vP z+*x)5o7M4flH$u%F3c2MWcS`wi^|~+Qh8R->`<3VK#==IltpPC|Aig_` z{CuzkrHN3udjnQ4mE(KQdZ6a(ZWGuEc-A547SGbMDS5M;x3&E0BR5RT_Uws|CC>El za0?{AycX&X+yw@Vs}GCQOFY>y#=hKk7Y2-b4c8C}R+iWH!(Rc(H*!cX=NaM6b{fMi z@wlbx29A*@5y;a9xq2gCB$T0j39p#5ajXS5kQE+mz%qh=M>`xrJsMhguB?vS{uOm3 z+ITd(?p84MV2P??W#m`IrN{g>7hOd@XVah2MbKIdn>hazz0S@Uey(Kr9~Fil@g?5z za3nY9B?f<6HgmpyJwhb|MC4_xnqHOxv5{c9O}z9pCs$2QE-qw>Sh3*OQH)w!qmYfb zJiR$)@g$ZYuT>u`XI!gZep3a91OC-v3_3cJzVq4Nrc9}9%t}28j}k{wdNLb?8H_3J zchT?v$$$5kS2(|uh{#FJc_X~L&f)Q~duKzfGIL^QQ1L7P)M^ zW&rSVbRpiF;32R=@bpZ~=jwiZD1+NEQ}@E9hh(AVNziD3|)9=7ZN< zi}1345gWGmgDufS`P>+x7kMoD%nKz_2VkAO89KY-A5sGNO=BxjUuN0CLm2M$vog{4 zG@)$=XzvhcJwzr8+P?qDft|43q@6`Vh2ycpW24Ke@W2JN!jVwn{=d00Lt9)LnmnoS zVAPAQ3^%^G=XZol!Icmj)RzaaNYQ*o(VXBB3?lngnO*O5LFiC$x2(F+sHdP&U09>= z6@;?pRm1RkRjIWxuNG`p=T!=zEGQKHL_?Zv{wP)z$s?v?47zI4F@F3)=!&4-HR2-@ zMuAM$m*AgFNC$s&;sWG}u#=JZK{N8t8RXt2sk{phe+uQ;W^D&KoFzi;4f6Z4u;yGI zhRC`Ik3P$>dJLHGc81QwNaaFYt~lSWdG7=}Uaz^2Kyy3D<^~c!5_o!^hxv~(+4V>n zE8DoubTf_r9Q`#}gvD^tzN3ROWIt?jnebWG@OM4~ifrQ$mNjpBkd`Lfe!#X;pI$69 zZNsv;4T>IN9Sn(9e!ktVl_Qi^Hs3@mW>Qu28rB-M&Pk>V=lxJJ4CWWkl}S8N5H*r^ zF5G}F)Za)&1Fp7(b0ABM6q{qnXvTB^Lzm_?A{3r9AlnS*BA0>R^rhVOuwPZguUInL zLrI!x4aE7~d_{`eg2ms3@EJs8OHb z75Q?>-S|UYH_6EO(HI#Y?(!9&eg1QPlNRryMug^L*rejnEvOMUkx}<6&^x=nB6wbh z!`?@*Jg%2e(w^Ar;(?iYP5WK|`YIqcx+{Wi`2?oL+Y^cDvqd$J|H;@k3KD$9dcQBAC%Jn37a|x3>1c&a zgutueAZUs*z>d40&k4L@eCQ-dK9K|!>t91s;{JdyD+7ZHEGR8if(lgTmXX6ByD)Av zn<=;5$7c$>z*H<}0G;UcPo>8-kzvmiP++Jbm?8soblaKQgi>bFiWeE^=Y*X=|43&M z-J*nHP7P@F1`B#e-=z45e?+WIvC7ttFPogGQB41sU%o_0FP|XdH2V)wPz&M8ZRw|Y zMf+e0)o(iT!Hw&~DR3pN$vvti1$A|t1DSo4gYUB+x*YPw4P^UY*j7AddlrSCg#d@p z%HV&H2a6o3qai45Y8CeX`SUA66bRiF+~RQX7|?Sn^1eLEhGRSSlb=x3<>;Tfh6b=5 zC(30U{3*ZuZsTlX-m%auhIYZc%IyDFQsBKEEz1=eb76Fm1$itW=hGUIw`Ul3oq0>D6*icv~=1y#m2U|J$3%G#ys}saDGcX02 z2IM}Nkl{(2t~}X8$zbu5k)uy;<^qG86`Z(U4vq}&!3iEUNO05Xdd5)o`+$e3!ZF9L z`QPIlvve%_>0^X3V^?O()XE>ahzSwUEA4WKc+Uwm{*gRN<3YTi-z=isu+Ah!Ee}pE zB`C3tE|S~mqB6G8Mg(A_ZSIru40S2pD25|MTEFo`R4yz%K0^`YVkkmPn7uPlkmC=< z8M@p1F3U3$h`Jqwwly~Ejjxe`ihXYSgd3K)o&`z@ekK&Fm4CSjg`~1{LF%r{FxYYO z5ebM&9un`fJ!L+Dgru*zIJ)%{)43-k1+JJ{m}W9sanmXLkH_2QN#^%1c6>;zAP#8V z@_^PY4`{(;ArENX-duyM{1wYZ9KICG@4%PV!uXKaoD{8j(u<-cNC*eUCe6U zZWv2XKOu*?!e(L0!+!sY=i+dT{Y`(1k+*yu#XtI8n}`B08CW-cref`aSY3d1rWNb= zwUzr37OWVQx)2RpPhgz6NQX3o9RZ&)45O^+H)|y$V;1MeD0_V{h0DGEV{HF^CFZ~T zx002q9{KNJe`5cFHtFv;jqgx|@!dwj>x?j$Iak@fj$*bFHDcx7sc4^HdV@a3vwwG2 zayW*w`i3d>cT>JUf;~L{-siE$^JWG6^r>#+FCU_QI7ATHSSF)AZu`3BDocdT4B2IWHLM%X#2xoT*iEQW}5FEQ5RgvW>#=N zBSvsfC0XXT%OnljV%bevN^E3uj96K=ya8>Eg$Qwo2BTJf`G!lhL}&_@c66n7K*)0} zp&^=05)HPd1iKiEs{H)J!A?e#2Jt5OP!eDeL$RIxNN#68GMS0a-z}*4rb5M!-?~vt zpS@CAY6SHB>nH~9Xvlv)eiY_!1jLTO-&2p0_l#-Ob}IGV`Id~t7I&29emx#`6Z?(f z7Gv4?ldDl{p3)UL;yqr{2~m!jg76;vDE_6iwA=2ah|n{DhixUWJQgK#MyBb-+B)G%z4uSumHRweQPo1 zu_wc~I-ubbZ`hgD01(`SYJDsmp{_p@GY0-w#g`jAfYRs>Gw?P7t{u>t%@kG35^J$U z&|G3Em8cQWHa}!75td$d8vB@&n&o3fb&WSnXd41zH}=*vGUQ zD$H~eeiDjD-7s=YYTF=DAPV2c8El?hhfCPxdW1;{{n3q(y#hj^J1SwXIu9V{c$UNl zkSS69#Uu9-sSw$i*t<4<0ei)hcF17N#%3y#Uq?cfH~9 z@|-}dL(%1SfMacXKh%zLv@EAHYlj{|I6?PY!3K3legt4R`4->Qt?(yiXAJg2usbN^ zgsKQbyhnJ3QLix$`B|p;Z6#R17U)EdaS1u7nz!8R%nEp-2oGcFY-bS?e4zOSh^?qS zu0R&c2@S*D*>e8+!XEJ!!!FdOZ}um$P1$lDC?*?1TB1%}-nU=#?R9IuT}~}!SGiO+ z=GlS4kr|2E{>1$mUTtO$OV4r4e;xV`7K>MHhOipgquw(F`ETaQP>d6)Ah^_kK>Lva zaW9NRdY@8hk7@~*`+d*&DUFR6eSp&RjH)zkd)wtu@GRvuH8g5E3z*X5p)~#<((0_Y z>BJyb7X2Pk#d8Z)n?wsMK*d2jS{aY4MDkC;EzzkOb*ZAZ^40sMlTrAn-KVlit0Q-hl`;1+GJ73UdJNpI3k+ z00BXV@PmHFA*y*la8T?GR^Rm<8_MJss}%*07%L3%1p2;Eo8!Iyl>kWQMz0_9D1uVA zok`Pdq0n#%@)$;C4;6vmv)&|EOl(-(J7k3uYoYn|&3}}2YR!7-t55}wj<6=@A#I;i z_qisU0<(@o$Z?X}5c#2X zik6fe&{D&RNr27Q3rdQ*T5YtWWDkXs&%NSunET>ABWKlxatwd^C>+96PPb8Zbfc1Y zt^`ppBm7%;;9V#t`pD}@rOmny$s!k z9KmA-E=L%ydarQKT53b5qpY`~Dh6<)*Hl{kl?}AGxoleOT5F|6?;_~iqOv&V+{=Pv zM2#2Uu;Z8yluE{~y^%)U{WRQ|z)Ee-;j!WxNV7#YB*+5N_{F>es zIi_o=w4U^^TdVrQ>FlpYyFM|adJ|D~LR6g$REJLS{eX(t_}Uc{L{C!lRC3*=)0xi6r7R2Imy1k&6LVm7o3 zNy$L>`10`r3Y+KdZepHaj8^W)ucp5H_RHtWDBQkpKV)w{iH0)%Ya{+D#Mj!Qw3I@3 z>g@(zdi5ceE^OdHyLqF?81Jc|-2Ye&?6aPiB+_;Sgh{Q*3g;y&9I_rq6G;)59;!g1 z@-~J-&sm2)IQo{C@a)C!%au9HyemYh8)^w`!2^U%mR&$$M zE{Lvyx0?@EhAM`-Gn4RXLpL>N!3!>WMrB+MISjS~v6R`QA6*uk!c%KwY73tF2rpa? zQ(N=Yc|7&+wUFALr)KcfBbeHWr>@|sJ215?PhH1T-^JAKJoU0t3*)heV96sr@mc|Qt!aj!TorAKDzCu%`XtwozYdg%D->sue@^=2wocZ25vw5B0SpnGqHenB*4|yWt z>EhxDoEe2m>A5Ei5fzZ9-56@~vCC%HP;qxbj{=1mX0dpMW;AQObbBw79_2;gMP5W6 z<@9axD6gb{1^sg1t+jWSTMXYv)&N6#cn#m-FSx+Okin6-2OWx7eFKF*(WQ0ZreB1W zQOb}ts!1QQrU|UFJWGsNZ!K9XyyRVWCHKB&EqQ!(SjkFuB|WcOOKw=LzRfUnynJ!! z2b|I#ec3B6u2z{*9eV{(8TSr~0$NWOos%o*J6%H)j`-PmwG!JNYdkSd);N;aSig)K zQ^IRJ6d6|I&a0*xcdfEU)5p3_t+Js`i&w1FS+pvQuAkYJ>>gHfV0g)=>`F$wYAsnc zyyTsBCHsbz%zZ%}%OztaWt~)B|7NW{Woy11M%k2dVU(SA#YEW$U$CX@$`|Z7cCEFo ztxavs8zbAANagjKWwdqK+OW1Bi3n@!w#%lrZeMBJ*5FE;w(bgR>w=YPTiY9LeVfYb zGhVW9YiT%LJA}8@Yi?`RmDV}3eYC8x3$O9wi}p2UhS&IwGpxo}E}3dvzrtGMK%>TQ zS5S?M%c$|b@L4%5yvEk%8r!Nh-p^{}l?`8E8W7XP!VNE3D&i5L-wc5LML~Wr(^S1Asy_$*ci412ix6y!`!Wd&}rktt*^9z~k0%E)Hwhy_NgG|tFL68r8p_!?(p%yN%M%Nh zH~D#yhdWKCVN57>$yoJ^SAoLX+f?G#uo9D%5{Xy>bES7*ATua~l}rU#t`6V*j2V^! z7xa_?J!UGf^=)D2O4vsg*wcPH*iR*FvI6_9E$rVCwyOfW#uj$03>0#Q0z1u2AA5w&X=%D6xgq9VZV^Dy%gBzY++l;;57jScCsz( zWC`0(f$e4sdql!!DzKGoVXMf19)lFv-!klAH3>UJf&I)D_MC(ruE1v4!cLU|Jyt2O zqciO0MYe=}S%K|f3wukcfPGzob=kIIfrNcqf&F=j9qd*K`@RDE{t`>fCGm7sW*Wm0qIaXS*F!gM@%MlU|c})Uz{(w0(1IF6-LpqLu;`o=GNKTDR z=pO0HjZj)vBzpLojD%3Ucu828i`%F?6=5J3o{=t7_#0$9Mx~Gthp(X%1cWT`5B9*z z_C}uTd9rkdnsIZ1JSW+B(IC*Hi;+NRJos#=aLU9zCOJ+f6ymu9>(gW4bVZ~GSGR$5 ztPF?(S|@I)*tA6T4*CghL6wwGu1K*hz|P$gvDFJ`Gnxs@PamZ@Cl+y&88}uH!x0gl z414*H!8k--ENCb+LO}F z8eAVUSUbZ2p7^gJTcr~%hSlbVUJ^E8MVcB#N)`%Q8us4jUt&eTkIfv(Dns2Apw~@v}{Ov z9aFafKxU8soK+7nm@jbiPc;P}B zx7NHaw|f8GpfJL;SRDS)n6Vw`aSzPB>AzAn&rmgGU0D6)V%#2lodRYM@?2@#%!N|9WZR@~lWnsaQ_s#2mUjTlXax%#=x7(txMawR zh7KiUg>MV;?@|iPG}%a6^elRYqpyE8p0=E&1roP7v-$Iux|2T{mA?S|&`e2sd$zvL z8Eb0Awd=PcF1!^En^sgXw*qcjh`2aZefRcoH<e`4QbUPu*Fc9bj-!K&ySHDT)h7B~9UZ!>Z5< zu4t81Nb+bUm6BR$B~eL3L$e7FS``kKoQb(5i-(s19gOJRN5J(2A|?92I)fZ`{MhcJ4hBGm4{a254v4wexmU zJH3gloujfDt#(quztfEN#`E^}a~p_%NDFW8v9z%E{&e==(RBnzJ14Cyx-NZEZSM#{ zS6`YvT?N9XHtoDEyq&GocD9Cg4iC-2_L2I{vEGuv#Mc;(dfx(SD-_7N77Ru*cA{a$%hHo2Gy#*xz+6A)+=eotvu8$z) zBPmg{REgTEhbmFecDG2>SpqNj4!u!<X}|-ij^GQU;)zxy6=uL@%bWWp6Vn zHoVcfbfptQYk9-k+>6=_G0*hdMYyQ8J7&jo?rYE4XLdV5G?-b7%#9MWzI7J&?#>BS zRlqOK!Q=KbhVyc}6!P><%!b$%e4%JC2Soo+tLSfkil`L>)anJ)niqVRR8Eqnib5j0 z^=G=(z`KntL&0=IRsAK|fDqzI%+4yFCi;dj3{Nr)5W&F1MOk>Qv%<>sofj@L?iUN9 zV)GdAk7D!a@elgjb9WU>>C}13&V!}J`8>j^Lz`Da&0O3Er4d1(V%sdBoo?)cB!TL;khWTeaJIk}(|&Mil`Kym&l zZV3%>HPUf4D#X<&A6KISsauywGL-;nN{CGKZCV6wHKgXhA2!?o^_PUtf3lkKE!eh| zku)cI3xq5V5v6E{5WS_8%vzBSd11b*X%oWPE;{t@7lOP;|GR}?;}dKi*AWw&FX3Fy z-~WW`y6t-8uNzIBA~|*XJZhdgMdqm^Ma#brAjr9UnzcuHY@UaP{P$Zdj<1#+w4yoT zbEFJPOb-5gO58r{e~S`_=KN1lV)nnL#7O<$Q=ElO-p`k$hNEl)Jz9UA&4bUrF3{Y^zUD zg-{rMaY3xxUESAr=wM#qRF&w~#}vmo{nt39T-q1yl;yLg`L}&gbQ94eo&JjzDtvIf zdjB}{>aHNB%}d&(_R1bBt#v!grtGFbT9bB$nXAND#L5|pnyl{V?RM1#($2M$mFx|q zebv@b&tsm?(u$lk)O_4r*b_rTS!zbg2;X*Yn4`)Q22Nj5OeP>*n3Y_c@6fVDw|GWU z6=fKk%wRJlmM65%qKoim^#j`x5!Q}htedcaR@CNt?dW>FcC3By-?ih(Ze}8@EO{xM zGSjZxqL12Mr$rV1WsCH#b}b5M+glqli`1#`3RX{AH5ppn*RjPIv$=inr98T7M9hEXYCYO_oWpsWZbU5(dBZfzWG6l zR;71gEJ>FZC~lUA2Rb0Rj{ZoWuzm!)m&0hA&g3fm47Hf4-Z(L*;ZgW4ei=ms16ARX zA3rTX3+bhY@wDJ}@Q;7Iyf`KOev@%7wrx7N3K)5h=NWnHr{kjnbUHr|P4VzG8$*V` z*EDp-LK+vtU$#jug1`HLs>rVu!lXb;#vngQ7MJ8?wfeA%fdf+LtDFa!B5Kq)7J zog!q^$^#f8A_~rI(8jx+|BmI|{oq%=N=?G>)4#Viu54#<6vZ=v+m-`^EQj!fa;?wI=?b6GPvki8*X!G^r?m$zfzxeW?nvKHt z1-8wHh?OrgCeVC&POw?#VGS_dAO9Y&pyNg@x9n3nBw^w-an4kJk1q!@LecDl4#|qu zvp2A@;1tM(a`k1o2rzbH+KlP+u!iNpt`!zWh?7(Cszhi5Yg^hbn0!)__0b!2x=f*O zA#k%x@Vw(^gXiCy;&K??FMfzu8$9cf$8Q6V5yw?#N?^>wtt=zBQ))#0m_qSTOT{+T z)KD~%T5|JL6+@D?&I+zJt5bOB(&}g9(503`J#@)~ChVR?hb|3`B!7~))#FHtVl>?i z+@CA@mzYP#;hmIjemhy-3SBhOMOTfG<}(13J9q@6aQCqa%!EAXekB<^X9AQJ!E>rV z7#1NXLL?y}jTPmEj*5?wi+zO#@Dkb6u`6jQ#Y_$9IlnynVExQWX&lf7M~aI$#nhqf zBSwdcCt*~Wu+?j*eoIGV%=62MF<+vT-7-lT^NQHiHNhW^dv(JgI)xskmDG({9B?Y} zV<70Cj2{DG-{3ItsLj6@%1Hme`S&&dXYkyT<_gbsG3mT$cfT4~H>eh1oU?SWwQwfEyK>_n9aeuyh<8f2f1AUjeK;5f&&TuSm%& zogk4+nud%khtW;LD4BEiRFlnCFzCFW%?#}yr!aJnsV?&d0yS6VVBX`hNIk_tVk~Pn zyoq6_xZkL3V=8O2m7%LP)WurZMK}PJUQ&Ia`Hca4IScky2KH|!xXMJPE66U4Eely2 z1KAT3T#7Egy{$5@qY*ck=yo>-f{H@kTJ1 z47-*Ch2Ye+?>Ah#4l7zpywL9dA#L@f1YzPgeOk>L+2pBf-Otb*qKbkwJ ztf!&4DT`w8lujFxh0_>K8{H z)IcRnnQCBE9cad7&|E#DE(Y_^Ta;Ww=%$ck;`Wb^#s^{!F9l|g=n`NZ$S^-4Fi$bS z{LU038yu!bP3r_L&lT_R1x4ZGsfi((qI-pjwY)cwWTz=wTY=-lGrV=_QLdz)jujm^ zL}Aw^MRAt!5Gs2C^nmDHA=6^+_*~GBNT!V{u?IR41_c0)>~xUE(hJV;!inm%{Jr7g zO2NSroAvHxYjq1uiOrEV)MeLlU}Yb@&E?*3wvQd*zZnctXh+2 z(nEs?M+Gi>-jbv|;KI6UE=K23_6n>i)9E`{?~E7|*sC?Abuqhzflc=TwQb|19bUM3 zZB^r&#ZqO($X*iFgnDut3d~*JWfYV zeh?b7_(`PZ_gyz(Vv~y6N8gJbb<|cs{Q}(p91`_?W?~2DR@E*vy93v7Y{&I`&0)zx zb6A-urrN`&YlgQ-%nzjM_02Q!3I3w5XrJU4Zvhxo7i3@XOpn9LND5sUBs1567-VtVKw zU`h&!sm4FRv?wH|?D3ke3#Pv|M(wcl8!ZMP{N>g9@jgw*(4lTZSbCiH{Bqm@XX@fc zDLbzS^?Q02jUskc%gwJcI#pCJTlAVTMg^=1d@MA+DFOJRC45f?;;RuFUy}fQZ6$p5 zMj7<}bX_%UQ$&>vH!@+`pGRsg16fX9{T<}0`spgz-bl6M?<^PP8k5ieFb}KlyvoD_ z+FzGMw;&>9d~?~AO)UiF*)URS?k0{D&0W+;I&uX`rm3ep_8O_#@X*w)YhSo96}7_n zM-XJaH0=%!Otm(at2-;jhH?>^>dcKr=a0>&Ri)L0jWP>IqSIrc4Y)+#;@G@f)?V%SAE5!kV2)zLUw*^JQS4f~dWCxq7p{t6)yAsf?gb!k_Duz16jt=5EW zCeWlEgOH}ZrRkUAF@XgX=@36awZJXIwJ>-v(dn^b1gRVYOdTJtn}THU+?Zb(8_v!d zDP)I0N#OgV#b`}aZ|9LX`O@0^@kn*@#X~1wv}?=Ih}sc7`#x2q+swMi-7B>VL*;`h z`9eN}@%bniFJ}kS=P%6jFJK;DTjmCBA4PX%7?02(NQ05#>{NOEK({okeg|#A3l|S1 zrjcLVp2aFl1h{)Rj*h9S^L8A;G_Zd}$r`w8_`lXbCX~P#ss@q*HSp$0V-2(t!qpoQ zOaom7(5>MmXrS#dtp?f;M-8;`?j5dbAjGbgHYBz0RapgXn84}jlv2IhYh3hO66~%y5r1#wCv^(MF1gp@#ggsRANzh8tUvf3toOf&P=C@X2{GaP?aa z*Z}m$5;{1QFd9w-7=9-b&lmV^KaFjKv7=e6^#dAf#n^EycFJ#TlnM`{>YG7fB8LFX zwwn}YTfPi4mcd*c3^4C77`F!IVFq&;Vb(Di_?P!RH_lS`J=c6ij8?r4X`I_AUp6%r z-yM!$^_WsQCii}UX}lq9+0Rs1)Ecp)?^5Kio{d0g3 z?0gPe?Hy@;&k*)?4(pciZZL%X@`4JxMuMGT2~5_Z zS6I{=m}lJ#VJmRh!xC%-L)f7l_NWAVsh<{>+U{X~g2o>vQQki?gw5b5T^yJ2diojh z@F_pNVvAId+>fw+UW0DY)SyopVsde5E=riHmH?aig5c0^H-bIGVZWB7Icx~qAYX<3 zPJ&%+2)l*D{vg3VYzSL?nd6mOhlCPf<=Vhn0<2sTFZK-$E0@H5CBVuh(Om+pToR{9 zu(Wjj%t}`)__F$SR=R|Zysoj4;h6q)on>z0I{!tkDf=2(@yCX+fA-Phr8UG`zpJoU zB-o8QSSs&WUXto%3D{YNu+MSWW)f^4L)as~8MWS3`xxn=&L6x*UL@InZzI@OIqZiL z>|sOL#s$2UmF?dU_U}Jc*lSWN`)~=ca_gO72s`30-g?XSUjnS$dSC2i1pBtYEAg<; z5Ox>mSN3AuCBVvF%oIb|QH4A&eiE>E_cF4FySVMh9z>)e-ZOuzu;^nm*dKZt!P>4F zd4BI1!amAjCnXEKPD9uaIqVb(cAO#XZ4PTXB4Aq>!uIFqhJ{J6#XXF4_5z1(CgsI9 zJ&f??ao7eD>^4K#cKj@`eNq{E)(|$C!#0ud4mX6I%+J?uRoG=8R(?0xn(ggwMzWv{2EA?)`Y zR`z=S=%)APY9FKR%|ZCGdLwIZHgfN+4VEPFUUQG$n=^G65JI;Jyq=cZl?~mr9?wQi zYqq4D22)fRVv3OLx!wRRL^#bG+2Z?aAs$DFN)p76I*7WomnA`bqJxMm0-56^hz&Xj zQxxE6DnZQCLBJ*Sp4IUZ#26jKqh{2b1aUV)Ot8t5I6wDN^GQ8Zu-}nzex{%aQyecZ zr`Q>gVvv;LBzlf5G&G(KNHI)GvALfBb3ly0HovL8y>r6IH&~QlWE&^GGV0lnPB6mz zJBO9q`zD5X<4zj2_cyy3!Dew-xxGJW2s=O9sJ(yP5H{|VQF}kn5H{f(qc(hS39xb- zUe^%zSAkdJ;Yw#C*qYxOwY8asu&9yWx%_M_2A&+1M__Pggh7546Mp@)YJVGsRe)W#$j!XD$W zvfpDhgf;(c)W%%wXasxxyip%)UkR{sAIxnCJ1*C#4>rXR_Gb<&_rdNqgngLvAosx{ zOW;@TgZ1WRLvxoN7b>*CLP~09HU@AL6If#Gj+BC zNv<*jNjoFiN08iqo1Ot#zl)YM`@DgD4(p<>`ofb3dzzWm0Y`SuQ3B)0g4bWRVmR6o z{~K@b`9@wpTmNloe7GIG^c!y9U2*|$3$?NLGDP_h*@O6a(O*Dx45iu}pMYu{?#*hX zn31Y(9fQ&xulURuR*8?@iqb|)0204qu7KS^E!W}(P;ZbavHoFq$g9KzRvnS45 z;SMl>h;=*Y{nZHMAV98u#0C2JQfQmm)ebamV`pU1LqrP{e!$}2x7ZPt^tG@XRoqe14CV8_v16V%0>$1Qz+vK+A^V_eh(TlocJ?{H}k0A|hs!>8>b#+G$*ta8} zCWMGjd!Q}z65g4Y@HVW3o3O0agEL9hYqazEHQM>^G5P!&?R@9acv&sscR(%Ao|jv@ z%)lw(6>X{B=64F$9e8;3_6>D~|IH%2D6$ONtFKVudnL+u!0Bi9;RpO+Bf`N6c&*?b zU3fXBz&d+(xFW4Kw-a*y${y!AZFK8S+-lag4-Z_VULM`46B(LB*F{BhpJ zy>!VWR;1SCLHvzIJm1+QE*R~^%RG6j-V%>JOg(_c#wfU+{>+o@vR@!TV)&ijAZ;ER^ViIli^lWOiosXzg zW9gaGY<-jCYi$7$yU{$Y#d;NSL>4#my4=hwYbv}T4qe;pH;PH710L_wt=JYvV`mNV zI7V`a#vG!S#(665ar$S|TBHtF@bt?aEl(V{JqK>VYnpnzrm3oP`_x)R$o1K^8^s~?i<~-UdYtahsS$x| z*S*9HAy&8gbJgk=Y!y})QIeovS%L@}XhW7DHiTU$A>qZpp@sbDa=5(yyS09!+d+gH z&GwF;Y^&XYrdC0#8q2%$T$pCc7Mf;bTRqN4@p~Wk{n>!;1Mz#ghv@eS@^_wt2I84T z`Qku|Vz36vcNOrrV5^;O#!L9+g;YT)m5K=|jz7^;W8gAl`z^Qq7A`asY8!=YwR?rK z71Naa0&uIOwuWnR6|EC7sk<>AY!$AE?O+dgphxLS?LQZ0}+yxi1f`dIO#(Bg< zYx;b3RJ9wr#fWNhEA(gxnylOCjuz@_Ms}O?2Q8_j z&$Q9)%0&_T7ODtM**IM1zg)V)HHdB7 zTGqB*YNB@pKe-}m0&5CwkZFmtCbQ!%dH2|woCZ2@wttq2wxxAAy=2idAzW?}`(RcS zke>f0HJ;ub*g9NY4*2Kq;KPI^Vt=s~i!`;+fM=1Mfe{t__EvMvw;kkLDzWlOn%EQA zY!ve20b<%vMj_sy{VKY{??ZmOx%Mys;nuy@T;4If@~<2G&I0TgmBBLMJFf8kF;$po z^@wQD-RXT)&Dys&O5?)~X`@f8xh}k#ibPu1-pY#OfKGV5R<73p>~ z`E)7OJiPbS=QYikC>LDJQTVy8#n@R-n3H#A16=i8la<{}c07R!+nHE`SOtz&*0=!l zNV-=xa34rVDnnJ?r{je3b`d_<_Ct>|e`L79zTr>mVdYkie9y+YKTKKH;ep|vmxgM*~<79V!{qHx5 zy-%z4Z($~kktsa)JRtmN_hs zs`Zj^cc`mv_-Pk?cK!{vkKe|N<(b7s#j;@?YzW1mcL9^Fe?v5Ho0$$Q=|(=$PGak_ z>Oyono%x;A75;q*B6i*j?7_~a%Lob@2()tP(V_mRjnC$Ldzf&bTVA69gG6n;fc$DTQ!SpJZd43v*A&G74%06`7+XVc1kX3r8^QB}$mY5- zk&AUTME2HIiEMvCL*&ouK@7WT0Im9hR$C3Nd0JY7IIXT)S}E1_0_|&{Cvxx?PUJ)# znMj0|$fY_ek+NDMaREef>+6Z!D~LR-A@XV+4Uxs1$eXDeBA-+fPLHAX=F~T+7S3Pd zWD9D`WSeWrMsu=lv}F5M)5~_Jo?f;ENCd6)OG-rTDr-|tuZ}by#{)nn!=!g2&YdUL zghG+1o@X_f2;wjAaUR-6uP*928YZ~N)9O)hDUb86eXPYF8iTj+(WO7DM9aLXDr+76 zHTY1lhLL&Iv?8?gew4>ulL@)S#eA}B4yc?;5=&KXlknWU!-ig|gGLkL+%5J?>d}e0 zS$kD}Z7@RZ9vUmy9xAbYsS0x_Ieeksrep*{H`l8v!k-s6GLWxy>LT!T;DLEEXLbTmxi~NDsLIP19;n719>y@ zd$E5o{{oC`MQ9OTyrUo08hL?d=M_AbF%rGsMR=%IdH-zii3h?dTYdcxx%pvvJz6+;^<(XkiyL=4r#fqftJONTatd>Q!%c-`d05OQKl5zgLT&)4BYK5qB zOIT4=k27Jh=Bq+P)OQ2a1I>P~CM|X7=^b+(ROH7^)A`s8T0PDd--hE^8oul>%9=eF zh9DAK`7KU+6!GAq|`{5!Tr`Kf4J_Jv2@TYR^@5j?^&5nBdHjH(qTQj_tj9k*;JLk5abJ_FY zx|=-D2$O3}9_iM42ANz#iLNTbsb;{{ob$dnsQsVc=uM$JtnG+<1X9_R#GwkAh_prrqugU2N1sJWcjh(5{o zog?h~liFzCSsXmvcaYR)-d0?mC%pA)N_O^-MDsYCe5m%b;V34XdrYni4}td5`WS^| z^>Hm5vr^D#_K)^#&$J?GkxsmK@Q=_*#thhAjV@T~8N;sn?HPp~^Fqa%{I-H~xUgS6 zo67Vs^b~Z){gE*4KUL$lNyg1rbO65^w+V;-D}l=kMDk79Xe9Yq*^#_y?RKbL@>{jl zF{9e=spBi1mkWuTTcx#8n4)Q{_Jr``oORiEigZ*f?3`DR zYMq|yCvPXA@zI56Nq9qge^7dMD(pNZcJ2isRe@AEzCUQZ?^n?tG>OrlMf5AFY{H`RsD=xCiGLLJUDKyK3JKNuApNXZEOH@guwh;Mb$+WH(8YS zq9KwuRo_)6do z5^Zt)ojlGbcBrJIo)e@il>@QGw|3IEYcb(?0(QZV#H8hA9yV{wv%Gk?D$_&!=rrjF z?ETsNojuNH7*g7AZpuo|F;p&jPS(Bz0yhF^FGMSW#lC&TPtYS^3d^$DxkB$?`TH#q?H_l zE5Y{$AHzsCUiQwO(Fq9z?`z~69?wBXl^zi6oYIf#X9(`NeBmRy9|!)`9XlJ6DMWLG zSj!;RN)Xc-jwpoKz#!ls_S`UBs#TO>GiDS4Bjipi^cQJ&0o$Db!7&e&FfY`> z(j_Zx`DXA3CD>7hcsFy{rzP0NI*t+6d0ygpjl^*+L(KQ`EmS)t%qPr7uzNY|#}e$; z5@0`-U=z(o)|S9a-RBbQ07JZ@CAcoNFg;6vtt7P?u_eHElVH6jdX74^Uek^U?p|aw z7j9Efh#Us-41=J#Gxi!z8j$ZVETZ4<(|m_Zb?Nt;PWruz=6ly-_4_L3cht~)AI`sD z?M%PlP=seezno{BeFa}uzsfw8S9Q7aHLr*ENOkN7h)oZBj>B3d-{Uz$Sn~y=dgwkw z*!mZY>bKU0upKTK)mdg8EIVX^!U=`jzuk9va-!_D;$zj7KybE=(j9)`P zWt0R9@4L<6n)kdcbY~^aJx5Z2>ukaN&u4jSE~C%&mzYDGbB9n^y_4noPF;QXqf~ob zk!p`TNJXI$OD*I}yblTB>vt)IkoumBMpiY^5OxNK9WUXHH-ue((J1#VhOlGuj4Uwc zc4*jQscza~2>TR=jg-2Tiw$AVa@dJe1nfOJSiRRLdy`EOcC8NY%}Yj}#;sdgSc|a&bl+Oh0d_&l#9QJ1^FNPSx-p%_`sVO@hr5uu|(9zC)A`^gUVmc=@JLZdfiES=vk;W?J$tNhPoRa{_ZuLq2>G^w4wx zUC|Ktw-R)d{!*#FVFdlX1YJ+!WbX~FjMSSN#t%#(5wB5iJN>#=N3^A{E!n|XNk=alLPtx`4@=Mw z8A4x|^uI`g?qCT0rv&}71RZtV$ku=2M;m=8Y3f{&ksVZ!xNbFBV1BpA2=f)52Vcs0 ztAnM!@4fsGt?%T#HN>0AVb9BXTVzx!PD#2bkaQ7e2z^_EzAZt2b0{T7boVfa?JL1{N7xNIiyr3V8~7#I-~ZOa(lWH1 zAI>JX;ztZ&HyD z)$=fO8Rel9^6&=3_J$7ITtjTN5ZfC%t9r@M%PlB0f_ZV!dXP*q1s$Vf9-~GjHj~Hw-mnMy}q{Nf5;UzhVR% z#Q!&h4dVYxu<)MyxAxt3P_LN}u2}f$0FQGylfN~3v%j(1VGF?uS9dfpfUo3wg`%M#1F=Ubp=g1HQ$8 zZ^^*7WZ-;$`+0$8rc}EBEuBwCbtUbId`}>|`CyQ`qV10MKEP!K7QFe5uGS*{D&G9| zg1nCTx0zDS^_NB34zwgcmT2Dl*5Mn)Y$>jXPTdp}_l9mSaiz?M3Dvdui>EfgO<|kV ztqfgm0JrP;TrShGFS;f@@qz79A&C;~n_${*_zUX%m`7skyFUp%qy+Q(NX)udS#Dov z-3x)ufmdU1km4g^u&~z0x21-nEKXY2z|q=or6^z~opO!8gz!`4=p!s7DC79v_c%!- zVx}ZYpX)gu@;K9gHG8V{kbNDUtV+c(>tZBXK}W@ZH&1Onlm)m`RJz1SAEk(hAsaJXmp{qdQvARW;OE6NPIj<~5>mFpdLd&V$j{;~qTKdH69uc9au%hUIr0+{#bRyb8By#SQ;?Fruo;x50X9-8;bIqW zMy}bEMGA9DBHA?kI*Tlhc(^nBSzes#?hzKY{CB9$d*G1^xtaDn$U^^24<^Na?KQAK zLpV*cYJ~g=XQ)JMmJGk;0vjX*Sp88cb9RmMGW$R2Gm+{6%aA< z2)uwRpW0V1XHgUDvb-vieXX)+?5j<k>9dLRe7*71EK6%XLlJuxIBq&F4&e4jSD!A^=v5>8v$d zGW6VHM3dvpjf4vPRMI(Uzs@aBd5YCis2F^cc=`<77Rrg!o@*lMv2f%-E^Ne8Q~=xE zK}%8MCRVS)~y*OBMA5iaJ~tH5#s(KcR{^ zEgMA4UY_!#hM6a|%v|`(qH;h*Jb@yf01;m|6meQMVx6tQI$Mi% zg`KLa#l_209@kKMTubTqKZyHUJ6JoG{;LHx3+E%@{FHQhwWDu`P0P-R(y_Cuu()`- zE9DW!4|WalP2y*ANPgl2_;CO~wQT=NJ;hhZf67CQ9h_sqH;J8-zlUPy)n5$NKa3v09dIlo1#QzqH>|#od$u?j8p22J5(+P&)2f!xD{@0T>6r z^jRowfR?qtE{9_6=mkA%a7&|aS6MA%X^srtXXMi4bl4cC!YiX41=#Lfz9+U4Ln%C@<>kAhwh+mH*iWrsQ z%EC-LgUj9a z<*0NTFO>cQDL{UcHcKm^{}Zk?$U&hb7Q;rqyx_w|MAPO)O@a%5^#|ym8<19VXaSn5Qh-=W|-La<&X2C&AGFqMDmT64Rna!6j)yB^dzq7*#;P0LmazWPA}8 zb{%^IdTxuG(7Ry9!!5KUd5Mn&CwAzpMG+bB$VHI>QBZo~+1rRHdE7Z)j5}AAR@BTn zi;P1*%y&fjS7VONgL4`S*i&E<7e_;41=okLpv0hR3l&F{x5alB+!(@ref6~7?^N;N zt=`Yn?Wgr{jMlXGo)0wbmSEbcX0iEIous&k1uR*Xw|(i#_`pkat5yn}g&SPc#rBeU{gvc(#*FQe&ZO;%<4Y3BPPk1*Gfl?oCXGguEOgt`psLtP{;hk7?iRr` zT-tJ*zYUF{#K<$|ZJK+`R<=!aE9O6WTMGaXla^rLI_Go146REtJ^W8D%0mff{wBu| zqhiEALN)n0&>ZL9E-v<4QXyKMhq5m9=Fw6B=dBk?2u}Zmt=*ENFd-)&u~jY&k6ZA> z;5d0;t?5jUC~^01jJ!dTP4qdFVjVCN{Q3qu5*!9+w87p`5)J1>py~>7GSqYgPMS!M zTW-#@7fglPqW}lV3cA5_9%hjq9*&Go_nNJTvPcm#WkIHxjiKmhpffk#l>z5aro(`F zdXa?D z_ZDyEGu6cY*ld1rF`Jr>!FhSSS>OopM(T(>-#R=K+Y8e$3+FXuPj%PrejKYsv>ty_RF)7r?N?s0;aF z)HZ$?wT&M}t=@BC(;jip&NEz+pA5@R?%K?EKCdDK*wT|^a36GM1RTLr0qEegx-12c za$QRgbY;s@QYMn`-Y-vps{+HszA=r!2Ll*fRss*`9)QbZ-OK*iTun_8fpehe*!q$R z1jpio@TGyHEi5qsEVu{*Y2$AKQifF~j07pkXMEo^gpz^lVZ;YqY@)u@b39}{gjDJB zk5^XHMTD)MLrrJQLWkeJW$2Y!IXiF$zJGtx*k2~%bpv+UqwBC{s=fTDA%N(FOp!- zom+-ZxhAJOU{#`fXfCO;aF~g30@5=!e!HP(!<_{cRBi&+Mo%GLXOdTOlu@z6;RLy) z^75UDQ%a>g1L@}mkc~yM1t9vuIJM-e>q|)2QO25)8wR8*=_7H0UJi8&G*@n9d<}Kx z5bOyKDb|zTkO_((qK^BVV@pB&{oMA`d=*CyOz*SCn!1U&1oIrmoO6%Kab7T`e4QBi zaa%|l{&76{TW)jOPZKjZO}Jg~f$z<_|9|8puJg z-S#ZLbmN49TqoT2Y>dfvk2$gA0`&WhM9U(gWr1k#`Ht5K5G__Cg(*GIa!u(dSh|5= zHWaKc3f4D3Fz;s|SYK7JqinSY46PmD%PrC^29l+_?ME@@sC!I0^If--#sz&>BGLgO z(g6@@kFTiqzZYq@fk@r5K_qD=M;NqP3Dyl1$wIv<(a21|NX@NTg9V-bdq4GvufR67a&&H?P~ za3<|ov@mkXj2#O0Om^U{30h&B9@UIUFZ$SDMcsxefWi=n4>&k}^i8kzAM$>eKVIg2 zc}frJG{K(%%Z^XAEPram@`z9MEPq&2vml$YOe^A3mpfC>katdbEFh1Pd%dN|4NPjnE!761j2uzmj6P*KRjCZ zr5i|aB||I0ej^FK%Fs*DHn;>Cm(4_kyHSK{1|I`u$ncs$h_)~iR|sQ{1(b8bCOqLR zBGI9i@h&>Xj~X#vIe_u`)&G}_=X1vM8RH*j=ow#h+!m)I{2!{@r!t6I3){Ahf>inPvl< ze;?E{e)NEr@l8g0-*P~&cXM#*{C#f%~0%r9?%OBqZ49}kr2Or ztQTTXrTs9-;LN7NG`mz0bp}(~K_m%ZPe}8&(CLAo8l1>GOGb-^}jg#I9 zXXXHb4(-zlw8OCd-xtvSTmO53I{Q0H0_~Rs+Rp{(70~|N-m4YhfRO+>d-Vb&gc1PE zu){w#L*G^yRoy76g5BI0ccY>0LPOgns`Fit?P86Qpy|8yf;K2;Fk}}vo;b-rK^vEn29pf@^Ng!*xhp6^zKC7cU`OS|Y8afZ7OgmiYr})a773EmU>(F-TMkt)#IGB6JL0ywQO-&q?U{6bm z<8LkOX(pH&V6QHFn(tBK>G~cbKX`WUbP1?P#f9~1~C;nUkGBdZ>JB<@G>jKy6Qzli`(Wy$F@8FqIOX(+HPv5eI@8C)4rVkCDUo4qfVH(NJ&q8 zfIibR&ok|daeOe=<7|3=q~ad4IMb3^j2)ZBrX@*>!Dm}|QH84KS>1FsoE^Lv4E*bU8me*mS^0sM1KM6v-qdw85b<@ zDLLbYS@$V9<3a@av=(U6Pb%w6N8-wYmBooqgX$8WLZO>*F=~gP?e7P`i9I=Rhkwl| zmK3z(p{ee*_%JF)UD7O8nDb)O>|Ii9Hbl^M@^W*k&P}Q1WCWc3no_vm^Bs12) z1dA(M;zxQ7(q5Q*v0-+=FWxRGXvPc`G+P2q1ce(T*`iYV8l~Rhhq16A16J+&p5sw; z`D)=uco&Z2F*Oo&hg@|&;uymtQSS@e(=XB??+)a3KBr5wZa&L8H}RuKiMI3(^u_g8k}yM;z4Ai6p4%WuJTe)Jux^VwYIvjcRl zyrt{Wd_?&0O~E!Aau*!|+!{k3?)X}h4;gR3he%!-Idd09BdqM}C8y87+fBCy6Dl9S zrFYVoZ-+fE1tvko_cgMpUb3=6tEAS*PyU{3wt7Rg1S^`wiX!XM z4QIiI0PT{a$$b4X9&9vh9$X?Bvk2B(#^5D1%WZhtlkcJl77WG|M=Hv^7UW}wH^P~f z&OpcyVIS|M?*XgF&GgRg_gEUj?7SYBCdQeSt4{-%STc+=%Xpw~;(J)dQ&*Wc8dFqs zW)%eDi3z}?#~mi)1~c`QRdE-G&3hPARC8t(1>&w~fIB7tx1OFwrWY;-9AZ+^=g&v@ zI?k+Hfz+xQP^)M_Ehd1Po`Z0i1Gvrwwnan@REzMI#-a$Dwp(;}&-ZZPT1+&}C~C}& z!1CCAO1Q+Z5tIzl87E#yOr~O@{)X=}S`#{Rdy-F$_$QCELVuNXzedu@#oH_tJdS|d zGVKLmKh9}|b#WCmet|oy%aJc(^o!O17)D32XbX*w^pD193yTh?(P41gI*B&3=m;8Z z@|Vn-|4n+fmI6(COL(#gP#0daR6y79-7lru^QKhQxyQt~%uqHH=lUNGuFpp4&(`7trjRlgHYSd^zhr#dH`>McOtz=I|CC zb6AACk8WW_K){RecDy3&7}0d+-LTBtB;DNv9lH9Crx`7Z#c3}h^op39QPd=HJo$n9 zD(fe9_SbM0wM-n~ESl)B3AnBk6U|s)ltdfj^a%X|TW4;WQvL7X$Fiyzc(rIwFj(g;nh6H{)e4>~>s|KKOOj46B^qB4O&+>5QIv&c z@H8A>Z*<3P-JsE*SIM*b-D!Aru4-uhaP%!)c9M(F4~J5hO-mi`Wq$!a8Las`K{TYE zljvN~F+#z8ucwu#i931vS`I&{FKd4Sq2B_|l{&f~MyZx09fd#ZF#A!LJw4N&OUsx< z&c5B_bOT1*-}?mf&?+P4d_2CXz_%B(=`^Wo!Ti2DPt9*VSWwDKbja0YOzpZ=bU!0 z*&pS!pEI#NDb~FqNl z9BoM3g_O6Ug3Y5ttvM9SWrBrWvFf~l&v&pnBGwBBeUS`?8tteV42G2Jqtjte$o_Bw zv&a)m3^NN>W*@aKgP8>P7?LCl*9;Wl)tH->B<;77Vl_A1=S@_UQ`7d72pdF15^3N8 z39WaJ*-yupP3g|`Fw|v)iR8{2XVMzQ5$<~@5g<2t+7S|KAjv7$5m$javgV2*JOdDejE8K{%_3k}6-*z30gGXZ*@7w&uMDNW{U5am%s ze@cjk6GW+=)gLc|3N(*YKD|l3o4X__cgbxW_M2l?a~|#>z2oQ~+OR5U49n7p8dgg} z#CvPCf(O|GE^t*ZFf@xst0ul0qevBN7DTMcUm+?zp~@OURT!a9Nf*JlSrIJL7NRP9 zB~)L20G0?_Y;r4cT9WBOp)ZfKWutI*5_!6$vRd0LiVXD&2!vkSX%*y~t5DP>o_SHf zgg*aiJkKCku`aKOUCUf(+^nm@O?amHPmmY6m=_85)7F&VQ8`^wMYM}1UsM;7B%hdx zNN$lVvnC%VFV`mJ`o5!$Mi|M&r=Nt!93|1>oRMP+CT$l(XoG(HwmAUFr}iRw5~*8Nq9I`QC=b8 zHVN~IO>ZKhLls5&wyL6hT}@FwtWIduP!vZ^@(*X+k}$fK!g-lXo?j#3D-t4V)41-n z#rN6dc{>TYEUsD|MHxWCd=l1@@HPo8>MF`~5-yX_j`WX?l2D#>nqDE{%X&23E%tA% zuP6_Y@FEFkNQh~mC_PA+)qs9~h5WxyLLLcaiLcRugi$2SCE;Zf4w2BQk)pgy!WSf5 zCZR%OMQKmMcoLFGxJJSZQXX7QD9%mf{{#uQn+Ulev~8*=Lz{~CN7?fh@_&+qToM|S z?oxXa`jRl3ga=4?l7z)1tRmqR5_XbsfP~{DoF&0WLJ;J{DgYcM5Lf00G@^?!`x!#J%+FDUQX+!iIuP7gpaE64dB;0IE z%>HhoHxeEtVKWJ*NC5Z_5r9wA{p2`5NUIx9*`67D78Q4-!DA*_p{RO&)`?@RvYk+6b5(*Q9oHa?8elQ8MNcfP12Hg~88wusQE6Q8lDa>!=-`<1JBH>FC>^&7_ z4+))nDav6I#`ji~DoFOB&;K0I|-kYaGiaNwi6sAOd??k32)d- z&X;cqOn5&R*=wofTAoQ;XM+5BH=m-O$REY4|{Jn}Ym4sFI(zJ)k|5*~Qk`Ozd)CVN=BO#N7`1=Sf z38^F;C!z8LQty)BCgF7wz9!)h5-bxHC5437NZ3b0r%4odBKiM#lHjwe{AZ!uL-hLi9B36t(8w)cRd)OwKUnuP8TQkYrfe>Dk5Ncfh7JQB)2MDmA( z?j#I-h{8Qd{x^|uj)Wf5h+d{E%BLh;na=e3aG=h6NO502LfWlIjPvF;iH~`dlphkB zlhB!j0VIqgVb-GpZWVccg@pG=I6^`W31N?sdXj|cBp%!msT8>};aj z+4MP%gkJw~{kc?nc9XE?NpKJz7_N4Xyjw0nM3)|_o3xa9L!FN}DtMfaaX{af4+0>K zz)6z>52Ck1o!zDAc+4G6y%on?{NPqHyk&8S(vsK!V{k>^De$MkkFoK%bzi}_Cw;Z= z@<+SvyI@8Tjx~md6dY^X1&g}~@FNVtE;o`tRO7 z7la%rSm6Vh(CLj)-7b8KLbt0cKEqi@8EP!@PNCT@cjh@(1bxn0YhhUYUDUJ$Piv2} zAM^EHt1JrpVsj&;%IS|ED}n<|)`qI&2#3Qy1di-bnz2Ch<{FF4d5j+Ks~-_D%c)|r zgPRVWl-?2CclmynqwxKw{VFrQujTk-`xR9MQ9{Y}IqSYO8T!CU9>4N_VR|q{;d_PoBb(WyIaViKron`xhX)yl_AJFO+PF01cz%!?^?1-X@a}slQ z9pidzbVd;*6FEtk0rK8@QObUO-oLRAm&V_-03yy?#Pjf_ zO_Iz1afv}D3{`^n)m8KoqpvhAO8Y$uk0mWiMvkUrr%epS z)mFxp(`Dfzs+1+=Oa?Jvx++(KDhp7zpXUl`Hc_q{rRuryQCCTlK^WDV1t0=eleU&%Gu)+B+i_l2XdDC zsa#O3$@iJWJ1+cAR&~{_Kb$X=vO?hi3`_4AxhUXye{X$kXlxBJ|2?X1mn0>_kev0o zi-en{J2A5H4C}|l8E`G2=FZAKU8Mk3@L25;;I~R9eTwuNBTjM>&>f5RZcM;)F-YW_J>#;!L`({WP{7)H`s=neC z7S-3ZNaa||cYWYH+|3}l17$M?Jhwt4piM8}()WmfzmJmyj4%-J!Sw$|z&fQCu;GeQ z3V72MTEJJ|1^-tTu+e9M0xmuL-w4>vRayc2xJoHtjS>V*-$?{)IabR@(}S93soYp5 ze~|jSKM;C}YUAUA(=I zb2*Cd2@YEYV5|1T5%znt!&64G@P=^ngCb&jzBR6wi0IXuE;Riw_oL5C0BuZ1bRYdZS@4e~Tel7RKO>~lTku5)GkXNUPY;*Cf6fd6-WkB(7#=XL^r93M zs^-6YX<(m-eXj=JD=p%qTU%)!Hk1>{h{(9-453j)i* zp}}jPv~9dferc%4C}aq}Pu|79&())e5olI?$iapW4brpC5il)+!R!_=k-=b61Z>lJ>}y&A)hI!=-5@FZ?tWjBebta7b7M-mGokDY zA5;qRZ!O^Hza6N0qd^j&mgb8DX#-2C*}fvp?15r#fc2+%-E>cgZ=Dz|II6;#RX#{X z-UXVF3T8fd6`cZssCmKq(MRd)6Y)6{+pbR&J=z4p8%I0U&*XzfOkN zN&K}jybj~9Jz3VfINs~x_c z;jhEsHIu&%hu8i56~=OWZ}C^?Y4~2|uh4k-+}hU#{Iw0d&fu?&;B_*8?E!>_^Vfy& zwI_e=24CCq*Y5CIpT7=)*Gl|#D7;$u>r_B>wO^1b|I-T^^G3`iYTQrbFL`^)AEUJ&kN4C{khx9sc?bRMnO!6U zeF8OmggXMu|67u7Uwb!Dx5e9~s+@QFyEx`c^*h}+INhyXVAI8M4~~1ki3mfC3 zBoy2|VtTn7^wy6urAo+8brlG1=<2WetNnny~TCg#kLVvsXBFO6~cMQWn4YMqsNSwpE*8QpeH_ zN#A*tOVnIS!lJ5nWbVi<{sFwcH-tOl-l7}9l)B};Ug4_$s)I2*l7d?TJF_0Ogk*QP zVE5S$M!c;^6TJQLN{K}6fb_;AK}QRx!1uF{aH0_%0(5kxz2w)@s9Y&3lr&2O;+-N% zfF9SZ;(C0py@8eL(KZ)o$F>hLakESmi@Vwf&%%Y=!Ey&qSoIM&es5R0{9W~M==`1a zaEN8E-|Xb{77BXtD$k+lRxN?%RL1XP1@>&bq%8g0nEe*}JXfc9jh;m&jkQ z`?&_IF%8Cr=F=+inIrMpb~zuNW-)L)YEaGxiW&OCR{x+5Or0vohYK9LTH&TDIB{eI zcPyj`YuXW$DczOnJZW+ri~~Nh7oQ+t4F%X9h;|=@7+1V&zQs4OmC)!0j~jqoCf7)d z>?C$-C1&RE#JeUK(VY$8)w$0ikI4FamZ2wp27*o&OySH@2!(LZzuHdI$c2j z-#c5tZ)yR`l~>@cXd!YG=0A*jZ4B82K-PdG^S9O;r;d09fWI=S=W{ojB^+f^xi*FYI_aV$3-rI{ZGwG ztyP>5ixJ{=Y8If?M$JmscpaO-vHaE4c;Jr_#Z|NG1o)>-C89)#<6C)|+8!EHYrr&A zV0yX~;B^G}pr*R+f9IwF-TzQ>;|Rc4_i=nTngpr&jY{@QkglI@i6NVhgq?co-l#={ zuUDJMIf{FK@j1vs#@hE-q=EoiEyeQF6ANMdLn0Jx7~}FL<6(@?#)lrpczmIv-J`gB zGj4xtFZN8*lbr*%EV7tHHpBfQQJ~M$Sn6eGa;7z`_6WjijS^T*Ptvg(9mwkJkgU#T zto}-ht?xo3tQZX&?`op)u7!;^g6*k>jdv~HcyH06*pY2Ib=l*7KyAc*3mZu}r%^(e zjkHi0?X#?KZ)ipATcA^fvZ;g}n144U0{_`CxWG0caI7S-4F!(n0>9g^1c5ybg9$wF zWkZ2+CuZEZ&;lQORwuA^V+ngS!$Dw0W+v1UcUbP^-8eRME#8YT;oI3DpelYjFlt$Y z;F^vSnvRh)9fg{X;hOH-pae}fP|1RRGjw9s3Ya(i8fX3Ybs_#4DQ~fvrco}NstOcxjz-9` zuo~#)x>|pqjslkCG`AYX*E<<3E&phq0xG(+F}RBIN9vZ?@om#)N@fuHe0IUJeb8yTf`2@W(@g z!`Yu+n*i?`8XV4x@SY#e!OPW_CZHO*11F%CrfB=_=W1!^$3(jwLleC4qOrI3#PHC~ zWyM4||D%?c+RR$Ij-Am+K}8`>_gXg#Vb#nE81BqD zIkZIFJBD)HIW-vW*wCAIo-P^dSb=qE4egqunBeTjiBv;shz>+3UVaT~$a3CMyg0UZvpRm%$voVs**1S(yr8XRm<2 z0GFcUBLI_1eiYo~hPQrX2LH69n%>*heD;vf-rxDWly+onb~S^&sS~R43=f$7Y`ak@ z#op8lgGzP-jQ6IlgvG9BtNx>I{Mf$#q8lGRix7H}5(>T7a{3FrV(V2J_znWTLe(IX z$V)klJ8+Erv6-;{p-L%89Zyw4*7$LNX=Q0JEhz!hnHg~YQI)`fL(PKD1dhd5rHa(- zg$b41sT{KP#M>HPAHa*mNdoSTUvF%^A5=E5-p!SRTklirOJlta2mD*>{c0W2QY~OP zX`M04#jyq~UycpVa{9W`u>55If6H=BVENHlHM8{H*1*%|B7vTX)AO~84c&et{HcCM zGx=9%aI^TeQYo;w`-PtQe7qm$YF#CH{MnieR}VlpV1A*;nf!+ZMX%V8{yzb=)iDu& z!)`Hhc-vz8ZzxEQX9-lKTV8AsFat9GaYgN31da~`Xc>3*Y_#hJVW2iFqk7wZQJ^(N zwZ&s3N7q#>gp8SQ%>5)M3n4Zs*lA%i}V4Qm3<9W2y1w~ z=X#+?l%Yn86&xU~CUYHSR=M_9k>eF}fHaiTm{jF?*;^OG65aC`6 z&yVLf7lm5afV2(m&RXZrk;Z=dW$xb&yQ)FHy(d6(wwsAJPG*nXBU;QJM-hEJ{};{uLQe+5b$U*j-9e z%FiQt#l#IuuJ5Gbj#mP4x+CS~2C=2;<@AQ!sM$pDeIy*`CyVwk07s~PCt|wZNnlzhFjd!M z3Q{>Cjy)2N(E`V};iX$7f;gU(a99P77gQV}YAj6OR!VG!eJf!o2N&oK1>(=_= zcmo)#hx(A()Mv%0icM%#JVf2HKvZuQRU4vw7&Vhcm4T=;7FM?KMy9#t@EMOYFeSpZ zObqs?XyO&u<%RR7r3#xA%wPZqqouMtV5vNc=$m5^llUek!(@vnYLqnhvI4131?u&A z$s{q8I0I)HOxNfj69%WIb3OaQLN{WdaIdEW?+r_}va5HD@9nT4B;d9RM&h}!kovsC zxP3lKqtEA;sNyteN&#j>L0{lGjIPWpKMSk;K8d#k)`;F|oC}3;;muCL)bZXQT!H4r zB(4b(Z*0#^E7L3*kmN;5u;N`EzCR?ArJ6%Z-J1!uK2js~`o*f$k1&9Fk&+de7b_tg zngPJV`F3Ld%dB-WZksgk!hedMtpvou6&b$cCSAMnv2^kXZTp{Ne4dPeQ*spXnfn>u z=FXxj)@68KgnD09m)V;8s$L?m5Yql-c*!8B@gC>9jK!9;Zq#j5kC~;a$su z-L1B<6O_%}8M)?bIVK{beYYtco{k3I09ny(KMG0VYD6Mj7QxBxU1O$c*0BT^uE9-c zsc@JJA^$wT7MEbM?o-yoeT8|>q&#JQ$0bR5aC_aN*6Pi5dDeZgy(!#u;+IxYudY*X z7tHe=_dv&kqkrZ(cI_#Ad(Q(`)aNMHlrWQe0VSLJzpS}A5VXXuDOGEdoh~?&W-5-Q zoS@g+KQ+ynbfP$sc(?FoB7$wDj|ujzaLeS@V!Xc;F4ydA&xVT|vq43@Ujob8if#BB z7;4LQCS@y$Q4nW8;kW6p<8&SboJwK`75RyIEesfC*u!1GAwK?QrnjwMV8!W2vp^Q^ z``UyT_k}qINcSaH5YLWy-15%RgwLiSrIo@ z(t9=eg`0_W4KlYLOWqgl$e;?4+w7ym!%-aI~vqG=qT zJ(6s~hFu`sSGey>k^2b8A|NOp0hG%Fya4rqUG4;M5@B5jqQU#|dh}88dVG`z;{hay zBq~Z!M3A6Sqs}r>14daSkY87I_e{@ZHt734ub=Plk4lOUsvkip7dn+`z5>|CBdZQa>6`HM7EpNV z#KJ;;kRm0fmZS<8&HpxIE+3?FbOjW;=}CT?{cu+;G?1D z6nWWev?24lxb4_>5a0tU#{-R)hPO0JBa(xOL4p{F6jhhFffWg zY?hq%q(Ho~TUijBHJ4xQJWzf3BuE>(=X>6J3AES3oz_o(n#lY^irni^BE5As_ap_} zLjfVB98-L+(sKUU(~WnR3}z7ea!X$~F3`PZf?gg6`m(;zWc=FfUTaB5i!}%hWtXKV z^g%IE_jdrjG>^~*8f_lZXc|;7w?_JmoaJ?753_n`-sp{jp|$3jBbnu#f_n*Wd9l|4 zIoE!r$j>1+D4RA2&$945>yYu>T6ms?Y~@ENunK9=k@F*;0_o~|J%ElfN(IlVZWd?426|V#8%$ZS)lX^La8Fxd;(=F`4Z63n2d(CrAT-SPIy~6tJ)oG z&Ql_p)m+{>w0G0`cNXqlulWhEK1;AF`PE=okv90?6QiYGfbufEko=aA49_r{Z~r6_ z?ZWNs{WW}#1pK(?^5lN{bx=_P8~r!&@3g8Ys0YuGzQ=E1mn&Q7U(tyg>H$hGjgO5i zPq?dFdS9=^V5mg?*9S-?X4)-ky3Htd<; z+V(M}Zk<3mq%nJ(eul0w%}WUYU`|NT^o||KA8M-e*I+s}erRRdoO9dc}?Z^M@^ z!8HL#1-WaYoP7B8|L4E!o%R3z46fCG5U$<-g5ES6nl2@2DdhiF_CAxm4?7R-Z7kpW zllL_8zbi{0&;GxJUb5;a%yV(=edh@n`b(^>DY@9pmjjy#F(P0j!r>KpJ%V+ z*_|x=Jer*tzobjIP~<(&Q~T}WUutX7s*E-;5Dz_46j@9~d+JS2$mJ%Eo?SW5IeL2k>_^`I292@7$ znFot=src;%U(WORZAEZZ^mh;h3QoJ%Ch7Wgk?C?g7|5v>ev`{2M<8Jm1L|5XF{IuY0+IBa9&xj1&R#EwWCTWWz1m;7$B$~zV zKNY8I_XbW$#epjLs0IjF^glS-SIX)FIo8Ct;Sos~m4*wW(v5j9k++?~FJgIrko;g{ z)nk_Q*Vz98A?;ouZfcc78%q8o9FeWZppVZaH;`m zTHXX`H1k~t16#WJ6!85PhLzk|ZS?Kw+USeW$FuwrT>Dr(Jm3!zSx7@OjrZS%~?&7 zf#%d-=)gsWbezYkYrfV7>BRRIiEnQZzaSlnPi2@=NvmmNNHa>Ic9_=>E>d}c=VwJe z^d!?gjfvP=%4C;L+`IuSj%N=(X$5m1F3{;1evl+Yxdn|N=htRPDPerO9rL2P0 ziwIaPnS9NMmiqx@>4K9?mMUpwrizuxFe>vJ;fOw5F~1?%ydv~2~x*NgrKkLLon{;k0=8BzhDF;_n`x;1k1+A zs(+v4SQjj9`i!TODjA$2wz+;c_(KxB<|A3>x)%c-li{ z3vVQ-HH@0EC#iTB`l$o5g1W?1I#)&oxB=$MvD#2KsJc|7HUS@8{~|iiY4bR3?jQVa z2`j42Rpi6(QdLp*uAm^xSR#JZXGGVvtbfb)%gtcM#5gt-n8kfQGhqJKTTT3uSWAPe zfFTuZkwDI>=o9j(9reU?Y`+RQ)(!Y{ZO|vjI{I>-z|p15sU_iGN1l4k31CJUYAk%L z0EgcgMJ|7p_pu70kHP*3aB5XRh8ItOfB&!pR^752s{i~M3n!%1hK{KZ8gJn1su&(9 z4{gP7#K7_YowD#5A9K3m+IF-b3Q@&~+TbW!tS+GyOhTV%gAzKKAet8(Z%buPVW<}) za3&fjWpa5sQ)Fm)jIH8!%5)Gn04ffYcVw(#%-CNAL*O4^!n0K-COr9*$b|chx8NjN zv$1h%_FikOSp;jA8>ePIw%;2|UPU;Zr^wF~Q6YQ2NRYh3BI-C-lvS>@M~Ilx>88WeL-WP9^P8yvl_ zJj&FZA17G}yAsj22O1oGK*_r(Iq)C#I`jE7vZRAng!ELFKIt7wbJJaPDrNLm_(D@M z(3;3Ug}ojg@9@62lCKupZz+6cBT7Vf~hlJ_R(pufO7^TuHZ>jSPF`H3C6FYW0a%pX0NDqIg;268izUUUw( zAGZ=Qze$EoI-Y!mMp_vT48GhhIH7(N;jnTOeR$atsOJBQiC0| zPV!=A3>=0Lp1~02CxVqg8lHS6gRK$+@HA`!yhKPpWl`9#&Nxnopw>u-pMicvtcvLEgiFy|&w)JK z@UZ(?;Gr)ER#c-9-(6j%!OjRAMjglNA4a=sd>CEg;KS(64r3TC(DY&S%yILkU-sWk zy`-o_GpY>{N{AMmMKV%gJ`rJ+-LON{H5WIoe`L(}fzHw1(nrSVK1l8zrL*GJggCW* zR4jA+**IKs)=)0>!;K{NEtDWqPgryJJ}kWt!8sy|HimCrV^nQ3BCEX^qH>aBuK!|y5JOB7EE@5Yw;o}ocJ5l|jM-xzFI0d*3( ziGJfA`Hcm9Iaix1=DMO<%oR)~4`9ZU$_hYjQ`}NKbVTr?Gy6l@lW*hdPN0B3vXw$& zh28o+uvnSkkYj6o8)|w8u7(=Pv^TGY=s@_D_cVUiclad|&D+yO|MQ;}=6pXpyd1aU zmZSw9>RWEqMUs{igR>J6^)AVh^m|)LTGdjLZXw^AH}{@w`j2lok5UT8 zL6xrU4K>duuan^Q^4_$Dy9h4}IsltTBZ|TT-9p+^z?WhBB6soK593Dy(pQ}C*7uz) zXoP!4a@_``Bo`(KR;n*om2AENYjS*a!$~m$loZer5CDnwapY@S5uB}+GG_kf;Ip-G zFKp1DQ(GcZi2DQOYJtLcX^TwZ6e!bNtSnN+;4p(QmuOnIt&Zx+xRNYmr+-NquzjJF(+D^?$dlujYpAYur4T(2;Zp7D7B;UCH2o zAZ#muqs`HqdV;;e@Qw)GYDkW77AX9xjuyDoRtwz7kQ^jD zD8Q>ruVN{?AqB5G9sB3VRmXj0v=d&P>gYk6Aw%t6m#)if>H4@krD6n7fp@6B!%})e z3SQ9qf+Z|$M`q=*@MUP_B_)9A1|vQ(-C)Wa4R#l56HA2c_=XWcw8d4X?@6Ilw4~=NtRon1m@t;)mM)RiXz4o#YbmdOi^6h4XR;iBk=I(>e|_WYM@pQv0$e!M-w%1%;n30wNU2i zQYM^|W6gaVV9`Z4lG0Pe^_cs}TbTSFCBK)+?-}y@&wuH+E(IxB3XRp9JlqBkoybEF z9$JxyW$@65JS>3+4|%u|`&w2RdOXYMJUVR)WmQd$3x)6HV_bcSEbh=1YFF_bbp;{g zxEnkTP8=xm5Qb+P!J}=r9TgWQquxxG&&~7YZZd`gzoChjuHqUOropwrg~F?jv7I>) zQ2UeV05l9WvVmcqFQ*$Oanj9Mb#ImvuBJT)D|{--5zt&Eb{oY#NG@^I7cXJ01LPQb zJdxwUAjg%RNpg#$R4%*1*dvV4($PA4VuG!nc(yau3Tb6TD*#AzOJ|D})793BS$0EsT>WMS7DTXv1=WYU^qt&&3ZQBDe50fph+-LZ}kuLy~ai>!}` zOg@)E1`)ix`kjtU{{uF>M*D@17Sem!k}h{NNTe9h^z+Fy<(FHhNt)xzx%SmpOSnCk-wdL7}_f02Au zKmYvCG83=0n`IA6sS)>hOQT%qQIEWoFjTb(z`za6QZ{xDn{R zE1gh|RbG=`mziIF&Z|Gy@>R_~M7va9VtOsnpHm7h0bKplY5vC{|71vM$5QV3+(c4q zktEVh9%8EE(8oq?U^7$!#IVOWvc&;#mPwJozHp!vPFcWa`h(~;HT11I`E$_7eS!({ zxn3b36#O1ZQQ!ZJ658LF`wu*$`x1411YT8NVV_n6$p|ykmzw}C$A>@T>YUL#OK*}E zE?kS8OopRSs+_e?BD08*;m|&^(K9vpZ8;P5Z5dSmvSA^O=o2Ho5a zg#LP9$yi_R*8p_aLB>Mh?SRlwvHrcG{;lC#68T0F#OjVGRZ}M((-IZ;jy7QsB<9)ej0qDU`EfR30D6td7+7E@=cNajri=ZMa zhLUQlBR}P=cni{&9{h8x*!H2oieFv!tVm(3*nFaXRya;uSpjimp>j@bkbLfTXzR;D zt<>-*l;(Lbq;X1meh7W7pKsF_s6z+)HXo2^X3m+Oz?qf=COaMcuOch@l4`3Le8O4r zzEIojRRA#S(>Q!#RsY>-Uv&?w`j)EttDYFMR()w}hCCJRpHJX$ofB)8-Bdm|bS)v{ zXIAxoACN|vMHF8!MLxFxxD*IAB$)dH<|_&1+9<&B;3xL(c2HHEj_gxbIcX?JHP!ps zuHK(qlDeEgQA>g34m?S+!b;j~F+S6frVY zZU*q>{_!~5(?5obe1{@1SvtGVl%*01g*7pr3b4gTJK>Fd$Jl_bLO? z{qQBCKuXvjo=6y#XDu8t+&>@lbe=N%JS;j^*uDF&kZCt~4K zjKWR6oQ*`2sN42Y5gisZy_sP>qB%DWQd63V zb0$Zyr|3g$D-5LlbTjG!B36XBcF;A(d}Q%8K#NEJ(-c&7$KU|@T({87aFsR8j9)UG zM=2*=y7Nf8s4i?{0rtxi0&Ft&|9!anKnZ|o{ROk3Rs)-wW&oz??I2NB`!QKg5=vPi zlv05E!M{r*$&Eg&Gk@(T*-by*NTZ6f%f5PM?Q^l2X4=j)E{XwzL&McR-303U-L7ghjwXNd8CVy^y1N`5FeFcCGR;6DTQVAg#B)M?~RTT>nX}rx=e=%viCQ z@7|4D%uJye7Zh_H`akO{M&#kqsLQfKHL<>|;z;>q)H+h8)|XW@QWgkV>slLoF^nwl zMo+xWyU{BoUHxKroiVkG?U=gGYu0lnz1j=)Y8P?+tLzN(;^F#Nm5Z*xpZQb6A^Lwv zTNMCF(Q^$93c`WEo1A%B1WIkVo9-?8N{-F(Z8+x=dh5(Crup#XZW8`R^)UX!4VUnP zXPyr4@c(`W55GL}Ex-Huf0Mh#|M4>Zil10sX;*U{|H|K3SVUb|NPFIh+BnAL^FLz$ zYb&3d2g=`eL9iW>dkEX1rtQYPi1Xmtvl}9)dfe1rm{?R>yA#sUtvCR?3TFAbhz@v1 zisJgx*o2i3^X+%S)bK*+1*?(`R1Yvzn~LE4%PzfQ<83Rpzw-PQO96DF2|5jpN4fA1 z&^WpdjfXri_5XWfs2QiUvXD|*LH=tmlT)js_ps9(6sChq7U`0f)i4@dvM$Ef{7 z3}+tsuWs3CQK+ecUvt@EfZM(cya|M=glPBLIQ8O`L`lqmkJ(Vua~Yu~JYg|OQ2(~W!lKEd>vtsQ0~m^I!FvtU*tZ;WNm^r? z_9{!uJjN{@HL>1`&OH7HE7}Yy3gku~NMvX0Y?CfOUn@^z;%Jf83|w%elv#q?gyqoCpQa$AUxsMGW(T z9mG8fViwMjK6Q6X1byOOhmll4J^l_?P~W^AuOkQEktEA#Z4?Oy6gtBJg^XY;?NV_Z zC;3o?J>W8LEEZG&5Rw5Cn0E%@7EBlB8i-pEUB*%fjnZN!4P$#j{tm(J0$lgFaHFxD z3jlXd)6ShlU+zKZ*?)hNSx>&)cO<**Cc!2m1G9ZO_uGS+_&h4O#0F%DsVPD|jE#m4 zVS9@X#aJ~e)Rk0WfP;>N-@X;6!DxFnehYN;J5p$fouqZDD{1||M_(J6F}-t7!r~f# zX=r>%%y37)bbz@Gk0V=>M_=v>QXM~v#dC@-U3#&=c=VZ~+g3rhZAo$|+`Ur(d$yu? zB578~>kuRR2+TeNvyU*c1KBQuq%|Oc4GH8Oq6*f| zJf!_idqQYxRNOvBN)voQ3BhhcBZyuJi|O9cW^Z0A`^_j~PlPtuQ)aXQojmZU!&mZG z8hAJ~j*R?C!I5f7Zwxb>#yQG)^c1z=T1b&%XDN;d`?02)G0ZK#~(h5rK}wjKEo zNAXbRJXmXwfXy3$iD9f3FnWxLPo354{{e?tX3DhJR*NSSArT7q($$|8UOG<44!R_k z15QSo4=6T4bjfz;lFjIPFmz8Nx@3EFd!E9RMR+WcR23K*z12aFWy6~{IvcxMm2f@9 zTCW@%mem}V7_zlK&^q1q;C31@OW(VmA8Nx;8^7TBG;g#CBJ$K1(EJ@;P_c!pfnW?w zCq$H+dyXDFBl3`f#X`ipJVkCCq@nWU@-q;*fu0#IxEVW^82azWql@=oGM8S(;i zuxdi|i|#0PT`Y(l24XyK`yfHA-OP{T2OH8S$_FhG8?&08kHu&h9xSF_j1bQkSUuLy zaAj6UuAtr6L%Tzz-?qFOR$dK{An3(YxxCOCqr#XqjidJx^pPuoE1FkNGnGeKfdcp( ziX#`!hp80tmY@gxMu(WqQaF8|ymF*Ra{>M{&%=P|YPh~Nx4Hrkg1 zC(VfP`*PEep6cNs^^g}KTC^*(*CJavQg32RQr8DJ* zxXUSS4<4c}&Y)prO*u!wDI%lsT9}mg=+Fdf{>It%8BnOR~~4+rmAOWMJcAfT^D>&p+Gd$c{IJI3+Y;x$tj5DJ9a02QnK`cMPY?Gy}P z;s}j_xG^MQHhxZHZ@W@3uuOfuZvR1$z%(!l)6izgg#H2E2521PS`l#WssA`=(8JvT z_q5l8q<-ZSX&aj&aT(l;AH3|XM+zHWvrSAJ792?athq&2Gk|H=;W2bwgalHCK(Wn1 ztZO8GoE&#jb%!I$o=~^?a~>n|8{I9@?i^fU>Sn`&1AvAb7!9*X^9x!_z%&#+AOw@P znukf(T0Nw5S4d!l>?mf5!<5Kr!FCA=bew7u3ewyfh&bVJ?yq9 zF|oP^vL5|}aG-*ijU-3<`EuaRQClI$YcxlP^X0(cIm;o(I+~;7`Etxb{SP@Vr8(fX zqtHc)99+$f&7y;GH;AA*NrK#dnj21agf1Ya4CZzeo@dMcz5^udXAaB?hOzH18f(i# z?P6apoEKDI>wwnoU?$x+?88Y__qr#ld0XS&u{!|wN{8M>K5K(m`mxu`+L*5lF>?3A z{tvmqsmDv9-$gwEXKBr0h_NliVHakXQ-Y^ z;{f8Of{7s*^FXzKnQxS~JMlkyY-~{Vuk7>D5`(-aPU? z^Szm4%wF>#9xMll0S!c@v?T$_#}3p)k)N4K(KP!9877llMpq%<-euo@e~Nt5gib-e zea^mp3E%b$;!nO+vTwWL+v|d$lW)$e>9=R$+k=7_lW(orx4YrnYNsHlXU%f@lcdYp z&i7F~yNPNDgAQ;W;zc%}d`udyuT#$zYb({Cs}JLHNPGVN$3;ej4?b31(K&(fE) zl$bf72Vm!T@IIWY0MDyby78tSmapOaXkgSscPq%bOpy2ChP?CA`+nkzS9%-3ap)t0 z;{pN41%|-KXFyxX`Y2>QCCK|sL*Azk{lh{(ueK0>XF<02}zGV5$aIGo5E??TlF$v$30kYmS)Q zWBr;#H|5bq@YKEfy$8|xPIFnRTaL~0ZI~eL{18K8`r1psIB4wPTk83J;4uG^E#IfG zUwt_KH$N!z{|a}!--{0V`)}f$i#z$ZUdcTfG*Z-$d`ANqR~WdkAp;^`|Mdv}`XG}d zIzd7qpuzr*Gee)G-o$zsd7K^5Lv>!~l5n~<)@!8g#Nb0*az`16I}=%Q@S+e?W8)(W zXXOK__vPLP0iW<#naOA6-z;3jG50#b?Gs9fE_Bi<4tc!RDy&_|48yk}Q54qcl6YPi z_S6QP=l?;BI`MbN@-D)D?J$0J>3^iW`hJxT`U4tHk)PA@9U_mmAU_Cf3I@ke@*C?A znmYOsdeopbz}qpK{8`>jj`dpJgs88jQ;}^B-v)@LsEq~}+AulRFL+UO2T2$SArY3h z9tp1dr5)wi++bTdwkAmQ&ZuCM=%WOp4@AF!v)mgLqtS_cJo+BG`tr1uirn{djG3+7 zuE;~#<9tOP&mON<k3k62U;4r9)0`J-uFF83kHdVqxx72E z@VqM&dD2vq;%|uS>FvAIRvEP0MekE0wj^vG;py#myY$PKCBvY>scG-s#>~}0jUM+H zWBPHdlGrjXFp&48v+Eqx+rxMh2Cx8fAWKZbMYKn6-v&5=#06~qNG@O{wdHvy$P53~ zvY%br|K%?x{LTjpG*L%`N91Y_c`c~r@`|-+3&wu)=f_O``_T0k2@hIUqXApoo|vk-*_~gZoI_Q zgML0^Y@lnGVr;-_oC3gSy3qV(J891v^e*jLJblm3_&w{Boy^G?&sX~Io#6L#h+ftg z&oUF;V+^AY@-#25!5hFc&eR1S$J4x~ktj3NLUi{NVP@`rt*Z%cdZ?KQSE$3Oog34$ zWHm|55cI2=7-Bqhqe0P^XIR8ZPR>VXSY-cd2Lv*zas=DKf?j5|qZC5O_fq6nt`XPt zS}AglxqW{4rnWYEH}t7o>?%)~Kd&U8wc88|QX5k*aiC5^e|wtsL(T(z$%8k4dymol zO+54tMoV!i-_TVMLmOJ=Z&MlIfS5QIY&mJx0c0F+$uvLApLv_%2;P8?A7}Kjsymwo zmf<}Kd{Q|2By3gjO2JzMFZ=L-Cv-I~AmKt`6;Jm~_k+W)a)?A0C3FkobATHaaX#q&4E5LrLU57vl zt|s=NPeLw`PfVgV&osZNkPUmM^iHaegY8Ugp9IsQt`lhAP5Y0luCwJlBDPD$*^4 z_!(YUe?ll&^)>uL0$q5bx5HbacSc#S2dgL4{HG!GtCXM@V(O;D38zWgBKrA2?9^dg z2B6((nn$BSPNObZC20r-w?qsxC|JM=JgwfOYp2ZA zEH5V-<|?)hWk`EInR06*>2kP`yb66RUe;mS3PjL3#wFva@U4B#Dtvw^I3^PCi^4hp z-btx^rOIZ}aB%?;>qp(;XB{*i?3e=4=R{bTVdBvw@b0o&vJDyHS_s4%QqR4?)|EjI zfH%`y5AcCE%swutpT|-^4=c}R-3m_UsmdEnPEa>?ip1RngwXTea0$1Fe3*S2hV~Zl zW&bu8z4Ga?>-9}daC-&2BNCrH{7KT2ywpCs_j3IX3Zilt65MG6s=t>Gz~u;Xsn7h| zWn`6#GyK?3O>T}J*)YL5a~(`@A{l8&AbVq{i}^P<-5aj4J+{c%eED zGFd8g7L_|;g3r$YZZ% zD6}h>-%Cit>jSFbJSIy5b{Bo)1ZyA0YS>RFs}8L>3qLync_RV7p6pSC26bEWB7$^I}OO0ur^(0bijZi9^H#d?4HKfp*;eptigT@F-+gDkQ zy$9-}zJT|s&6`K}uU>K~6=_lsJsv3}MEF=tG&{>d;j(XKula={7X)OcROUhYFWJnq7wtk6Z)P$Yc)F;c-6=2 z_Z7GO7jQrok*o4=<7$-~={OUz3paa)V=2r^WXc=hEdZ1JGCXaHt3*vp@6U>Ji zq0O8sNpvDsLri4sxoOl2<`(u_CiAzmdSE#S?Spzb)z;lvo)@7WPtT%5gje3}pxYOX zVc>2+vHn^e;y6Gk>#w3XGx`2k^?HOGLg7Fgn0BLDp(1NBwLv+l7={7u2 zO+9PtfIfDo)0IFA@*)GDfER=4-d^AZ>qzSbir@lhn5zoCw-4Q(fqF3U(BgVZFcS9bU zy`5g6xP~%Z(U)xxo1tZQ>SfsW{^N8oH+b}C1wv3^nt1msyTvFYbQ#UEG6}!HN0#B|uf7k1fK!hI8aocn#GpfByt3+?Q4u7PE3Tnd)|&p?mTra~1Ml zqE$HlDyu91u-quut_pX)6u$~L3Us*zx)?P^K67J28EQ|ER$dR&RX%Cz#-(TklmJ{c zHu~Hdc+~|`|JKQ|Q|Q!)TECO8T6gj9bfcZbPxEE_ zHY^fd`r`$W%^>`H)b;<)Z!)20u8aP}^seMLmOb%RX?#@DlGRpq0`_D4?2bO44x8BL1Qi&B!8nfICKblzB3u#@Gju!#inm4ZX>{b zIx^_dTEhpesgQM4C=t`7yr|o0&8UvVmHy)lV_vkpF7A=R21K4iZ4gVkfO#at?A2Hw zcnhd|!c2KjM)^kgCfnC)5q!IeeT&XD1I-CC?6b}3BUxtHxrv8S>%F7xp_kYSvEU!3 z)*w|wm(-iPC(Yeek>`&St{)Y26;}gq0QwH`;JDtZX64qNr`(rb#ByKN$$YjR3hqXS ze!8jUyl_5(h(?E?Q$u!XF5KtC?zLm8F~Mfb&0`Chzt;<%lcks8qWjcpoGP z^6I2nhPYE(vC$Ut$;%aroO(q9Y#xb>FLnF2v$1T*?$AKf7vcEJtuOKup!~bLlu#Hw ziCv(|A*K_!$0M0=e=I$sJ(3>tg_Y}1 zdWyi)r8SA@d--v90EDR(a-vJ^-G|O!1Oh&Cz9Mil!ijUN2xN#f9~n}lI#CclxVWB4 z{dJH%^Y8lESJ2s5{YsV*HZ^h_L>IyeDy$1v;W^$)vsJL+20AzY7m;38_~qv<{P3f+ zaDEB_C3Fm@;c|n9%l{A!E~J5$UxnVo>lFE;35ndDNccF7I`u$x4KnZND|?77wpa7T z;%d6LAelN4u|SU&!Ig= zA4tTltG)5yJJzTLJfeFR_IRRxeB?nFUVL9A7kYWr1GY2~+RG?8^q|YI&eXTA!-k|A z3BAhtbr;g!qg_cj>JE{VEj(omu56K%Yk5jPlH!DvjXb3-4%);rYY53}cyc0IkZvCP zftHqpmker`VnNIxAHz1ybdYFBN)Kq2bR3Jw3+N#@C9JhFib=v4R2tnVDVLAtE9ay& zK!>OUh?Z1)5;cf>5#G0aZ315#o4%s&>A7ox>EZF*^%{Bz99ko%CSXt%58KzE$Rp9Y zPxbCaU5t&WlODt4RJL%dYF#>9hsRu&&8>Z~^pC-RD7Z zr^dJQ0Mqt!E)mlUe>eG%*H_~0zy^PE^CjRr2*5|J&I0aKP=J{!jx5+ zT)q}=Ma_K(hy1dlm`9E^@#Tc|SGtylD?_MHR5?qPg8k{p?x(l^l9Fe|E%_T_S!mrT zJOR8dF5sI_=p-6z1k)dONd}oY^gIQ;LTJaZ{zmO%^y#8HD3`O!k;)26WjQT;b=<;7 zFe)o3;O4l1P3)+A^&yu|R%k3`9NmFJa6J|mSMlRExaLz_UDio>da0mea( zk-NX|5)=DkuB!EWK9WD=3>dHD?P z(TGc5-&Rb!R5ou$kvgQvYtf}@KG^jb!-d;4pJx}qND;F=#`&`6ZZ!LBM~LSYy1wh# zEE;K}2zqtaJxIX-($Tu}o|D~4k-gpd<`xe%9Y?e_Ow4T?O*C}Z10_X-6TW#-*7q9wB^WkY zKq~5MEh=}O*Z1fVyfT&CTY5EVezs}`hIA>=v@#FP2QiuLyVz*{PziJXDt*_I*HJ<@ zIyu2&p*AfTRShb|gf7H@Jmiikfv5leG%XZLBQj z6b8H0>l{wmf=c@%nl<__EV=@Gi0SlJA^Ilft}t@{BOi0GV2L}~F5*os>5hRu_oF;r ztrl!Vq4(38d8lH0x+S&r6!}^DL~?FrIUA7{R%3az)yvc+B)=Mk*UU23>d`R{dha|f z8{C@XaC0-bCwuG;mxgy29dGX_JrJY&Ezj$dOA}iEa3pY;n8gM%tLu7~f#8%Kf`fun zk4f`EsMlpshfujP!Hv5^{@s$>s`&Lnuo)90iG*iEG@_!*aZ4K^qZFp;99WMp2GD_0 zxVS3xDFNn!a~K7usnOjI1MYdmiq-t;Gn-v3Y9vyqEU4xiDoEGmMC1Mr@GBTN=xLVC zAl_*Ndk2LEnYvxU6!cf-5|v3G=iz8ZeYdcnq>Hzo_Mf4c%aly}PkBzA50LBw1D0<< z^{#mtX$U3Zc68WUpT>^!^uv=X7{iLjGt0ziWFCwN={861;ywvV__QLK{femaco$9#_)y>R8LNW)8TAIo($356?x-GW2oHIk!n*Vzs1MK z41BTdqT$rwrDh!;dfmb4lrRVj+D6RWtC^vL`(x&6Jd2|5DZ?<4gaHbraZdzw6Skx5 zVml-6Llu+yVE7{;VosFF%n;q0|RDnMjb%rp$V2Qq-2v6 z^|4L5sT!*djw9_jRlSg8RzJBDl|@fFDB}Dt5&6{yQJ^Iw>s2TuS*Y1S43U{l*-d_T zA{&?iB08_;U52D$Sj6XK7aMtr66*4AJL{_9dKbp|&LE{h{1)&kH|7%EgKeYM($T-p zhS(}P#3FHlyV%L{Bt6mqj_|kN!dk~|JE#*6&4q2$coULg zFSYs@wcCVbY=yfvGbbh>q}`m9;gpO+E(%WWc|F_U7+fJ|Rn&&O>c$8&hJy#tZ4qE& z52vO3W-c~vPDfKr;?@W@}!UBk+r`9UiXgGu8ZqL5z)b${DW#ZmBMTXaNppwXUTl9ri^63_ULh2N>L+`7%)VlA*1SO}g;>R@HBL}>l|8Sq79 zePPRbdVQeKo%~aX3*H#mCmv;h~;uUUqzP$JI}6XG*TxY=cX(+pym-jAE$% zc9)Az-*tkfAVHP31bv(ppP<*))k{$RI>x^={c{@qj0Q~*KUd<};7a3MDbiXNHgoxf z)gqVaoV9$SF;aG~he|IgM={(6O*SIxhKfA2A=_EK8dWe%epr7hT1nl3Dp1DKUS4TT zRbN~I(=%aOI$pg(pyrV^E;c<*qhlg57pm#d_}XiBL*p+iu>rtkLJ#m(a^Le>^vHv~ zO6Mv8OsEKFs)i2*z4TfG#=5qkn21Hss{4Xo-GWqF-MOpSC@;Db($sq|HY#vrH7K{` zy;vkPqFDyF()k=@`(WxFRu`G(^J`0wyb+8;e7az-O42$1_rDlbQWjgTX0Yu~1-OqmR ze#V}1OICgE=S#8-$=iRIxu|00&qv+D*O(dg+G4`vSN! z(5WLz!ki!on8~vooM5<s8di}(vJPH?9NO^&p^;tC%Y20N=#Q}U!)^~v%nKf{< zsf&@>=rWT?ZuQ6C;wz*zA=)V~%`y#u2eQo_@{Kz{Yim8t10cS$0nm|U9ROEba4aP_ z)XFt>1K>T`bqB!l0RG|{^8onr8tVXfxuuj;3#D`bq?7`D2MheM+fDEc>zS;&1K{ff zQtiFQIsk6TG9$dlK!^ptYWcp%w!piy%md&ADD22e>j2pA_BaFJrqy=3xP|&+b^Nwp zx75Ym&A4hNSt#9o8)_%6*VTChm%5l2gi`$U8grJ-n8jnFAzty0rF1NOyjF;b_tq-= zdi1EL9tBIx^|-)7)bESU^*Htm0lHz8Sdaay%~^^U|M7YZU1F(+v6mDnhtdYq($pVT z(rNLm#W>uWAp-#UAsZ$C%^(LbNT(zo2maeI7z$^!Gw!0IVUaOlgLr~$A^}vNJxLI6 zV~FRkWD{XflEFos)4~L>h^QvD%VJbrqZPStJLBT1Z@RMOTcV#oWa}S1zPBP@xM$qE<$p{y`k-u0=9|xsBw+KrgES4VO9^Lv1erFMj6@X~9=IZ^+tbIsZ zD6BYJ_}X$P@YU7U!q<1tTKQTvgz$CC2|{YC6_l^iiu3Vx0MzWGg`BTnv@!U4Uzhs$ z>TJNPcWj|OUuk~ESNdWt{dHm@tCub0d^Jk1hp$G#^A}q9Ix=MCYyPbQUvbTHw)*~H zoPnb!`7$DSCw)B&Px=}K%9Q}F`^-aMLm3>jSVz8O zKfJ~`tPQ8pbC&=&W($Xz2dZxmI&JJ&koSE?_qi$ZuQgPI3||UE!{wb4nCm#k(wi@( zI|^uUk{#did}rAAA8&FQ?tl58-}CQxzDvJ(^oB4k@h1dKCHL*XaNR z?S8K>x%8VU)h&2rvEEM+R)}d^^~!I_!zZ4A`q}VVz%a*ac#5u)tDo z-Fb+^{_~&#>o^bWB%yAf-Czg1RfjDSV7J=B{-VP!c*KCsvW4BJ!*2S_fW6!ncAn0M zTLeCIv4wqJhg~ATp1$4=_IDk2l>qyZE$rB%95zRQeaaR#Ux&R%fDPHgmg=xu1lUQo zur13t>=OcPTU*#$b=c>{-hQ23Ps0(WPFs9#3wvCLeNDjoqAhG^jl;enz^=80y+()K zCBR;Jon3E#P=_rRV7pmhoe2B04!d7~J#(!c4L4VE*w01naIGEQ$932*1=y!8uyicz z_alcrF2F7~!_s|#`M(Je7cDeHP;9Pq21FAJ1RWVu1&9;3+Hvq>ouV{hWc+TPoz9-~ z6Nl{}z`i*T*Js)0O7PIFRY%Wr(fyYcg?|38xj@f#x6+kQ@MCeXDhk#`02>w`Y@k?w zbL~9ISi5mz{co}3@?`?V{#)#Lc#Qz@@-24laH9Ybu|PCJh@}F=EfxqoXq&%YfVkKK z(EuSf2@qW@5UB|9kO1MbK==^iIRWD9o9#;3CP3`E*{*N=R>h@Yd5Ho0`W!pyYIs8L z%L3jFbL{Z$sphb&1-!Ft@s{bZdj!1wZ1GO}ox{E_^2ZkM%{uJI0_^9r?eMlZ!(k5! zurJQG!`oknJtDxaoo%O2AT$8;U<$BT+R||0uev@FV7uAUa6*SYA;6xQWrw$MjKiK5 z`^PLhyghVSmvDOQXD9or3pZ3{sGhML&@(e zKzw@*Kzzm^4#e#b7l`$TJo0C8Wn6?^j<8m|hTIMkU@x)7YsjJq+unkgw$}o^PYx4c zf4SO>m%`o{vx6c2m(7xfNdh+MVqP~*dJ!v z@%Ok+!?gmeHq(y3X*G7RyKM26>v$Ilu=%!lQ)=vBZ?eTZP>0PDV29h{z2~eQtj`wj zO*-CtM46vqhj-UmJJ>g77=4}Cyuz2Q@Fo8U>p4f__M8WWJhvb=E3DBQ9v5Io+QJ&W z>{$V}ku9vzb6yr;zngA`rBeIC&m8tY0_>Z%u$TS9Vc!v86~*%V_v?5+ z6ksp1g&m~x_fr8j*%tPwj`yeldo0roOGl_RI^J&u*niu?cF^0RQh?31z*0L()A9Z) zz+P<&o2TQg6=3^h+O_ecI&89Jj5>}?m$8NMuQ>UQ{^Dpy@E3oT$dA`wW!z&UoN&2) zx_M_3;V$NjRJ$b>lT?wPPBd(53lj9-_MGmr1Wa0lNDlofAenm}B;RFPPFwlat{wFg z+2iKA=%xaA={=7Qwq?IC{1A#0*}hIQn&_dc_=e{+wXB1F`|1ijn&J*WWf z%sw_Fc{E3!F0JxbJqF5WB`%S1DG)SRPaFyES13m9X3ILU1$q?R)_Tpu%i(cOCoP)L6_&>lt627 zTM13YkuHB>f+@HqHrn6Pfd@CK2{aOJaUh1GJtGo`Dbx6uv^;p7$Y0$Xi_~@lN^dD$ z5KdYIU%XC^A{O@M>CX}2nWW1E_xh~Z?L+}&KS`{()C7IiVxgO_AwaxUU8d61Puio~ z|Aj-n9O@*PNzzU7?8rpH9ZrbHl6?gNxpBjuRl-3YID@K^V% zP?3^b3R7=vDGe~4zw*HOD|RHyspG@iaooZ))~?zCN43^xRbvfXvyXZ2By)m*y=;$v z6&y}U;wL{AS2{305FSe^4pahy=%)WkMHWP6&Vh14+kV5RhLO(jXa74}KUucQDjXi!B? zBrr-oW&tuFlA3lok++0_i4q2@ieV6CXu zM*~WLN3pPeeQAnpOj&bnli?OnkUtZ+SHYwZ37&hl1w9W zu}7HQ&?LBUQoZdQF!>n?O|pq@B_AHcDWLRY1Z9!hU;uq6PwKL6dhf^hn~xB>_hdjJC{Hr~)KBZfP$kZ!qhxg@8zoWEXA~KVe%VwS7^~2wOmpQH`X=r!iaF^P z2Nq<1+cY4jlAig*omenU2292J={($TeRKw(DyIyC(}P6fAZUKruU$)t#qfny+7!Mm zOehfcby;?E>1wl*s7()I@JB*bMc+FeRS_wlM?jH6DpH%H7rmO$t@G^qshxM}#nCoujC_c8HZk(WD z%k{T{0AjYt{rEQ}l6i2z2c$hP5tJ&X%e&q1snqzStnhFcI5wU;A2#ZoolZN?y#>#n{Mol6XpM(}w4gAxFMglcOjV>l;?Wh{(1%4Z#3fAd6Ow z#iK$#HGykS=mKmrX>mZMCQ=2BpsHm@*zMny`p(iaRHCU&rcW9N-FXnT-s)P?SvukJ z0G?0mNMK-vlEV!a#LEQ!?oC zSWTyv*)?e81TfH#I_MxAOjRE{LpLkGfcN;}PS)Y4VdzK<_9TDSHU!bifkW%j0GwQp zrTB8@;X^77)D-QDJ04Xs7)~T}owX@+T)ffOw`fUsnw~+@H;{CaCM`+hX(xTX;ZD|Q zD^vcYWPqDd2>F+FG4dyU3F+NE?CLV`s|29MRIRCj5R$vnWT*?ut8aUTCkF!nVCCK% z?_wv;sH&^t(>T;U`bRn%W#IW(IaV8LqZUqaNyp}tlalzB88_)a(I=@1eIN$;yR&#* zfG2tv{elxmlCG^3HeMqs-b-{!$=iFUfa|HDgy;^M|&Ubd{D!<>@uEHg}#qvMUqd;7*TJRecvM25Qj zmOw+`(;JR-PXf#Mc);q=C~rjP{=iK?3I^LOnZmbChb~eAmBy@C5yl=x{tr6{a1=$7 z28~HjhhVs!G|7DaK*|5@*93a+2gY_}{q>4Vf!RO(N;mm?jd2aX4#ck~*;l{iYqLwO zz7>BXVB4UReE-{*6J6|dVcm1WYzyxRJXKXK;u)xx_(9Rwp1$k8+D`3+#sZ;-s*Nx? zxW)YDskm`3U6F4LQsMewB04_4Fq%^uM-T3S;o`fWjBzC_*5j1U0y_|0fY8bgfrtX% z3aFL)3xy+1CrFo~=8*k;*VK`8%!d*L@zq#d`34Ss2jw#tn;bi@UyP$A3t|2(nr7NB zMzILAYKjZ*EfGH;vg_aj+hTvA3Dd)f>1QjZ{tVO4R!q$WOwkD}U^}XwD-`(`D|GY{ z#s|V(K=+HaoDufmYzovBlD#J^EYF-w zn^ydQ$VN;yn${DjDW zL_;wU+8x}}VQk=+O(Cw{Y5=t<{h19ebS}WO*F6)(DwVLYNFRgrFusQ;SjV3Eh;`M^ z7UG8M#7);mL;@`g^+y__wp5XC!yJ4_Di)$PFfIAUC}4yij))tHBWv)|=~uJ`+W5Ly z@uF#`Ht7#wzuFG_v>JWB0HlW=lZA)~IKa}lHgs_H;j=RB9ERrAdg3czRpEUb%(gzv zZ2N8`jjQFPNXQht8RJ%;(KNu!H0<7f;V>fM@g1s^LZ;J=MxyZdQST!UG?qBiBP?#1 zP?6LTVDh=4r#1Y^GMLdB1QDpl)-N0Ii%R4F76%SOJ@A~G5;#al&AkilM$OT~Vbr|s z7@yo9xmqsuy?Hb`?XT(1_%I&7;r^0T4;lL|f6r&i5 zHHac)Lh;Ef28wHkQ?C%-yO@)U1~5A!E$NB};eUl9H?wjqGr*)gBq->!~%XL@C_!-cpEljs37DEEg(ku~=vfs*?Rkh46$kd= zs1zv0CI8HQ3TkPulE3zMaVj|KG@T00CpFM|73(ntSzA`pw)|ReOFBb2g?H7 zbrbn|Af~mq`2@NY{vW`;h29EiKd%+;60v|fU;F3<)cMqy6>!H;!5@U~l19Whaj=L% z3E0kZRtqVZNd7wER)Ff^fwXL=wirbrEjtlx89F0anTT1d6xnYr?&(q15eu);4HFwP z4QBf*lZpdrw9UQjh^#-7nZ|FPU4SH9V}p1yM;s)bq4#1UK8Z?x-U_e`wjHD$B{EgT z!&+#vRMKwa7tVIPf1w^6L5zmwbU?fNzq1s0WF%!=?r>($q*)=}U=TM9XC^hOw5=Sz zg%y6oNMm?$8IuAk^Y6cC()oemdayP=3UH$W)-W#OHY6OCuA*rj@CnkIBipONOJ5af zy#5Z$t2 zFPN%6d6>28o?-v}s(Wi=5O3mJ3!&fg)@x_|@;+~6uRvLL72XXtQ;|Db;U6B(tK3$v z4Neh{{ET@zQ;F|r{@8~ij62a!Q?EP+grC@N*bj=lp6x05S3htYt^6thcbX)WQj1xU zA;p-C9RMhnW}FyUtDi1_^ClcNWPzo-X>QR^7liINU?JJ8bpX}~VF#d}8I>6k9i zJH{5av%Y(=s{q^dJb3*(4A^L&`tgnwV0YNU2J}6lR||E!*A_NI-`R9lkcS1fuzhrS z*dUw-7}}?Pd1(270h@Xr*w4N*V88ASu&Hc!)e{$j%wPQtmHA05HH@iqSSoT6Mq5n1 zj-`%dsdF%O0ZZM&`qIUidJCpfLm@9O55jqT{CmuF*UgrFv@05S-8|1z^t*0?JVn3j z=I=bkeAi7LPyVa#x7gF<3d&F)_die=e2og#vNY5s=NZ}u22TTa9S5ZDVU!{ zpMDcA?Xfp}0xvSGw;-5DsKWylp@(gm@L_+9vw}MmqQ6MzVc8RdZCF+;#bMbXJC?=e zl9e&LwXuvbEOh<#X0Fu72y5N9+t%j5Ags;9c<)#l#vs@Cv0!&*J5fCPP88A?*Hvr= zkE{oyUdI91=RZHlItKDzC@uXjEiL0OEzN`qb(mLx@?R_24msd%|059=oj#yruHM8x6@@o_WSQpc*vkC5I^~mmL;K!PGxR%FBI3 zQl97|k^+{4L5keRLP}@#*z0(=%Pneu44oHz#AY`hFDj(P#SE zNKvVUsLB}8FE}Pr-wm&qU4Z(aiz2`C9dc^F|L{ka$E@M+6Y)hd@uOZs|k;f{bA$q@ov0rH9-oOvD^`p9h zAA~H|_cpFfF(aD@G)_j3N<6NBFQ7hV)TQv3Uaq+KbOuHbOLcDe9%yQ?$8qoqwzsEz z6DcH`R~Lu5(H9|((B%<+hc>+?&Cg6v=0x>K#!U@M;48>@v=3#On6wQ!I2Wd2;6_21f>S`KL>J`M$B`6XY@00+GS3Ox82 zozqV`SR|u`T<*@rYur}7Wy(3fCy0#>cqlf?^AbxC^KRf;TJKE-Uea zmReM_v|`0d6)`GcLL-TqYE)FJQKF5y>!uoM#4QcUf6keid*|-Gdy^RYzW+YY`#ibX zduPsk{pQTfnKLtIl)caBobLwb0b%9dgz^*TGIJG%M^#~X3@QvyG8G292LtruXi3zG zH+Vwd#{01gN3+UhquE4tG&`LQT{2z_HeAisKs8srSPuFYza?Y3uj@7FTaW3}49#+i zi@5h47q8R!-9MoFWOr0&lvCDQFp6z6#P|wWd|2@`)3}$mDMW9K$9aU^ec$8AbxqLY zjB*Dal=-#f;4sx4xJOpEDk|P4*xqb|4suOC3$xUps?2I}fXW5GZ^3UFrY88k3cqbJ zTdX$;FZ1ouYb)SNku_H`s`36A+*gQqQOmz|@h)m<)5W`}<+w3k=0Dos3*Crcv>$GI zL+5kdfepyM&1Bw$`>HANT@1AaBhi>cJ`BB=oeX%^>u!L_7y1>pEpsJd3WSBkM%Qhe zs`?34{7;NTk)7>3h(wO&*m>9oVPJ%Po5QTD3b}~&0;%P^6`)Xj-6`6-D0n;d-cz|Z zYxzC&-r1+HMb4Jtbwu#?Ee|WVT&bgI8_6PYKv!%?{_sdGdA~9Fiji9Keq-{FkA!h0 zeZJ0d!$_{H)UK34k5_+y{&QeL`A4!X{;U&dP&)yq+6g$-PM`s{ag$EKsqX|D^xEs` zUxR#;VH5zI$8fVjSoz}~D(A4q4z+~D9;!Qhc^A`SXTg#T?h=5TOW$M~Ll@gn7k&^4 zSrcEACG4rwI)?Q}g@(*k7UuUBVtt*=`Y7xpiwE08t}5$PZWRMPdz{@U57#lZXqE8QSMl~p%}`!T z)zS!>d%SF#@Xj+Y=?+-5s57k=b*9y#9y1>d12o2@MWTz&OQ`thg zM?eo(8gfiT{fS@2`S!CE@*BIGl`iEZ-OjU&llbn9Z_CvUAb-|)`-wq#E*0(K7as%F`(p?sH~w5qon!6a|X z+7V&pCoI#dp`I&*ynmU=a|mrvHT)c?xN`s=F`;cJ~FVgxl2S?JTjsRU`|y4b7IF|{8qN6pU8<^)d;dw3*|jJANa0a(^w2p zBXF?}(J5?VX$~vF*VuuF3vlK*#X4!KJ;8E0NmB@2nW?-XlYWOCKh>i1V{|_2=)U~H z7~SV>T67)iUGnuRm)mQoUc)y^i-4uHkzE_-4D|>5yNMB1ey??T)JbnH*P~g{IODOOUtz9Va1%qjU_r698T=|+>;FAX@6u4Pe z;AXhw3%hqR4$~he{c-U=Uk^vvUk@9=LjL9?sN6o7?d@QBAFAfPpDynyk@bThYfWwb z(I!k24z1f^*X@>#e-B3}nxs2VVz-%nKgh23?>Az<*%U1V8KNLl-S-XWuGm05ULB}M z;6ObRH^!`|mS47^JU}`|;SQlugWxP4 zmSr>y!~Pc5Lk9uF3!O}$SwAq@h5$1`n$M=4iL+^AaF-g~WeD!l2X9iU^7-DfBA0p% z@;qcEI(#_SGRF&Gjqc7ts+UZ1M_jmf4)=gQi;WR*+Df-~T)3du;qQR~qm7Iq)%2Kq ztwisxyfe#?B%}DF5K>5$Uz)(EN(3sGNYyNE>}jH{hwZtc!z9*bQzqYYq(U9BggO!` zjF>e-Ugn9{o9Ht4tE8D* z%W}kK)rh%zA1LXZnsxJadb7a5|5R>So^CPE>XoXHgeO8J|H?SVvZ{{$2>tf(G1tc5^qOO-YL^- zV=aUzSrDQcnL7PBI+Ze%HIs|kOri2Z54#jy$B8u=(8B_&0AfI$zp^!~KI>hyJ@>fR z_p$zLGjpQ^)!Cp{XMIxu2>8g30VVakv|UX_Ied)`Ik5`=^6e z6=`8&f|?7knf+p4;Z&#AKk&Pu`icsLA%BZgJvDG`_`#3ZZtbLJ94=4S&u=C~{&pzd z4t*%xss8jI`J{l|ss7$PVgG+mrpTr&=Rpo@iiDi3JM^fW=21D#W8gG?lh3ca$vwyc zH}vZ&_mDtGU3?2$UzMMb7Z0Zy_~~kTT34tIuH~ussIrHDAm=mQg+JE!#C2fMk}1tp z{sClXGO6`PbcQsfwiNi`2dY&5bw9QpkoGLFa~Ch7j)avfuA@EB-F@t|$6!4+8YH;7 zk9xUwbT`hT{;`m&Cy-!bACLiCq&1g@c!T=zaoV5&JhXJ-eUHyQ;Z+NJ+ygqwDPCQK z5f7ciXx(kGTRj)y9~f4iS*xQj_tstuh|;YA`|B{SpY==~Pb^J3Gd8cLNT$x5rLlAFqBw zJ_fOm>`%#ui+!vkA7wOsI%PuFDZPLn-rCJ(Ql~1jGGq%iol{lQk$BvKmYCaii=hph z;52ZD#aH5XFrBIGJTFlbHy(x)JC{uRS8AEf!%m@j@M`!2OqJ+Cqbe-tDG|j5dA4m= zmDV|G#ST_0me23|u6qM27tAh+n!wZFi?>HS>6+fssn=V6zpeXvOR?Hp^t&E*z6;B& zUp~*LP>b5xxxeJ+%inJ`oG;IbEPj`_nXvLJUWq>bu1-KF?)P6GR_@zPXSxqR&-Qy< z8de@)ZujhFZs#z0r;^Su)u{C9i;TYX$OHF|UB#mH&}4!on|KD35WS!RbzRLL%m67v9Z6QO0F)8tRI4Rd>pDp3achaBC!xp7A~&Ao7i7 z-#I-Xs4FoSPm%7JLNblzqPj;%xp>nl=0cb@s~3Z9N)s2G{&0|HMNM=<8ZY{}iOJ}9 zS=``M7g&VyHXF*Th4?H@6M$HV0g-*FVs_~GC9J$GeG4?bO%j{P!>Jr64uw;<@)7L8 zOSKW~8;RaENsULmhkWP`9HgP;HK#ymzcZ}-={I>b+aRg_R|sTZl=>T28waW(E#x*FG?uEu4=1}d|fJh49He|D@V z`JWq0A^-EsSo&NADbdy(-5|3vO#rsh4E?$JCp{rMK=Z{@Cj?8GqG5LjPN&E^dZ8t!NALtMiZab4iSQ+Ia2AQPZz-rvk@+J6 z^B+X!-(u4-KeT_1`Ry9>yHAp~r%2L%@_14u=_vWFXYVKCxlf3zD1|u-;@9s9xMoH6ikS6-?dSPf@F+ zE!4V~M>P+5%}>o#n~xr~;ZDT3!*^Pk>f44(RIHlV21Y$GJ~Bz__Y|Eh;WwOq4$}_e zt9F~j`ikb`HoO{k^>K94oA9~{b9ei8Z4duYZ4dvcv2j$i@=(#r6U(9}#7?K;|0sI$ zb~u%e2lIHS82I8wnqMy|if-?UD97c9avV!+AWdu_Nz8-s(z9p%%5&kzRh};;o3UM8 zr`s%+CaQ)`QEM(sHx2B~!d_2$-*l9oHA@cX{)I0zqmh}2pohpDQy62eOOtx zjTPD%riLr{$%dzn*`#PSj1NT-*&SFfiTkxs_<89FsmllV+=pVfEBxT+x%h@^IVW2_ zy5g9P-L5Rl6?DJ$0b?+FNnV;Sl-6YaJCo_CFU1tAAQaRXepQ6X-wYyNR-wAW zwTT^jp2_bksJ>5|1x5Y|1;x;7-UE1Ra6_X|@T1K(yx!GYD5Pcv;6=g~IUQ;E0x2EdMl*h1Pi@@U(b9-C9`J&5zZOT)_V!jvPQ z{x@XhU@R}J{GK^&W=WN2no%c)bDo^^PE@FdPBKR_#?Yxh5gpTnKlB$4(c|Bd~ZL zky(0$5&g<}L|Xe94rVFQm!Sta9|VnvtheomaUUZ?H``r6C~PM7rw$Q{*h+|P)$KF` zq-R@ei!C%u+ZLO}iou;djBHj!Sanp3>kRoL%(%Rv*w<452_?EGP}AK^-tV}UH-Sc> z2~0i8nm}Vx2KOALO&~eNH{aW6`@)UDcGRr%ZE2BSuMjC}-Pp&d6eUPZgJ^%=BbFIn z_xinfkZ0Xn@Y_4Q?%jJ)bGGh7^2?W-Bk%l^_MV4Ha$0xz=a=a^F{!fisQpX54r#R5 z^v;n)9n!za*Tr(zpZ@Ik^dAvcd`?9AVdc$7(ZIh4g%uoO7Zadjvs9mK^a1XSTn0X8 zsXqDWgM-)v@EN4~2%^tm;*)UZb;?U18DIa1dg}>W7i^&w^xP3atc|#Uc-^#z*@}uz zJ+4Lhb$OhRI;VeUV{-BfC3Fzp7z4T9rKZQ_RXjRV7gn+!PLYJmKtKUqs|~SS4cwct z>uHJcyCPIivaetUy3GZUQ#G9@|HZ2R zo6NsX=U=4yzs3A_>ii#2{oi5!W&ea6e$>WuSl-4d4l;^ckz&0{(bmc-K4cU#lTv)j zD8?Ygtw9mRRU@h2mtO&jslTBp!Y_zNn;LP?3f9g8fHU3(bwX48T%of7*eG-8 zE0tO>b@TQS^-5Aj6TEhGK-(A@C$irnF&>p!=qdnP_ z_SLUAt+Pa1KW166!`33*s<8t?EoIPC}5 zX|xxb(ti6or@c$SY_KWq-70N{kcV#?&1ju(aoQmQ?S50*b5z=q0`0F%Y3Hf5;|1EE z>uK4A(q{=2=YFfFpqEmgCs1T2P|&NdFA{6~8=z?WZ3^}S=SGZu#FMO#*#CFvBidL` z3%{IVHXEdN(zrI$-s6+EwccaS7n%>Q#J&ja>rtU5KmWT%p>as4&a71xB+yb6N4~>p zPrF>B&FO&lGl4_mpPS_&Po-TT;8tr&+w)yc8(yK&ZZ)NSN~L{Sp!I!jmfzPu;k5S% z`JItKOM9($mGwh!KjyUS1loL4+Mx|*Jw}Ep?LQjKdW=Ye8SUpP?X)c#?Vbif;=5`tUWbzM|2ca zrbW)RD{&(8i*-fL_B2Ua3CCP2BdPA)6|Ra{A-J}yt}bwGh)qS;lelYNaBYr_M%Q-x z${y&N>59Wrxie6)iaHE)wGu(NjVF!3O^zIX27v4TKnmTG=MH>CSK-&L^e_Q)1SvL} zUgkMG4JG2KAZpSRNHEU(xE?qYJzKS;2*Wf}~MFogxHrUevZfIEePt8-jhEKjgl zwn8jH9t$aQ0gDB%HG&K8s+F&%QgDI?lrN{U7`K8-Z&o834b?^b6pQ#Gh&Y!=Oi;Tq zA!2-QU@@zS^c54C1c?+j628m`&1x{F^&Cs<<0g_;6_xkNJfid^KC7@uEm%}9BpCdG z)~T5K-s3#=OqzN|2v6H2WMVJN#9Wez*fdgF;DD6_*&em9@&@z&3HYCkGuk>#Ox-oD z)oI@*XnB)4b6+)=%uzyKk9&81d#U$3_wK^)aQh!MwB_FI{VqHMlpk^p)tFT_IMeHH z3VOex8pRbUQH9KvfKI8f$zIP*)!w7z`$g|Jgy={VY><&9X0Vd`EK32OX71xj*L+fSK2E_WP4G#tnYe#VFYr<-WRmjHm=iuA zXXN+jO_Rv)J=fw5ZRCs0{?vRwq~*JX=lg3iX(QiXSAK2}z7s0+$ZxoJlXm-V@SV!f zGMPtsxePu>s88kRp7eKAADhm{Dfrk0pY%7BujPq#t0CK8(`>h3w!dcCZlT$}j%OS8 zcbB{@V}6ek2yhENS8xKJ`Piry2yV!dm_2j=As#ZAIBnc))iNp0#(sIO`j5 z2cwN7Jo#*?{LCJFD>Ta^zeT=|DwQ%gybTWj3bJbqQBU9^k_?e0brrUsc0;8$j(mZv zIeP?Wo965kobB9MaC0y>mpl#_ zti%S3(I{2ftV>gxKr4*;br%Jhgl)r4CyiaHXUD2+o4CJdJeCikaq(Uc`}MS7<$K41 zb=*vbtm&X$PnyUv66rDQ2XWVA#Kz-;%R%-1=dqiVqGJ-bTMmO5={ei55g#Mot!TcA ze6$i6+ISBXCk}BAxGRw}hQSK}9j3D7IB)=(S(wj-1vFuq+;{P*wz;K0?(!H<*aUi8{D zC}}x!`Wl=rL8sT4(}&=64m!QVoT|Vn2c14;PJafcJ|KS^L`u zZ2`>xdz)=)ZvV}~UJhZOO~w0tmb3DI4$I-qw&-Sb9>$zU0%ZW5#xbY9obuMxBw0L< zQAWtzFFFGQ&ty*TgVWjQRLGq6gVV`0F51gY&V;?piQkN5dwg)?Q8j)MSqE=zmK6^me@jZC*rDK@Ye+Fg8 z8%onw)RLLsB!A25Ct!Cg$vYz5%scIhP+>C>#58FQuHoWIA0)Ha=pUfR@HRUzkVEM7 z(7`VBbz-#G#i>wH0RWaTjwm^m=qsm{Sut&2)5$JvXqhJ0Yy6YhfQ}VSNx5uAT}jFEOVZz^T&SrRe_3`5(dt zx)!#9g^hkn!d~39u>WFVpMkKwx)#=T2Mv2Wgnh@>xv(llgMp%fQZ(o~ya6l`7bLRe zKa|L5mdKNzkVMk{Ly1geiA;k;o{+l)bpZ>T3Sn>RTG;DY*f;9cJq9PfaJREW7C|Df zq4@Ai=5!@EJ%>)4nA22ndJLU@$DGE2(*|_f!JG!wgL>`-_SKVo#cmBNud?rde@wow z3oEtkI|kpEg_VQs`(yY%FRawF?>FK5w6O98`+fz!hl%|UEy+Ka|8GCWP9OGa-Sf^- zSPXlhC-n(`Y!j@~&<%(o$#D8W+dXj2QT6nDcI5EGuyF`4 zEz{>5_HcK;Zl5p2IhXzBTjV9Anz!|~hP9g#y)2Hef{KLw0U~rXY-xEs3LKE1vEkyxfX|PAieazm4vc5_8w0Zw9RpGhB!c z2K`&2^JJa&F;Y8OMeW4*vFmPYQS!`f7A zd^sFAkDXEu%J$evLl2Fe~z*gZaX)z zmwf?=&Q**%znBFaOyG6SRu#l^k0hiw1#d4nmE>?FbkBI12vxV>p=F#T!KLf;I)&GnN`GBbEosY<^j}en7`c#8 zU=C`E;hZh$3j;pba2zN&4oZ307uQIC8t6|wioO1W;lPsP>egA@xtju98T3|Ed-upl zT24dkqCY$7&kov~dhn?3cJe|^s(I7{)#MFt35!hql3AB_P_qui!;!@T;z)|Z> z8{YkGLmxELU%#Hx<&vC6?<6c9^^t!eEa@aHAtAbZJ$DMPl7oBU1uKYjW!tHkpA}Xn z-Nokk9{w)V3sK_Xi+)hzcevNTvs##l->5qm*n5p6!84To^7j+*h;eNujr%OS-v8mz zl2mblB%SG`4|jjm-Tk!1`2HuJeS|k|Tuk4;O8K8n8=k-aRFdY;<9w?$z$#72P*T;= zZ#hE-1IyYuzfJ+e1cbrz0(`e8UeN3bqArsYtm#2{8s5Q~daA3Kt7{Bgoyy|#1iuSm za-g*hPaq>rg9PVXh)-x%gXRT$nj4gⅆ(5?^(~B59*x{qOBMnrcv+N2oq)x>;Mhc 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%0A7S~{SD_XNeQDJ{2DIBf4CX1dqoTef$q>>E#$F= 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 diff --git a/plugins/ruby/linux32/.gitignore b/plugins/ruby/linux32/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/linux32/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/linux64/.gitignore b/plugins/ruby/linux64/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/linux64/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/osx32/.gitignore b/plugins/ruby/osx32/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/osx32/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/osx64/.gitignore b/plugins/ruby/osx64/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/osx64/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/win32/.gitignore b/plugins/ruby/win32/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/win32/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/win64/.gitignore b/plugins/ruby/win64/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/win64/.gitignore @@ -0,0 +1 @@ +libruby* From 7abb82b6d10ff7b5698e1dfa313e03ddf0d5fbf6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 26 Sep 2016 17:36:57 -0400 Subject: [PATCH 0226/1012] Switch to downloading 32-bit libruby on Linux and Windows --- plugins/ruby/CMakeLists.txt | 26 ++++++++++++++++++++------ plugins/ruby/libruby187.linux.tar.gz | Bin 382504 -> 0 bytes plugins/ruby/msvcrtruby187.tar.gz | Bin 387577 -> 0 bytes 3 files changed, 20 insertions(+), 6 deletions(-) delete mode 100644 plugins/ruby/libruby187.linux.tar.gz delete mode 100644 plugins/ruby/msvcrtruby187.tar.gz diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index ac5b4a959..1377155ec 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -14,15 +14,29 @@ IF (APPLE) ELSEIF(UNIX) SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) SET(RUBYLIB_INSTALL_NAME "libruby.so") - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.linux.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - FILE(RENAME ${CMAKE_CURRENT_BINARY_DIR}/libruby1.8.so.1.8.7 ${CMAKE_CURRENT_BINARY_DIR}/libruby.so) + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + MESSAGE("No ruby lib for 64-bit Linux yet") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux32-libruby187.so.gz" + "gz" + ${RUBYLIB}.gz + "2d06f5069ff07ea934ecd40db55a4ac5" + ${RUBYLIB} + "b00d8d7086cb39f6fde793f9d89cb2d7") + ENDIF() ELSE() SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) SET(RUBYLIB_INSTALL_NAME "libruby.dll") - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - FILE(RENAME ${CMAKE_CURRENT_BINARY_DIR}/msvcrt-ruby18.dll ${CMAKE_CURRENT_BINARY_DIR}/libruby.dll) + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + MESSAGE("No ruby lib for 64-bit Windows yet") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" + "gz" + ${RUBYLIB}.gz + "ffc0f1b5b33748e2a36128e90c97f6b2" + ${RUBYLIB} + "482c1c418f4ee1a5f04203eee1cda0ef") + ENDIF() ENDIF() IF (APPLE OR UNIX) diff --git a/plugins/ruby/libruby187.linux.tar.gz b/plugins/ruby/libruby187.linux.tar.gz deleted file mode 100644 index e6e9e86ab39cb8a94305358e29c47983bb536ec9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 382504 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�`(Kx zrytAgH@{rbxNFq&g{xR+6F2N}w14mV#6Z{EQqOKd7q?#qN_uq7Gx#5b;&MmwE_;bq z%~AgTCFT=kVz^tmQ3Xxm$TDa8JY}d{ibX;j$Wf|x0 zJ{IyEc0JD0`*cQy?^+S+@-H%13kI%(1Og@D0lY+=-Qa2{gY9oRLGlDX+~;qHSLHqt zV~mUV%8fX4*Dh&vtgQ5lqzw9$LFg_ACle25r;U(jvuI!aR1CSP)r8Sy86Y)2FZNRA zsWH=J!GjA=M)e>eBIP3`H&_x_ZK=>UhQ)4}xttRZo?Pt>Kl06&4Ij~2QLU2{R59gV zPlLQSIe-9PJ>QP6762ZK> za?Z_xmn0Q5pYA9a9Y-+tGOX3Anc+4%*SG0@vbl64GM|9+q@F62?snFYWy03MYVb^| zF7a7V!Fmc$JQ1dfMzECdP7Es*Wkfbrzxid68KybKDamDnJ{jY$sO(WK29ECKf}%Vf ziqV%ML~%5-=kvIDN=U+zK740&!UXJORXK_+_m4s{Z zn@pj%ah{G2XYr8sK|n&wV!28r8|9LfA5mA?D2_lDTYnvIv; z^4d8QA#Vo7wvVM}Pz!`Y;Oa|E@`BgXo1A^UL1El-F$OY$zwzf731gV)Ckp4S!l)Vr z@860TAK$)3YI?L-h;-$Zv>yHtQScX-UPNQMb{!o_rq$4@g;6_Sf!QdTVFnTbXQ%~j$V)_ZteydDQC?O z${oaJq>0vumPy<_3DrY!{wVbIWH%SRp~)|fO<(eRou1H(JLD=K-{H?2h(<6jm=YbH zaCnmXsVp4(`NE#}mr|PuIC&WzZschzj}F@UekXnhC2GIxOZ_HNRzENQ`V(54c*Q3z zqM;R$^8ExJO3}nd9AqfOJB_F_xgC}1+9(6N@Q@74h_Q)>mrF=%@9Ow>uV54MEprneHz_-oxRK^bFDp@lk)5h;Zq}#Y_6vxP)I7w#a9ncz@qBbX0Dc+#_2WvILtq$Bk}Zxg!Z2FSUC3`cBAN?6k{O z-uB4PHda*c&q;I_w8V2I7TOaS8>ck=EE5@}t*nLA?}7%lJ^k7>Iz+GGNUC~AQ9{u9 za`9tMyx<_Hnif7lL_{@vGsY0n3!h+g4K`FBSde2zlU3>GW0>|D%cn=wTm7hgT# zeM|7Oe)MKYCEegJaxI2WOX5P3Z!SL2P?nLUyQ)mS!?;& zXLfcA`UN&`QUfOXHrNT=NT0G3xD?3^+&EvYp+obvClg32Cn2LLr3%w5|v~Bt5 zsjg9pzp+NeKG$iR?q!iRh=sX+KCIQ>BJ7ilJtmAMa@^Gtd@xoT(_j6 zILDpbQ)78$o@qXfyblxc8LzJ9P;gn*d%+s*I!>Bo&U5|o%L1?N6QjzTBOj-??N-8a zshZ!1L_AGoKl#!0OG*S~>%lT6U~sRxrmLSw)NX0>(Ah=;)w8i9yw)Nc_mr?M-X>p! zEx4LokQq1NaoM)>?#D#NqPHpdk6d(qC?>JeyvTZIbtnBH(_RQVJUZ@iw3im%KuG=c z*J&n+q|7IcMnSbr}6?~%Vu66qK+>56f+oG+UPT#fW>lxq$>&~B^I(xEHm2>!wNbI9|< zU6Vrlyeu)eve)Xpgs^xd>;c|Khy(>2D7Zf}VuOz>`ALa_ftS@rXn(RxyF6C2X`CKlR6uqnDsG>--J#f=l zs#;*2&h@q&3lr@?b#TJ=bCycN{L!*XW;y~5J)!8TnpbZ%cKmiCLrqK$%JZ%U6#win zuGiN1w!=|Fd+k7jg4B=Ah}ZC|ExDeH&-G|UJn|EUsltL+xd%roqX zgi*?*GSjEH64k4n0<5YkN3VK<`Sw~E-Ymx-j&wdOV#^*Tf!yj2oAKKe+K$`f=pa}K zw@;_Nz;CPXl|e`}c~c5pIAp$p=_5cp&!dmXcuwtm&;X6nyp3mZfTEuF6dYL=)k5_E1a45ApOME z_rx>`)x^Xcm9+Hj_7LPu4|xnYGwUheXL%|pa>TiCG#Yv{N>edfU&LwJEEneBxi(=Zvm#d?+a;tyeM} z{L~}~IiSjBgXPEP-SIQYvXlO-%xGk8(#P~>x}Q2ZKC9rG#+~#jKQ)cL>Bzk(^@NMV z<1D+Xsk<`wWdc6QkcV2K!`B8rwJh>OAMc`Uh}s3o=}XS#>qMV9e#=Pc8KJHWH;?*E zv!AAsLf=oQ>9?z+JQg-i%eHnaJ1Qk@J5mG%uL!6k^3(5+xy(gjI&CuR^Y&g)2uI_C zHs0*gC1DPxvqy?Id;)_ES)a~WroNhg3+$7VjcfeMI}*P&P(@H6gXNyYO-|)9}() zmegs)S^+KDJnJZ#aEL08tcrTe=^>A9ve17vgbCE%u zsq>Wch6{VC6lvb+GHR-TGWaIym5u%=a<3(eS77i;@^jcCoEYZ*H1O5PTYU!+q2|JNW+^ zuUwItXmoGv+=`7o?kPTdkXxm*(mh1<_%k~s3g^ly`EA1Z#3+UV6xXS@ z$?e6T?1?iKUIfKi%009;x?aS|qC~Y}NzBQGGFJ<4f7lHBgj-!u@aAQr#G4$ND&cwZ z5|W2x`@bT2K5s$GL~pAJEDGTqrY7;PN4&xJ(=?)k?EM;`vx+?*57+CyIV=9Vf7J@l z-m5_DbpWKPY(-z2GAb=tDlvbdTetJQf8_e?7avo@Gl~y)a|djZ@AQ8PsfApE$Nk%oq+3Sq!Z?&JIhA*4chSaL zyt&Ib;=fiGgp+jS#bu}j?BIPjdTyqqwNyW6YiKZlJi{3cjl5KvPA@Po#n^tw$Mm=6 zTmkoe5m~E9IZr`^;eb40vdp{h?>_Vj%>8CnNIAWxY_NymY<%N?q7nJ~O)j6AR<%I; z?MoUqgtEap$$c5BcA*PwZWdpSoDI5rG|%l+UUy2#=a;|I8KazP{@J$MFqMm{3>IJ^ z9~~x->DPJU!_xQor8CAeCCTBlXoBe-FF}?wzBEpliTtcC>;V5axzU9?Uxgpt%9r2P zlr#mRxuH9Gco^C5TO%YlkKzwh3}4_@j3Enc4cnT7)VzuCy0Lap;trMM&QSib5ycDj95KEb%x)N?#>ql*%J9k9Sz%l1XI5+ zp)TGog7SeYBavlCnRZK}gJ}QQ?I+jyi$7>3dEoRgay`$=e|N5G{-IFHrRfu9mxMIb z;c^K59_kb2qf!}9>)dG0oyvD}AJe7BiJf~@6Jl<`h=N}g&&0mxniu*)Cz2F)wNVgZ zJY)POx`i`}LOOKXH}%_C>;r9PUa1Kj`?8JxZU0A43Ci92=A=n?Y#Bs!jLhEAES4{%27e?$Z&FqsM9-29CCZ%G%8ztcrU+SV3FQoP4(I z3?anhW!_#B29|ZHzdv~Vuzsf6^joM>-g!jFW8rQyZwMng11i34co8*8*Yrsu3b$P# zhKm`~H5QQgkr?B0)8nc~4^qL4@O7T_Ott6D>(eGV(ql8`(+TNY=1?NbvPVVIinN^` z&cZyG&4rP%p}!{i-|b%E9nH8oUT`3B%fBerIE8!mTGU~640jVORmW9#>+90&)uevm zwDb&y-^u=Fn-Qew38x#Vk_w)gn{qz7%jBn@)RB8S6f%fN5-}8$a+_}EiWSs5XP!RRVu%zmPaZdL(vr-OMb`@W?*;@Ur zi*Sg?URMFzSi%m)f)|49)LZg< z`%c%>x4An#d)BG5ZTlkf=LAsX)U9>U0!A}bH3}t8dIh!-!FNA6oD8dj<#dyeJ9h=q6BIo_OSn* zROdAW3xluSw`4m)Y(T{}M#Nq(7$LYAW9`#Ak=^eC^HS{s?b7VTL40)?4eVrN7Q7LT z&{eU_UQgW4M~El}5yys1JA%$+=%j?@$h(9EX~Yl4-}ZN~aaV)JiI?B^%UHOJplA3h z*7A8awV~=?GkXVGBL}w2a#!x8n@Z&okfX5hvukE)`J2M0$^qIs)=R2j& zhX^&s*n6^(c4TVwop=aq1TMl3(GBGj6LORdUATrAV@wgx?DS*>s1GuV*f%6CJVMYy z%_J-bJrz535I-2)of@L-XgdR-+Hv}Saw`RD zA*tu^Ey#`@Va0e~Y@`1jIbcZ*BblRY!U8`+4r&D@Ven@pWI!;|NoIEZf0O0}?tI2} zTpYO`z})^?=v~Kv2m=8vkAT2sm;B;O`I?Tn!lV3`ZmB$Q_4;8S3{bQ ziGbUv{aYOi7nnwLWW&caB^@4HVwDihD?63CQk;muwDaM;&#kXXyAJoB2DG)e&3cd< z`u_f$DJSVPEVnn3PfIuRaI`_AOvv1I)Qzs9-Z@0$wb0bVQM#xMx^6DKq#_L~IvLC?+G!zL#alIymwC5=#=)&$z8S|C8e8j9vz~qc#o2flD<>hpD#=|<1rdSr|5EP zl_;`FIKy!?kB-HCbd-+8Ve~n6ZJH>9nm?nB3{2@BGy5g1UfqLf;z+iLRQO>C052))=DR-~k zAFZPku6Lf!KcI8_;9RC5Rb|qqQ6rq;0Q{l5>-|Av^=|FweDSEtI_E}>C2VMFG%SQm zNoxn{QFo$NEWnHl?1rIq!ZoPd*j=acU4@%w!PXV;z_NANM+!7v3%i&DQ#a`*2(PZa zFP_X-6UuNKrKB6G2CLBB@qVunB_!XOUn9I$kK)AI^>Fm<;at0qYb$06`6;)(e>^4l zMtYtw{*2%z{%IrX0a8Jtb|M$^dHieO{rk?YDr}z4>&i0sT-H_CJ|m?AU#uVpR6K@| z(!uEIJd;3?{&DG$7yO$<@*=P@yqWBhiD2tjd2aX@7KNtdOoQW0%A!N#Oe$Ob$P8tV z!SM{0c3)@V+(ecPqEpwf4{YAiYOVx|?570#gV2hsUy`*2xp4})p0%?fSsB#cW`!$w zt(Z`bC@*Z zB!XFmT)y@>QlG2>Xa6_U4G-lBzB0aF0d*rrnWURjqTIuzN%xf)r=Z9JGB;k-mSpYs z+$Z;)p9Br}BhPdBOylp?zu!Mn>o5T=7Rq_Q>DEhnG&2W zx(lfoLfYqU$<>-H>n@X@4no~Xo#Xr1t_kfzE@ag#$fv;Fwa?=ND!t+7-(PUTkt6rA zaaw!xx>amwPeN|2B3)F}(x;@6@FiBOyr*OpxV7zSu9sS6V6J!pa%!%Gt=D0$xHBnv z>A5*k=ikZF%>|2pl(2BKrJF0942;JH8w??9w_e8fAv1FwG|;(}@k%HXuuF|Zoe=T@ ze2@e;eaM8b>{l^Pc_If{rY<9Ihe=>=+awS0!t4oadl! z94IkhfC?if*AT?WC+q`FtuV}j(7#44DhMsFWM?tTokBJ+Uu45wBd#4$+i2h=|RK?l*l=WLRTp#h=VKz;~Xx$k_CJJNRy2@nQR zs_=H!&*lBSK)05-L*66$N&qn5U8MUwH#*eCJG?zy40{geX6KJa*CDbJFof5o+z|E+1 z^BbsqXa$aUMl>8blRkA1sS6f78U)Vtj~51IZ;js%M6<(Q6ZvO@Ws~$aMUamrrIeg~ z5YqJffNtd8QQ$!anXe$f2D?b0RjMd2Mtnh{y?X&JX29!*L@hD0P6B;$q$f<~|Gtl{7PdTcu8s+D zr#lHT058_~r*!+wjF|Fz6dZXAJOPoFqV^8?mi*alUkTO9IHiI#1u&hEZStd(88KPT z5a8nhb}NdLki&uTmfW0s&UbS~fqNI@d87nZg1eA_&v~SfiwNLFCnLzV*OIl&NR9L< z1|%1V5fyko3p{U_yhF|eUQMcCp9ydy4lqGRzLqgp5YWc(zDtctuUu_&S{!UIWS0Gu z&ASK~sO0E^i-eA1-Y`yaA|Gd&Gom*1grTQqz(R|A=OL^V7bDN_p&^Qf%j8XSc@=hN zfC8e@x_=h+82{Nlx>D(PZfumDYTW z${ghdd6U5k9LY3_$qfM*)@x(vntOu6Eb6F_Z`4zJ)A`0+4wws?q^P~&$HwZd;H!L@ z_RQ(BbKx<4u0c5q(I(FeLT3-gN${imRUxxHd-BdjeYE@DEv+KnWV0`6PPu;<(l=Qz z4{UW?vro0Vg(S)`@PSziNTE4jxkksDPrnej%INv>gpr3%}}*Iuex^A*f_ z_Ux7Tttu-%$Rdr(+yL;p09M_PvUX~guNx}ooBd7ckARig2dIOQZnjI^XYsjblC^|J z)fgrfLki5RO`%5wiu{iVS~bBMxWEc88zoc?*(N*6Wpq8aGjrBumsy+1EC`O zE+i|pgf=iTKpuBL1nMS%G6jUW7G`?sH2(N)Zb;V)2<@2IZWV2 z4Y*9z&azMP>nN89JOTq;vjfq)!Qsn-ipcl?y#^H4KLIFNYhTVUU{?hQ)Be5dk}uJm zyAT#3^o`afYJlF6M*<+qIfU}6%Om?4($xF*VBvS`Dt410reN*}eKXlLEg($_U}LR_c1Yw= zuu2uEB-T9sEc?RglPaPhf35jixT%4-KD-|EJZ3<~^_#+L~cbtd=td_|I(BUMt+C5-|%i3s1?LGK1+y%M3*@iq& zON?X|$aP(yu9_eislc5wu$eUgl^bg~y%Nhl40wyJV`W{H_*nv{Y}X8dud-kU4j5Qm z{Y0c^pl+K88nFkm*2A=71YCV;2-}{5nSoq`!fK3i70CbD{09yyPT}X+9!e#LuAOWjTe|8WOwl0I0nDTJm>@Fxc2@5c6x>K{ zncSSiR}tj_K(yvLWJATIPhCeU0QZW*FCYTw3r?gn!jy2$)5Q*{(2eGr1Fj4MielM( z@@j`XLl8V%sOKi^Yy`4}3+$r=eA*Xjp@%kNWiI~#ZLStDa2OO3|N76E+!p{KLvSO~ z&YFCRAH>56nDlXRa%YRIf^mN}<15}2NNa!PbAKc}5}>O-Miz`#J(ovL0t;uk_TV1p zR^vF%9ssm%Y?$Q(T_?7l+Jn^^8K+$d41z!6h03(gPrXo!Ym*T4n$@BlPM!1|Mn4=Hf^v$zwGx2|BN89`v2#LhZU zoHO$M=O9uFSm64{OO>}kZ*v1E{zct4crFl_)h7UAdXN{wAZn+XU`GWk(XowQFqlyx z0TNCCFog)L78Z`rH>{rX80=sLcBnN5!P5rYi-K@zVja-wQF4A(m_mBm2p)|B)HW!R zX4+T7?hjDL&zib}VHc5FYx-3&`m!FxnnrLw+hY>pY2Hd;%F{2^JZ ziA-Od3_+`IC?U+e_1C8Ww+B0(Rgl}MJ;Azny$ZH6QY8gNx zKo4l6F5s3Z08_(~{2FYTe9CMCjvNDn2aYc5$|K&`Ln9rAZ9f3G5dpSI1(ES@2Byp! z2phD4d0Cs_1jGjTLIzG9H#l}7R>FW**v?ET8N?hk+yvf*eL0%~drHo)frETl4YB~l z$p`2c^&M{s?$-maJ_3Tl0s9pN4%wV@pu!~kM}SnbKrr_yfw|5albdIW+Aw}2Qa0b}KjfrD7F#-2K6gur^sdTt^p`5lZh zX&4=j8zA%)7AHf%tQ&&6PH14r-o5jh=fHy;knb}^56V5;`Z1R91DFDG>HwH4p z1uj~_lWa~dT*@`w82pQ{KDKdDG#lzISX>j_7)k|ALIQ|5`0=V-)@l%5kkA!L;ARql zj~;L};MUmv5O?0On`fG3z3&ivk98C6MU~~>xVxIS4P#G=^ju9FF z1nkkX0zqW5GHtLJ9Ko38T3j>OQche(>Dz1$g`b zq&&8$LqOsM$iaRPHmQMBhl4GjM53L}0PR??)KCOautfrb<~}%6Spr`au?F!yldtnd z{sMjuKeAnV_00r)LhCLYW`HyCB$#{p3P?&jEeJEV3xI$Jo?vTv8}LF2z&pha46k_L zpbTe6D>)Z{90YX29|E*!(gLtN1QmM@G+0ak{3Zi%LuUu00+9!QTaf||whw@nu3)G-h~0RAbY*&)0%}#_4mt}{cCF^-%BC~H&7K1RvhRD;l#X0 zWA~@6kM6HV`SH~5G5*$HXrKP37!2plQ1}TSnI8{hQ{r2Ts0rNZRnPRhEQU_x=j(A8 zeW7kk?HaLTh`Bt^AZ~s2XsK8?Kbk@5#4CeaJkIZ$C+_w`d+q~6LJ`iMetV^CJOdhv zB^U-e&%F42{(HywuD$_s1eLXY%kAI2urZ<-hP!7wG>`M^@ENM^oDKFViAU~=@_>JE z33OP_VbC};ON1|`cewT|iI*AUvq zbLxUMv?llxm|6YB5O}=&7<{LIna~}qXFD`!fNsaP);v+@Q1Xi2g;ercYn#UMRgw^~2k~ZX3g3HP}wkaB#U-$&>MfPt$DgF9ud4?HM4} zm#jgQM`2kz+ zMTQNC-&No+`QvjKI(5O~+OGytKe^mua~L=r9tkYZjO0u!aZ04|4AX5d@HOkTo2$)~4=cML)N7X7g( z$QWT!h{uLj{|^d7UaE-_VUq=me=;{o595@)B z$8_!xv`QIAD1wu%4>eiHfm*6xCv3Rf5Xey4se~^ZCd=k0?I|s895Dwtl#O$p{0XqyW7$+M1%{|Wh8}u3!=v48GTNzc5wVR4~*mCiYI(ME0^GMfFCZqX0PCjjU8UQ zhES8AReZ$E#+V%dGIIb zx!53cVfTNHrT(-Y79Ton{9j|oXaC@3ioIrggPp2?of?P5gQ0Z#Zx*;5)hA-ApX5*SOnqiBrM1f>N$H)YrS1uS{f9>&%>bpd zu|Sai0pd?|ss8|hrTDZeR%jBj<7a=avBI+$@@FbGF?McYF>w$$dcB zgj8lshwPOEkO;Q`~!j*?LQ!NdjA6g6!s|&NAr{J zbzDEzcbd?#Pgpe~iG|Ng04~Ei+mp_z4e?{+;r3~)=n0S(yxZp3fiA4(&k_B3?m2CA1U;?{a8Ls4RT$RcmL%XiX5qN#{45y0$N&__uqARA z5=&iGcpfwuF$rYeHDdBMx;<00Fc3c|G(=&+M^eV=hO;87JzJe3UGxJ!$;@%t`{BPO7Wk9 z@N1O~&(h#JjqDXaVR1bbY<=GGoM)bwuunsvt2_3>weab2MtBh=sDIR`umDczN#=E9 z`{DdU`?Q3ADnj8~IEUx@DgfLOWMg3={3;+hAv2IBF~y5AKoPxpKy`+~Vyqmsku+qU zQ+HlF_^U>+KXn13UsUBLd5OP((m=&I5F>zKNy(#h>f9So*7Ar`BVW=$IdyGcCZ8*Dg=tbCMv7Bd>G(R6zX( zroR=T^I*FQlvl~1Nzf@YaH2twk45Gmft;A^`dgJPY2m`4{NezuvTqQ{at6A;c!KPkX*gBHXt#9FrnL^77L3IH@14#*6c z1d3}%O~fG3A!UEKr{|*cm22TDH z1JBKKfWaIf=M>JiA=GT-Z$(%*Jqg1}r6wmBCbZUtnjw|&WHqld%R(BW87lMOLfh9D zM>`0B^$P;0e4wj{!xh03VMnZ^a&R-%GRd!Bk$ufj`R1Rh?E1^*^7JXue{@EOvR^hp z42y7kL8oeIh;i99yiV;0f{YMc``ey+>R-?TkrMI5=7h-sz-ST26J!ZueeJ)j%5hkx z{*P4zX>8;5-%t@clmAwPg(+-B&;l4Vtc#E8LCxm=1>x*32+>A?@Jpbtll3I?WE5A$ z|3{28i#wDU8^Zmdo|K-r;;IldBj~PAI%Axr_{a+~T z|BC|h|3cv(Nzz0i|3x9x-n|{og@Il3)FFCOO+G+RSFN#+SWuYfn!2y1L$?XhGYNU z@zwvosY`vid%=6c=ZjYlI+B-%phbc!02HJz0l6Lj=d5t~;@`^f7Uv%ngh-Ej@kY2< zNiA`%zh=-SK=*^gjzeHnAW3+&_xwNgS(oSqvyryn|Hh%Zb{<=yMgF5g_uLNo+o|69 zw=@W89REGErSP8rXNh(w`Goxw(tot7y7d2VKzuZx!bBbz78y!mcroU?D_b{ERiTH^ z2CaXvX9v+`I==kc*BkF=8%Sx+D~B3h;60qY!qFfxN}u{-<~ySmV+tmA+T?pY<>{yR z$2@oWIVNYN@$loa}Q}E)$d2ITyVE-)+5xl73#PZ8hIDomGuA)gtw6*Cxn( z>H)EEYGs`96A_@9d7j-}#ciF#c>QGL8xiX^gSkbrB+9L9m+(w$)wR*u^>D`*q%oC)tksWC0=Q;SC0QcWp)1B_opS_FNQ#oiIJDnAgGN!THIJhx`NZjSWy0~)!HAL@VWPgH(A^5`jl4m;1xJ^t0o-MlwA zJWTUr_e_{`-i;+IOLz9UBe}U>E#(yN&4Mboo3Vw(Tb|$&zno zV(48t)>ZsHP83S*3A=nF5z?0a@2KY(j+-Mc+*kr{#~W@guF;g%5iWPtGPy$qtx zncH`Y8f``82lJ_JO6w<7oKy98%=i0J-E{l?EyP&scKfd=MDupD+MSPEm~SC#3rIXP zF`N|W;ztf zH}@Lb$Z$>|e)*O@<0~q9|MZ*>7Gv<1Nvf^Lctm{SeZOyMFWrIhBGpaDWAo_bE3u`# z4r+qG`kh|Qv-I&NVRD7rGj~`_BaQuUN~M&1jjo0TObF8o@{|yYPoT;+jUD_yyJFnm zOu<95TrA^CO4D7`1WZ)oO6EP7I5L@ZCPGRAO2)Mha9}3htgl^k>W#1S8{psdN6ncXi!a@HfpQ6kl_nMsGzh34^ZIj;xYW|Mnk1KvorU(i3x(|L^c z`E~FS9q#P>L(Z2h>Y30l6X)z?^0$P++7E(HRdsYG+2TqQzW6sSH!er+Q^8e)rGJ_? zcWFI)IMgrXj&fSbw2@m8G>ot`(X13R(QA z7skC1=c%2#(|qnc+R z&1Gn0!}yv1!?Pbomd^0r>eGd}86D=%7U|Emd#5d0EV6+m9|$&$AG4m}AQd}|VGA?@iqVp1Sz~-ZmzpiJu zQ*YK4E`7^>?EAw1nOyqh=TP4Wu4mhtBMl+Lpde&~aJTGc0*wGc*^M z{*yaZDsk*_qX)#%;hPlGoI^~ZSQU|0T*YYJjElDjY)H0pcG6q-t++|2m!jZa|8TAG z*5qSj%$G{DrIcY-|F4>bQ!Di_;kctD7daS9>kk8;d-{eY=X!M0#Jk-D^CdCnfJ_ zrZnqNS|?Do<6FuH9LLw2@UF>POJ{}YJCt6PnJTev$uGL$l~+3XP+=m%?9dq_Hi5hj z`0#AQCg(iyi^1EeZ(MP&(_Qvu6(+7It{lk1+zj59u8CJYP0$%hJ+ay#Q>>+Y@zD8M z+ipP5w#S_m5|fy=i&zDxRyn@=Cx(M%d0zRL)z&dkYw8F z?M9M6lpT!!?!NOW`etRz+84T3`7d6CvjR2~xH*1on6MR~cpWULmbPek-hHqMz7!^+i$y*8f z3|P#YpFcIT`do%~QbeCvANliZ)eh!l6Bo^_N?(-2s>dI+a*6qADO{P;Vq4Odd>^=E z@Ju^W??vXWQ}^72ZmiBq4OvEKdav^K!QDdEhm&O?7r(ywbH;TfoR?Ce*_g+e%Dr3{ zv+3kwlFQW1!g?Hq+HTG~-D8YNtLgs8vCZbCzfa&EkYMJq zHT-^DY3(^tH}?|_HMZ~g{IyI%yFbb>L%4!!H@>UB7!sGf=63UE)@@SLA)f0@x(ss2 zH7XhQVb6!>8mAu4@HR^C^wjv!nx8+kEsvzuyM?oVHe_(@H>$}SyBAqc+tM@_42P*& zuryWO5}i$H`TBdZdzNXnjNuF+N<#PMR*OEHq~+SWG&dhZLbveBBjahltAgKRrD9tW zR=(s6*hH5TQ)1{fy$Ws&Z%ZwoOh=)4**f;0ml|aBPTr$=%HyPV8ds&!Gxos$W`Mip zoZCIcd-oJeofMfP6?)FhgKe024m7kNnqKCnK2iOL-hG`u3Zl8nKf zD{x`lD)n0QuDzcv_>(DA*%ZG| zij=v1;aI*;PsaO;TQf*^&dvQk89aqq-TLWf)QX0$NGAAsVbn;OO)dW#YpPi@fB1@! zyY!a!7xTD{aY6i}0k(b9)_46ceeTgnM>u+XKKfKVLKCkwV<9#l`Fhf6O+%#CDPF;^ zbZwq*fFV&Ob)(9qm-Tz=k=yMhx8?XVuG_VZTaV+_-g5WPluQ;T@U$fdH`ntmIl2d! zHaChARnC_6ypk1Q(c4G9W7&*9{!|llcQYY|zrd8M@1eQ8u{3J@@f>|XEgzFSQZ1|( z-Rv+$efoq__w<2}4#u=a_d?-&!>xVMN0x_4rH4z<3# z>g|>swT9hEY^lG!S+}2K`fMh_r7BCYe)3UG@1guo+M)ToGkBd@75s488aA{_T|Bc@ zHtv^D8<1T0D)O+gK#-B0R?z{Se?w}xX=SPL^TJmFS{u@)XsRd1(`IaBFFr0laS#($ z%{@?^a2GixUrBI#hZH$2WL2mkQbUExoAF;P+$hp&fFg{}6?^*nPvzCC+d{NbiSj-Bd_k}7?=E9xFA%IgMi zsA@_|ElY9w)>MIk6(j2_n=^D@RWf4nE#qJcnrdreDkif~ZV&x_R8wl2vnD~GXg{}6 z=#9jKCmA=n%AJv&fj-97USmS+XdUEGpU;}KH2D= zBh&DndzwF()w1WFPee|JG_5n%lVOyuZ!o_|&oSq)kg1i*B$2tMgw~(gCY@a5Ryr!0 z^}5xlGS{1TiMIsV$vnXo2Jp;Q`*%F8JaZH# zrg^T+1uj?=(re$XQ`@bvXgW&xfu_*jGMjk zLFU!o%Fi+o3b8u0S@*V@{_b_j+>~C!F8l0OR&JuI90VZ=q&io@f-Q7M>hzl6AYE$N z-3F5(|9LcvHW;dGHHx(WGgV4WzH_Yo4RcBw(Pe(tf)X3v5E+YRxY-pQ6U*J1*ZQk0 ztZUVU#yUNZRfghD$J=?;3%*3hex@43oa=CD{ZHBRE^{@riy*rK$}v!#(tPu^W5z$z z-ugyLzpt;c{3FXB8D`tlV}^^0H%C@0H}M~zD38QU^Nz@#`P`2GIseu4>>q{p&yeywT8Km#at-EQm2`W{FDgGtFw$-}Bhk;62hq#E`g* zhQzg^Xv8);GrTi+ZK%UBCl-*On|rWhj9X;jJed4+sZv~hcZv(Mskk8$zyEc~!clww zE8(4i*U|ap*S~%&a(z|l>yC`za&d&k!nQtL-k?5B1X4jQUrGlJN$H^Hu;4I1&At{1 z?)rrMzb+CifhxJp9H`g|{|i-I;1%hJb)8o&L&Fo=BY(@a^_YrF4f>dKZ>Zf7`lvHh z?aT`{Ecg`e^G~bL_R6|ty`wUSIZsm4ckj0O(rwKr-a^U2tGbu^tGjbM`Mny3Rygc~ z&9F(*%KSL_h^=-)Gp|tI`h3J}yJdFYi^(AZIX>S!rhd7di3!qHF}r<6L&cVO%@;pL z6Rq707esvdz=9Mb7aq^Zuj*2IUgqJEQfHZpS?!dylD(O1W-Hf!4_*J=)XB%sr^Dau znq|!eRU9z6UHR8_izht%kurm-Rn8wV=e@DyGuC)n@NQQy)*dV(sdW2CN=VG_?8%;0 z?O{IKjK)~`n$=$d5B#^rm?Qp%SZ|S?rPt=RB|TQXE&DCUmS?JA`n*Q|J_6&Twx*dE z)_2F)Ddyd8GW}>8)M85yNH%eNsQl}@GXBMfY?#2O$#wYdm;d}h`ALSh=oow9XHQeJ=D@Nk4qS}GJOf7!pSS)@( zxt{zg88}ybI;nK1#%Wyb%4;n>7rq_#4Fos7f-jS=TXx#e+<0355X>zIJ@}7SeMQN( zeaQLm6_M8@RCJtFbAa`|YNrvp|+% zYwVC!*QdYT#CbXIKG))ZVc4a|=foaBm`SGGeS7KuRajH5H`Ja%J@58}aYLo3l)$;s zl~DOome;CoY7GkZIm(KPrZTQNR6|QVf_sJa_WS{!@P{frzk~ZkM?}p0W-4XX-_0UQ zUEdm*6FTB#j}CY-Lns@k)tuW3H|ltE${1XBWI&)49@>N*N5ncZCOKu+nJu%9_^r+N z_s*Q|((O#nlT4DYu-`x#Z-BXEODS-`X=v;g0V1{#3KH3GMvzm(_k9pDQ)axRqqebqA@SBQK>pO6m6E z2+MxEjQO1Xa=QKY^8?aJ+V$!{p_=QK{or7yEtyM@V|(wwpl&Uw=8s9PQ4?LEb}ev{ z=MQnurg+pDp{vti#Ty{aWKFD?|41plLa+Ic4^WAxHZc?;KF3t!K9^Ww6LUM0-Z`-y z?b3&LROeRbJ>V+6Sb1DZ4(6v^#uVEi_`dvQN44dMok0__g18WQeX`pD70MDJoVp2+>tXpugmbI$81cLDo2$p!j{fj!VSQ z6IS=g+RieeB-Tbl!|Zhlr9tjS@{%TTeTKC8S~7!!4*6PGn6zZel76@`HBGU^U><1a z;IF!`DKR`XYuf%58)XWFc<&yx%N-jrX}TkAp2)MBrr2}I^q995QOy-k;%|j5kvh-v z2VrKl=_OUknKEDH?JBv|D6A6W144emScxTxKyfUux}->kgRxlJ=w(R=TXPEc_yd=YCP>x~~;_&v(##`!(#2Xqs{mwodQp%DJ~3?2P*M!^(!efQmXT zEeQ!sWKiMLIP^h|-g=C`A^s!dRR@*QR(in5Fo&@CUN81n%e;N0A+1;`HQhw^uohzw z?UKQ^S@PxIuqnm3-aGyVr})k{yT(s<1yAU%azo2{aN_4|>+H1VlP=fUT)7*SD0@LF zA@yR=%v|)MD8w40_c>Q(N?rOuspcU>FiG-bXn$-*sq0V;LKA@-Rfr7MAJww(14%M*J{&|T9eKYyA9+1yu4)BYY zko+s_Kh^wj7b^YEKf1-UqM@N`kAd6T?MJVN4E_EcTT`dm$Kp^v&U`|Wk8!=I#?srm z+ZXq_L^s)k7!Li^S16KDyHl?T>=mz32L7%z3xw_5QO-o7s$P@PW;uPaH!Ks`!pFv! z+^osra^+w8T+tHPq2>V@0V1f}ouaT$R`bA^CydEneaxewt&Z`mzZ8FS zG5>7(7jngJ^Yso-xKbK)N4xH;HuJEi_E`JG?)N7t)xKh^> zekp_45%Yw1AaSrh>U@bB;t=;6>{K?A{9l1v_tomHj(DE0!V|>>utrJ6sFM0nrx+R0 z3}~Nvd@}4G_@o)S4k3%p@{bx^XT}7OrSgI^D8AxaXu{Fx@`bfE4orA8zKvWJDXGtG ztMSDtt1rb2D!2U7!dgAJO{8a(Y}12PM%F(NtyEE-;?27ARU=rH7pyOxYApmgD)qv3 z@=>O5vmR`SPJ6{=6vEENSEXJfH&p3_2EN!B^KxjbQ_qodLvd%w*Wk!2X$bByeC0-Q z)hOS#XduI36mI6uppts@*y(jD1n@lek`&h(g%wff3(+jQe`yZ!eF6iKct_}SN9Nd& z>CD?6_$=g$OWosnB^x~}F5>cz2zPF3+Twngt!Tn}bNI^?VZ3WSMOQz9x|)1JTmOi;ZUU`?{MiS;NG_pt>yar_jL=s-Rcc? z=P;z7Nli}vE8Ddu1HHm1*%;~ctl0OR*nV##Tqt#?+gSd*s`rQ;-I~%9oyfLjmt6LI zs_g&W8UOH?w9c5g1IsL-=Thnx*N-bTaHu*hUSW>jiII59-!Q)OWfBmt!u-igY7~A2 z<)vabUEybd^?SKB{8!pr}B%<|+h_9blp zbNStg@|}47GT+>HoIg7o(@0jixJkkPh=Ua{BRc~vp(7dM&mQTG`UQ5ukJ@2mgqp7k zwf7CygdT7?N-OQ|BC}P2)0(YxN*s5zUfNN#(yV_G*qd$RV8uXp@?PEQ0YP{wtx@z=XN5$sktX5AyNgVV=i)AJ;}+mPuMz` z5ADeCgoFG?Z%=qG;>E3qT>J>ST(xB%Ph^@PJGwC$i94O1@KxA1BKEH*d@27V8I9&Y zB%`yD5knxSAfrBZM*TZGQ&EMsQTAIaN><~;W~+xZuZ`bF%$ zGG}eFno7QV`em+8>(zl@h@X9yjMXzj?HPen5CzD}A225t7?2ucV^#lr!}36w=Mh!_IJwn$t`K? zA-Qq2qiI&VdCNQCaKf`(em5}>>(w%rL}G(2rMPo-SKoyH-nGEq%|9Ap@82+;%<`*0 ziDqriltv4lXDxINGfr-+cB;v(t=j8}{Eq){1czC6pS9^IbMZ_W03PeBKE}yq`?7;Q z#ZxA7r40KO#M==!Wy!gnAJOd*-(;fOn)B(jh)#PI)=8;1`EoS0Cnf!PDe3or1O4K* z`;=hrd6JtEZTCDh{5w0{zqfqP{sC*hQ17?F{nzXI)3fw7>aAu|2aKCW5!c;9LSH)U zCC1{WWN#6R{Kec{fw9)amCnYVS2}ONHXpsD(ebukTqQ0gHXcFNAn|WfRYxY zq*d70OAZ<(Eqbse>89P@B59Gwm~o*R8M^(%f* zilwzbRF!S>TjSeL&p!3Xi8-1oUycBAgYo{h?mjJ(#s#WG{-_qz3aU6uPXpk~C? zKplSmstTrw_2NspvR1s^ry?zBe?NYG-tM2()u{7}PpA6l3oqIIbNKi7PfMHW{CNJ; z_GTiH(4C@0LZ$BPnu!emmYxoyq)b_z$n-_AGixTgvo-F~NYB3eexDQzGHE}R}i3o z75~9`uVEuw^J*73#S!@2aUkk^%rd_bjVPrKXoSs*0zj{SYN6}E5 z{F-dG&VQnXW&FY9Y&X4cp-H~`gB1sx=(1;wRV%y8tZP3~X#Y&E|5kDR_YVwB;zVvW z`jt2yGGXtoWM~5a+*WD%cPdJ;&31S;;{=ZF?sYkSUTn3@K-9yt{4kuXBSy)#EwCq` zUnab8R4-m{u6rND=Vb&Mir41_>lg1citBUZM&V|w-*)*&TNfWN3bz>!W3nrBSo4Gp zL{^r+g`D5kYn40P?(jrj#9s>Ixpm_HCnwteO>mpxT+UFF?yL2LZ(tOr*7Ns6c^#f* zcn4{`pk)j2?E$?Nxs}xBwR<8z!Cwx+&n~QA{EA)@$2|7OKOmkIkF@E&3eWOGYG4_~ zn_)=@H;bShTg4xbI-__+%Tb-_+a}XRjjxuyDp;=jHVBt+NN+_(h4t9wQ99lz*^Ib% z1N(*ZW%dDK*!d4@TXxfn)(H)@2j!n)N}EH|Jl_3p-=zG>Zu=F^-Ae!ONfk&>02>F_vSkEkSc~<<8QM*Q^#v4<6hE@OO=ZRmaeazU0qx#6J|v_$}JPSxg$=Cu2UhjoYP~lEeQC-=}Z+FMOlj zEmQj&G04F?_%3enthf~cqJCCffyt#FiC=XdyT6z5-#FvHzf=1wOI-Ay#2Z*>?e|Fh zUa2tbNAN7yH7v0t&&T>oYw0Aa@RlyQVuQGR^AprZg%xhMqMd4In6)Z((iXL`-~%qM z2VYVgZL#)S^!eNW+Gp_CAMLPNYOg~o-j)9b_!-&7&%!@i`(YJ7r~F<8ju{3Sk=qvQ zc8=rMuXm;|ZLQg?Hc@?$wt$8Cjnf>n`}{>a4xL)yI&S}iRN~n!-2d=f8Gk{ynAE3< z{^%{%O4`oQ9Bj?`(h>X&9l4t&(rthAOy+dmZ`FQKF$PyPSnGk?q!VXe!&;5`9sagW z_f=u=|K=l=LUq;BdC}q)(VC)8OKOt?V)6^-l54P}d2=NGMz=-K{Br5;h(DIJq%TDq zs+jLKuxVd6d{t?Sg?GQk;+W&&=Z%tD1n|`IYgfq)$*k0QgKa&Qzb&o&hMvnW>9PFJ zXwz-8%a`Bm#kd~MOPKE0+5ewV@lk2d3GZ2_1zdVH7JBPTGjxvlkHn?KR`+dUpW$o| z8@)ks_S0w~I4i7Ap76igv+!7crlk1!+g~ky3b)z(xQCmAU+vbOzHP@SvhMukZ)X2b z@dNMf-kqqq`N&@ORde587{yonOXlTvCP#5SyuEY=Vwy6+rHyBD{+QTOIO0+2D!n@D zeBGYpd?_B_^<=%uvmt6j<9nP)f^UmWC1jl%;D59Bqt&MvI6hu!E_#jo{~x#dH;2Yg zo}jLUPHreIi555Kb|P#o{m8dX`frxue09Aec%@P7ws(=JHA^>a2I~Y1RxnRk%~}=O zpLoI#qkPqj>26eK@el|pOId@DS19&Nyo-M$mc%jL31RV%MHNXecwa`8m=?Kh*1kJO zYih5Jm8-qS{@My?_U6U!%kW4(liDwm|7TI$2s0^tHr4#at7wheA46*l$2_$X&*BO* z-n0ldI4pA6%e3ul;Z_a5{d@YyQ0CZ|IgU2JeL?wRXRlIvFB;OWQn7@4|H|nK|J?VO zHB(FHDa{iDLKvJ6`@rg?;_Y_Dz+L9<-*nTVGhXH-ws?D*j--8(if3Zeu^D~^^}2VJ zwZjO}O>^XbR1l}e%zT)8RymjY8q6`jRg-i)c#|C{`D&A@Z?IXcem55qyi(t2XlTSb zx9Dq99PU3y?V|bU;bS)pZL-X8%MIuhL+`_5&VN`Qnm7;dRiMK0l?<84sJ#&e|2t2! zhmDGc7G2M1sxvjrVSg^~cGAP%cX`^Cs>$DdzWYHC-uEJZ-_-5z$IdTPu%0}TmAM3! zQRc|G(zx7Bl5OoK2@Hl(8(6l_5jdg7hiCb;GiPrnnf6?P}?I@xMYPZfD^1sE;ej>z}gKmy{GT9iG2m z<8stMWY7PD{R*n&Iu|x@)TWw426%CE)R=;k&!U550xGOZ%@@?9V=AmT_eBW%ogZln zoQpMrGZEw-s()2>ctGGJJ2D$MDb(KA6Gq@p>?`aKeHPw%1dZZzqy8SXZ8aEb1^2SB)1a#z?SA8el33_c_Lu0K$WH8tY2c2S275SUr-r zD=+|cTlxodp*gX@$(Htgc>@oJ++dk+(CN}1($9S@9QuV^Fpzl;iGQ|?iO=j>H#)SH zn~B1BzuFii^7llJe%IL|5{L1@5Ae(1x9f#GRWNxY zHzb;KtL@Wq!e^1Ibc`|GmD?$HQ(=oW2UUK8^8+}GsxI6?Gno4C7~68}#yP?FxFP8r zdqdJtOO9EV9Nk85F<-eCsc%L%6At>=iUs)pe5{3Mt#1g|%bvjr2(TAHi=+Dbi`;>m zyAw$h&%FgV?UE~xkK5n$XZ&(z*LhF1|8FDr|E-|DkNOVwyfmxS@>TFadw)C$>rXxAm#1V1D}fgCQ)Xmy{y&hV;^*#>x@uMk@P0s`YkgZcgmj{?Wc774AEB zezD_|6)z(HKP^Z8PFe+I`Hc1H+ZKz(mST^3gsB%Sw=5Qy`AI{{xGIEwW!!|-GEexA zP)$2UyDfhR|EiN|sJKPnE_Oh2Ao>OEE^f{HslMCVp(R63?JIDX>xEtRii|8Z;Faa2 z7j83i;8YwD(@S*ui&#^R-r}$(I1Di^Uz@*PqKrc^FHz9qCm@R=wXeb26Q3S8%JZ@h4$c)BGMiFdo3fvf-HkP-&;8=|^ z(72!?J@bt+E5r@8;dC5<14;|WWls1LwnovWP^)O{1y??C+zL()`xhy)>VGi)xr`@T zLhaHI8$ICyhMR7pwrk97dsS_sX~xHxqNwfXE2kOH%v*vg6u)^X?9bu`v!cd!#%nxF zpFx_*uDl&0NKd2#x$F%5G&io7H0d?T>++72UI!lM}-2;f%#N-Q2QxuTjz@{x+4kwGHFP ze?a%>S|AZ0{6I7``l|G}C(J?gTPyy#E_EECyZu*A`_D-E$1_&a>6oXt=eF^nlX$i@ zz7{2=(t?=U7r=vM&F1aTQz_%P#L)4X*FzooXJ&54=Ap8Q|ukpA`?>oi09Ii&xxpUsM==lz}QpO4t_bTjxHvUn0k!8gYa zFY}2t)ZG829_Rdq1{n7a1nqV3uJY=hM|qp{RC&ErdFP|Y;KvNf*F!T+=T zKRw!W`8@y2!t?a5^S_U>@B+QvJmudw$b&uCBXZli!Y_|o29Am{vFkmvNCi6HPrJWj z@$dhad`@`uzu#ZM^&G34t@|75`RG!fsOain6RrM*sZF_C=w@Si1 zR5oYYiy6{l6V}=nJ0v$7%B;QVuP_VTX%OY-(sZN8PjY*rSS`SD&El$& zTf48uycI&WRcPK|qv4CA_Pr!^Ua+e4O6!cJ-mdS~wMTiv@&Oyu!jv5kt6hxmb#R{;%-3+We1pJ1sfLIEmOdKq_hd>YTd}~g^USS`csDWa zBUai!YHE9HLqcax4VC*cG8cHGSr6sy@`TP$+3hVlLVkG%798y2QtT_MLrj9flbF;f!ThmdG`bu+{qkczwsNJ);7oO_TqFG*jx*HiyOYLcAe0{o0pXg0K*|ooAEYJV4 zP7}lF=YLzx(WsUfpJ6N(RO>Z?8_dHMw0Oi!xKIBo^l_#b4ndc){euNK#4I!G;fK{G z!CRyXUYT|EiEe#5W_K|I+*aE2S?^=^vKRx(KFtoia?FS7RD5!$_?ZVnpJeLAEy;Dq z-Y@gp^{D?tq3-9G^+RUibDh>5JmWJ0r&xR9{b6g)+4O{(G=%{rhoy+B1Ls zU-oBAHJ9brgEQGKL<_A%TyeX>Z-xqu~ez6TmN+2 zzv2SyUyiy$b^+#i+^Q3#(NXa%^zLMBSN_&odT-EgaVG}TF14B^lIT#JbS_mp zz{LIzV<)P;P_^=?D2dCuPP6jJH23>44BL=s?)bjz8W#NyzuV=JF)dE=3jhJtX zq&w@yP5A4$_j#ZLMt@;rUJV$DE=P@>d3nC(l3rrzk31}6+fhHVR!s8o6J=6EPsrO5 z7|%mD&GPlg_K~&Lo-6rIK8ZS)Fiz+khkv0to6O;>X;$bgEV8q7n9@~TP_vdL^n2Z|SXb;K%aga&v;1Z3Eq)&JLV?RsgZs4f)q+OW@ZQND zw@hqVjdMDBtv%DE>f(doZ-6dlS#AUE-<&AuMEYY>Lo!)UsQV{W>cU3(An_2o7~Ja6 zBm_MkRQ15-46aPZy7{-*BI-8hJ%b)ItCWf|)aeLZ8|f^aYh0`%e*xc)4&2-_Rc{S# zb;_g?`qJU3w&TG!VFTa+D`;WGL`?s8YPC0AeR6!an3a!m+o(8M9i73ysm1T)pQ52@ zR~_HYAA7>C?+ojFZ1o?h|4QuWb*Md4-Gkr>FM@7r&+tSt(9?UbcLdJa(QZt4TEmSu z-weMOJ=xiB_`}y6<+w`RL;2#D!Upr{7=~bj zzSZcR+(x}vTrYn|Q4gt~jNnuEzbuI-V@>xYSPf{~Ry$&K-?x~7LRDFJ9CN=$xBcgtPeX*Q zzvliZnhU|<)+9L;@m{AVg5Y>tmDdybGymZT4nX@_C(|+)FOrfO2$0F+^hjOc0%M{( z>K+u$ADCCOXe=zE&mFKWdcO9>D(fATl^v*x+54!)aiRTFx}{R=i5uK$Hqzf#rfj5j zJ?{@quKKCTvMS%$*Uf#1{fKw;;VrPj*K4}QEHfAHa!^NgU9%`ZZ2iHuYWiJB4O)&(X2J8KJTco{y4#*8T5I?0m_0 zjZkZ6&nYUHV{$_@w4jGBdj`TVO0aMK0=;)#4_f5#qh0&q#k{=WNp!_i7osa>8$q!q zTWfp=tvTU)L+dSJXtY*FEU~rpw6;n|;7a{%Sd+m4p{ndOe+E%{-W`DZ^Niwyl4AH4 z6{E6}S^k_``lG>n171yEUl{&1Mv_a{941bIyrb zW!BQ43+_t3VJ~YqBS-D|>KZj$OUFAE>s*rA_if03U`4uGZMx_C)bGtJdbqzc?R<>e z{MGN!KC^@Nl^c<`w38={l`q?w4gB0N*cf@4o)zWFR#3;_#tb(%|HFl?_^_rLc zmiIpdaH5DkdZbnTGp(-mPlwgDcJdz%>E9<=deQD*>P+&nUTFHYR$T+`#WKu`8G)av z8~F74zz~^htPwtOMc0){Tu)0~bdqIVbkdl%LBYH`$h?&Qipe%;XBzw~y5Qi4Wv+fk z`E#D|TUPL?Nvtj4KUug@W~h9)MmJ#N-qPna_&tlxkLG9O9WLpk*N&`bn&1fAO40&d zwN&?5u;k!K#! zY*QYRX|hPgx48cWI7eZ}+n%N9TH;bruOzN!+^VMz(`#9Jx48Rf-`9Qi8r|bt>OCNY zOnN`T^Dp;WAroBDbf@Y$fs48h`21Lf>7AU7Wr*JTJbSwI?6mNP)zRvBY$$kFTeUan z6D1l8ysY|{Xn*^jDs|8Ab3g6A?V~3S&o;h~r@lry?>|RgZoc3|{=DMy^9)$Pd+a_J z^sJ=t`I+=Jp6~n+N5A&u4+#9r@P>H|ei1CSeJ!!T$ES!x$jxLPt7-Uq|ZhUzxa`_S2HXLav$<^Q;(0oP4C)drgTf zE52ErTc>}TyHoyl%XDL&+gRvTm!Gzkp2YfG;(j8u)sc4~*rsaT!~RO-XB4XTNUs~@yVP8k|wPn>^?>#LoP@-rS=@~Ds`Ih&2yk4n>{Q#jrP8carWkZa$a&oB3EkgC*veG3-yWN)>*WLPe`Tsb z=dr1}UQs}*`M(5@AC`8#=rXjX+w&y$%SwezxUstPd)}bB;Fq2D+az6O?N4!uvih+` zcOCcY!}e~eT4)~b>iQes+GvvYO>fUFOm*}vyFK^z*y^j9>a%WnrT@N=zbC)<_Zh!H zjW--uRk4x3{#spHinQFvw^0={Rzo z?kQE%T#l<$iKBXaS7sAaZChcd;)7quwM2}mnx3&!wf8vse#TVGQmy)3kEtdwl{;pq z>Uy~;J=Ga=yCR8m((aF$>^7&zpoOmexoDxoj>Q*H6{H^ z&|g+6b3(?UMq}u31J3CF^z(f7eK*fCiC;0o6TPVZ`;D3;^O`c?+dk!;KEcVW9;V`wL~ z3LJXhx-P_(8_(T|Pm|IL8@siFYJt~65X=zui|g2yQfZzDb-NFg_d8ZEXZ-J{!rx?f z=y1kjT%0G9(L5!Wv!YX$AC{Vr*F{%dXehSJY}tMwmN=G>ol zt^dfp1983b9U_GbPlCwA$x2(2k?*fS^92V{oNoC|Bx+kE%{`R+=!3`aFR{vhC5rN& z^{1E5?vnSN)m?f^E!`>@5nN8En0+bHmwMJ(tFKUNfEstNok zI$`P2+?|$vv(<6Pi>qzk4j$wgy4=p(xE{pW1GNu5*dnG))K7s)rrzmu*)jgENnXPKE_1v zDLX@-WFYc4ZRyc;`#N?vo6HK zols*lN>5gIVg(0Smss_#>2m^C!d&_-Zd|rI$%R_V6nlM;3qw7;Pf@JzkQ#hE>=wOd zy1t7WiwoT5e+;hw&grjJe$h)jhUl~$hi41Vz8Il3DneVh&EzEqZARfXoGZXJI!&dg zG_CG)AFCL-KvnsAvd%~wnJMM|^%^JR^Zttx1duOdSFBk7+ zuWx-MzSpA$|RDE-$gd|k3Hw{1^o*|i;=o|k?5ctE}-dBUEz8;zy%w>@u% zsvUbj`KbA+`TO_0z2}~m4~iT1+_R6{HiAui-``WW=ZigW>j(CJ{!#PZJx_U7;+BfE z=9hBK_;>1U5-C_(KX9uQ8J$*sbWbQI1z_h{-1BnbK0R3Vk`!L3_M0}5_w-AJN{M2 z)O~Ticr*5gN)P>k4xx>`OGb;Y%D5RLcBtA3qX%)<0KEVs7*BdseRJcXk_HXZ?P^1F z@lh2ImfpKiRCf1Xsh2bv54a3r#}sS5@g z9f$S8=Hx*7MZQL7$k*&dqMMA8Mkx-bZN`a;3T^ES?dU}IgY~!d9hHZN>D&A64*nBK z&qaP&%+Ild^)2<{euDhLh=b*YDv{yh&yOD-IOfOgn7)SS1ELGnwOEaa8Oe~!e?|0O zUj9c8Y=RvM8mQ)$o6;lP38QdIozur#IuF3H>KJ13fVoq6y6>ND)VERi;?7lkF#IU?OWF{6XpwA_ILWH<*K^x`W0-9&-< zyOM49pU%NLW3a&%d*Y-H`#kIp`vkWn^lZ7=WqKdx`R^b0*T!?Zv1ip*&!z*OEk&-p z1A)OZpxzdRvsJIPj^!rq|BmbE4Pdy)jZ^YlJuC7NBr0+s_%73kbV2qh@$RKtjlg00 zV)V+$PQJl|{Rz2kdS#6|*=!9zJ-AWyU9d_tV#!78m}lc$@BgS>LVZscl9{OUSp;kyo-&25oRNx^OLyEf>~GnR;=fXH#))c&8`)s)pm* zjh-mh{-XJ6hgPs9*$?Hm%qftZ|~QLp#JNyAoGfyp7Fv;X1T%t#n%~ zLzlGVl{A))(vRpJ0@=Sx8nFNRMz4$@!#z-UqoYaAIG^5#PlC0)KX9OZ$k{$WwbhZR zWiM*LjNnc+jWzZflfS`wVqvZ1k$kwPcwdRjoEYKxm)T-}EpSKL0;6zCe1<5{t3w@* z!0BRywJr80PhvgY4@T-Fo8Y-PB454uOT*{ht3l~3U!cy#r5#Y?ItS%>z&he2QoUKa zcWxb07S~57ibZAI?A436p~w=qC<45HP~7;@J&jH78qvf?$u`f+#rrmi>Md^R(zDnG z{BYnm(TN>K&>eMir0xx^5rwV$wnek5@%+(ZFKiKC!(K5UT#a?zl=K*vn2w=)wmCv2 z+d6|gL_v42ck!!T_eJ6TuQ1>Lx-;#3l{B2ql&uFB9KuBM3Ikk4UVWT?pEKAew@!55 zW0krXUFk-{_uNlX>^3VxT>rbHKX<_&(dM6MhYgc=rtbW>0%4s6`y99eD^%K zpP%^^tNg?P$g;Crb7$8%1e_A3xRTsFl)Uyg|{XxC|0lR9}L1kL)rU|{|&9+(n zwI$g8R}k8*g*vtQXV_V_-PzfB{5?K=1#Izx_l1F4NAjas@vG|or6dHRZ~D8R8=wUe8xg%@Ds^EHIh56k+`1NFt5k=BJA(S^80gU;p+W`9(?dj z>H}#f{NXqAx;2k={>1veeF?t*bf)x`txK>=s$k*3bUAhHPYiUmf4B1ho0!8hGwBb> z1R_4r-Fv=>iV1AnBr)US+)mmcI9dO>GY?0aKDwv*!>z$j(U)C}HD>H-7UOE~tXi{j zI((PCzS_XaFdcHNDBIijb$0Fvt&%M5zQ5;-WM%3+&4sMTX*0XsS97n}A2Qtk@T+6* zpTYVIpS;I_`MM0BHs4O?!oI9*p)=r-zEOH{Ze4PwfH_K2$%v^d(Nw}jqx zptp$bZddCki(J<4Rmy){9KtEZBbJ-F>uX+# zD81+6EgatuI1OrZqf1=0tw%la%@NFudsrEMi>ql5F z7e6cbBZJNsJlbF2)`FBrJEF^@{qO#|mM?1IIcKf@CxCf8$ zv_SXwfYR0zuU!mAa&AkzKMz;<6w=$9SN%XVh;sNj{ z_xnXQ;#E@9im)ob>;)TXp4(hm!u8)7xU{hJR5E($8w`mb)4pP?ZMw!4jt5SsDdmOf ze_-Shy*M7cME?2?PpT&C^g>?d()U(wJoUAh?|jLPr}F1ul`-G#*;L?4zCI`B*&?QQ zyqo`8VJ^nw0fiMczItVQsO=IpV$-r>1G;|bR=<20yycg21V#&Okn z$z~L*cCzd3rb9SMeA;o`_zmsH?7HZrK>xZ#w8M%6)mi$vE^&VA@#~{Y_HRImzOeYo z@SaXs1SuSu4)1BbWv4a;a&ZdlCNK87!-&cWi7g)(Di*zr{m#-8#oSKzF~&R?X$zhN zBdT)o0khIiiyUtj2E|uw$}3s#S+*h*#}npeBGeZ)t`iSp8?y3*&+ZMoJ&a<4O%U(6 zt6gIB=LOe`)$a+v-3vdjM||b6xoWL2HGwvKAvnUNF)=3#S7D7a*of65=i||QE;UP) z&k=7z|4JMxxFU|j$=)&j5P~q0*deMZy5K0*qhxwKH7#D`h~Ix04N&HZOvJ0D(jvvH zBJmn0ZkE^3Dq@&%R{(tKW}{@iV@GE3Dr;QlN>fPI^qceA@qT4jJ$cXWh5XK;_pNzICH~O_?Q)(X2-iw|IRK7dG+T!hfSP?Q;?Q7Y>r}2i!R&TdSSi&d!bYbYzxDM&6&RViN#g9 zoidB=Kz4mAg%N489R0BE+80WzG=+Ti4%WS}UKHS>S49Wm3BJ})tD|pw&`9u1y}@|a=LTZ_csqf9jT`slv<1qHmzcdwW@C|kiCbz|+$7>&h+ULX=M%{j?03(Ko8&K% zoq@iFOLVH(CjT)C%Y@97@`Tl5Nh|^=ZpPg$jpFb68jT#iUN2nc*;H6&6ff)Bs{582 zzNMbO)xY<-($n?u&NB1^p?jJg`i{^&jm}r8B1)P&gKxk@%oPpC2NB&+Y#E+SH@TJ; zxzZbLd#ENO;b8aSIg@*DrUr<#Q7HALKg1>S&D}iRxO7FtJuB>ffeL#5wDrAa75dzo z8#jU#;umK6noqGbo2{X=`uEg^)eA898lWwo2|e@TQfw5 zdlM-gzR(M0Sh5O^Km?tp#`!S&+VCSvE0lzd%Oodka=hRsIOy9%?<0t}UJJ~VZ=+L| z9L+^&NrYTjk3Lu}l2$XT;eg&~l+oFBzLM+Gi$p7a`*2?keJTSd3(|hawL5rFv%fCC z2iJ4N-P7dElgasM)F#;M*>s~zH0Zb+Tri$OC2lQ%S2D~ie%#5M#U;nE)g?kDGLMI! z$G|Ju2J`I7#M4LZ^&{*3VBFAah6B9+TEhDZBTU6~piD}@+LbvWn{3Nx$!S$iv z{)*?HZb+>U$tb`1MrE>kRuyz@rb&$5+wJ|%F8^{mv$z&n;A#e1$it@Fw}P#Acp^`7 z{iB(7RCt~`7No$ne0!n=p3Tkf@kMUWa)(N*?xMFZYiz(@&^ue8&wS0WAD8;x;g>3> z$8e=dvt0-f#K45(u7NqzEl^+FART}3KI;xCs}I*}rKj3*%6jOMtcUfiC4B$7KKP~R z(&C2F3D)*G&!&=h9F>mh26KtJ|1{(vgO(5{r_8eZcm)r0Fpp$ zznL!2x*w$E`H1A5*m{gS|NMiLJb#AYA3IK-gMXyt`HTM|&&Pks`!B9fm*<@~r^_># zF3-v7@;vQ0dG7hYlP8x3AkyG}73t4E=}Dv~iagKmL7ta=zdUo};hn*rh51Q0VV?4F zH(`!z#D2H`V>zDHO^%N1Q*zweY`;yCCdZGjOUdz%`2ES_`;0&y9#IYMs{*mk}eC0nVKaNyE z_yf_3NIm5=Xt2;U!jh)RuZyWk*Wk&zedh4OjD?5f&9&wI55WNrgz2ZLN?PBJ7{(d-A z%}Y@V*LeQkX}&($ZbKSv<jMp zR*8wz;DpmurB~+_uM$hA5J6jUamtMej~>NiWa8Q(bW`XRHr1(^RldJw7RTZ9O8$i# z;V}sx_rx7`?p9%D9CcV}9_2YP&n9Qa(yKecZ|U{wn`0|AyR@%nRYt6wfrbwoa{K8= z`X2Cv8_=qS8>POEyux_!k;I#-f0mW}RST5Qi6vgN<|i=ViIlU=H)IyZrAMKQ&U1|l zDuw<4?uW)RK{&TlxPklM@4yILr_XcAAC&B>ow?F}Yjv;r{8;&MWfK&{AR|XAHriOZ z3_PKiXB#W)@$Y^pv=2nLay4(H zzbbI8oj8-egX=wMxl72rL^ka$3+~nu_$F;bYNz4(( zQ@rZZ^>7ESMcOpDZEIQZf~Cdf=Ar9h+RHf8lDKy>R3q~Bv{jbrZwF8CY^rOUFxinE zOvo2yM@D(VI5%B(ZDTkUyp zpXqrr9*XypnS^=ec)~jnp4+lh@6hYdDP!Es6Zv5VTmz_mzRW481 zKnb7c@cxGJ#cogZekmv{pWN$E2S1)KgqKtA`Fne)Gu!jfL5BUHMf#Rqd8azes<%Vy znWshb$9lH-8Y(+`MQ?QIZ+DBvmYZMUV0U3THg#2~N4Wy0M(z8XLSH)6p+H=}-Psw$ z`fYAqjjw^rZ(K+4)#|l+@ivv-vuQxI)PXH9TXu-AG$?dL>T(ONN8DQG2`^`3iF%27 z!Z&58Yr8Nb&F@`S+IeRnKV0Vt=OMAkV|(xd^ZM%;yB|C!CL_VK>0~UfpQ19;cLz^c z@>!=8**0OJi0s2?sN^^HOPTDd|GZk}Sju;>XVZa0)oAnJNlT@*zn;ve$fdd4Cd`v> z-!D6IXW(BDcm&t<$u|@@=bZ||DT0mUXb#EQQ6JaC!8a3(7iK%b37d5oSgs58lT(6;6Xihn0X>k zJFMjCe@^@bJNu602vj%!eVUQFSJks+aC!#||3Q+n);yh-cmW&Lef1dBiMz1BLOS14 z=>s26V*gVEBh?2H`IbJ26D<>^UDyq;zy!M^H;x#YsGZ^%g2E+h@=6*Oi}qO6-xmwq zj-#9EZ>9T<;x)O_y*A5y7|nVVd)A8Ujf?e?b$W2E=kMJjp-ap=LAUa~6rduu;Y9kyJ=6RW;c=59D1_=1Q|(OY{Jkr_$b5!5m=DKf;_joc7a`r; zc=8nK=fSgay?=PM!+IjmvuV0RKO`eQbR;Lbz!7YcbV)qehHW;UOuwBpRw`x*XY_$Y?aT5@+Ha8M19vQp78m8a}@~@neP`t@}ArPd7)d<4#3to{W4M zexG8fmw2HYaV=!tE>C#7MD6d4@5eC!IzGu8XLxP@Je-hLRG zPityuzUzrB$7JGKvQG`bdu0&)EAhZltA56Jte18Di zTHtCi{kdl#;gh^xfd!XX-k}Cvbn=$6!W5>7X7m4={WA=ryjO`y?_Hw|s zm$yvswqAt$8SNQ(-w*lzcRpXra7M%)ad}p4wJ-d6>7ws@j~Z9|MzQ~!->=NK4>SJc z=`4+x!eb%jD5}3?GA7^Py>J`~++yxs4rA>BS6Tj8Eifv2%dpTVts@(v(}tfW|KM$O z4ZN$6_rXu3&sOamry+Ffrgdd)-Z!?`&#$8 zcISUK`5)gi|38`8GygCL@T8FW96w(*9@oFS#H{cne@Odb?zLEG4^9iUJ7a+_gz3+5 zi15=;d+#~1z=!z*0`I4eV%5`4yv>-7fZxE-Lbs#zHU!KVEC}?n<^gZldE*`J3$ygC zdaHSM18Rp~5++^--FoPK`RlAtQh(i9hyG#h5r1sEv_p*rmTu=c-ul#eYKx!UcY>d) zamVtr=9>RIegd}{Hz07R_?h`%`N4~m3kONfpZPdd^C41ma&y&tslPevtgwS(;jMSc z!Uq=Iq+kX2tAL+uY%uCWeh%i5AH3st4bJ(WBQ1R<9`PF*I?_89I2otm2U?H#^$QFO zwVx0;m-h*?qZJgSj6>?F9Y@fu`yph#Rau>BS5o|vVVi^IEw!nS((AL}4%Fwa|EKzt zU)5869w_R$J_%KynwlQ#^Vuxar(x{>OMS9?tk35++V#O1bo&^w`H`vWb9vA8xrOza z@c&ew*p)ri=QgW8tvt+UAB`_!bz05!IJ#Diiak}`nR-gEZE`B7WA)z%ZnNY2OV7+i zonx2Vw|-fV8y1B+oqfEj5b=Q>6&ZfJ$^IDe=lh390%XQWN z-?yXw^_TsBRe$e)t^dwKtNsqV{`tq&|Fo|9uls@2e-i6oSD0S^u`ouY^5?ShC#EXD z-hn6nrr-?hd7`GyhMGD96+9&$_l7Ga)h{qgG*uu6Pk7!Mv!%WI((kD^gJ!1|Viun6 z8du~B^b+&xUop{e<0rd=UmNqWqsUn3&8z$R*Uxudf-k|Me8! zNm&i1wu=Y0cUB{Q@ip$Ldq43}^HaeMzphTM@7k|68T_TTKo#5uE-$zg@4`WFrS9OZ zU~!DFm;)9^fyHz42k^Y_87Vf$fXzH%(-Vd{V(HrnN>1zlE5GMYN%6b*{zX3?ztFwt z7uLSE8}VkKdi-~3@dv!Hyb1A(FC8H)4KK2Sw#7|!@mh>Ayw^Po+gkH>1saWDi(V-{ z(FcZe%XsH2^9P4_1_z5=IYA_Fc371of8Jj~vu!%h2eUnEQS;^3;V4G!-kHPMGde{T zCoWEX#~DTiO}qK;Q_RCN(2V;oQNhjNoHn1^5uDFE$;L^0OJiOlAR};r{I_@Dys{EE z?xm3SEOw`~%_MaBo29uu;iyBs-&Nw~UDC?j7&ML-;D`@mZ@-rxRFm!&xuZzk=L$R` z<*C0`Ua#&O#fr1bxXK(*aBjo58DhTIYZQu@A2NK6p1em8%GPaaj2!{Dm=61+w2JWTBHRx>%lnAP^{y#TzL9S-_^G|(AOy0mRVS@ zJ6FJqN07a^KCj5_S&_@_99=EV>%7fh!ov)@n{Ht5c=Wns1@iszsK)8&AaCXNPme>D zO|*=_#Dj74L;F4?T>HAvZLZ@tkIFZp&PAT(XCOFYaA$e@j`oo?2m>FncX~Q-d3Ryl zOw_@4-G-xE(tO2DJn`biEewmgwj{0|pL!(BD2(G~a)((CJI~jMgCYf9BiLl6g6(oqx8q3E`55n0DOT5}tJ|k8f>$@) zowr+`=q)`v>gLi%G86Y)ZFST*d8rW=Ab0E2-HQ`A)=<)nSJiHDN1e6CjmX2gMvt46 z>c`Qu;q`KjJW!|JUl`$2yeyP1s+Vk(%6h^T3?->}c(T8xuWAI#*z9ZI-AXYkzfckQ zNGI;2kbbt%ZS>ReY&)lv!Ya@5AsGmsBY3otITSbRg)Mrp-dg`|#J&jZC@>a#GjH(f z9XOrgP@5rmGQ+d(ki*f2eMeS5b*Ljg+}`JFa&k*wbMPe|SZk5IlBdeJVpKV|&X7@A zYu^NgqaV`wN~-j+*xo3zzxph-^QbTmA!%5K3NiGI-ECeY#K7}dc+g2mz68&l>wR$k zC-754+OT9}X2~{bx4d?}q}j6^R}Z3Bl{D$waa3kk(v#Y!#5>K;lRhDpC~V5aet~X0 zwc78hh2y~;`T--@(08{df~_tx!$fesPGDGcQYU25N9OnAPLAvngf>3Y|=$*$li<9ZqcXB+pWi~$+~Uq9=4 zqSEu5tz<`dV_yjT=(XB6Xs=`*~xxIsIntf4IPMz=A*Hg~YiI6=ECdP*)Cyv-ywS z?9ZO?AVIhb5ciwfewZh66tO2Rtm4T&8D_?01ZE;@U%& zqLpSZ`(n+fkPS9)L|#KS^S$UyGMJi;-iPY3Gjb<(9u4OXcWZn#V^f|nM%446UEDH` zCnH-*_FD4>76xMt0lVz;eZch_nU12z5Fz zi7a$`mS18YRNss|GE;e2_aaw`Z<092T0fnrs%|)!ChqG@+ke8(R8S*j6u(;pIlMN? zwkvT-<#fw3z5|wVo><10ie)VR?}f09N5VD^oSia_^TbZ@goh|OdBRvYviG%FcCwjk z?UOv_9rhiV-AkH0%ekNB1~=|xacZ6wVT4xkPrV*jR(IxAE)LZAnup1s5j`!i_uoZI zngab0@y9J&dh3o(Yi)+@yTS9& z;GKd0;3S?X+9G%Ik8@(t(CyY4jdQQ=?EJvjWb-wK=Q+N*s+)24;JNBhM!#!B5(Clf zdndV)@A18PJD(2=)NOv7Q+xr7$#mqxdM0{z#b2d{yqBn(kMVLv z_wxor=ufnpyKdn6_egsSlMKPDpb2i1rkjonb;KUL0m|Syk+v@t^cZhf`b|uB-Ot}E zGw=O^F2C}rZi#qbQl5Q*W-ztp-E~c7$2C3Ohc%b)f3m(ON_{ck%K&eSmWs|arAzPm zLV85jE9zX1m~8r2dPxPwLD%`5ZPxi591B+GbM$wOk_yDoi(TAEVV~2%_Ga_sv!O=U z;k3?rPk0KBJjU@pQ`Gqi_F;F32N1zqNH~Qca`^)GSgU?7-ZC?;_ZkzH9K{1vfr3!G zW6l6r1xBhJuR`Jx5^lNc9M)~3*1`Is&wjoMS{pgA= zkw}Z4CI5ehY9IMm7TPyCK>pQF{@-rbVs&+^ay|x)PXAtAGUrkD+6N?#%0DUEM4cqv^F`u@qa@fX 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

      @J9yy8A4MslEr`00u~? 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∋}3swm# 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

      ;)cPPOVbV0FyYf-G!cueoO0>F9hXyr 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%0A0vdMFtAxnb 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

      >6%_`~Ui?nS+1)mYvpgH)EXO8sHpnJ%)<-%M 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

      jqfU6@(>;lA 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


      S{;v;_#>ii8JX@X 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

      `JS(dJtSzoaTQyR?ijQUw&w_>@C=m(to?uk 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 From caa6a81669be9a2e171223fb8c0d6c3343519599 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 26 Sep 2016 17:46:50 -0400 Subject: [PATCH 0227/1012] Add ruby library for linux x64 Built by @dseguin: https://github.com/DFHack/dfhack/pull/989 Closes dfhack/dfhack#989 --- plugins/ruby/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 1377155ec..d028ee998 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -15,7 +15,12 @@ ELSEIF(UNIX) SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) SET(RUBYLIB_INSTALL_NAME "libruby.so") IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - MESSAGE("No ruby lib for 64-bit Linux yet") + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux64-libruby187.so.gz" + "gz" + ${RUBYLIB}.gz + "8eb757bb9ada08608914d8ca8906c427" + ${RUBYLIB} + "e8c36a06f031cfbf02def28169bb5f1f") ELSE() DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux32-libruby187.so.gz" "gz" From 714ba1a0306b3789259b92d08218a94d75447a1e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 26 Sep 2016 18:28:59 -0400 Subject: [PATCH 0228/1012] Warn if ruby library is missing instead of breaking installation --- plugins/ruby/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index d028ee998..bfc41c032 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -65,7 +65,11 @@ INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread") DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) ADD_DEPENDENCIES(ruby ruby-autogen-rb) -INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) +IF(EXISTS ${RUBYLIB}) + INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) +ELSE() + MESSAGE(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed") +ENDIF() INSTALL(DIRECTORY . DESTINATION hack/ruby From a5338d2f594de69d903931bd853a141bced1add7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 28 Sep 2016 20:43:02 -0400 Subject: [PATCH 0229/1012] Fix rebase_delta type mismatch See #984. GCC wasn't complaining about this on x64 for some reason. Also reordered includes. --- library/include/VersionInfo.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index b3d6c8346..dc959f1db 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -25,13 +25,13 @@ distribution. #pragma once -#include "Pragma.h" -#include "Export.h" -/* #include "Types.h" */ +#include #include #include #include -#include + +#include "Export.h" +#include "Pragma.h" namespace DFHack { @@ -53,7 +53,7 @@ namespace DFHack std::map Addresses; std::map VTables; uintptr_t base; - uintptr_t rebase_delta; + intptr_t rebase_delta; std::string version; OSType OS; public: @@ -76,7 +76,7 @@ namespace DFHack }; uintptr_t getBase () const { return base; }; - int getRebaseDelta() const { return rebase_delta; } + intptr_t getRebaseDelta() const { return rebase_delta; } void setBase (const uintptr_t _base) { base = _base; }; void rebaseTo(const uintptr_t new_base) { From 58ed20b25b6019aa95d8e44028bff091050723e2 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 6 Oct 2016 22:16:40 +0530 Subject: [PATCH 0230/1012] Send world map snow coverage over remotefortressreader. --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 36c2442ce..f0fc57a6b 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -524,6 +524,7 @@ message RegionTile repeated SiteRealizationBuilding buildings = 14; repeated MatPair stone_materials = 15; repeated MatPair tree_materials = 16; + optional int32 snow = 17; } message RegionMap diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6bc7c8173..c3d2e42e5 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -2105,6 +2105,7 @@ static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) plantMat->set_mat_type(419); } } + out->set_snow(e1->snowfall); } static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) From 70d3c07cdb3d9ada0fc10fbdfdcecba7b6877cee Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 7 Oct 2016 23:51:58 -0400 Subject: [PATCH 0231/1012] Initial lua getDwarfmodeViewDims rewrite --- library/LuaApi.cpp | 27 ++++++++++++++++++++++++++- library/include/modules/Gui.h | 2 +- library/modules/Gui.cpp | 4 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 18b4ab270..20b1bc0e4 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1428,6 +1428,26 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { /***** Gui module *****/ +static int gui_getDwarfmodeViewDims(lua_State *state) +{ + auto dims = Gui::getDwarfmodeViewDims(); + lua_newtable(state); + Lua::TableInsert(state, "map_x1", dims.map_x1); + Lua::TableInsert(state, "map_x2", dims.map_x2); + Lua::TableInsert(state, "menu_x1", dims.menu_x1); + Lua::TableInsert(state, "menu_x2", dims.menu_x2); + Lua::TableInsert(state, "area_x1", dims.area_x1); + Lua::TableInsert(state, "area_x2", dims.area_x2); + Lua::TableInsert(state, "y1", dims.y1); + Lua::TableInsert(state, "y2", dims.y2); + Lua::TableInsert(state, "map_y1", dims.map_y1); + Lua::TableInsert(state, "map_y2", dims.map_y2); + Lua::TableInsert(state, "menu_on", dims.menu_on); + Lua::TableInsert(state, "area_on", dims.area_on); + Lua::TableInsert(state, "menu_forced", dims.menu_forced); + return 1; +} + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), @@ -1448,6 +1468,11 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { { NULL, NULL } }; +static const luaL_Reg dfhack_gui_funcs[] = { + { "getDwarfmodeViewDims", gui_getDwarfmodeViewDims }, + { NULL, NULL } +}; + /***** Job module *****/ static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } @@ -2758,7 +2783,7 @@ void OpenDFHackApi(lua_State *state) OpenRandom(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); - OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index ae53e7603..c7f7d2c80 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -134,7 +134,7 @@ namespace DFHack int map_y1, map_y2; bool menu_on, area_on, menu_forced; - rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); } + rect2d map() { return mkrect_xy(map_x1, map_y1, map_x2, map_y2); } rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); } }; diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 2cc955d63..5368a0fbd 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1404,8 +1404,12 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default() auto ws = Screen::getWindowSize(); dims.y1 = 1; dims.y2 = ws.y-2; + dims.map_x1 = 1; dims.map_x2 = ws.x-2; + dims.map_y1 = dims.y1; + dims.map_y2 = dims.y2; + dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1; dims.menu_forced = false; From d414e60ff0445b777fe7b01d3f7efa13cceaa087 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 8 Oct 2016 16:57:33 -0400 Subject: [PATCH 0232/1012] Mention DFHACK_BUILD_ARCH and other settings in Compile.rst --- docs/Compile.rst | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index b693f4cc3..4cea7713b 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -33,8 +33,8 @@ To get the latest development code (develop branch), clone as above and then:: **Important note regarding submodule update and changing branches**: -You must run ``git submodule update`` every time you change Git branch, -for example when switching between master and develop branches and back. +You must run ``git submodule update`` every time you change branches, +such as when switching between the master and develop branches or vice versa. If a submodule only exists on the newer branch, you also need to run ``git submodule update --init``. Failure to do this may result in strange build errors or "not a known DF version" errors. @@ -67,8 +67,12 @@ For lots more details on contributing to DFHack, including pull requests, code f and more, please see `contributing-code`. -Build types -=========== +Build settings +============== + +Build type +---------- + ``cmake`` allows you to pick a build type by changing the ``CMAKE_BUILD_TYPE`` variable:: cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE @@ -78,8 +82,30 @@ Without specifying a build type or 'None', cmake uses the Valid and useful build types include 'Release', 'Debug' and 'RelWithDebInfo'. -'Debug' is not available on Windows, use 'RelWithDebInfo' instead. +'Debug' is not available on Windows; use 'RelWithDebInfo' instead. + +Target architecture (32-bit vs. 64-bit) +--------------------------------------- + +Set DFHACK_BUILD_ARCH to either ``32`` or ``64`` to build a 32-bit or 64-bit +version of DFHack (respectively). The default is currently ``32``, but this may +change, so specifying it explicitly is a good idea. + +:: + + cmake .. -DDFHACK_BUILD_ARCH=32 + +*or* +:: + + cmake .. -DDFHACK_BUILD_ARCH=64 +Other settings +-------------- +There are a variety of other settings which you can find in CMakeCache.txt in +your build folder or by running ``ccmake`` (or another CMake GUI). Most +DFHack-specific settings begin with ``BUILD_`` and control which parts of DFHack +are built. Linux ===== From f6a91c2f30dd482e44bbfced45e31bfa5d915468 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 8 Oct 2016 16:58:08 -0400 Subject: [PATCH 0233/1012] Log architecture on startup --- library/Core.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Core.cpp b/library/Core.cpp index fb1dd80bb..75fe576f4 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -250,6 +250,7 @@ static string dfhack_version_desc() s << "(release)"; else s << "(development build " << Version::git_description() << ")"; + s << " on " << (sizeof(void*) == 8 ? "x86_64" : "x86"); return s.str(); } From 2806fe73a95f75368e74ac5451abdb024b2bfb50 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 8 Oct 2016 16:59:03 -0400 Subject: [PATCH 0234/1012] ruby: Support for loading Ruby 2.x libruby Check for rb_float_new and rb_float_new_in_heap, as documented in #271 --- plugins/ruby/ruby.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 340f38c58..276e5052b 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -355,12 +355,14 @@ static int df_loadruby(void) rbloadsym(rb_string_value_ptr); rbloadsym(rb_eval_string_protect); rbloadsym(rb_ary_shift); - rbloadsym(rb_float_new); rbloadsym(rb_num2dbl); rbloadsym(rb_int2inum); rbloadsym(rb_uint2inum); rbloadsym(rb_num2ulong); #undef rbloadsym + if (!((rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new"))) || + (rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new_in_heap"))))) + return 0; return 1; } From 1c20ebe62cb91beae891b76b26cf0429a1f78399 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 8 Oct 2016 17:02:45 -0400 Subject: [PATCH 0235/1012] Make title-folder work without SDL_WM_GetCaption --- plugins/title-folder.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/title-folder.cpp b/plugins/title-folder.cpp index ee69ccc5a..205f7225a 100644 --- a/plugins/title-folder.cpp +++ b/plugins/title-folder.cpp @@ -63,8 +63,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Sat, 8 Oct 2016 17:05:59 -0400 Subject: [PATCH 0236/1012] Add dseguin and figment to Authors.rst --- docs/Authors.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 8592dac0e..195b90c44 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -25,6 +25,7 @@ Carter Bray Qartar Chris Dombroski cdombroski Clayton Hughes David Corbett dscorbett +David Seguin dseguin Deon DoctorVanGogh DoctorVanGogh Donald Ruegsegger hashaash @@ -35,6 +36,7 @@ Erik Youngren Artanis Espen Wiborg expwnent expwnent Feng +figment figment gchristopher gchristopher Harlan Playford playfordh Hayati Ayguen hayguen From e6bb7357df6e21ca2ad3d37024193fe25b231a3a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 8 Oct 2016 17:09:23 -0400 Subject: [PATCH 0237/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 7422b712f..1c1479db1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7422b712fcdbb8ee767ff08949329936fc0d8bc9 +Subproject commit 1c1479db1d3f45a187c1f9d355db1b033c32d4ff From ada96b3162380cdcbe717c6589fa0a4f858aa4b1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Oct 2016 09:33:52 -0400 Subject: [PATCH 0238/1012] Remove GCC version check in protobuf/CMakeLists.txt The only thing this check was used for was disabled, and the root CMakeLists.txt will enforce the minimum GCC version before protobuf is included. --- depends/protobuf/CMakeLists.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 59fe3f0d8..24bac7988 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -2,16 +2,6 @@ PROJECT(protobuf) #Protocol buffers use C++0x hash maps, so we need to look for those. This is a rewrite of stl_hash.m4 in CMake. IF(CMAKE_COMPILER_IS_GNUCC) - EXECUTE_PROCESS(COMMAND ${CMAKE_C_COMPILER} -dumpversion - OUTPUT_VARIABLE GCC_VERSION) - STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION}) - LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) - LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) - #IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2)) - #GCC is too old - # SET(STL_HASH_OLD_GCC 1) - #ENDIF() - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") ENDIF() From d5e8b5eeba79c267c823fabe44594966bfbeaf27 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 13 Oct 2016 19:35:40 +0530 Subject: [PATCH 0239/1012] Update XML --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 1c1479db1..7422b712f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1c1479db1d3f45a187c1f9d355db1b033c32d4ff +Subproject commit 7422b712fcdbb8ee767ff08949329936fc0d8bc9 From 980b6dd12f9bc216e3fd1da63513c7e8e0b86124 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 13 Oct 2016 19:42:40 +0530 Subject: [PATCH 0240/1012] Update XML again. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 7422b712f..693d58c85 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7422b712fcdbb8ee767ff08949329936fc0d8bc9 +Subproject commit 693d58c8588120ad0a179bdd154cf0ce6035c782 From 0c0d68c9eddf8886c9e1216d3db5f20c984d0001 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 14 Oct 2016 08:56:54 -0400 Subject: [PATCH 0241/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 0a022e862..352dc4ac8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 0a022e862e009b37d06cd0f4e1d4097ae3395ecf +Subproject commit 352dc4ac8a169bf4789448f01d5121c740669400 From 2e290f483741cc24fbe2f877e33149a00e7c8bdc Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 15 Oct 2016 00:18:25 +0530 Subject: [PATCH 0242/1012] Ignore the new VS version folders. --- build/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/build/.gitignore b/build/.gitignore index 10192a42e..08f9b16d3 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,4 +1,5 @@ VC2010 +VC2015 DF_PATH.txt _CPack_Packages *.tar.* From 9f541481ea8b2267b330501fe3fdc4b9db2a6bef Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 00:37:18 -0400 Subject: [PATCH 0243/1012] Fix some warnings with GCC -Wall --- library/Core.cpp | 11 +++++------ library/DataDefs.cpp | 2 -- library/LuaApi.cpp | 4 ++-- library/LuaTools.cpp | 1 + library/LuaTypes.cpp | 1 + library/LuaWrapper.cpp | 4 ---- library/PluginManager.cpp | 5 ++--- 7 files changed, 11 insertions(+), 17 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 75fe576f4..6afe907aa 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -300,12 +300,6 @@ static void listScripts(PluginManager *plug_mgr, std::map &pset, } } -static bool fileExists(std::string path) -{ - ifstream script(path.c_str()); - return script.good(); -} - namespace { struct ScriptArgs { const string *pcmd; @@ -1612,16 +1606,19 @@ bool Core::Init() cerr << "Starting IO thread.\n"; // create IO thread thread * IO = new thread(fIOthread, (void *) temp); + (void)IO; } else { cerr << "Starting dfhack.init thread.\n"; thread * init = new thread(fInitthread, (void *) temp); + (void)init; } cerr << "Starting DF input capture thread.\n"; // set up hotkey capture thread * HK = new thread(fHKthread, (void *) temp); + (void)HK; screen_window = new Windows::top_level_window(); screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; @@ -1819,6 +1816,7 @@ void Core::Resume() lock_guard lock(d->AccessMutex); assert(d->df_suspend_depth > 0 && d->df_suspend_thread == tid); + (void)tid; if (--d->df_suspend_depth == 0) d->core_cond.Unlock(); @@ -1858,6 +1856,7 @@ void Core::DisclaimSuspend(int level) lock_guard lock(d->AccessMutex); assert(d->df_suspend_depth == level && d->df_suspend_thread == tid); + (void)tid; if (level == 1000000) d->df_suspend_depth = 0; diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 06f1ac6c9..c9586fcc0 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -271,8 +271,6 @@ virtual_identity *virtual_identity::find(void *vtable) Core &core = Core::getInstance(); std::string name = core.p->doReadClassName(vtable); - virtual_identity *actual = NULL; - auto name_it = name_lookup.find(name); if (name_it != name_lookup.end()) { virtual_identity *p = name_it->second; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 18b4ab270..47532c17b 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2243,7 +2243,7 @@ static int filesystem_listdir(lua_State *L) return 3; } lua_newtable(L); - for(int i=0;igetNumBits()); return 1; diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 6e7f81102..69fdd734e 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -46,8 +46,6 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; -static luaL_Reg no_functions[] = { { NULL, NULL } }; - /** * Report an error while accessing a field (index = field name). */ @@ -1594,8 +1592,6 @@ static void RenderTypeChildren(lua_State *state, const std::vectorlock_add(); if(state == PS_LOADED) From 98ab357df0bd50b1f508242d98e2403fe038ba38 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 15 Oct 2016 10:17:38 +0530 Subject: [PATCH 0244/1012] Use size_t in both path methods, to avoid warning C4267 --- library/include/df/custom/coord2d_path.methods.inc | 6 +++--- library/include/df/custom/coord_path.methods.inc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/include/df/custom/coord2d_path.methods.inc b/library/include/df/custom/coord2d_path.methods.inc index c36c5c0fa..623dfe758 100644 --- a/library/include/df/custom/coord2d_path.methods.inc +++ b/library/include/df/custom/coord2d_path.methods.inc @@ -1,13 +1,13 @@ -unsigned size() const { return x.size(); } +size_t size() const { return x.size(); } -coord2d operator[] (unsigned idx) const { +coord2d operator[] (size_t idx) const { if (idx >= x.size()) return coord2d(); else return coord2d(x[idx], y[idx]); } -void erase(unsigned idx) { +void erase(size_t idx) { if (idx < x.size()) { x.erase(x.begin()+idx); y.erase(y.begin()+idx); diff --git a/library/include/df/custom/coord_path.methods.inc b/library/include/df/custom/coord_path.methods.inc index 5421796e3..cc9a67fa1 100644 --- a/library/include/df/custom/coord_path.methods.inc +++ b/library/include/df/custom/coord_path.methods.inc @@ -1,5 +1,5 @@ bool empty() const { return x.empty(); } -unsigned size() const { return x.size(); } +size_t size() const { return x.size(); } void clear() { x.clear(); @@ -7,14 +7,14 @@ void clear() { z.clear(); } -coord operator[] (unsigned idx) const { +coord operator[] (size_t idx) const { if (idx >= x.size()) return coord(); else return coord(x[idx], y[idx], z[idx]); } -void erase(unsigned idx) { +void erase(size_t idx) { if (idx < x.size()) { x.erase(x.begin()+idx); y.erase(y.begin()+idx); From 08cc9f3188e17555ac30df8bf145e63d7e180c44 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 15 Oct 2016 10:31:38 +0530 Subject: [PATCH 0245/1012] Cast size_t to int explicitly to avoid compiler warning C4267 --- library/include/DataDefs.h | 2 +- library/include/DataIdentity.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index bccc9b3f5..ed37a91fc 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -398,7 +398,7 @@ int linear_index(const DFHack::enum_list_attr &lst, T val) { inline int linear_index(const DFHack::enum_list_attr &lst, const std::string &val) { for (size_t i = 0; i < lst.size; i++) if (lst.items[i] == val) - return i; + return (int)i; return -1; } diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 556731e46..62a9ff274 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -319,7 +319,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode) { - return ((container*)ptr)->size(); + return (int)((container*)ptr)->size(); }; virtual void *item_pointer(type_identity *, void *ptr, int idx) { return &(*(container*)ptr)[idx]; @@ -385,7 +385,7 @@ namespace df } protected: - virtual int item_count(void *ptr, CountMode) { return ((T*)ptr)->size(); } + virtual int item_count(void *ptr, CountMode) { return (int)((T*)ptr)->size(); } virtual void *item_pointer(type_identity *item, void *ptr, int idx) { return &(*(T*)ptr)[idx]; } @@ -410,7 +410,7 @@ namespace df virtual bool insert(void *ptr, int idx, void *item) { return false; } protected: - virtual int item_count(void *ptr, CountMode) { return ((T*)ptr)->size(); } + virtual int item_count(void *ptr, CountMode) { return (int)((T*)ptr)->size(); } virtual void *item_pointer(type_identity *item, void *ptr, int idx) { auto iter = (*(T*)ptr).begin(); for (; idx > 0; idx--) ++iter; @@ -472,7 +472,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode) { - return ((container*)ptr)->size(); + return (int)((container*)ptr)->size(); } virtual bool get_item(void *ptr, int idx) { return (*(container*)ptr)[idx]; @@ -498,7 +498,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode cm) { - return cm == COUNT_WRITE ? 0 : ((container*)ptr)->size; + return cm == COUNT_WRITE ? 0 : (int)((container*)ptr)->size; } virtual void *item_pointer(type_identity *item, void *ptr, int idx) { return (void*)&((container*)ptr)->items[idx]; From 7f7102f90c0c061926702237a5b385450c02f7c5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 12:07:50 -0400 Subject: [PATCH 0246/1012] Distribute a gunzip.pl script gunzip isn't reliably available on Windows, but IO::Uncompress::Gunzip should be. Suggested by Quietust. --- CMake/DownloadFile.cmake | 4 +++- depends/gunzip.pl | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100755 depends/gunzip.pl diff --git a/CMake/DownloadFile.cmake b/CMake/DownloadFile.cmake index 1a8a40562..8f9938f90 100644 --- a/CMake/DownloadFile.cmake +++ b/CMake/DownloadFile.cmake @@ -31,7 +31,9 @@ function(download_file_unzip URL ZIP_TYPE ZIP_DEST ZIP_MD5 UNZIP_DEST UNZIP_MD5) if(EXISTS "${ZIP_DEST}") message("* Decompressing ${FILENAME}") if("${ZIP_TYPE}" STREQUAL "gz") - execute_process(COMMAND gunzip --force "${ZIP_DEST}") + execute_process(COMMAND + "${PERL_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/depends/gunzip.pl" + "${ZIP_DEST}" --force) else() message(SEND_ERROR "Unknown ZIP_TYPE: ${ZIP_TYPE}") endif() diff --git a/depends/gunzip.pl b/depends/gunzip.pl new file mode 100755 index 000000000..4a21daafd --- /dev/null +++ b/depends/gunzip.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use IO::Uncompress::Gunzip qw(gunzip $GunzipError); + +my %args = map { $_ => 1 } @ARGV; + +my $in_file = $ARGV[0] or die "no input file"; +# remove extension +(my $out_file = $in_file) =~ s{\.[^.]+$}{}; + +if (! -f $in_file) { + die "input file does not exist: \"$in_file\"\n"; +} +if (-f $out_file and !exists($args{'--force'})) { + die "output file exists, not overwriting: \"$out_file\""; +} + +gunzip $in_file => $out_file or die "gunzip failed: $GunzipError\n"; From 8a138fcc4cc3d68222c255541736fc7e27627adb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 14:53:10 -0400 Subject: [PATCH 0247/1012] Avoid polluting global namespace in MapCache.h --- library/include/modules/MapCache.h | 6 +++--- plugins/Brushes.h | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index b1d411ab8..b64cc7859 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -38,8 +38,6 @@ distribution. #include "df/item.h" #include "df/inclusion_type.h" -using namespace DFHack; - namespace df { struct world_region_details; } @@ -47,6 +45,8 @@ namespace df { namespace MapExtras { +using namespace DFHack; + class DFHACK_EXPORT MapCache; class Block; @@ -626,4 +626,4 @@ private: std::map blocks; }; } -#endif \ No newline at end of file +#endif diff --git a/plugins/Brushes.h b/plugins/Brushes.h index 99e9e4125..eaf4240ea 100644 --- a/plugins/Brushes.h +++ b/plugins/Brushes.h @@ -152,10 +152,11 @@ public: class FloodBrush : public Brush { public: - FloodBrush(Core *c){c_ = c;}; + FloodBrush(DFHack::Core *c){c_ = c;}; ~FloodBrush(){}; coord_vec points(MapExtras::MapCache & mc, DFHack::DFCoord start) { + using namespace DFHack; coord_vec v; std::stack to_flood; @@ -198,19 +199,21 @@ public: return "flood"; } private: - void maybeFlood(DFCoord c, std::stack &to_flood, MapExtras::MapCache &mc) { + void maybeFlood(DFHack::DFCoord c, std::stack &to_flood, + MapExtras::MapCache &mc) { if (mc.testCoord(c)) { to_flood.push(c); } } - Core *c_; + DFHack::Core *c_; }; -command_result parseRectangle(color_ostream & out, +DFHack::command_result parseRectangle(DFHack::color_ostream & out, vector & input, int start, int end, int & width, int & height, int & zLevels, bool hasConsole = true) { + using namespace DFHack; int newWidth = 0, newHeight = 0, newZLevels = 0; if (end > start + 1) From 4c21bbd5aed028a9378b3d952044c368c947eb2f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 14:55:48 -0400 Subject: [PATCH 0248/1012] Add Lua::TableInsert() helper --- library/include/LuaTools.h | 8 ++++++++ plugins/confirm.cpp | 13 +++---------- plugins/dwarfmonitor.cpp | 16 ++++------------ 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index c8237eb56..e1d828271 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -341,6 +341,14 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); + template + inline void TableInsert(lua_State *state, T_Key key, T_Value value) + { + Lua::Push(state, key); + Lua::Push(state, value); + lua_settable(state, -3); + } + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); DFHACK_EXPORT bool IsCoreContext(lua_State *state); diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 3ea5d540c..b9927f4ea 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -176,19 +176,12 @@ namespace conf_lua { { Lua::Push(l_state, val); } - template - void table_set (lua_State *L, KeyType k, ValueType v) - { - Lua::Push(L, k); - Lua::Push(L, v); - lua_settable(L, -3); - } namespace api { int get_ids (lua_State *L) { lua_newtable(L); for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - table_set(L, it->first, true); + Lua::TableInsert(L, it->first, true); return 1; } int get_conf_data (lua_State *L) @@ -199,8 +192,8 @@ namespace conf_lua { { Lua::Push(L, i++); lua_newtable(L); - table_set(L, "id", it->first); - table_set(L, "enabled", it->second->is_enabled()); + Lua::TableInsert(L, "id", it->first); + Lua::TableInsert(L, "enabled", it->second->is_enabled()); lua_settable(L, -3); } return 1; diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 8e2a88611..2c78c0854 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -187,14 +187,6 @@ namespace dm_lua { return safe_call(nargs); } - template - void table_set (lua_State *L, KeyType k, ValueType v) - { - Lua::Push(L, k); - Lua::Push(L, v); - lua_settable(L, -3); - } - namespace api { int monitor_state (lua_State *L) { @@ -229,7 +221,7 @@ namespace dm_lua { } } lua_newtable(L); - #define WTYPE(type, name) dm_lua::table_set(L, #type, type); + #define WTYPE(type, name) Lua::TableInsert(L, #type, type); WEATHER_TYPES #undef WTYPE #undef WEATHER_TYPES @@ -242,9 +234,9 @@ namespace dm_lua { { Lua::Push(L, i); lua_newtable(L); - dm_lua::table_set(L, "value", misery[i]); - dm_lua::table_set(L, "color", monitor_colors[i]); - dm_lua::table_set(L, "last", (i == 6)); + Lua::TableInsert(L, "value", misery[i]); + Lua::TableInsert(L, "color", monitor_colors[i]); + Lua::TableInsert(L, "last", (i == 6)); lua_settable(L, -3); } return 1; From 16c77efb43aded0c4ba62928983d9fc0c2f98f3d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 16:05:33 -0400 Subject: [PATCH 0249/1012] Fix whitespace issues --- plugins/proto/RemoteFortressReader.proto | 38 ++++++++++++------------ scripts | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index f0fc57a6b..3ca15e0ec 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -188,15 +188,15 @@ enum MatterState message Spatter { - optional MatPair material = 1; - optional int32 amount = 2; - optional MatterState state = 3; - optional MatPair item = 4; + optional MatPair material = 1; + optional int32 amount = 2; + optional MatterState state = 3; + optional MatPair item = 4; } message SpatterPile { - repeated Spatter spatters = 1; + repeated Spatter spatters = 1; } message MapBlock @@ -225,7 +225,7 @@ message MapBlock repeated int32 tree_y = 22; repeated int32 tree_z = 23; repeated TileDigDesignation tile_dig_designation = 24; - repeated SpatterPile spatterPile = 25; + repeated SpatterPile spatterPile = 25; } message MatPair { @@ -475,21 +475,21 @@ message SiteRealizationBuildingWall message SiteRealizationBuildingTower { optional int32 roof_z = 1; - optional bool round = 2; - optional bool goblin = 3; + optional bool round = 2; + optional bool goblin = 3; } message TrenchSpoke { - optional int32 mound_start = 1; - optional int32 trench_start = 2; - optional int32 trench_end = 3; - optional int32 mound_end = 4; + optional int32 mound_start = 1; + optional int32 trench_start = 2; + optional int32 trench_end = 3; + optional int32 mound_end = 4; } message SiteRealizationBuildingTrenches { - repeated TrenchSpoke spokes = 1; + repeated TrenchSpoke spokes = 1; } message SiteRealizationBuilding @@ -503,7 +503,7 @@ message SiteRealizationBuilding optional MatPair material = 7; optional SiteRealizationBuildingWall wall_info = 8; optional SiteRealizationBuildingTower tower_info = 9; - optional SiteRealizationBuildingTrenches trench_info = 10; + optional SiteRealizationBuildingTrenches trench_info = 10; } message RegionTile @@ -519,11 +519,11 @@ message RegionTile optional int32 salinity = 9; optional RiverTile river_tiles = 10; optional int32 water_elevation = 11; - optional MatPair surface_material = 12; - repeated MatPair plant_materials = 13; + optional MatPair surface_material = 12; + repeated MatPair plant_materials = 13; repeated SiteRealizationBuilding buildings = 14; - repeated MatPair stone_materials = 15; - repeated MatPair tree_materials = 16; + repeated MatPair stone_materials = 15; + repeated MatPair tree_materials = 16; optional int32 snow = 17; } @@ -725,4 +725,4 @@ message KeyboardEvent optional uint32 mod = 6; optional uint32 unicode = 7; } - + diff --git a/scripts b/scripts index 352dc4ac8..de9962079 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 352dc4ac8a169bf4789448f01d5121c740669400 +Subproject commit de996207994e4ce3e221c010c9cc042b06620a2e From 55d2f00555c482191456fc93e8ffbcba30f36e69 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 16:37:16 -0400 Subject: [PATCH 0250/1012] Update NEWS.rst and Plugins.rst --- NEWS.rst | 40 ++++++++++++++++++++++++++++++++++++---- docs/Plugins.rst | 6 ++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 0715ccae4..dd16cef7d 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -33,8 +33,31 @@ Changelog DFHack future ============= +Internals +--------- +- 64-bit support on all platforms +- Visual Studio 2015 now required on Windows instead of 2010 +- GCC 4.8 recommended on Linux and OS X (and now supported on OS X) +- Several structure fixes to match 64-bit DF's memory layout + +Lua +--- +- Lua has been updated to 5.3 - see http://www.lua.org/manual/5.3/readme.html for details + + - Floats are no longer implicitly converted to integers in DFHack API calls + +- ``df.new()`` supports more types: ``char``, ``intptr_t``, ``uintptr_t``, ``long``, ``unsigned long`` +- String representations of vectors and a few other containers now include their lengths + +Ruby +---- +- Added support for loading ruby 2.x libraries +- Fixed some layouts on x64 (incomplete) + New Plugins ----------- +- `dwarfvet` enables animal caretaking +- `labormanager` (formerly autolabor2): a more advanced alternative to `autolabor` - `title-folder`: shows DF folder name in window title bar when enabled New Scripts @@ -43,7 +66,20 @@ New Scripts Fixes ----- +- The DF path on OS X can now contain spaces and ``:`` characters - Buildings::setOwner() changes now persist properly when saved +- `devel/find-offsets`: fixed a crash when vtables used by globals aren't available + +Misc Improvements +----------------- +- `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.) +- `remotefortressreader`: Added support for + + - world map snow coverage + - spatters + - wall info + - site towers, world buildings + - surface material DFHack 0.43.03-r1 ================= @@ -52,10 +88,6 @@ Lua --- - Label widgets can now easily register handlers for mouse clicks -New Plugins ------------ -- `dwarfvet` enables animal caretaking. - New Features ------------ - `add-thought`: allow syndrome name as ``-thought`` argument diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 7746b8b51..90533aa96 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -818,6 +818,12 @@ Examples: ``autolabor CUTWOOD disable`` Turn off autolabor for wood cutting. +.. _labormanager: + +labormanager +============ +A more advanced alternative to `autolabor`. + .. _autohauler: autohauler From 4d74090b34b10fc64d72d2aa56242ea161e56f71 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 15 Oct 2016 17:27:02 -0400 Subject: [PATCH 0251/1012] Bump version in CMakeLists.txt --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d25404e27..1cae58c8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,8 +137,8 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "alpha0") -SET(DFHACK_PRERELEASE FALSE) +SET(DFHACK_RELEASE "alpha1") +SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 0e0da1edbe0dd6ae264019cda7ee3ee8b47da684 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 16 Oct 2016 06:32:35 +0530 Subject: [PATCH 0252/1012] Update windows build scripts to reflect the new folder structure. --- build/win32/generate-MSVC-all-breakfast.bat | 2 +- build/win32/generate-MSVC-all.bat | 2 +- build/win32/generate-MSVC-gui.bat | 2 +- build/win32/generate-MSVC-minimal.bat | 2 +- build/win32/generate-MSVC-release.bat | 2 +- build/win64/generate-MSVC-all-breakfast.bat | 2 +- build/win64/generate-MSVC-all.bat | 2 +- build/win64/generate-MSVC-gui.bat | 2 +- build/win64/generate-MSVC-minimal.bat | 2 +- build/win64/generate-MSVC-release.bat | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/win32/generate-MSVC-all-breakfast.bat b/build/win32/generate-MSVC-all-breakfast.bat index 4ef5ed677..ac26c4f75 100644 --- a/build/win32/generate-MSVC-all-breakfast.bat +++ b/build/win32/generate-MSVC-all-breakfast.bat @@ -6,4 +6,4 @@ cd VC2015_32 echo generating a build folder rem for /f "delims=" %%a in ('DATE /T') do @set myvar=breakfast-%BUILD_NUMBER% set myvar=breakfast-%BUILD_NUMBER% -cmake ..\.. -G"Visual Studio 14" -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 +cmake ..\..\.. -G"Visual Studio 14" -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 diff --git a/build/win32/generate-MSVC-all.bat b/build/win32/generate-MSVC-all.bat index 106913925..254492f2e 100755 --- a/build/win32/generate-MSVC-all.bat +++ b/build/win32/generate-MSVC-all.bat @@ -3,4 +3,4 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015_32 cd VC2015_32 echo generating a build folder -cmake ..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 +cmake ..\..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 diff --git a/build/win32/generate-MSVC-gui.bat b/build/win32/generate-MSVC-gui.bat index fe4503717..514f894e7 100755 --- a/build/win32/generate-MSVC-gui.bat +++ b/build/win32/generate-MSVC-gui.bat @@ -3,5 +3,5 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015_32 cd VC2015_32 echo Pre-generating a build folder -cmake ..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" +cmake ..\..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" cmake-gui . diff --git a/build/win32/generate-MSVC-minimal.bat b/build/win32/generate-MSVC-minimal.bat index ae0187d02..e42072d05 100755 --- a/build/win32/generate-MSVC-minimal.bat +++ b/build/win32/generate-MSVC-minimal.bat @@ -3,4 +3,4 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015_32 cd VC2015_32 echo generating a build folder -cmake ..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=0 -DBUILD_DFUSION=0 -DBUILD_STONESENSE=0 -DBUILD_SERVER=0 +cmake ..\..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=0 -DBUILD_DFUSION=0 -DBUILD_STONESENSE=0 -DBUILD_SERVER=0 diff --git a/build/win32/generate-MSVC-release.bat b/build/win32/generate-MSVC-release.bat index eeecad111..9735a1469 100755 --- a/build/win32/generate-MSVC-release.bat +++ b/build/win32/generate-MSVC-release.bat @@ -3,4 +3,4 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015_32 cd VC2015_32 echo generating a build folder -cmake ..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 +cmake ..\..\.. -G"Visual Studio 14" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 diff --git a/build/win64/generate-MSVC-all-breakfast.bat b/build/win64/generate-MSVC-all-breakfast.bat index edf540f48..8fd64caaf 100644 --- a/build/win64/generate-MSVC-all-breakfast.bat +++ b/build/win64/generate-MSVC-all-breakfast.bat @@ -6,4 +6,4 @@ cd VC2015 echo generating a build folder rem for /f "delims=" %%a in ('DATE /T') do @set myvar=breakfast-%BUILD_NUMBER% set myvar=breakfast-%BUILD_NUMBER% -cmake ..\.. -G"Visual Studio 14 Win64" -T v140_xp -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 +cmake ..\..\.. -G"Visual Studio 14 Win64" -T v140_xp -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 diff --git a/build/win64/generate-MSVC-all.bat b/build/win64/generate-MSVC-all.bat index c34e03d70..a27f12697 100755 --- a/build/win64/generate-MSVC-all.bat +++ b/build/win64/generate-MSVC-all.bat @@ -3,4 +3,4 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015 cd VC2015 echo generating a build folder -cmake ..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 +cmake ..\..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 diff --git a/build/win64/generate-MSVC-gui.bat b/build/win64/generate-MSVC-gui.bat index e798897c6..c9f35ff4a 100755 --- a/build/win64/generate-MSVC-gui.bat +++ b/build/win64/generate-MSVC-gui.bat @@ -3,5 +3,5 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015 cd VC2015 echo Pre-generating a build folder -cmake ..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" +cmake ..\..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" cmake-gui . diff --git a/build/win64/generate-MSVC-minimal.bat b/build/win64/generate-MSVC-minimal.bat index 895215a27..ea57b99f6 100755 --- a/build/win64/generate-MSVC-minimal.bat +++ b/build/win64/generate-MSVC-minimal.bat @@ -3,4 +3,4 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015 cd VC2015 echo generating a build folder -cmake ..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=0 -DBUILD_DFUSION=0 -DBUILD_RUBY=0 -DBUILD_STONESENSE=0 -DBUILD_SERVER=0 +cmake ..\..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=0 -DBUILD_DFUSION=0 -DBUILD_RUBY=0 -DBUILD_STONESENSE=0 -DBUILD_SERVER=0 diff --git a/build/win64/generate-MSVC-release.bat b/build/win64/generate-MSVC-release.bat index b84c85cdd..7a564a1d1 100755 --- a/build/win64/generate-MSVC-release.bat +++ b/build/win64/generate-MSVC-release.bat @@ -3,4 +3,4 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2015 cd VC2015 echo generating a build folder -cmake ..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 +cmake ..\..\.. -G"Visual Studio 14 Win64" -T v140_xp -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 From b04083690e5dd6ceb02fc67ee7729acf09893eb0 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 17 Oct 2016 19:48:24 +0530 Subject: [PATCH 0253/1012] Update .gitignore to ignore the 32bit build folder --- build/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/build/.gitignore b/build/.gitignore index 08f9b16d3..3ee46f78e 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,5 +1,6 @@ VC2010 VC2015 +VC2015_32 DF_PATH.txt _CPack_Packages *.tar.* From b1e3c1088ca84fa30c2e548f8cd2258155bc3e8d Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 19 Oct 2016 09:51:48 -0400 Subject: [PATCH 0254/1012] Give loadfile() result a better name for tracebacks --- library/lua/dfhack.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 900607c08..340b6ce64 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -543,23 +543,23 @@ function dfhack.run_script_with_env(envVars, name, flags, ...) end env.dfhack_flags = flags env.moduleMode = flags.module - local f + local script_code local perr local time = dfhack.filesystem.mtime(file) if time == scripts[file].mtime and scripts[file].run then - f = scripts[file].run + script_code = scripts[file].run else --reload - f, perr = loadfile(file, 't', env) - if not f then + script_code, perr = loadfile(file, 't', env) + if not script_code then error(perr) end -- avoid updating mtime if the script failed to load scripts[file].mtime = time end scripts[file].env = env - scripts[file].run = f - return f(...), env + scripts[file].run = script_code + return script_code(...), env end local function _run_command(...) From b19a0b4305331a98e130f4085902d88c3cbad097 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 19 Oct 2016 09:52:15 -0400 Subject: [PATCH 0255/1012] Update scripts, xml Fixes #1005 --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 693d58c85..81e2cf023 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 693d58c8588120ad0a179bdd154cf0ce6035c782 +Subproject commit 81e2cf023422ad6f01061db12586a0589ef6eac5 diff --git a/scripts b/scripts index de9962079..b285334a8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit de996207994e4ce3e221c010c9cc042b06620a2e +Subproject commit b285334a8a91c0b43bac9d3e362b95fcbfa472c7 From 919507d9a0f7fa639182410d80c3b3a8638de465 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 19 Oct 2016 20:00:30 +0530 Subject: [PATCH 0256/1012] Send over dig designations from un-taken job postings in remotefortressreader.cpp. --- plugins/remotefortressreader.cpp | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index d0272b854..b0f64b680 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -62,6 +62,8 @@ #include "df/historical_figure.h" #include "df/item.h" #include "df/itemdef.h" +#include "df/job.h" +#include "df/job_type.h" #include "df/job_item.h" #include "df/job_material_category.h" #include "df/map_block_column.h" @@ -1334,6 +1336,54 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N } } } + for (int i = 0; i < world->job_postings.size(); i++) + { + auto job = world->job_postings[i]->job; + if (job == nullptr) + continue; + if ( + job->pos.z > DfBlock->map_pos.z + || job->pos.z < DfBlock->map_pos.z + || job->pos.x >= (DfBlock->map_pos.x + 16) + || job->pos.x < (DfBlock->map_pos.x) + || job->pos.y >= (DfBlock->map_pos.y + 16) + || job->pos.y < (DfBlock->map_pos.y) + ) + continue; + + int index = (job->pos.x - DfBlock->map_pos.x) + (16 * (job->pos.y - DfBlock->map_pos.y)); + + switch (job->job_type) + { + case job_type::Dig: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::CarveUpwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_STAIR_DIG); + break; + case job_type::CarveDownwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DOWN_STAIR_DIG); + break; + case job_type::CarveUpDownStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case job_type::CarveRamp: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::RAMP_DIG); + break; + case job_type::DigChannel: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::CHANNEL_DIG); + break; + case job_type::FellTree: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::GatherPlants: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + default: + break; + } + } + } void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) From adad7e75c399609ffec4cb9bdbb0e129c0b3e99c Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 19 Oct 2016 20:00:56 +0530 Subject: [PATCH 0257/1012] Ignore df_path. again. --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5ef3ce759..4d3986c67 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,7 @@ tags # Mac OS X .DS_Store files .DS_Store + +#VS is annoying about this one. +/build/win64/DF_PATH.txt +/build/win32/DF_PATH.txt From 701adc12b3ff837d0894c89874e2deb2941fc096 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 19 Oct 2016 22:21:50 +0530 Subject: [PATCH 0258/1012] Add ability for remotefortressreader.cpp to accept dig designations. --- plugins/CMakeLists.txt | 2 +- plugins/proto/RemoteFortressReader.proto | 12 +++++++ plugins/remotefortressreader.cpp | 41 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a81d0993a..9522491dd 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -124,7 +124,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(prospector prospector.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(regrass regrass.cpp) - DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp PROTOBUFS RemoteFortressReader) + DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp proto/RemoteFortressReader.proto PROTOBUFS RemoteFortressReader) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) DFHACK_PLUGIN(resume resume.cpp) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 3ca15e0ec..4c049d1a9 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -117,6 +117,13 @@ enum TileDigDesignation UP_STAIR_DIG = 6; } +message Coord +{ + optional int32 x = 1; + optional int32 y = 2; + optional int32 z = 3; +} + message Tiletype { required int32 id = 1; @@ -726,3 +733,8 @@ message KeyboardEvent optional uint32 unicode = 7; } +message DigCommand +{ + optional TileDigDesignation designation = 1; + repeated Coord locations = 2; +} \ No newline at end of file diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index b0f64b680..afd7e2dc7 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -140,6 +140,7 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out); static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); +static command_result SendDigCommand(color_ostream &stream, const DigCommand *in); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -243,6 +244,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetPlantRaws", GetPlantRaws); svc->addFunction("CopyScreen", CopyScreen); svc->addFunction("PassKeyboardEvent", PassKeyboardEvent); + svc->addFunction("SendDigCommand", SendDigCommand); return svc; } @@ -2856,3 +2858,42 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve SDL_PushEvent(&e); return CR_OK; } + +static command_result SendDigCommand(color_ostream &stream, const DigCommand *in) +{ + MapExtras::MapCache mc; + + for (int i = 0; i < in->locations.locations_size(); i++) + { + auto pos = in->locations(i); + auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); + switch (in->designation()) + { + case NO_DIG: + des.bits.dig = tile_dig_designation::No; + break; + case DEFAULT_DIG: + des.bits.dig = tile_dig_designation::Default; + break; + case UP_DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpDownStair; + break; + case CHANNEL_DIG: + des.bits.dig = tile_dig_designation::Channel; + break; + case RAMP_DIG: + des.bits.dig = tile_dig_designation::Ramp; + break; + case DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::DownStair; + break; + case UP_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpStair; + break; + default: + break; + } + mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + } + return CR_OK; +} \ No newline at end of file From e3ff89ba03b143acf8030324fa46869f3f0c013c Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 19 Oct 2016 16:21:38 -0400 Subject: [PATCH 0259/1012] Fix compile error --- plugins/remotefortressreader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index afd7e2dc7..bbf998704 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -2863,7 +2863,7 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in { MapExtras::MapCache mc; - for (int i = 0; i < in->locations.locations_size(); i++) + for (int i = 0; i < in->locations_size(); i++) { auto pos = in->locations(i); auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); @@ -2896,4 +2896,4 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); } return CR_OK; -} \ No newline at end of file +} From b3ff4814610dfc2ad8ecec1cf626eea452321526 Mon Sep 17 00:00:00 2001 From: James Logsdon Date: Wed, 19 Oct 2016 20:55:14 -0400 Subject: [PATCH 0260/1012] Add documentation for manipulator professions --- docs/Plugins.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index ba502c38e..7882d0298 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -405,7 +405,7 @@ military and social skills. .. image:: images/manipulator2.png -Press :kbd:`t` to toggle between Profession and Squad view. +Press :kbd:`t` to toggle between Profession, Squad, and Job views. .. image:: images/manipulator3.png @@ -440,6 +440,20 @@ The following mouse shortcuts are also available: Pressing :kbd:`Esc` normally returns to the unit screen, but :kbd:`Shift`:kbd:`Esc` would exit directly to the main dwarf mode screen. +Professions +----------- + +The manipulator plugin supports saving Professions: a named set of Labors labors that can be +quickly applied to one or multiple Dwarves. + +To save a Profession highlight a Dwarf and press :kbd:`P`. The Profession will be saved using +the Custom Profession Name of the Dwarf, or the default for that Dwarf if no Custom Profession +Name has been set. + +To apply a Profession either highlight a single Dwarf, or select multiple with :kbd:`x`, and press +:kbd:`p` to select the Profession to apply. All labors for the selected Dwarves will be reset to +the labors of the chosen Profession. + .. comment - the link target "search" is reserved for the Sphinx search page .. _search-plugin: From 951d2930500781698ed0b1dd74045d34459521dc Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Thu, 25 Aug 2016 10:28:07 +1000 Subject: [PATCH 0261/1012] Add, use, and require auto docs for all keybindings --- NEWS.rst | 2 + conf.py | 105 +++++++++++++++++++++++++++++++++++++++++------ docs/Plugins.rst | 34 ++++++++++++--- 3 files changed, 123 insertions(+), 18 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index dd16cef7d..b7e80cf77 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -72,6 +72,8 @@ Fixes Misc Improvements ----------------- +- Documented all default keybindings (from :file:`dfhack.init-example`) in the + docs for the relevant commands; updates enforced by build system. - `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.) - `remotefortressreader`: Added support for diff --git a/conf.py b/conf.py index dc033016f..b3f8949e7 100644 --- a/conf.py +++ b/conf.py @@ -15,15 +15,70 @@ serve to show the default. # pylint:disable=redefined-builtin -import fnmatch from io import open -from itertools import starmap import os import re import shlex # pylint:disable=unused-import import sys +# -- Support :dfhack-keybind:`command` ------------------------------------ +# this is a custom directive that pulls info from dfhack.init-example + +from docutils import nodes +from docutils.parsers.rst import roles + + +def get_keybinds(): + """Get the implemented keybinds, and return a dict of + {tool: [(full_command, keybinding, context), ...]}. + """ + with open('dfhack.init-example') as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + keybindings = dict() + for k in lines: + first, command = k.split(' ', maxsplit=1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + return keybindings + +KEYBINDS = get_keybinds() + + +# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments +def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, + options={}, content=[]): + """Custom role parser for DFHack default keybinds.""" + roles.set_classes(options) + if text not in KEYBINDS: + msg = inliner.reporter.error( + 'no keybinding for {} in dfhack.init-example'.format(text), + line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + newnode = nodes.paragraph() + for cmd, key, ctx in KEYBINDS[text]: + n = nodes.paragraph() + newnode += n + n += nodes.strong('Keybinding: ', 'Keybinding: ') + for k in key: + n += nodes.inline(k, k, classes=['kbd']) + if cmd != text: + n += nodes.inline(' -> ', ' -> ') + n += nodes.literal(cmd, cmd, classes=['guilabel']) + if ctx: + n += nodes.inline(' in ', ' in ') + n += nodes.literal(ctx, ctx) + return [newnode], [] + + +roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) + # -- Autodoc for DFhack scripts ------------------------------------------- def doc_dir(dirname, files): @@ -46,28 +101,41 @@ def doc_dir(dirname, files): command = line +def doc_all_dirs(): + """Collect the commands and paths to include in our docs.""" + scripts = [] + for root, _, files in os.walk('scripts'): + scripts.extend(doc_dir(root, files)) + return tuple(scripts) + +DOC_ALL_DIRS = doc_all_dirs() + + def document_scripts(): """Autodoc for files with the magic script documentation marker strings. Returns a dict of script-kinds to lists of .rst include directives. """ - # First, we collect the commands and paths to include in our docs - scripts = [] - for root, _, files in os.walk('scripts'): - scripts.extend(doc_dir(root, files)) # Next we split by type and create include directives sorted by command kinds = {'base': [], 'devel': [], 'fix': [], 'gui': [], 'modtools': []} - for s in scripts: + for s in DOC_ALL_DIRS: k_fname = s[0].split('/', 1) if len(k_fname) == 1: kinds['base'].append(s) else: kinds[k_fname[0]].append(s) - template = '.. _{}:\n\n.. include:: /{}\n' +\ - ' :start-after: {}\n :end-before: {}\n' - return {key: '\n\n'.join(starmap(template.format, sorted(value))) + + def template(arg): + tmp = '.. _{}:\n\n.. include:: /{}\n' +\ + ' :start-after: {}\n :end-before: {}\n' + if arg[0] in KEYBINDS: + tmp += '\n:dfhack-keybind:`{}`\n'.format(arg[0]) + return tmp.format(*arg) + + return {key: '\n\n'.join(map(template, sorted(value))) for key, value in kinds.items()} + def write_script_docs(): """ Creates a file for eack kind of script (base/devel/fix/gui/modtools) @@ -97,10 +165,23 @@ def write_script_docs(): outfile.write(kinds[k]) -# Actually call the docs generator -write_script_docs() +def all_keybinds_documented(): + """Check that all keybindings are documented with the :dfhack-keybind: + directive somewhere.""" + configured_binds = set(KEYBINDS) + script_commands = set(i[0] for i in DOC_ALL_DIRS) + with open('./docs/Plugins.rst') as f: + plugin_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) + undocumented_binds = configured_binds - script_commands - plugin_binds + if undocumented_binds: + raise ValueError('The following DFHack commands have undocumented' + 'keybindings: {}'.format(sorted(undocumented_binds))) +# Actually call the docs generator and run test +write_script_docs() +all_keybinds_documented() + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 90533aa96..54a9ccb81 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -353,7 +353,8 @@ This plugin adds an option to the :kbd:`q` menu when `enabled `. command-prompt ============== An in-game DFHack terminal, where you can enter other commands. -Best used from a keybinding; by default :kbd:`Ctrl`:kbd:`Shift`:kbd:`P`. + +:dfhack-keybind:`command-prompt` Usage: ``command-prompt [entry]`` @@ -372,13 +373,11 @@ Otherwise somewhat similar to `gui/quickcmd`. hotkeys ======= Opens an in-game screen showing which DFHack keybindings are -active in the current context. +active in the current context. See also `hotkey-notes`. .. image:: images/hotkeys.png -Type ``hotkeys`` into the DFHack console to open the screen, -or bind the command to a globally active hotkey. The default -keybinding is :kbd:`Ctrl`:kbd:`F1`. See also `hotkey-notes`. +:dfhack-keybind:`hotkeys` .. _rb: @@ -659,12 +658,16 @@ Unit order examples:: The orderings are defined in ``hack/lua/plugins/sort/*.lua`` +:dfhack-keybind:`sort-units` + .. _stocks: stocks ====== Replaces the DF stocks screen with an improved version. +:dfhack-keybind:`stocks` + .. _stocksettings: .. _stockpiles: @@ -676,6 +679,7 @@ See `gui/stockpiles` for an in-game interface. :copystock: Copies the parameters of the currently highlighted stockpile to the custom stockpile settings and switches to custom stockpile placement mode, effectively allowing you to copy/paste stockpiles easily. + :dfhack-keybind:`copystock` :savestock: Saves the currently highlighted stockpile's settings to a file in your Dwarf Fortress folder. This file can be used to copy settings between game saves or @@ -874,7 +878,7 @@ Invoked as:: job-material -Intended to be used as a keybinding: +:dfhack-keybind:`job-material` * In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, changes the material of the job. Only inorganic materials can be used @@ -887,6 +891,8 @@ job-duplicate In :kbd:`q` mode, when a job is highlighted within a workshop or furnace building, calling ``job-duplicate`` instantly duplicates the job. +:dfhack-keybind:`job-duplicate` + .. _autogems: autogems @@ -1076,6 +1082,8 @@ spotclean Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal if you want to keep that bloody entrance ``clean map`` would clean up. +:dfhack-keybind:`spotclean` + .. _autodump: autodump @@ -1098,10 +1106,16 @@ Options: :destroy-here: As ``destroy``, but only the selected item in the :kbd:`k` list, or inside a container. Alias ``autodump-destroy-here``, for keybindings. + :dfhack-keybind:`autodump-destroy-here` :visible: Only process items that are not hidden. :hidden: Only process hidden items. :forbidden: Only process forbidden items (default: only unforbidden). +``autodump-destroy-item`` destroys the selected item, which may be selected +in the :kbd:`k` list, or inside a container. If called again before the game +is resumed, cancels destruction of the item. +:dfhack-keybind:`autodump-destroy-item` + cleanowned ========== @@ -1139,6 +1153,8 @@ Options: :prefs: Show dwarf preferences summary :reload: Reload configuration file (``dfhack-config/dwarfmonitor.json``) +:dfhack-keybind:`dwarfmonitor` + Widget configuration: The following types of widgets (defined in :file:`hack/lua/plugins/dwarfmonitor.lua`) @@ -1264,6 +1280,8 @@ zone ==== Helps a bit with managing activity zones (pens, pastures and pits) and cages. +:dfhack-keybind:`zone` + Options: :set: Set zone or cage under cursor as default for future assigns. @@ -1738,6 +1756,8 @@ Basic commands: to remove designations, for if you accidentally set 50 levels at once. :diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. +:dfhack-keybind:`digv` + .. _digexp: digexp @@ -2209,6 +2229,8 @@ Usage: * When viewing unit details, body-swaps into that unit. * In the main adventure mode screen, reverts transient swap. +:dfhack-keybind:`adv-bodyswap` + .. _createitem: createitem From f170b70fde3e5dfca7e9b98123c2ac2c8087a7f6 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Fri, 2 Sep 2016 20:43:04 +1000 Subject: [PATCH 0262/1012] Expand plugin docs for workNow --- docs/Plugins.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 54a9ccb81..37cad8a94 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1242,7 +1242,16 @@ Options: workNow ======= -Force all dwarves to look for a job immediately, or as soon as the game is unpaused. +Don't allow dwarves to idle if any jobs are available. + +When workNow is active, every time the game pauses, DF will make dwarves +perform any appropriate available jobs. This includes when you one step +through the game using the pause menu. Usage: + +:workNow: print workNow status +:workNow 0: deactivate workNow +:workNow 1: activate workNow (look for jobs on pause, and only then) +:workNow 2: make dwarves look for jobs whenever a job completes .. _seedwatch: From 75d7773a945d66e3cdda4c6c84685a7f93abff58 Mon Sep 17 00:00:00 2001 From: Jon Pamala Illo Date: Fri, 21 Oct 2016 10:26:20 +0530 Subject: [PATCH 0263/1012] Move set_df_path.vbs into the directories where it's actually useful. --- build/{ => win32}/set_df_path.vbs | 0 build/win64/set_df_path.vbs | 32 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) rename build/{ => win32}/set_df_path.vbs (100%) create mode 100644 build/win64/set_df_path.vbs diff --git a/build/set_df_path.vbs b/build/win32/set_df_path.vbs similarity index 100% rename from build/set_df_path.vbs rename to build/win32/set_df_path.vbs diff --git a/build/win64/set_df_path.vbs b/build/win64/set_df_path.vbs new file mode 100644 index 000000000..1d494a2e5 --- /dev/null +++ b/build/win64/set_df_path.vbs @@ -0,0 +1,32 @@ +Option Explicit + +Const BIF_returnonlyfsdirs = &H0001 + +Dim wsh, objDlg, objF, fso, spoFile +Set objDlg = WScript.CreateObject("Shell.Application") +Set objF = objDlg.BrowseForFolder (&H0,"Select your DF folder", BIF_returnonlyfsdirs) + +Set fso = CreateObject("Scripting.FileSystemObject") +If fso.FileExists("DF_PATH.txt") Then + fso.DeleteFile "DF_PATH.txt", True +End If + +If IsValue(objF) Then + If InStr(1, TypeName(objF), "Folder") > 0 Then + Set spoFile = fso.CreateTextFile("DF_PATH.txt", True) + spoFile.WriteLine(objF.Self.Path) + End If +End If + +Function IsValue(obj) + ' Check whether the value has been returned. + Dim tmp + On Error Resume Next + tmp = " " & obj + If Err <> 0 Then + IsValue = False + Else + IsValue = True + End If + On Error GoTo 0 +End Function \ No newline at end of file From f586692ed67371272f134f2c993913a9b56e2a5c Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 21 Oct 2016 14:52:26 +0200 Subject: [PATCH 0264/1012] plugins/ruby: update for 64bits --- plugins/ruby/codegen.pl | 30 ++++++------ plugins/ruby/ruby-autogen-defs.rb | 79 ++++++++++++++++++++++++------- plugins/ruby/ruby.cpp | 60 +++++++++++++++++++---- 3 files changed, 126 insertions(+), 43 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index eb9828490..3a9db2c61 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -227,7 +227,7 @@ sub render_global_class { $seen_class{$name}++; local $compound_off = 0; - $compound_off = 4 if ($meta eq 'class-type'); + $compound_off = $SIZEOF_PTR if ($meta eq 'class-type'); $compound_off = sizeof($global_types{$parent}) if $parent; local $current_typename = $rbname; @@ -244,7 +244,7 @@ sub render_global_class { indent_rb { my $sz = sizeof($type); # see comment is sub sizeof ; but gcc has sizeof(cls) aligned - $sz = align_field($sz, 4) if $os eq 'linux' and $meta eq 'class-type'; + $sz = align_field($sz, $SIZEOF_PTR) if $os eq 'linux' and $meta eq 'class-type'; push @lines_rb, "sizeof $sz\n"; push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name; @@ -425,8 +425,8 @@ sub render_class_vmethods_voff { for my $meth ($vms->findnodes('child::vmethod')) { - $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; - $voff += 4; + $voff += $SIZEOF_PTR if $meth->getAttribute('is-destructor') and $os eq 'linux'; + $voff += $SIZEOF_PTR; } return $voff; @@ -470,8 +470,8 @@ sub render_class_vmethods { } # on linux, the destructor uses 2 entries - $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; - $voff += 4; + $voff += $SIZEOF_PTR if $meth->getAttribute('is-destructor') and $os eq 'linux'; + $voff += $SIZEOF_PTR; } } @@ -598,14 +598,14 @@ sub align_field { sub get_field_align { my ($field) = @_; - my $al = 4; + my $al = $SIZEOF_PTR; my $meta = $field->getAttribute('ld:meta'); if ($meta eq 'number') { $al = sizeof($field); - # linux aligns int64_t to 4, windows to 8 + # linux aligns int64_t to $SIZEOF_PTR, windows to 8 # floats are 4 bytes so no pb - $al = 4 if ($al > 4 and ($os eq 'linux' or $al != 8)); + $al = 4 if ($al > 4 and (($os eq 'linux' and $arch == 32) or $al != 8)); } elsif ($meta eq 'global') { $al = get_global_align($field); } elsif ($meta eq 'compound') { @@ -800,11 +800,9 @@ sub sizeof_compound { $sizeof_cache{$typename} = $SIZEOF_LONG if $typename; return $SIZEOF_LONG; } - else { - print "$st type $base\n" if $base !~ /int(\d+)_t/; - $sizeof_cache{$typename} = $1/8 if $typename; - return $1/8; - } + print "$st type $base\n" if $base !~ /int(\d+)_t/; + $sizeof_cache{$typename} = $1/8 if $typename; + return $1/8; } if ($field->getAttribute('is-union')) @@ -820,11 +818,11 @@ sub sizeof_compound { my $parent = $field->getAttribute('inherits-from'); my $off = 0; - $off = 4 if ($meta eq 'class-type'); + $off = $SIZEOF_PTR if ($meta eq 'class-type'); $off = sizeof($global_types{$parent}) if ($parent); my $al = 1; - $al = 4 if ($meta eq 'class-type'); + $al = $SIZEOF_PTR if ($meta eq 'class-type'); for my $f ($field->findnodes('child::ld:field')) { diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 7e26e93ac..c1fb07ac1 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -1,5 +1,32 @@ # definition of classes used by ruby-autogen +$sizeof_ptr = case RUBY_PLATFORM + when /x86_64|x64/i; 64 + else 32 + end + module DFHack + def self.memory_read_int64(addr) + (memory_read_int32(addr) & 0xffffffff) + (memory_read_int32(addr+4) << 32) + end + def self.memory_write_int64(addr, v) + memory_write_int32(addr, v & 0xffffffff) ; memory_write_int32(addr+4, v>>32) + end + if $sizeof_ptr == 64 + def self.memory_read_ptr(addr) + memory_read_int64(addr) & 0xffffffff_ffffffff + end + def self.memory_write_ptr(addr, v) + memory_write_int64(addr, v) + end + else + def self.memory_read_ptr(addr) + memory_read_int32(addr) & 0xffffffff + end + def self.memory_write_ptr(addr, v) + memory_write_int32(addr, v) + end + end + module MemHack INSPECT_SIZE_LIMIT=16384 class MemStruct @@ -62,6 +89,8 @@ module DFHack case tglen when 1; StlVector8.new(tg) when 2; StlVector16.new(tg) + when 4; StlVector32.new(tg) + when 8; StlVector64.new(tg) else StlVector32.new(tg) end end @@ -207,10 +236,10 @@ module DFHack def _get v = case @_bits + when 64; DFHack.memory_read_int64(@_memaddr) when 32; DFHack.memory_read_int32(@_memaddr) when 16; DFHack.memory_read_int16(@_memaddr) when 8; DFHack.memory_read_int8( @_memaddr) - when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32) end v &= (1 << @_bits) - 1 if not @_signed v = @_enum.sym(v) if @_enum @@ -220,10 +249,10 @@ module DFHack def _set(v) v = @_enum.int(v) if @_enum case @_bits + when 64; DFHack.memory_write_int64(@_memaddr, v) when 32; DFHack.memory_write_int32(@_memaddr, v) when 16; DFHack.memory_write_int16(@_memaddr, v) when 8; DFHack.memory_write_int8( @_memaddr, v) - when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32) end end @@ -299,11 +328,11 @@ module DFHack end def _getp - DFHack.memory_read_int32(@_memaddr) & 0xffffffff + DFHack.memory_read_ptr(@_memaddr) end def _setp(v) - DFHack.memory_write_int32(@_memaddr, v) + DFHack.memory_write_ptr(@_memaddr, v) end def _get @@ -316,8 +345,8 @@ module DFHack # XXX shaky... def _set(v) case v - when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) - when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) + when Pointer; DFHack.memory_write_ptr(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_ptr(@_memaddr, v._memaddr) when Integer if @_tg and @_tg.kind_of?(MemHack::Number) if _getp == 0 @@ -325,9 +354,9 @@ module DFHack end @_tg._at(_getp)._set(v) else - DFHack.memory_write_int32(@_memaddr, v) + DFHack.memory_write_ptr(@_memaddr, v) end - when nil; DFHack.memory_write_int32(@_memaddr, 0) + when nil; DFHack.memory_write_ptr(@_memaddr, 0) else @_tg._at(_getp)._set(v) end end @@ -353,7 +382,7 @@ module DFHack def _getp(i=0) delta = (i != 0 ? i*@_tglen : 0) - (DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta + DFHack.memory_read_ptr(@_memaddr) + delta end def _get @@ -364,10 +393,10 @@ module DFHack def _set(v) case v - when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) - when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) - when Integer; DFHack.memory_write_int32(@_memaddr, v) - when nil; DFHack.memory_write_int32(@_memaddr, 0) + when Pointer; DFHack.memory_write_ptr(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_ptr(@_memaddr, v._memaddr) + when Integer; DFHack.memory_write_ptr(@_memaddr, v) + when nil; DFHack.memory_write_ptr(@_memaddr, 0) else raise "cannot PointerAry._set(#{v.inspect})" end end @@ -557,6 +586,20 @@ module DFHack end end end + class StlVector64 < StlVector32 + def length + DFHack.memory_vector64_length(@_memaddr) + end + def valueptr_at(idx) + DFHack.memory_vector64_ptrat(@_memaddr, idx) + end + def insert_at(idx, val) + DFHack.memory_vector64_insertat(@_memaddr, idx, val) + end + def delete_at(idx) + DFHack.memory_vector64_deleteat(@_memaddr, idx) + end + end class StlVector16 < StlVector32 def length DFHack.memory_vector16_length(@_memaddr) @@ -733,8 +776,8 @@ module DFHack @_tg = tg end - field(:_ptr, 0) { number 32, false } - field(:_length, 4) { number 16, false } + field(:_ptr, 0) { number $sizeof_ptr, false } + field(:_length, $sizeof_ptr/8) { number 16, false } def length ; _length ; end def size ; _length ; end @@ -769,8 +812,8 @@ module DFHack end field(:_ptr, 0) { pointer } - field(:_prev, 4) { pointer } - field(:_next, 8) { pointer } + field(:_prev, $sizeof_ptr/8) { pointer } + field(:_next, 2*$sizeof_ptr/8) { pointer } def item # With the current xml structure, currently _tg designate @@ -946,7 +989,7 @@ module DFHack def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0, a5=0) this = obj._memaddr vt = df.get_vtable_ptr(this) - fptr = df.memory_read_int32(vt + voff) & 0xffffffff + fptr = df.memory_read_ptr(vt + voff) vmethod_do_call(this, fptr, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3), vmethod_arg(a4), vmethod_arg(a5)) end diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 276e5052b..70250a5eb 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -284,16 +284,15 @@ static command_result df_rubyeval(color_ostream &out, std::vector // this code should work with ruby1.9, but ruby1.9 doesn't like running // in a dedicated non-main thread, so use ruby1.8 binaries only for now -// these ruby definitions are invalid for windows 64bit (need long long) -typedef unsigned long VALUE; -typedef unsigned long ID; +typedef uintptr_t VALUE; +typedef uintptr_t ID; #define Qfalse ((VALUE)0) #define Qtrue ((VALUE)2) #define Qnil ((VALUE)4) -#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1)) -#define FIX2INT(i) (((long)i) >> 1) +#define INT2FIX(i) ((VALUE)((((intptr_t)i) << 1) | 1)) +#define FIX2INT(i) (((intptr_t)i) >> 1) #define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func) void (*ruby_init_stack)(VALUE*); @@ -313,9 +312,9 @@ VALUE (*rb_eval_string_protect)(const char*, int*); VALUE (*rb_ary_shift)(VALUE); VALUE (*rb_float_new)(double); double (*rb_num2dbl)(VALUE); -VALUE (*rb_int2inum)(long); -VALUE (*rb_uint2inum)(unsigned long); -unsigned long (*rb_num2ulong)(VALUE); +VALUE (*rb_int2inum)(intptr_t); // XXX check on win64 long vs intptr_t +VALUE (*rb_uint2inum)(uintptr_t); +uintptr_t (*rb_num2ulong)(VALUE); // end of rip(ruby.h) DFHack::DFLibrary *libruby_handle; @@ -582,12 +581,27 @@ static VALUE rb_dfget_vtable(VALUE self, VALUE name) static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) { char *ptr = (char*)rb_num2ulong(vptr); -#ifdef WIN32 +#if defined(_WIN64) + // win64 + char *rtti = *(char**)(ptr - 0x8); + char *typeinfo = Core::getInstance().p->getBase() + *(uint32_t*)(rtti + 0xC); + // skip the .?AV, trim @@ from end + return rb_str_new(typeinfo+0x14, strlen(typeinfo+0x14)-2); +#elif defined(WIN32) + // win32 char *rtti = *(char**)(ptr - 0x4); char *typeinfo = *(char**)(rtti + 0xC); // skip the .?AV, trim @@ from end return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2); +#elif defined(__amd64__) || defined(__x86_64__) + // lin64 + char *typeinfo = *(char**)(ptr - 0x8); + char *typestring = *(char**)(typeinfo + 0x8); + while (*typestring >= '0' && *typestring <= '9') + typestring++; + return rb_str_new(typestring, strlen(typestring)); #else + // lin32 char *typeinfo = *(char**)(ptr - 0x4); char *typestring = *(char**)(typeinfo + 0x4); while (*typestring >= '0' && *typestring <= '9') @@ -909,6 +923,30 @@ static VALUE rb_dfmemory_vec32_deleteat(VALUE self, VALUE addr, VALUE idx) return Qtrue; } +// vector +static VALUE rb_dfmemory_vec64_length(VALUE self, VALUE addr) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + return rb_uint2inum(v->size()); +} +static VALUE rb_dfmemory_vec64_ptrat(VALUE self, VALUE addr, VALUE idx) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); +} +static VALUE rb_dfmemory_vec64_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val)); + return Qtrue; +} +static VALUE rb_dfmemory_vec64_deleteat(VALUE self, VALUE addr, VALUE idx) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + v->erase(v->begin()+FIX2INT(idx)); + return Qtrue; +} + // vector static VALUE rb_dfmemory_vecbool_new(VALUE self) { @@ -1136,6 +1174,10 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2); rb_define_singleton_method(rb_cDFHack, "memory_vector32_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insertat), 3); rb_define_singleton_method(rb_cDFHack, "memory_vector32_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_deleteat), 2); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_length", RUBY_METHOD_FUNC(rb_dfmemory_vec64_length), 1); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_ptrat), 2); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_insertat), 3); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_deleteat), 2); rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_new", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_new), 0); rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 1); rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_init", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_init), 1); From 97f29229cd8fc330c65bdf55cee33c19524dc68b Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 21 Oct 2016 17:00:02 +0200 Subject: [PATCH 0265/1012] ruby: fix weird freeze when printing large strings to the console on linux64 --- plugins/ruby/ruby.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 850ca0912..7709276c8 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -2,14 +2,24 @@ module Kernel def puts(*a) a.flatten.each { |l| - DFHack.print_str(l.to_s.chomp + "\n") + # XXX looks like print_str crashes with strings longer than 4096... maybe not nullterminated ? + # this workaround fixes it + s = l.to_s.chomp + "\n" + while s.length > 0 + DFHack.print_str(s[0, 4000]) + s[0, 4000] = '' + end } nil end def puts_err(*a) a.flatten.each { |l| - DFHack.print_err(l.to_s.chomp + "\n") + s = l.to_s.chomp + "\n" + while s.length > 0 + DFHack.print_err(s[0, 4000]) + s[0, 4000] = '' + end } nil end From 3df74de021e864c97ac75a8a33978470cd3d8f51 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 21 Oct 2016 11:46:53 -0400 Subject: [PATCH 0266/1012] Fix rb_dfget_vtable_ptr on x64 This was causing rb_dfget_rtti_classname to receive a truncated pointer and crash --- plugins/ruby/ruby.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 70250a5eb..bb3b7fe30 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -593,17 +593,10 @@ static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) char *typeinfo = *(char**)(rtti + 0xC); // skip the .?AV, trim @@ from end return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2); -#elif defined(__amd64__) || defined(__x86_64__) - // lin64 - char *typeinfo = *(char**)(ptr - 0x8); - char *typestring = *(char**)(typeinfo + 0x8); - while (*typestring >= '0' && *typestring <= '9') - typestring++; - return rb_str_new(typestring, strlen(typestring)); #else - // lin32 - char *typeinfo = *(char**)(ptr - 0x4); - char *typestring = *(char**)(typeinfo + 0x4); + // linux/osx 32/64 + char *typeinfo = *(char**)(ptr - sizeof(void*)); + char *typestring = *(char**)(typeinfo + sizeof(void*)); while (*typestring >= '0' && *typestring <= '9') typestring++; return rb_str_new(typestring, strlen(typestring)); @@ -612,8 +605,7 @@ static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) { - // actually, rb_dfmemory_read_int32 - return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr)); + return rb_uint2inum(*(uintptr_t*)rb_num2ulong(objptr)); } // run a dfhack command, as if typed from the dfhack console From a0b0c16a8b184e85dd31d8d2c9900223e703fba6 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 21 Oct 2016 22:55:28 +0530 Subject: [PATCH 0267/1012] Actually apply the designation changes in RemoteFortressReader --- plugins/remotefortressreader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index bbf998704..05ad56e75 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -2895,5 +2895,7 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in } mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); } + + mc.WriteAll(); return CR_OK; } From 33ca7638f2c23cb3860c0aed88e0aca327f2e47d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 22 Oct 2016 13:04:15 -0400 Subject: [PATCH 0268/1012] Make DFHack libraries take priority on Linux Fixes #1008 --- package/linux/dfhack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/linux/dfhack b/package/linux/dfhack index b7ce1d812..7c01fcbd7 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -59,7 +59,7 @@ fi # Now run -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack" +export LD_LIBRARY_PATH="./hack/libs:./hack:$LD_LIBRARY_PATH" PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}./hack/libdfhack.so" From 4feab67a9311c80c262d39ed7875835cb52723d8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 23 Oct 2016 18:28:41 -0400 Subject: [PATCH 0269/1012] Fix Python 2 compatibility Ref #972 --- conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf.py b/conf.py index b3f8949e7..ed62ad5c6 100644 --- a/conf.py +++ b/conf.py @@ -38,7 +38,7 @@ def get_keybinds(): if l.startswith('keybinding add')] keybindings = dict() for k in lines: - first, command = k.split(' ', maxsplit=1) + first, command = k.split(' ', 1) bind, context = (first.split('@') + [''])[:2] if ' ' not in command: command = command.replace('"', '') From 5c83c16a99516d2ef0d9364705ca83a558a7524e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 24 Oct 2016 10:06:41 -0400 Subject: [PATCH 0270/1012] Fix memview x64 address display --- plugins/devel/memview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index 27dc72861..b93319a07 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -69,7 +69,7 @@ void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream for(size_t i=0;i Date: Mon, 24 Oct 2016 10:06:55 -0400 Subject: [PATCH 0271/1012] Fix mismatched backquotes in History.rst --- docs/History.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/History.rst b/docs/History.rst index b399fbbdd..e789d48c6 100644 --- a/docs/History.rst +++ b/docs/History.rst @@ -221,7 +221,8 @@ DFHack 0.40.11-r1 ================= Internals -- Plugins on OS X now use ``.plug.dylib` as an extension instead of ``.plug.so`` +--------- +- Plugins on OS X now use ``.plug.dylib`` as an extension instead of ``.plug.so`` Fixes ----- From 4fdbba0207f1dbabedb86c26fca200d082b82871 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 24 Oct 2016 22:28:51 -0400 Subject: [PATCH 0272/1012] Only touch protobuf generated files if they actually changed --- library/CMakeLists.txt | 28 +++++++++++++++++++++++++--- library/proto/.gitignore | 1 + library/proto/tmp/.gitignore | 1 + plugins/CMakeLists.txt | 26 +++++++++++++++++++++++--- plugins/proto/.gitignore | 1 + plugins/proto/tmp/.gitignore | 1 + 6 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 library/proto/tmp/.gitignore create mode 100644 plugins/proto/tmp/.gitignore diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 0958a6efc..cb919f935 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -215,20 +215,42 @@ ENDIF() # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) +SET(PROTO_STATUS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/proto/status.txt) STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}") STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}") +STRING(REPLACE "/proto/" "/proto/tmp/" PROJECT_PROTO_TMP_FILES "${PROJECT_PROTO_SRCS};${PROJECT_PROTO_HDRS}") +SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + PROPERTIES GENERATED TRUE) + +# Force a re-gen if any *.pb.* files are missing +IF(EXISTS ${PROTO_STATUS_FILE}) + FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) + IF(NOT EXISTS ${file}) + MESSAGE("Resetting generate_proto_core because '${file}' is missing") + FILE(REMOVE ${PROTO_STATUS_FILE}) + BREAK() + ENDIF() + ENDFOREACH() +ENDIF() LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_HDRS}) LIST(APPEND PROJECT_SOURCES ${PROJECT_PROTO_SRCS}) ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + OUTPUT ${PROTO_STATUS_FILE} COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ - --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/ + --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PROJECT_PROTO_TMP_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ + COMMAND ${CMAKE_COMMAND} -E touch ${PROTO_STATUS_FILE} + COMMENT "Generating core protobufs" DEPENDS protoc-bin ${PROJECT_PROTOS} ) +ADD_CUSTOM_TARGET(generate_proto_core DEPENDS ${PROTO_STATUS_FILE}) + # Merge headers into sources SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE ) LIST(APPEND PROJECT_SOURCES ${PROJECT_HEADERS}) @@ -299,7 +321,7 @@ ADD_CUSTOM_TARGET(git-describe ADD_DEPENDENCIES(dfhack-version git-describe) ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) -ADD_DEPENDENCIES(dfhack generate_headers) +ADD_DEPENDENCIES(dfhack generate_headers generate_proto_core) ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${PROJECT_PROTO_SRCS}) ADD_DEPENDENCIES(dfhack-client dfhack) diff --git a/library/proto/.gitignore b/library/proto/.gitignore index befabf79d..3a96bb3d9 100644 --- a/library/proto/.gitignore +++ b/library/proto/.gitignore @@ -1,3 +1,4 @@ *.pb.cc *.pb.cc.rule *.pb.h +status.txt diff --git a/library/proto/tmp/.gitignore b/library/proto/tmp/.gitignore new file mode 100644 index 000000000..75feca5b1 --- /dev/null +++ b/library/proto/tmp/.gitignore @@ -0,0 +1 @@ +*.pb.* diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9522491dd..f0bb847bf 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -42,18 +42,38 @@ install(DIRECTORY raw/ # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) +SET(PROTO_STATUS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/proto/status.txt) STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}") STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}") +STRING(REPLACE "/proto/" "/proto/tmp/" PROJECT_PROTO_TMP_FILES "${PROJECT_PROTO_SRCS};${PROJECT_PROTO_HDRS}") +SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + PROPERTIES GENERATED TRUE) + +# Force a re-gen if any *.pb.* files are missing +IF(EXISTS ${PROTO_STATUS_FILE}) + FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) + IF(NOT EXISTS ${file}) + MESSAGE("Resetting generate_proto because '${file}' is missing") + FILE(REMOVE ${PROTO_STATUS_FILE}) + BREAK() + ENDIF() + ENDFOREACH() +ENDIF() ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + OUTPUT ${PROTO_STATUS_FILE} COMMAND protoc-bin -I=${dfhack_SOURCE_DIR}/library/proto/ -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ - --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ + --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PROJECT_PROTO_TMP_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ + COMMAND ${CMAKE_COMMAND} -E touch ${PROTO_STATUS_FILE} + COMMENT "Generating plugin protobufs" DEPENDS protoc-bin ${PROJECT_PROTOS} ) -add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS}) +add_custom_target(generate_proto DEPENDS ${PROTO_STATUS_FILE}) SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) diff --git a/plugins/proto/.gitignore b/plugins/proto/.gitignore index befabf79d..3a96bb3d9 100644 --- a/plugins/proto/.gitignore +++ b/plugins/proto/.gitignore @@ -1,3 +1,4 @@ *.pb.cc *.pb.cc.rule *.pb.h +status.txt diff --git a/plugins/proto/tmp/.gitignore b/plugins/proto/tmp/.gitignore new file mode 100644 index 000000000..75feca5b1 --- /dev/null +++ b/plugins/proto/tmp/.gitignore @@ -0,0 +1 @@ +*.pb.* From 2c230f0d3e38579c56640751df871cc93fc06878 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 24 Oct 2016 22:51:27 -0400 Subject: [PATCH 0273/1012] Improve protobuf file regeneration (no longer uses a dummy status.txt file) --- library/CMakeLists.txt | 23 ++++++++++------------- library/proto/.gitignore | 1 - plugins/CMakeLists.txt | 23 ++++++++++------------- plugins/proto/.gitignore | 1 - 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index cb919f935..ed5bfdcd2 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -215,7 +215,6 @@ ENDIF() # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) -SET(PROTO_STATUS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/proto/status.txt) STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}") STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}") STRING(REPLACE "/proto/" "/proto/tmp/" PROJECT_PROTO_TMP_FILES "${PROJECT_PROTO_SRCS};${PROJECT_PROTO_HDRS}") @@ -223,33 +222,31 @@ SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} PROPERTIES GENERATED TRUE) # Force a re-gen if any *.pb.* files are missing -IF(EXISTS ${PROTO_STATUS_FILE}) - FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) - IF(NOT EXISTS ${file}) - MESSAGE("Resetting generate_proto_core because '${file}' is missing") - FILE(REMOVE ${PROTO_STATUS_FILE}) - BREAK() - ENDIF() - ENDFOREACH() -ENDIF() +# (only runs when cmake is run, but better than nothing) +FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) + IF(NOT EXISTS ${file}) + # MESSAGE("Resetting generate_proto_core because '${file}' is missing") + FILE(REMOVE ${PROJECT_PROTO_TMP_FILES}) + BREAK() + ENDIF() +ENDFOREACH() LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_HDRS}) LIST(APPEND PROJECT_SOURCES ${PROJECT_PROTO_SRCS}) ADD_CUSTOM_COMMAND( - OUTPUT ${PROTO_STATUS_FILE} + OUTPUT ${PROJECT_PROTO_TMP_FILES} COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_PROTO_TMP_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/ - COMMAND ${CMAKE_COMMAND} -E touch ${PROTO_STATUS_FILE} COMMENT "Generating core protobufs" DEPENDS protoc-bin ${PROJECT_PROTOS} ) -ADD_CUSTOM_TARGET(generate_proto_core DEPENDS ${PROTO_STATUS_FILE}) +ADD_CUSTOM_TARGET(generate_proto_core DEPENDS ${PROJECT_PROTO_TMP_FILES}) # Merge headers into sources SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE ) diff --git a/library/proto/.gitignore b/library/proto/.gitignore index 3a96bb3d9..befabf79d 100644 --- a/library/proto/.gitignore +++ b/library/proto/.gitignore @@ -1,4 +1,3 @@ *.pb.cc *.pb.cc.rule *.pb.h -status.txt diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f0bb847bf..612a49715 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -42,7 +42,6 @@ install(DIRECTORY raw/ # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) -SET(PROTO_STATUS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/proto/status.txt) STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}") STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}") STRING(REPLACE "/proto/" "/proto/tmp/" PROJECT_PROTO_TMP_FILES "${PROJECT_PROTO_SRCS};${PROJECT_PROTO_HDRS}") @@ -50,18 +49,17 @@ SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} PROPERTIES GENERATED TRUE) # Force a re-gen if any *.pb.* files are missing -IF(EXISTS ${PROTO_STATUS_FILE}) - FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) - IF(NOT EXISTS ${file}) - MESSAGE("Resetting generate_proto because '${file}' is missing") - FILE(REMOVE ${PROTO_STATUS_FILE}) - BREAK() - ENDIF() - ENDFOREACH() -ENDIF() +# (only runs when cmake is run, but better than nothing) +FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) + IF(NOT EXISTS ${file}) + # MESSAGE("Resetting generate_proto because '${file}' is missing") + FILE(REMOVE ${PROJECT_PROTO_TMP_FILES}) + BREAK() + ENDIF() +ENDFOREACH() ADD_CUSTOM_COMMAND( - OUTPUT ${PROTO_STATUS_FILE} + OUTPUT ${PROJECT_PROTO_TMP_FILES} COMMAND protoc-bin -I=${dfhack_SOURCE_DIR}/library/proto/ -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ @@ -69,11 +67,10 @@ ADD_CUSTOM_COMMAND( COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_PROTO_TMP_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/ - COMMAND ${CMAKE_COMMAND} -E touch ${PROTO_STATUS_FILE} COMMENT "Generating plugin protobufs" DEPENDS protoc-bin ${PROJECT_PROTOS} ) -add_custom_target(generate_proto DEPENDS ${PROTO_STATUS_FILE}) +ADD_CUSTOM_TARGET(generate_proto DEPENDS ${PROJECT_PROTO_TMP_FILES}) SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) diff --git a/plugins/proto/.gitignore b/plugins/proto/.gitignore index 3a96bb3d9..befabf79d 100644 --- a/plugins/proto/.gitignore +++ b/plugins/proto/.gitignore @@ -1,4 +1,3 @@ *.pb.cc *.pb.cc.rule *.pb.h -status.txt From cffd3be591be530438a58f22f27675a3390a6948 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 16:34:20 -0500 Subject: [PATCH 0274/1012] Move labormanager out of dev --- plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/{devel => }/labormanager.cpp | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename plugins/{devel => }/labormanager.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c24b940b9..97491a90d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -110,6 +110,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(lair lair.cpp) + DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index a1e5b7f14..e5001fff7 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -9,7 +9,6 @@ DFHACK_PLUGIN(counters counters.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) -DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(kittens kittens.cpp) DFHACK_PLUGIN(memview memview.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) diff --git a/plugins/devel/labormanager.cpp b/plugins/labormanager.cpp similarity index 100% rename from plugins/devel/labormanager.cpp rename to plugins/labormanager.cpp From 18235da9d60f16de276bd0f7bc8ccd522a0bf129 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 16:45:25 -0500 Subject: [PATCH 0275/1012] add a misisng labor rule for leather crafts --- plugins/labormanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 7d41c7814..5a2774cd2 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1001,6 +1001,8 @@ private: return df::unit_labor::WOOD_CRAFT; case df::item_type::CLOTH: return df::unit_labor::CLOTHESMAKER; + case df::item_type::SKIN_TANNED: + return df::unit_labor::LEATHER; default: debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); From 9aa6b84e24097b5596dcbe739be7d2aa8a38a404 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 16:55:37 -0500 Subject: [PATCH 0276/1012] alphabetical order --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 97491a90d..13425d2c6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -109,8 +109,8 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) - DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(labormanager labormanager.cpp) + DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) From a04ed641b723e9a8d664280e07c9dbf805910b6d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 17:03:45 -0500 Subject: [PATCH 0277/1012] SPACES not TABS --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 13425d2c6..6763c2c56 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -109,7 +109,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) - DFHACK_PLUGIN(labormanager labormanager.cpp) + DFHACK_PLUGIN(labormanager labormanager.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) From 385d34490bd51d3395018e7c7d7200a2ea05cad8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 18:02:07 -0500 Subject: [PATCH 0278/1012] Docs for labormanager --- docs/Plugins.rst | 100 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index ac73bb58f..c8f24e12d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -840,7 +840,105 @@ Examples: labormanager ============ -A more advanced alternative to `autolabor`. +Automatically manage dwarf labors to efficiently complete jobs. +Labormanager is derived from autolabor (above) but uses a completely +different approach to assigning jobs to dwarves. While autolabor tries +to keep as many dwarves busy as possible, labormanager instead strives +to get jobs done as quickly as possible. + +Labormanager frequently scans the current job list, current list of +dwarfs, and the map to determine how many dwarves need to be assigned to +what labors in order to meet all current labor needs without starving +any particular type of job. + +.. warning:: + + *As with autolabor, labormanager will override any manual changes you + make to labors while it is enabled, including through other tools such + as Dwarf Therapist* + +Simple usage: + +:enable labormanager: Enables the plugin with default settings. +(Persistent per fortress) :disable labormanager: Disables the plugin. + +Anything beyond this is optional - autolabor works fairly well on the +default settings. + +The default priorities for each labor vary (some labors are higher +priority by default than others). The way the plugin works is that, once +it determines how many of each labor is needed, it then sorts them by +adjusted priority. (Labors other than hauling have a bias added to them +based on how long it's been since they were last used, to prevent job +starvation.) The labor with the highest priority is selected, the "best +fit" dwarf for that labor is assigned to that labor, and then its +priority is *halved*. This process is repeated until either dwarfs or +labors run out. + +Because there is no easy way to detect how many haulers are actually +needed at any moment, the plugin always ensures that at least one dwarf +is assigned to each of the hauling labors, even if no hauling jobs are +detected. At least one dwarf is always assigned to construction removing +and cleaning because these jobs also cannot be easily detected. Lever +pulling is always assigned to everyone. Any dwarfs for which there are +no jobs will be assigned hauling, lever pulling, and cleaning labors. If +you use animal trainers, note that labormanager + +Labormanager also sometimes assigns extra labors to currently busy +dwarfs so that when they finish their current job, they will go off and +do something useful instead of standing around waiting for a job. + +There is special handling to ensure that at least one dwarf is assigned +to haul food whenever food is detected left in a place where it will rot +if not stored. This will cause a dwarf to go idle if you have no +storepiles to haul food to. + +Dwarfs who are unable to work (child, in the military, wounded, +handless, asleep, in a meeting) are entirely excluded from labor +assignment. Any dwarf explicitly assigned to a burrow will also be +completely ignored by labormanager. + +The fitness algorithm for assigning jobs to dwarfs generally attempts to +favor dwarfs who are more skilled over those who are less skilled. It +also tries to avoid assigning female dwarfs with children to jobs that +are "outside", favors assigning "outside" jobs to dwarfs who are +carrying a tool that could be used as a weapon, and tries to minimize +how often dwarfs have to reequip. + +Labormanager automatically determines medical needs and reserves health +care providers as needed. Note that this may cause idling if you have +injured dwarfs but no or inadequate hospital facilities. + +Hunting is never assigned without a butchery, and fishing is never +assigned without a fishery, and neither of these labors is assigned +unless specifically enabled. + +The method by which labormanager determines what labor is needed for a +particular job is complicated and, in places, incomplete. In some +situations, labormanager will detect that it cannot determine what labor +is required. It will, by default, pause and print an error message on +the dfhack console, followed by the message "LABORMANAGER: Game paused +so you can investigate the above message.". If this happens, please open +an issue on github, reporting the lines that immediately preceded this +message. You can tell labormanager to ignore this error and carry on by +typing "autolabor pause-on-error no", but be warned that some job may go +undone in this situation. + +Advanced usage: + +:autolabor enable: Turn plugin on. +:autolabor disable: Turn plugin off. +:autolabor priority : Set the priority value (see above) for labor to . +:autolabor reset : Reset the priority value of labor to its default. +:autolabor reset-all: Reset all priority values to their defaults. +:autolabor allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. +:autolabor forbid-fishing: Forbid dwarfs from fishing. Default behavior. +:autolabor allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. +:autolabor forbid-hunting: Forbid dwarfs from hunting. Default behavior. +:autolabor list: Show current priorities and current allocation stats. +:autolabor pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. +:autolabor pause-on-error no: Allow labormanager to continue past a labor inference engine failure. + .. _autohauler: From 6383ca13bf86bfda4c06e5067cd2dd2fa315d108 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 18:10:29 -0500 Subject: [PATCH 0279/1012] Finish clipped sentence. (erk.) --- docs/Plugins.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c8f24e12d..13ea50661 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -882,7 +882,10 @@ detected. At least one dwarf is always assigned to construction removing and cleaning because these jobs also cannot be easily detected. Lever pulling is always assigned to everyone. Any dwarfs for which there are no jobs will be assigned hauling, lever pulling, and cleaning labors. If -you use animal trainers, note that labormanager +you use animal trainers, note that labormanager will misbehave if you +assign specific trainers to specific animals; results are only guaranteed +if you use "any trainer", and animal trainers will probably be +overallocated in any case. Labormanager also sometimes assigns extra labors to currently busy dwarfs so that when they finish their current job, they will go off and @@ -926,18 +929,18 @@ undone in this situation. Advanced usage: -:autolabor enable: Turn plugin on. -:autolabor disable: Turn plugin off. -:autolabor priority : Set the priority value (see above) for labor to . -:autolabor reset : Reset the priority value of labor to its default. -:autolabor reset-all: Reset all priority values to their defaults. -:autolabor allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. -:autolabor forbid-fishing: Forbid dwarfs from fishing. Default behavior. -:autolabor allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. -:autolabor forbid-hunting: Forbid dwarfs from hunting. Default behavior. -:autolabor list: Show current priorities and current allocation stats. -:autolabor pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. -:autolabor pause-on-error no: Allow labormanager to continue past a labor inference engine failure. +:labormanager enable: Turn plugin on. +:labormanager disable: Turn plugin off. +:labormanager priority : Set the priority value (see above) for labor to . +:labormanager reset : Reset the priority value of labor to its default. +:labormanager reset-all: Reset all priority values to their defaults. +:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. +:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. +:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. +:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. +:labormanager list: Show current priorities and current allocation stats. +:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. +:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. .. _autohauler: From defedb351ec9fc96cebc926f565cd088fdca320c Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 26 Oct 2016 09:34:14 -0400 Subject: [PATCH 0280/1012] Fix raw_vcall crash on Linux/OS X x64 --- plugins/ruby/ruby.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index bb3b7fe30..6d3f5d8e4 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -1086,7 +1086,8 @@ __declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, static int raw_vcall(void *that, void *fptr, unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { - int (*t_fptr)(void *me, int, int, int, int, int, int); + int (*t_fptr)(void *me, unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, unsigned long); t_fptr = (decltype(t_fptr))fptr; return t_fptr(that, a0, a1, a2, a3, a4, a5); } From cf329c7cebad4a1fcbb1860e1332a7dace6a9e8d Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 26 Oct 2016 20:40:47 -0400 Subject: [PATCH 0281/1012] Ruby: fix Qnil and Qtrue constants with 64-bit Ruby 2.x These have different values on x64 Ruby 2.x (see USE_FLONUM in ruby.h in the ruby source). This was causing dump_rb_error to crash, since it was walking an array until it got to Qnil (but thinking Qnil was 4 instead of 8) and trying to print each element as a string. There were probably more subtle issues with Qnil and Qtrue being wrong too. --- plugins/ruby/ruby.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 6d3f5d8e4..10e63c706 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -287,9 +287,15 @@ static command_result df_rubyeval(color_ostream &out, std::vector typedef uintptr_t VALUE; typedef uintptr_t ID; -#define Qfalse ((VALUE)0) -#define Qtrue ((VALUE)2) -#define Qnil ((VALUE)4) +static struct { + int major; + int minor; + int teeny; +} libruby_version; + +static VALUE Qfalse; +static VALUE Qtrue; +static VALUE Qnil; #define INT2FIX(i) ((VALUE)((((intptr_t)i) << 1) | 1)) #define FIX2INT(i) (((intptr_t)i) >> 1) @@ -337,6 +343,26 @@ static int df_loadruby(void) return 0; } + const char *ruby_version = (const char*)LookupPlugin(libruby_handle, "ruby_version"); + if (!ruby_version) + return 0; + sscanf(ruby_version, "%d.%d.%d", + &libruby_version.major, &libruby_version.minor, &libruby_version.teeny); + + if (libruby_version.major >= 2 && sizeof(VALUE) >= sizeof(double)) + { + // USE_FLONUM defined on x64 + Qfalse = (VALUE)0; + Qtrue = (VALUE)0x14; + Qnil = (VALUE)0x08; + } + else + { + Qfalse = (VALUE)0; + Qtrue = (VALUE)2; + Qnil = (VALUE)4; + } + // ruby_sysinit is optional (ruby1.9 only) ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit"); #define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0 @@ -359,6 +385,7 @@ static int df_loadruby(void) rbloadsym(rb_uint2inum); rbloadsym(rb_num2ulong); #undef rbloadsym + // rb_float_new_in_heap in ruby 2 if (!((rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new"))) || (rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new_in_heap"))))) return 0; From d62d26379302dea38819681295cd235de4ab8a61 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 26 Oct 2016 20:55:16 -0400 Subject: [PATCH 0282/1012] ruby: Fix potential onupdate crash when cur_year is missing --- plugins/ruby/ruby.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 10e63c706..97a4a4553 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -214,15 +214,17 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (!onupdate_active) return CR_OK; - if (df::global::cur_year && (*df::global::cur_year < onupdate_minyear)) + using namespace df::global; + + if (cur_year && (*cur_year < onupdate_minyear)) return CR_OK; - if (df::global::cur_year_tick && onupdate_minyeartick >= 0 && - (*df::global::cur_year == onupdate_minyear && - *df::global::cur_year_tick < onupdate_minyeartick)) + if (cur_year && cur_year_tick && onupdate_minyeartick >= 0 && + (*cur_year == onupdate_minyear && + *cur_year_tick < onupdate_minyeartick)) return CR_OK; - if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && - (*df::global::cur_year == onupdate_minyear && - *df::global::cur_year_tick_advmode < onupdate_minyeartickadv)) + if (cur_year && cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && + (*cur_year == onupdate_minyear && + *cur_year_tick_advmode < onupdate_minyeartickadv)) return CR_OK; return plugin_eval_ruby(out, "DFHack.onupdate"); From cdf24efe43c09ecb92fed56565a5a6cb100c7238 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Thu, 27 Oct 2016 23:35:29 +1100 Subject: [PATCH 0283/1012] Remove plugins - all broken since 34.11 This changes nothing at all as far as any DFHack user is concerned, as these plugins have not been possible to build since DF 34.11 - and would have to be rewritten for compatiblity with new native-DF systems. "treefarm" is additionally replaced by "autochop". In the unlikely event that a developer wants the source code for something... that's what version control is for! --- plugins/CMakeLists.txt | 3 - plugins/advtools.cpp | 826 ----------------------------------------- plugins/misery.cpp | 218 ----------- plugins/treefarm.cpp | 175 --------- 4 files changed, 1222 deletions(-) delete mode 100644 plugins/advtools.cpp delete mode 100644 plugins/misery.cpp delete mode 100644 plugins/treefarm.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 612a49715..c9f120867 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -82,7 +82,6 @@ OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON) if (BUILD_SUPPORTED) DFHACK_PLUGIN(3dveins 3dveins.cpp) DFHACK_PLUGIN(add-spatter add-spatter.cpp) -# DFHACK_PLUGIN(advtools advtools.cpp) DFHACK_PLUGIN(autochop autochop.cpp) DFHACK_PLUGIN(autodump autodump.cpp) DFHACK_PLUGIN(autogems autogems.cpp) @@ -133,7 +132,6 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(mode mode.cpp) - #DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(plants plants.cpp) @@ -160,7 +158,6 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(title-folder title-folder.cpp) DFHACK_PLUGIN(title-version title-version.cpp) DFHACK_PLUGIN(trackstop trackstop.cpp) -# DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp) add_subdirectory(tweak) DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua) diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp deleted file mode 100644 index 77cc02934..000000000 --- a/plugins/advtools.cpp +++ /dev/null @@ -1,826 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "modules/World.h" -#include "modules/Translation.h" -#include "modules/Materials.h" -#include "modules/Maps.h" -#include "modules/Items.h" -#include "modules/Gui.h" -#include "modules/Units.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/ui_advmode.h" -#include "df/item.h" -#include "df/unit.h" -#include "df/unit_inventory_item.h" -#include "df/map_block.h" -#include "df/nemesis_record.h" -#include "df/historical_figure.h" -#include "df/general_ref_is_nemesisst.h" -#include "df/general_ref_contains_itemst.h" -#include "df/general_ref_contained_in_itemst.h" -#include "df/general_ref_unit_holderst.h" -#include "df/general_ref_building_civzone_assignedst.h" -#include "df/material.h" -#include "df/craft_material_class.h" -#include "df/viewscreen_optionst.h" -#include "df/viewscreen_dungeonmodest.h" -#include "df/viewscreen_dungeon_monsterstatusst.h" -#include "df/nemesis_flags.h" - -#include - -using namespace DFHack; -using namespace df::enums; - -using df::nemesis_record; -using df::historical_figure; - -using namespace DFHack::Translation; -/* -advtools -======== -A package of different adventure mode tools. Usage: - -:list-equipped [all]: List armor and weapons equipped by your companions. - If all is specified, also lists non-metal clothing. -:metal-detector [all-types] [non-trader]: - Reveal metal armor and weapons in shops. The options - disable the checks on item type and being in shop. -*/ - -DFHACK_PLUGIN("advtools"); -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(ui_advmode); - -/********************* - * PLUGIN INTERFACE * - *********************/ - -static bool bodyswap_hotkey(df::viewscreen *top); - -command_result adv_bodyswap (color_ostream &out, std::vector & parameters); -command_result adv_tools (color_ostream &out, std::vector & parameters); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - if (!ui_advmode) - return CR_OK; - - commands.push_back(PluginCommand( - "advtools", "Adventure mode tools.", - adv_tools, false, - " list-equipped [all]\n" - " List armor and weapons equipped by your companions.\n" - " If all is specified, also lists non-metal clothing.\n" - " metal-detector [all-types] [non-trader]\n" - " Reveal metal armor and weapons in shops. The options\n" - " disable the checks on item type and being in shop.\n" - )); - - commands.push_back(PluginCommand( - "adv-bodyswap", "Change the adventurer unit.", - adv_bodyswap, bodyswap_hotkey, - " - When viewing unit details, body-swaps into that unit.\n" - " - In the main adventure mode screen, reverts transient swap.\n" - "Options:\n" - " force\n" - " Allow swapping into non-companion units.\n" - " permanent\n" - " Permanently change the unit to be the adventurer.\n" - " Otherwise it will revert if adv-bodyswap is called\n" - " in the main screen, or if the main menu, Fast Travel\n" - " or Sleep/Wait screen is opened.\n" - " noinherit\n" - " In permanent mode, don't reassign companions to the new unit.\n" - )); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap); - -DFHACK_PLUGIN_IS_ENABLED(in_transient_swap); - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_WORLD_LOADED: - case SC_WORLD_UNLOADED: - in_transient_swap = false; - break; - default: - break; - } - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - // Revert transient swaps before trouble happens - if (in_transient_swap) - { - auto screen = Core::getTopViewscreen(); - bool revert = false; - - if (strict_virtual_cast(screen)) - { - using namespace df::enums::ui_advmode_menu; - - switch (ui_advmode->menu) - { - case Travel: - case Sleep: - revert = true; - break; - default: - break; - } - } - else if (strict_virtual_cast(screen)) - { - // Options may mean save game - revert = true; - } - - if (revert) - { - getPlayerNemesis(out, true); - in_transient_swap = false; - } - } - - return CR_OK; -} - -/********************* - * UTILITY FUNCTIONS * - *********************/ - -static bool bodyswap_hotkey(df::viewscreen *top) -{ - return !!virtual_cast(top) || - !!virtual_cast(top); -} - -bool bodySwap(color_ostream &out, df::unit *player) -{ - if (!player) - { - out.printerr("Unit to swap is NULL\n"); - return false; - } - - auto &vec = world->units.active; - - int idx = linear_index(vec, player); - if (idx < 0) - { - out.printerr("Unit to swap not found: %d\n", player->id); - return false; - } - - if (idx != 0) - std::swap(vec[0], vec[idx]); - - return true; -} - -df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) -{ - auto real_nemesis = vector_get(world->nemesis.all, ui_advmode->player_id); - if (!real_nemesis || !real_nemesis->unit) - { - out.printerr("Invalid player nemesis id: %d\n", ui_advmode->player_id); - return NULL; - } - - if (restore_swap) - { - df::unit *ctl = world->units.active[0]; - auto ctl_nemesis = Units::getNemesis(ctl); - - if (ctl_nemesis != real_nemesis) - { - if (!bodySwap(out, real_nemesis->unit)) - return NULL; - - auto name = TranslateName(&real_nemesis->unit->name, false); - out.print("Returned into the body of %s.\n", name.c_str()); - } - - real_nemesis->unit->relations.group_leader_id = -1; - in_transient_swap = false; - } - - return real_nemesis; -} - -void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis) -{ - auto &cvec = new_nemesis->companions; - - // Swap companions - cvec.swap(old_nemesis->companions); - - vector_erase_at(cvec, linear_index(cvec, new_nemesis->id)); - insert_into_vector(cvec, old_nemesis->id); - - // Update follow - new_nemesis->group_leader_id = -1; - new_nemesis->unit->relations.group_leader_id = -1; - - for (unsigned i = 0; i < cvec.size(); i++) - { - auto nm = df::nemesis_record::find(cvec[i]); - if (!nm) - continue; - - nm->group_leader_id = new_nemesis->id; - if (nm->unit) - nm->unit->relations.group_leader_id = new_nemesis->unit_id; - } -} - -void copyAcquaintances(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis) -{ - auto &svec = old_nemesis->unit->adventurer_knows; - auto &tvec = new_nemesis->unit->adventurer_knows; - - for (unsigned i = 0; i < svec.size(); i++) - insert_into_vector(tvec, svec[i]); - - insert_into_vector(tvec, old_nemesis->unit_id); -} - -void sortCompanionNemesis(std::vector *list, int player_id = -1) -{ - std::map table; - std::vector output; - - output.reserve(list->size()); - - if (player_id < 0) - { - auto real_nemesis = vector_get(world->nemesis.all, ui_advmode->player_id); - if (real_nemesis) - player_id = real_nemesis->id; - } - - // Index records; find the player - for (size_t i = 0; i < list->size(); i++) - { - auto item = (*list)[i]; - if (item->id == player_id) - output.push_back(item); - else - table[item->figure->id] = item; - } - - // Pull out the items by the persistent sort order - auto &order_vec = ui_advmode->companions.all_histfigs; - for (size_t i = 0; i < order_vec.size(); i++) - { - auto it = table.find(order_vec[i]); - if (it == table.end()) - continue; - output.push_back(it->second); - table.erase(it); - } - - // The remaining ones in reverse id order - for (auto it = table.rbegin(); it != table.rend(); ++it) - output.push_back(it->second); - - list->swap(output); -} - -void listCompanions(color_ostream &out, std::vector *list, bool units = true) -{ - nemesis_record *player = getPlayerNemesis(out, false); - if (!player) - return; - - list->push_back(player); - - for (size_t i = 0; i < player->companions.size(); i++) - { - auto item = nemesis_record::find(player->companions[i]); - if (item && (item->unit || !units)) - list->push_back(item); - } -} - -std::string getUnitNameProfession(df::unit *unit) -{ - std::string name = TranslateName(&unit->name, false) + ", "; - if (unit->custom_profession.empty()) - name += ENUM_ATTR_STR(profession, caption, unit->profession); - else - name += unit->custom_profession; - return name; -} - -enum InventoryMode { - INV_HAULED, - INV_WEAPON, - INV_WORN, - INV_IN_CONTAINER -}; - -typedef std::pair inv_item; - -static void listContainerInventory(std::vector *list, df::item *container) -{ - auto &refs = container->general_refs; - for (size_t i = 0; i < refs.size(); i++) - { - auto ref = refs[i]; - if (!strict_virtual_cast(ref)) - continue; - - df::item *child = ref->getItem(); - if (!child) continue; - - list->push_back(inv_item(child, INV_IN_CONTAINER)); - listContainerInventory(list, child); - } -} - -void listUnitInventory(std::vector *list, df::unit *unit) -{ - auto &items = unit->inventory; - for (size_t i = 0; i < items.size(); i++) - { - auto item = items[i]; - InventoryMode mode; - - switch (item->mode) { - case df::unit_inventory_item::Hauled: - mode = INV_HAULED; - break; - case df::unit_inventory_item::Weapon: - mode = INV_WEAPON; - break; - default: - mode = INV_WORN; - } - - list->push_back(inv_item(item->item, mode)); - listContainerInventory(list, item->item); - } -} - -bool isShopItem(df::item *item) -{ - for (size_t k = 0; k < item->general_refs.size(); k++) - { - auto ref = item->general_refs[k]; - if (virtual_cast(ref)) - return true; - } - - return false; -} - -bool isWeaponArmor(df::item *item) -{ - using namespace df::enums::item_type; - - switch (item->getType()) { - case HELM: - case ARMOR: - case WEAPON: - case AMMO: - case GLOVES: - case PANTS: - case SHOES: - return true; - default: - return false; - } -} - -int containsMetalItems(df::item *item, bool all, bool non_trader, bool rec = false) -{ - int cnt = 0; - - auto &refs = item->general_refs; - for (size_t i = 0; i < refs.size(); i++) - { - auto ref = refs[i]; - - if (strict_virtual_cast(ref)) - return 0; - if (!rec && strict_virtual_cast(ref)) - return 0; - - if (strict_virtual_cast(ref)) - { - df::item *child = ref->getItem(); - if (!child) continue; - - cnt += containsMetalItems(child, all, non_trader, true); - } - } - - if (!non_trader && !isShopItem(item)) - return cnt; - if (!all && !isWeaponArmor(item)) - return cnt; - - MaterialInfo minfo(item); - if (minfo.getCraftClass() != craft_material_class::Metal) - return cnt; - - return ++cnt; -} - -void joinCounts(std::map &counts) -{ - for (auto it = counts.begin(); it != counts.end(); it++) - { - df::coord pt = it->first; - while (pt.x > 0 && counts.count(pt - df::coord(1,0,0))) - pt.x--; - while (pt.y > 0 &&counts.count(pt - df::coord(0,1,0))) - pt.y--; - while (pt.x < 0 && counts.count(pt + df::coord(1,0,0))) - pt.x++; - while (pt.y < 0 &&counts.count(pt + df::coord(0,1,0))) - pt.y++; - - if (pt == it->first) - continue; - - counts[pt] += it->second; - it->second = 0; - } -} - -/********************* - * FORMATTING * - *********************/ - -static void printCompanionHeader(color_ostream &out, size_t i, df::unit *unit) -{ - out.color(COLOR_GREY); - - if (i < 28) - out << char('a'+i); - else - out << i; - - out << ": " << getUnitNameProfession(unit); - if (unit->flags1.bits.dead) - out << " (DEAD)"; - if (unit->flags3.bits.ghostly) - out << " (GHOST)"; - out << endl; - - out.reset_color(); -} - -static size_t formatSize(std::vector *out, const std::map in, size_t *cnt) -{ - size_t len = 0; - - for (auto it = in.begin(); it != in.end(); ++it) - { - std::string line = it->first; - if (it->second != 1) - line += stl_sprintf(" [%d]", it->second); - len = std::max(len, line.size()); - out->push_back(line); - } - - if (out->empty()) - { - out->push_back("(none)"); - len = 6; - } - - if (cnt) - *cnt = std::max(*cnt, out->size()); - - return len; -} - -static std::string formatDirection(df::coord delta) -{ - std::string ns, ew, dir; - - if (delta.x > 0) - ew = "E"; - else if (delta.x < 0) - ew = "W"; - - if (delta.y > 0) - ns = "S"; - else if (delta.y < 0) - ns = "N"; - - if (abs(delta.x) > abs(delta.y)*5) - dir = ew; - else if (abs(delta.y) > abs(delta.x)*5) - dir = ns; - else if (abs(delta.x) > abs(delta.y)*2) - dir = ew + ns + ew; - else if (abs(delta.y) > abs(delta.x)*2) - dir = ns + ns + ew; - else if (delta.x || delta.y) - dir = ns + ew; - else - dir = "***"; - - int dist = (int)sqrt((double)(delta.x*delta.x + delta.y*delta.y)); - return stl_sprintf("%d away %s %+d", dist, dir.c_str(), delta.z); -} - -static void printEquipped(color_ostream &out, df::unit *unit, bool all) -{ - std::vector items; - listUnitInventory(&items, unit); - - std::map head, body, legs, weapons; - - for (auto it = items.begin(); it != items.end(); ++it) - { - df::item *item = it->first; - - // Skip non-worn non-weapons - ItemTypeInfo iinfo(item); - - bool is_weapon = (it->second == INV_WEAPON || iinfo.type == item_type::AMMO); - if (!(is_weapon || it->second == INV_WORN)) - continue; - - // Skip non-metal, unless all - MaterialInfo minfo(item); - df::craft_material_class mclass = minfo.getCraftClass(); - - bool is_metal = (mclass == craft_material_class::Metal); - if (!(is_weapon || all || is_metal)) - continue; - - // Format the name - std::string name; - if (is_metal) - name = minfo.toString() + " "; - else if (mclass != craft_material_class::None) - name = toLower(ENUM_KEY_STR(craft_material_class,mclass)) + " "; - name += iinfo.toString(); - - // Add to the right table - int count = item->getStackSize(); - - if (is_weapon) - { - weapons[name] += count; - continue; - } - - switch (iinfo.type) { - case item_type::HELM: - head[name] += count; - break; - case item_type::ARMOR: - case item_type::GLOVES: - case item_type::BACKPACK: - case item_type::QUIVER: - body[name] += count; - break; - case item_type::PANTS: - case item_type::SHOES: - legs[name] += count; - break; - default: - weapons[name] += count; - } - } - - std::vector cols[4]; - size_t sizes[4]; - size_t lines = 0; - - sizes[0] = formatSize(&cols[0], head, &lines); - sizes[1] = formatSize(&cols[1], body, &lines); - sizes[2] = formatSize(&cols[2], legs, &lines); - sizes[3] = formatSize(&cols[3], weapons, &lines); - - for (size_t i = 0; i < lines; i++) - { - for (int j = 0; j < 4; j++) - { - size_t sz = std::max(sizes[j], size_t(18)); - out << "| " << std::left << std::setw(sz) << vector_get(cols[j],i) << " "; - } - - out << "|" << std::endl; - } -} - -/********************* - * COMMANDS * - *********************/ - -command_result adv_bodyswap (color_ostream &out, std::vector & parameters) -{ - // HOTKEY COMMAND; CORE IS SUSPENDED - bool force = false; - bool permanent = false; - bool no_make_leader = false; - - for (unsigned i = 0; i < parameters.size(); i++) - { - auto &item = parameters[i]; - - if (item == "force") - force = true; - else if (item == "permanent") - permanent = true; - else if (item == "noinherit") - no_make_leader = true; - else - return CR_WRONG_USAGE; - } - - // Get the real player; undo previous transient swap - auto real_nemesis = getPlayerNemesis(out, true); - if (!real_nemesis) - return CR_FAILURE; - - // Get the unit to swap to - auto new_unit = Gui::getSelectedUnit(out, true); - auto new_nemesis = Units::getNemesis(new_unit); - - if (!new_nemesis) - { - if (new_unit) - { - out.printerr("Cannot swap into a non-historical unit.\n"); - return CR_FAILURE; - } - - return CR_OK; - } - - if (new_nemesis == real_nemesis) - return CR_OK; - - // Verify it's a companion - if (!force && linear_index(real_nemesis->companions, new_nemesis->id) < 0) - { - out.printerr("This is not your companion - use force to bodyswap.\n"); - return CR_FAILURE; - } - - // Swap - if (!bodySwap(out, new_nemesis->unit)) - return CR_FAILURE; - - auto name = TranslateName(&new_nemesis->unit->name, false); - out.print("Swapped into the body of %s.\n", name.c_str()); - - // Permanently re-link everything - if (permanent) - { - using namespace df::enums::nemesis_flags; - - ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis); - - // Flag 0 appears to be the 'active adventurer' flag, and - // the player_id field above seems to be computed using it - // when a savegame is loaded. - // Also, unless this is set, it is impossible to retire. - real_nemesis->flags.set(ACTIVE_ADVENTURER, false); - new_nemesis->flags.set(ACTIVE_ADVENTURER, true); - - real_nemesis->flags.set(RETIRED_ADVENTURER, true); // former retired adventurer - new_nemesis->flags.set(ADVENTURER, true); // blue color in legends - - // Reassign companions and acquaintances - if (!no_make_leader) - { - changeGroupLeader(new_nemesis, real_nemesis); - copyAcquaintances(new_nemesis, real_nemesis); - } - } - else - { - in_transient_swap = true; - - // Make the player unit follow around to avoid bad consequences - // if it is unloaded before the transient swap is reverted. - real_nemesis->unit->relations.group_leader_id = new_nemesis->unit_id; - } - - return CR_OK; -} - -command_result adv_tools (color_ostream &out, std::vector & parameters) -{ - if (parameters.empty()) - return CR_WRONG_USAGE; - - CoreSuspender suspend; - - const auto &command = parameters[0]; - if (command == "list-equipped") - { - bool all = false; - for (size_t i = 1; i < parameters.size(); i++) - { - if (parameters[i] == "all") - all = true; - else - return CR_WRONG_USAGE; - } - - std::vector list; - - listCompanions(out, &list); - sortCompanionNemesis(&list); - - for (size_t i = 0; i < list.size(); i++) - { - auto item = list[i]; - auto unit = item->unit; - - printCompanionHeader(out, i, unit); - printEquipped(out, unit, all); - } - - return CR_OK; - } - else if (command == "metal-detector") - { - bool all = false, non_trader = false; - for (size_t i = 1; i < parameters.size(); i++) - { - if (parameters[i] == "all-types") - all = true; - else if (parameters[i] == "non-trader") - non_trader = true; - else - return CR_WRONG_USAGE; - } - - auto *player = getPlayerNemesis(out, false); - if (!player) - return CR_FAILURE; - - df::coord player_pos = player->unit->pos; - - int total = 0; - std::map counts; - - auto &items = world->items.all; - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - - int num = containsMetalItems(item, all, non_trader); - if (!num) - continue; - - df::map_block *block = Maps::getTileBlock(item->pos); - if (!block) - continue; - - total += num; - counts[(item->pos - player_pos)/10] += num; - - auto &designations = block->designation; - auto &dgn = designations[item->pos.x%16][item->pos.y%16]; - - dgn.bits.hidden = 0; // revealed - dgn.bits.pile = 1; // visible - } - - joinCounts(counts); - - out.print("%d items of metal merchandise found in the vicinity.\n", total); - for (auto it = counts.begin(); it != counts.end(); it++) - { - if (!it->second) - continue; - - df::coord delta = it->first * 10; - out.print(" %s: %d\n", formatDirection(delta).c_str(), it->second); - } - - return CR_OK; - } - else - return CR_WRONG_USAGE; -} diff --git a/plugins/misery.cpp b/plugins/misery.cpp deleted file mode 100644 index a4468079c..000000000 --- a/plugins/misery.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "PluginManager.h" -#include "Export.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/ui.h" -#include "df/unit.h" -#include "df/unit_thought.h" -#include "df/unit_thought_type.h" - -#include -#include -#include - -using namespace std; -using namespace DFHack; -/* -misery -====== -When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default). - -Usage: - -:misery enable n: enable misery with optional magnitude n. If specified, n must be positive. -:misery n: same as "misery enable n" -:misery enable: same as "misery enable 2" -:misery disable: stop adding new negative thoughts. This will not remove existing - duplicated thoughts. Equivalent to "misery 1" -:misery clear: remove fake thoughts added in this session of DF. Saving makes them - permanent! Does not change factor. -*/ - -DFHACK_PLUGIN("misery"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(ui); - -static int factor = 1; -static map processedThoughtCountTable; - -//keep track of fake thoughts so you can remove them if requested -static vector > fakeThoughts; -static int count; -const int maxCount = 1000; - -command_result misery(color_ostream& out, vector& parameters); - -DFhackCExport command_result plugin_shutdown(color_ostream& out) { - factor = 1; - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate(color_ostream& out) { - static bool wasLoaded = false; - if ( factor == 1 || !world || !world->map.block_index ) { - if ( wasLoaded ) { - //we just unloaded the game: clear all data - factor = 1; - is_enabled = false; - processedThoughtCountTable.clear(); - fakeThoughts.clear(); - wasLoaded = false; - } - return CR_OK; - } - - if ( !wasLoaded ) { - wasLoaded = true; - } - - if ( count < maxCount ) { - count++; - return CR_OK; - } - count = 0; - - int32_t race_id = ui->race_id; - int32_t civ_id = ui->civ_id; - for ( size_t a = 0; a < world->units.all.size(); a++ ) { - df::unit* unit = world->units.all[a]; //TODO: consider units.active - //living, native units only - if ( unit->race != race_id || unit->civ_id != civ_id ) - continue; - if ( unit->flags1.bits.dead ) - continue; - - int processedThoughtCount; - map::iterator i = processedThoughtCountTable.find(unit->id); - if ( i == processedThoughtCountTable.end() ) { - processedThoughtCount = unit->status.recent_events.size(); - processedThoughtCountTable[unit->id] = processedThoughtCount; - } else { - processedThoughtCount = (*i).second; - } - - if ( processedThoughtCount == unit->status.recent_events.size() ) { - continue; - } else if ( processedThoughtCount > unit->status.recent_events.size() ) { - processedThoughtCount = unit->status.recent_events.size(); - } - - //don't reprocess any old thoughts - vector newThoughts; - for ( size_t b = processedThoughtCount; b < unit->status.recent_events.size(); b++ ) { - df::unit_thought* oldThought = unit->status.recent_events[b]; - const char* bob = ENUM_ATTR(unit_thought_type, value, oldThought->type); - if ( bob[0] != '-' ) { - //out.print("unit %4d: old thought value = %s\n", unit->id, bob); - continue; - } - /*out.print("unit %4d: Duplicating thought type %d (%s), value %s, age %d, subtype %d, severity %d\n", - unit->id, - oldThought->type.value, - ENUM_ATTR(unit_thought_type, caption, (oldThought->type)), - //df::enum_traits::attr_table[oldThought->type].caption - bob, - oldThought->age, - oldThought->subtype, - oldThought->severity - );*/ - //add duplicate thoughts to the temp list - for ( size_t c = 0; c < factor; c++ ) { - df::unit_thought* thought = new df::unit_thought; - thought->type = unit->status.recent_events[b]->type; - thought->age = unit->status.recent_events[b]->age; - thought->subtype = unit->status.recent_events[b]->subtype; - thought->severity = unit->status.recent_events[b]->severity; - newThoughts.push_back(thought); - } - } - for ( size_t b = 0; b < newThoughts.size(); b++ ) { - fakeThoughts.push_back(std::pair(a, unit->status.recent_events.size())); - unit->status.recent_events.push_back(newThoughts[b]); - } - processedThoughtCountTable[unit->id] = unit->status.recent_events.size(); - } - - return CR_OK; -} - -DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { - commands.push_back(PluginCommand("misery", "increase the intensity of negative dwarven thoughts", - &misery, false, - "misery: When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).\n" - "Usage:\n" - " misery enable n\n" - " enable misery with optional magnitude n. If specified, n must be positive.\n" - " misery n\n" - " same as \"misery enable n\"\n" - " misery enable\n" - " same as \"misery enable 2\"\n" - " misery disable\n" - " stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to \"misery 1\"\n" - " misery clear\n" - " remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.\n\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - if (enable != is_enabled) - { - is_enabled = enable; - factor = enable ? 2 : 1; - } - - return CR_OK; -} - -command_result misery(color_ostream &out, vector& parameters) { - if ( !world || !world->map.block_index ) { - out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n"); - return CR_FAILURE; - } - - if ( parameters.size() < 1 || parameters.size() > 2 ) { - return CR_WRONG_USAGE; - } - - if ( parameters[0] == "disable" ) { - if ( parameters.size() > 1 ) { - return CR_WRONG_USAGE; - } - factor = 1; - is_enabled = false; - return CR_OK; - } else if ( parameters[0] == "enable" ) { - is_enabled = true; - factor = 2; - if ( parameters.size() == 2 ) { - int a = atoi(parameters[1].c_str()); - if ( a <= 1 ) { - out.printerr("Second argument must be a positive integer.\n"); - return CR_WRONG_USAGE; - } - factor = a; - } - } else if ( parameters[0] == "clear" ) { - for ( size_t a = 0; a < fakeThoughts.size(); a++ ) { - int dorfIndex = fakeThoughts[a].first; - int thoughtIndex = fakeThoughts[a].second; - world->units.all[dorfIndex]->status.recent_events[thoughtIndex]->age = 1000000; - } - fakeThoughts.clear(); - } else { - int a = atoi(parameters[0].c_str()); - if ( a < 1 ) { - return CR_WRONG_USAGE; - } - factor = a; - is_enabled = factor > 1; - } - - return CR_OK; -} - diff --git a/plugins/treefarm.cpp b/plugins/treefarm.cpp deleted file mode 100644 index 0e24ad28f..000000000 --- a/plugins/treefarm.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "Console.h" -#include "Core.h" -#include "DataDefs.h" -#include "Export.h" -#include "PluginManager.h" - -#include "modules/EventManager.h" -#include "modules/Once.h" - -#include "df/block_burrow.h" -#include "df/block_burrow_link.h" -#include "df/burrow.h" -#include "df/map_block.h" -#include "df/tile_bitmask.h" -#include "df/tile_dig_designation.h" -#include "df/tiletype.h" -#include "df/tiletype_shape.h" -#include "df/world.h" - -//#include "df/world.h" - -using namespace DFHack; -/* -treefarm -======== -Automatically manages special burrows and regularly schedules tree chopping -and digging when appropriate. - -Every time the plugin runs, it checks for burrows with a name containing the -string ``"treefarm"``. For each such burrow, it checks every tile in it for -fully-grown trees and for diggable walls. For each fully-grown tree it finds, -it designates the tree to be chopped, and for each natural wall it finds, it -designates the wall to be dug. - -Usage: - -:treefarm: Enables treefarm monitoring, starting next frame -:treefarm n: Enables treefarm monitoring, starting next frame, and sets - interval to n frames. If n is less than one, disables monitoring. -*/ - -DFHACK_PLUGIN("treefarm"); -DFHACK_PLUGIN_IS_ENABLED(enabled); - -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(ui); - -void checkFarms(color_ostream& out, void* ptr); -command_result treefarm (color_ostream &out, std::vector & parameters); - -EventManager::EventHandler handler(&checkFarms, -1); -int32_t frequency = 1200*30; - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "treefarm", - "automatically manages special burrows and regularly schedules tree chopping and digging when appropriate", - treefarm, - false, //allow non-interactive use - "treefarm\n" - " enables treefarm monitoring, starting next frame\n" - "treefarm n\n" - " enables treefarm monitoring, starting next frame\n" - " sets monitoring interval to n frames\n" - " if n is less than one, disables monitoring\n" - "\n" - "Every time the plugin runs, it checks for burrows with a name containing the string \"treefarm\". For each such burrow, it checks every tile in it for fully-grown trees and for diggable walls. For each fully-grown tree it finds, it designates the tree to be chopped, and for each natural wall it finds, it designates the wall to be dug.\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { - enabled = enable; - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -void checkFarms(color_ostream& out, void* ptr) { - EventManager::unregisterAll(plugin_self); - if ( !enabled ) - return; - EventManager::registerTick(handler, frequency, plugin_self); - CoreSuspender suspend; - - int32_t xOffset = world->map.region_x*3; - int32_t yOffset = world->map.region_y*3; - int32_t zOffset = world->map.region_z; - //for each burrow named treefarm or obsidianfarm, check if you can dig/chop any obsidian/trees - for ( size_t a = 0; a < df::burrow::get_vector().size(); a++ ) { - df::burrow* burrow = df::burrow::get_vector()[a]; - if ( !burrow || burrow->name.find("treefarm") == std::string::npos ) - continue; - - if ( burrow->block_x.size() != burrow->block_y.size() || burrow->block_x.size() != burrow->block_z.size() ) - continue; - - for ( size_t b = 0; b < burrow->block_x.size(); b++ ) { - int32_t x=burrow->block_x[b] - xOffset; - int32_t y=burrow->block_y[b] - yOffset; - int32_t z=burrow->block_z[b] - zOffset; - - df::map_block* block = world->map.block_index[x][y][z]; - if ( !block ) - continue; - - df::block_burrow_link* link = &block->block_burrows; - df::tile_bitmask mask; - for ( ; link != NULL; link = link->next ) { - if ( link->item == NULL ) - continue; - if ( link->item->id == burrow->id ) { - mask = link->item->tile_bitmask; - break; - } - } - if ( link == NULL ) - continue; - - for ( int32_t x = 0; x < 16; x++ ) { - for ( int32_t y = 0; y < 16; y++ ) { - if ( !mask.getassignment(x,y) ) - continue; - df::tiletype type = block->tiletype[x][y]; - df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, type); - if ( !block->designation[x][y].bits.hidden && - shape != df::enums::tiletype_shape::WALL && - shape != df::enums::tiletype_shape::TREE ) - continue; - if ( shape != df::enums::tiletype_shape::TREE ) { - if ( x == 0 && (block->map_pos.x/16) == 0 ) - continue; - if ( y == 0 && (block->map_pos.y/16) == 0 ) - continue; - if ( x == 15 && (block->map_pos.x/16) == world->map.x_count_block-1 ) - continue; - if ( y == 15 && (block->map_pos.y/16) == world->map.y_count_block-1 ) - continue; - } - - block->designation[x][y].bits.dig = df::enums::tile_dig_designation::Default; - } - } - } - } -} - -command_result treefarm (color_ostream &out, std::vector & parameters) -{ - EventManager::unregisterAll(plugin_self); - - if ( parameters.size() > 1 ) - return CR_WRONG_USAGE; - if ( parameters.size() == 1 ) { - int32_t i = atoi(parameters[0].c_str()); - if ( i < 1 ) { - plugin_enable(out, false); - out.print("treefarm disabled\n"); - return CR_OK; - } - plugin_enable(out, true); - frequency = i; - } - - if ( enabled ) { - EventManager::registerTick(handler, 1, plugin_self); - out.print("treefarm enabled with update frequency %d ticks\n", frequency); - } - return CR_OK; -} - From c64000606968a532cc188ee52a613de91cf406bd Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 28 Oct 2016 16:40:14 +0200 Subject: [PATCH 0284/1012] ruby: some more updates for x64, fix df_flagarray size --- plugins/ruby/codegen.pl | 12 +++++--- plugins/ruby/ruby.cpp | 65 ++++++++++++----------------------------- plugins/ruby/ruby.rb | 5 ++-- 3 files changed, 30 insertions(+), 52 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 3a9db2c61..3f5ea4ffe 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -577,10 +577,14 @@ sub render_global_objects { # define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world indent_rb { push @lines_rb, "Global = GlobalObjects.new._at(0)"; - for my $obj (@global_objects) + for my $oname (@global_objects) { - push @lines_rb, "def self.$obj ; Global.$obj ; end"; - push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end"; + push @lines_rb, "if DFHack.get_global_address('$oname') != 0"; + indent_rb { + push @lines_rb, "def self.$oname ; Global.$oname ; end"; + push @lines_rb, "def self.$oname=(v) ; Global.$oname = v ; end"; + }; + push @lines_rb, "end"; } }; } @@ -743,7 +747,7 @@ sub sizeof { } elsif ($subtype eq 'df-linked-list') { return 3 * $SIZEOF_PTR; } elsif ($subtype eq 'df-flagarray') { - return 4 + $SIZEOF_PTR; + return 2 * $SIZEOF_PTR; # XXX length may be 4 on windows? } elsif ($subtype eq 'df-static-flagarray') { return $field->getAttribute('count'); } elsif ($subtype eq 'df-array') { diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 97a4a4553..f9368fdf6 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -214,17 +214,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (!onupdate_active) return CR_OK; - using namespace df::global; - - if (cur_year && (*cur_year < onupdate_minyear)) + if (df::global::cur_year && *df::global::cur_year < onupdate_minyear) return CR_OK; - if (cur_year && cur_year_tick && onupdate_minyeartick >= 0 && - (*cur_year == onupdate_minyear && - *cur_year_tick < onupdate_minyeartick)) + if (df::global::cur_year_tick && onupdate_minyeartick >= 0 && + *df::global::cur_year_tick < onupdate_minyeartick) return CR_OK; - if (cur_year && cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && - (*cur_year == onupdate_minyear && - *cur_year_tick_advmode < onupdate_minyeartickadv)) + if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && + *df::global::cur_year_tick_advmode < onupdate_minyeartickadv) return CR_OK; return plugin_eval_ruby(out, "DFHack.onupdate"); @@ -283,21 +279,13 @@ static command_result df_rubyeval(color_ostream &out, std::vector // - ruby.h with gcc -m32 on linux 64 is broken // so we dynamically load libruby with dlopen/LoadLibrary // lib path is hardcoded here, and by default downloaded by cmake -// this code should work with ruby1.9, but ruby1.9 doesn't like running -// in a dedicated non-main thread, so use ruby1.8 binaries only for now typedef uintptr_t VALUE; typedef uintptr_t ID; -static struct { - int major; - int minor; - int teeny; -} libruby_version; - -static VALUE Qfalse; -static VALUE Qtrue; -static VALUE Qnil; +static VALUE Qfalse = 0; +static VALUE Qtrue = 2; +static VALUE Qnil = 4; #define INT2FIX(i) ((VALUE)((((intptr_t)i) << 1) | 1)) #define FIX2INT(i) (((intptr_t)i) >> 1) @@ -345,26 +333,6 @@ static int df_loadruby(void) return 0; } - const char *ruby_version = (const char*)LookupPlugin(libruby_handle, "ruby_version"); - if (!ruby_version) - return 0; - sscanf(ruby_version, "%d.%d.%d", - &libruby_version.major, &libruby_version.minor, &libruby_version.teeny); - - if (libruby_version.major >= 2 && sizeof(VALUE) >= sizeof(double)) - { - // USE_FLONUM defined on x64 - Qfalse = (VALUE)0; - Qtrue = (VALUE)0x14; - Qnil = (VALUE)0x08; - } - else - { - Qfalse = (VALUE)0; - Qtrue = (VALUE)2; - Qnil = (VALUE)4; - } - // ruby_sysinit is optional (ruby1.9 only) ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit"); #define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0 @@ -465,6 +433,11 @@ static void df_rubythread(void *p) r_result = CR_OK; r_type = RB_IDLE; + // initialize ruby constants (may depend on libruby compilation flags/version) + Qnil = rb_eval_string_protect("nil", &state); + Qtrue = rb_eval_string_protect("true", &state); + Qfalse = rb_eval_string_protect("false", &state); + // load the default ruby-level definitions in the background state=0; rb_eval_string_protect("require './hack/ruby/ruby'", &state); @@ -1085,8 +1058,8 @@ static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set) /* call an arbitrary object virtual method */ #if defined(_WIN32) && !defined(_WIN64) -__declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) +__declspec(naked) static intptr_t raw_vcall(void *that, void *fptr, uintptr_t a0, + uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) { // __thiscall requires that the callee cleans up the stack // here we dont know how many arguments it will take, so @@ -1112,11 +1085,11 @@ __declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, } } #else -static int raw_vcall(void *that, void *fptr, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) +static intptr_t raw_vcall(void *that, void *fptr, uintptr_t a0, + uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) { - int (*t_fptr)(void *me, unsigned long, unsigned long, unsigned long, - unsigned long, unsigned long, unsigned long); + intptr_t (*t_fptr)(void *me, uintptr_t, uintptr_t, uintptr_t, + uintptr_t, uintptr_t, uintptr_t); t_fptr = (decltype(t_fptr))fptr; return t_fptr(that, a0, a1, a2, a3, a4, a5); } diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 7709276c8..c696c23e5 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -109,13 +109,14 @@ module DFHack def onupdate @onupdate_list ||= [] - y = cur_year + y = yt = 0 + y = cur_year rescue 0 ytmax = TICKS_PER_YEAR if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode) yt = cur_year_tick_advmode ytmax *= 144 else - yt = cur_year_tick + yt = cur_year_tick rescue 0 end @onupdate_list.each { |o| From 30801697d924ff15f993466d9f91e4fd637de521 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 29 Oct 2016 00:28:37 +0530 Subject: [PATCH 0285/1012] Send items sitting on the floor through remoteFortressReader --- plugins/proto/RemoteFortressReader.proto | 11 ++++ plugins/remotefortressreader.cpp | 84 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 4c049d1a9..6b730d8c2 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -206,6 +206,16 @@ message SpatterPile repeated Spatter spatters = 1; } +message Item +{ + optional int32 id = 1; + optional Coord pos = 2; + optional uint32 flags1 = 3; + optional uint32 flags2 = 4; + optional MatPair type = 5; + optional MatPair material = 6; +} + message MapBlock { required int32 map_x = 1; @@ -233,6 +243,7 @@ message MapBlock repeated int32 tree_z = 23; repeated TileDigDesignation tile_dig_designation = 24; repeated SpatterPile spatterPile = 25; + repeated Item items = 26; } message MatPair { diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 05ad56e75..5c5068ed2 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -141,6 +141,7 @@ static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); static command_result SendDigCommand(color_ostream &stream, const DigCommand *in); +void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -958,12 +959,45 @@ bool IsspatterChanged(DFCoord pos) return false; } +map itemHashes; + +bool isItemChanged(int i) +{ + uint16_t hash = 0; + if (i >= 0 && i < world->items.all.size()) + { + auto item = world->items.all[i]; + if (item) + { + hash = fletcher16((uint8_t*)item, sizeof(df::item)); + } + } + if (itemHashes[i] != hash) + { + itemHashes[i] = hash; + return true; + } + return false; +} + +bool areItemsChanged(vector * items) +{ + bool result = false; + for (int i = 0; i < items->size(); i++) + { + if (isItemChanged(items->at(i))) + result = true; + } + return result; +} + static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { hashes.clear(); waterHashes.clear(); buildingHashes.clear(); spatterHashes.clear(); + itemHashes.clear(); return CR_OK; } @@ -1417,6 +1451,17 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net continue; auto out_bld = NetBlock->add_buildings(); CopyBuilding(i, out_bld); + df::building_actual* actualBuilding = strict_virtual_cast(bld); + if (actualBuilding) + { + for (int i = 0; i < actualBuilding->contained_items.size(); i++) + { + if (isItemChanged(actualBuilding->contained_items[i]->item->id)) + { + CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); + } + } + } } } @@ -1459,6 +1504,42 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB } } +void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) +{ + NetItem->set_id(DfItem->id); + NetItem->set_flags1(DfItem->flags.whole); + NetItem->set_flags2(DfItem->flags2.whole); + auto pos = NetItem->mutable_pos(); + pos->set_x(DfItem->pos.x); + pos->set_y(DfItem->pos.y); + pos->set_z(DfItem->pos.z); + auto mat = NetItem->mutable_material(); + mat->set_mat_index(DfItem->getMaterialIndex()); + mat->set_mat_type(DfItem->getMaterial()); + auto type = NetItem->mutable_type(); + type->set_mat_type(DfItem->getType()); + type->set_mat_index(DfItem->getSubtype()); +} + +void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + for (int i = 0; i < DfBlock->items.size(); i++) + { + int id = DfBlock->items[i]; + + if (id < 0) + continue; + if (id >= world->items.all.size()) + continue; + + auto item = world->items.all[id]; + CopyItem(NetBlock->add_items(), item); + } +} + static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) { int x, y, z; @@ -1518,6 +1599,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in bool desChanged = IsDesignationChanged(pos); bool spatterChanged = IsspatterChanged(pos); bool buildingChanged = IsBuildingChanged(pos); + bool itemsChanged = areItemsChanged(&block->items); //bool bldChanged = IsBuildingChanged(pos); RemoteFortressReader::MapBlock *net_block; if (tileChanged || desChanged || spatterChanged || buildingChanged) @@ -1533,6 +1615,8 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in CopyBuildings(block, net_block, &MC, pos); if (spatterChanged) Copyspatters(block, net_block, &MC, pos); + if (itemsChanged) + CopyItems(block, net_block, &MC, pos); } } } From 148202bcba8c700647fc7b79acb62a0a3a29a93e Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 29 Oct 2016 08:54:27 +0530 Subject: [PATCH 0286/1012] Use Binsearch for finding items. --- plugins/remotefortressreader.cpp | 42 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 5c5068ed2..0ec2056db 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -964,13 +964,10 @@ map itemHashes; bool isItemChanged(int i) { uint16_t hash = 0; - if (i >= 0 && i < world->items.all.size()) + auto item = df::item::find(i); + if (item) { - auto item = world->items.all[i]; - if (item) - { - hash = fletcher16((uint8_t*)item, sizeof(df::item)); - } + hash = fletcher16((uint8_t*)item, sizeof(df::item)); } if (itemHashes[i] != hash) { @@ -1451,17 +1448,17 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net continue; auto out_bld = NetBlock->add_buildings(); CopyBuilding(i, out_bld); - df::building_actual* actualBuilding = strict_virtual_cast(bld); - if (actualBuilding) - { - for (int i = 0; i < actualBuilding->contained_items.size(); i++) - { - if (isItemChanged(actualBuilding->contained_items[i]->item->id)) - { - CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); - } - } - } + //df::building_actual* actualBuilding = strict_virtual_cast(bld); + //if (actualBuilding) + //{ + // for (int i = 0; i < actualBuilding->contained_items.size(); i++) + // { + // if (isItemChanged(actualBuilding->contained_items[i]->item->id)) + // { + // CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); + // } + // } + //} } } @@ -1530,13 +1527,10 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc { int id = DfBlock->items[i]; - if (id < 0) - continue; - if (id >= world->items.all.size()) - continue; - - auto item = world->items.all[id]; - CopyItem(NetBlock->add_items(), item); + + auto item = df::item::find(id); + if(item) + CopyItem(NetBlock->add_items(), item); } } From ec222c0b245c3d93070635f8ab0f67712fbb729b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 29 Oct 2016 02:35:27 -0400 Subject: [PATCH 0287/1012] Catch exceptions in stockpile (un)serialization From #964, protobuf exceptions in loadstock/savestock would either fail by only logging an error to the console (when run from the Lua UI) or by crashing the game entirely (when run from the console). Figuring out what actually causes the exceptions in the first place (possibly a misunderstood structure layout?) would be a better solution than this, but this will at least stop crashes for now. --- plugins/stockpiles/stockpiles.cpp | 32 +++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 3cd2cb31a..ab9d9c8f7 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -244,11 +244,20 @@ static command_result savestock ( color_ostream &out, vector & paramete cereal.enable_debug ( out ); if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - if ( !cereal.serialize_to_file ( file ) ) + try { - out.printerr ( "could not save to %s\n", file.c_str() ); + if ( !cereal.serialize_to_file ( file ) ) + { + out.printerr ( "could not save to %s\n", file.c_str() ); + return CR_FAILURE; + } + } + catch ( std::exception &e ) + { + out.printerr ( "serialization failed: protobuf exception: %s\n", e.what() ); return CR_FAILURE; } + return CR_OK; } @@ -296,9 +305,17 @@ static command_result loadstock ( color_ostream &out, vector & paramete StockpileSerializer cereal ( sp ); if ( debug ) cereal.enable_debug ( out ); - if ( !cereal.unserialize_from_file ( file ) ) + try { - out.printerr ( "unserialization failed\n" ); + if ( !cereal.unserialize_from_file ( file ) ) + { + out.printerr ( "unserialization failed: %s\n", file.c_str() ); + return CR_FAILURE; + } + } + catch ( std::exception &e ) + { + out.printerr ( "unserialization failed: protobuf exception: %s\n", e.what() ); return CR_FAILURE; } return CR_OK; @@ -508,13 +525,16 @@ static int stockpiles_list_settings ( lua_State *L ) return 1; } +const std::string err_title = "Stockpile Settings Error"; +const std::string err_help = "Does the folder exist?\nCheck the console for more information."; + static void stockpiles_load ( color_ostream &out, std::string filename ) { std::vector params; params.push_back ( filename ); command_result r = loadstock ( out, params ); if ( r != CR_OK ) - show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true ); + show_message_box ( err_title, "Couldn't load. " + err_help, true ); } @@ -524,7 +544,7 @@ static void stockpiles_save ( color_ostream &out, std::string filename ) params.push_back ( filename ); command_result r = savestock ( out, params ); if ( r != CR_OK ) - show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true ); + show_message_box ( err_title, "Couldn't save. " + err_help, true ); } DFHACK_PLUGIN_LUA_FUNCTIONS From ea9992239b551f2d7509fe4d269ea0972c451a8c Mon Sep 17 00:00:00 2001 From: Jon Pamala Illo Date: Sat, 29 Oct 2016 13:31:51 +0530 Subject: [PATCH 0288/1012] Send building items, and send fake item types for boxes and bags. --- plugins/remotefortressreader.cpp | 37 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 0ec2056db..0d642e589 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1138,6 +1138,17 @@ static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(-1); mat_def->set_id(ENUM_KEY_STR(item_type, it)); + if (it == item_type::BOX) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("BOX_CHEST"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("BOX_BAG"); + } int subtypes = Items::getSubtypeCount(it); if (subtypes >= 0) { @@ -1448,17 +1459,17 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net continue; auto out_bld = NetBlock->add_buildings(); CopyBuilding(i, out_bld); - //df::building_actual* actualBuilding = strict_virtual_cast(bld); - //if (actualBuilding) - //{ - // for (int i = 0; i < actualBuilding->contained_items.size(); i++) - // { - // if (isItemChanged(actualBuilding->contained_items[i]->item->id)) - // { - // CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); - // } - // } - //} + df::building_actual* actualBuilding = strict_virtual_cast(bld); + if (actualBuilding) + { + for (int i = 0; i < actualBuilding->contained_items.size(); i++) + { + if (isItemChanged(actualBuilding->contained_items[i]->item->id)) + { + CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); + } + } + } } } @@ -1516,6 +1527,10 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) auto type = NetItem->mutable_type(); type->set_mat_type(DfItem->getType()); type->set_mat_index(DfItem->getSubtype()); + if (DfItem->getType() == item_type::BOX) + { + type->set_mat_index(DfItem->isBag()); + } } void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) From 4e7f2b95796d0d476a19968dd9df84aba63b878d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 29 Oct 2016 19:00:28 -0400 Subject: [PATCH 0289/1012] win64 ruby 2 --- plugins/ruby/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index bfc41c032..208a851e5 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -33,7 +33,12 @@ ELSE() SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) SET(RUBYLIB_INSTALL_NAME "libruby.dll") IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - MESSAGE("No ruby lib for 64-bit Windows yet") + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-libruby200.dll.gz" + "gz" + ${RUBYLIB}.gz + "81db54a8b8b3090c94c6ae2147d30b8f" + ${RUBYLIB} + "8a8564418aebddef3dfee1e96690e713") ELSE() DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" "gz" From 9e97ccbb50401dbbb84a33f34bf8a60cf3414868 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 29 Oct 2016 19:08:54 -0400 Subject: [PATCH 0290/1012] Use system libruby on osx64 (since ruby 1.8 and 2 are supported now) --- plugins/ruby/CMakeLists.txt | 6 ++++-- plugins/ruby/ruby.cpp | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 208a851e5..6b5e62c66 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -2,7 +2,7 @@ IF (APPLE) SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib) SET(RUBYLIB_INSTALL_NAME "libruby.dylib") IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - MESSAGE("No ruby lib for 64-bit OS X yet") + # MESSAGE("No ruby lib for 64-bit OS X yet") ELSE() DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libruby187.dylib.gz" "gz" @@ -73,7 +73,9 @@ ADD_DEPENDENCIES(ruby ruby-autogen-rb) IF(EXISTS ${RUBYLIB}) INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) ELSE() - MESSAGE(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed") + IF(NOT(APPLE AND ${DFHACK_BUILD_ARCH} STREQUAL 64)) + MESSAGE(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed") + ENDIF() ENDIF() INSTALL(DIRECTORY . diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index f9368fdf6..7a21e32eb 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -322,7 +322,11 @@ static int df_loadruby(void) #if defined(WIN32) "./libruby.dll"; #elif defined(__APPLE__) + #ifdef DFHACK64 + "/System/Library/Frameworks/Ruby.framework/Ruby"; + #else "hack/libruby.dylib"; + #endif #else "hack/libruby.so"; #endif From 2e6c7a90be21a0b2e746f3557f5f3716475197c6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 31 Oct 2016 01:58:40 -0400 Subject: [PATCH 0291/1012] fix whitespace --- docs/Plugins.rst | 4 ++-- plugins/ruby/ruby.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 13ea50661..85b6ba58d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -883,8 +883,8 @@ and cleaning because these jobs also cannot be easily detected. Lever pulling is always assigned to everyone. Any dwarfs for which there are no jobs will be assigned hauling, lever pulling, and cleaning labors. If you use animal trainers, note that labormanager will misbehave if you -assign specific trainers to specific animals; results are only guaranteed -if you use "any trainer", and animal trainers will probably be +assign specific trainers to specific animals; results are only guaranteed +if you use "any trainer", and animal trainers will probably be overallocated in any case. Labormanager also sometimes assigns extra labors to currently busy diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 7a21e32eb..0470541e1 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -308,7 +308,7 @@ VALUE (*rb_eval_string_protect)(const char*, int*); VALUE (*rb_ary_shift)(VALUE); VALUE (*rb_float_new)(double); double (*rb_num2dbl)(VALUE); -VALUE (*rb_int2inum)(intptr_t); // XXX check on win64 long vs intptr_t +VALUE (*rb_int2inum)(intptr_t); // XXX check on win64 long vs intptr_t VALUE (*rb_uint2inum)(uintptr_t); uintptr_t (*rb_num2ulong)(VALUE); // end of rip(ruby.h) From 45ff1a0353a36e66ecd581c15213337f82ed9fa1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 31 Oct 2016 02:05:46 -0400 Subject: [PATCH 0292/1012] Add architecture to package names --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cae58c8f..e4911928f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -410,7 +410,7 @@ IF(APPLE) ELSE() set(DFHACK_PACKAGE_PLATFORM_NAME ${CMAKE_SYSTEM_NAME}) ENDIF() -set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}${DFHACK_PACKAGE_SUFFIX}") +set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}-${DFHACK_BUILD_ARCH}${DFHACK_PACKAGE_SUFFIX}") INCLUDE(CPack) #INCLUDE(FindSphinx.cmake) From 4941c06654b74d0baedc9a0c61a23b325505dd8c Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 31 Oct 2016 02:06:06 -0400 Subject: [PATCH 0293/1012] Only use TBZ2 generator on OS X --- CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e4911928f..61b9dce12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -396,11 +396,7 @@ IF(UNIX) execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) string(STRIP ${GCC_VERSION} GCC_VERSION) SET(DFHACK_PACKAGE_SUFFIX "-gcc-${GCC_VERSION}") - if(APPLE) - SET(CPACK_GENERATOR "ZIP;TBZ2") - else() - SET(CPACK_GENERATOR "TBZ2") - endif() + SET(CPACK_GENERATOR "TBZ2") ELSEIF(WIN32) SET(CPACK_GENERATOR "ZIP") ENDIF() From 335607d1f85a055575f1ad7898a6319569a5594b Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 31 Oct 2016 17:38:43 +0530 Subject: [PATCH 0294/1012] Update Stonesense. --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 5db5f9151..0cad9f7b7 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 5db5f9151d4140b53169fcb7bed3383991b33326 +Subproject commit 0cad9f7b7e88a1508aeea0bef848ed5eee39987f From d8f3d7d8f94be8cd3a962e54096fe74ae29e7955 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 31 Oct 2016 22:52:17 +0530 Subject: [PATCH 0295/1012] Send dye color with items in remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader.cpp | 62 ++++++++++++++++++------ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 6b730d8c2..4e6f5ba3e 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -214,6 +214,7 @@ message Item optional uint32 flags2 = 4; optional MatPair type = 5; optional MatPair material = 6; + optional ColorDefinition dye = 7; } message MapBlock diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 0d642e589..dbafb22d6 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -61,6 +61,10 @@ #include "df/graphic.h" #include "df/historical_figure.h" #include "df/item.h" +#include "df/item_constructed.h" +#include "df/item_threadst.h" +#include "df/itemimprovement.h" +#include "df/itemimprovement_threadst.h" #include "df/itemdef.h" #include "df/job.h" #include "df/job_type.h" @@ -295,6 +299,14 @@ void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) ConvertDfColor(index, out); } +void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out) +{ + df::descriptor_color *color = world->raws.language.colors[index]; + out->set_red(color->red * 255); + out->set_green(color->green * 255); + out->set_blue(color->blue * 255); +} + void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build) { df::building * local_build = df::global::world->buildings.all[buildingIndex]; @@ -1030,10 +1042,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->set_name(mat.toString()); //find the name at cave temperature; if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) { - df::descriptor_color *color = raws->language.colors[raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); + ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); } } for (int i = 0; i < 19; i++) @@ -1051,10 +1060,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->set_name(mat.toString()); //find the name at cave temperature; if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) { - df::descriptor_color *color = raws->language.colors[raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); + ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); } } } @@ -1071,10 +1077,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->set_name(mat.toString()); //find the name at cave temperature; if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) { - df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); + ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); } } } @@ -1116,10 +1119,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->set_name(mat.toString()); //find the name at cave temperature; if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) { - df::descriptor_color *color = raws->language.colors[plant->material[j]->state_color[GetState(plant->material[j])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); + ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); } } } @@ -1531,6 +1531,36 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { type->set_mat_index(DfItem->isBag()); } + auto constructed_item = virtual_cast(DfItem); + if(constructed_item) + { + for (int i = 0; i < constructed_item->improvements.size(); i++) + { + auto improvement = constructed_item->improvements[i]; + if (!improvement || improvement->getType() != improvement_type::THREAD) + continue; + + auto improvement_thread = virtual_cast(improvement); + if (!improvement_thread || improvement_thread->dye.mat_type < 0) + continue; + + DFHack::MaterialInfo info; + if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) + continue; + + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + } + else if (DfItem->getType() == item_type::THREAD) + { + auto thread = virtual_cast(DfItem); + if (thread && thread->dye_mat_type >= 0) + { + DFHack::MaterialInfo info; + if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + } } void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) From a50af3e96cec71329ee1d414b846a02d40d4df2f Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 31 Oct 2016 22:52:45 +0530 Subject: [PATCH 0296/1012] Update stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 0cad9f7b7..642e493ef 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 0cad9f7b7e88a1508aeea0bef848ed5eee39987f +Subproject commit 642e493efcf8e8d803186950cd11eef3982d496e From e769041983176d3cf142b8efccad6b407bc92a14 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 1 Nov 2016 23:34:56 +0530 Subject: [PATCH 0297/1012] Fix crash bug in remotefortressreader, that happened every time the user resumed. --- plugins/remotefortressreader.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index dbafb22d6..4caa8e55f 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1641,7 +1641,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in bool itemsChanged = areItemsChanged(&block->items); //bool bldChanged = IsBuildingChanged(pos); RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged || spatterChanged || buildingChanged) + if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged) net_block = out->add_map_blocks(); if (tileChanged) { @@ -1682,20 +1682,6 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in } } } - //for (int yy = in->min_y(); yy < in->max_y(); yy++) - //{ - // for (int xx = in->min_x(); xx < in->max_x(); xx++) - // { - // DFCoord pos = DFCoord(xx, yy, zz); - // df::map_block * block = DFHack::Maps::getBlock(pos); - // if (block == NULL) - // continue; - // { - // RemoteFortressReader::MapBlock *net_block = out->add_map_blocks(); - // CopyBlock(block, net_block, &MC, pos); - // } - // } - //} } MC.trash(); return CR_OK; From 04ad7a0a42fedd3a0a33c4c0b2c9380f1341b900 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Nov 2016 16:23:32 -0400 Subject: [PATCH 0298/1012] Replace some instances of "cmake -E copy_if_different" with a standalone script @JapaMala reported that CMake < 3.5 doesn't support copy_if_different with multiple source files. https://cmake.org/cmake/help/v3.5/release/3.5.html#command-line --- depends/copy-if-different.pl | 30 ++++++++++++++++++++++++++++++ library/CMakeLists.txt | 2 +- plugins/CMakeLists.txt | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100755 depends/copy-if-different.pl diff --git a/depends/copy-if-different.pl b/depends/copy-if-different.pl new file mode 100755 index 000000000..2ba08a80e --- /dev/null +++ b/depends/copy-if-different.pl @@ -0,0 +1,30 @@ +#!/usr/bin/perl + +# A replacement for "cmake -E copy_if_different" that supports multiple files, +# which old cmake versions do not support + +# Usage: copy-if-different.pl src-file [src-file...] dest-dir + +use strict; +use warnings; + +use Digest::SHA; +use File::Basename; +use File::Copy; + +sub sha_file { + my $filename = shift; + my $sha = Digest::SHA->new(256); + $sha->addfile($filename); + return $sha->hexdigest; +} + +my $dest_dir = pop @ARGV or die "no destination dir"; +-d $dest_dir or die "not a directory: $dest_dir"; +my @src_files = @ARGV or die "no source files"; + +foreach my $file (@src_files) { + my $dest = "$dest_dir/" . basename($file); + next if -f $dest && sha_file($file) eq sha_file($dest); + copy($file, $dest); +} diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index ed5bfdcd2..9f8477993 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -239,7 +239,7 @@ ADD_CUSTOM_COMMAND( COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} - COMMAND ${CMAKE_COMMAND} -E copy_if_different + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl ${PROJECT_PROTO_TMP_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/ COMMENT "Generating core protobufs" diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 612a49715..8546c6e89 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -64,7 +64,7 @@ ADD_CUSTOM_COMMAND( -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} - COMMAND ${CMAKE_COMMAND} -E copy_if_different + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl ${PROJECT_PROTO_TMP_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/ COMMENT "Generating plugin protobufs" From 99051f176b6584561cd94ea96cd173be1f0aeed7 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Sun, 6 Nov 2016 10:08:28 +0530 Subject: [PATCH 0299/1012] Add a single bool dfproto message, to pausing and checking pause state --- plugins/proto/RemoteFortressReader.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 4e6f5ba3e..c0ed000c7 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -749,4 +749,9 @@ message DigCommand { optional TileDigDesignation designation = 1; repeated Coord locations = 2; +} + +message SingleBool +{ + optional bool Value = 1; } \ No newline at end of file From 5fb986bde1d5c6600ec5b0d4ac8f1f4677aa31c4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 7 Nov 2016 15:07:27 -0500 Subject: [PATCH 0300/1012] Warn for GCC versions before 4.8 --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61b9dce12..4a47d0721 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,9 @@ macro(CHECK_GCC COMPILER_PATH) # http://developerblog.redhat.com/2015/02/05/gcc5-and-the-c11-abi/ add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) endif() + if(${GCC_VERSION_OUT} VERSION_LESS "4.8") + message(WARNING "You are using GCC < 4.8 (detected version: ${GCC_VERSION_OUT}). Support for GCC versions before 4.8 may be removed in the future.") + endif() endmacro() if(UNIX) From ebc1a6d85e89db03bcf79ae671bedb6dad9b64ca Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 8 Nov 2016 13:40:08 +0530 Subject: [PATCH 0301/1012] Add functions to get and set the pause state in DF --- plugins/remotefortressreader.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 4caa8e55f..d1803dc0b 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -145,6 +145,8 @@ static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); static command_result SendDigCommand(color_ostream &stream, const DigCommand *in); +static command_result SetPauseState(color_ostream & stream, const SingleBool * in); +static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); @@ -250,6 +252,8 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("CopyScreen", CopyScreen); svc->addFunction("PassKeyboardEvent", PassKeyboardEvent); svc->addFunction("SendDigCommand", SendDigCommand); + svc->addFunction("SetPauseState", SetPauseState); + svc->addFunction("GetPauseState", GetPauseState); return svc; } @@ -3008,3 +3012,15 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in mc.WriteAll(); return CR_OK; } + +static command_result SetPauseState(color_ostream &stream, const SingleBool *in) +{ + DFHack::World::SetPauseState(in->value()); + return CR_OK; +} + +static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out) +{ + out->set_value(World::ReadPauseState()); + return CR_OK; +} \ No newline at end of file From 2935032a1e13b59b069ad8ee94b4db391e85e7ce Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 8 Nov 2016 15:10:01 +0530 Subject: [PATCH 0302/1012] Add .proto files to plugin sources when used, to make it conventient to open it from the IDE. --- plugins/Plugins.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 4871c1f0c..fe167ccb3 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -76,6 +76,7 @@ MACRO(DFHACK_PLUGIN) SET(PLUGIN_PROTOCPP) FOREACH(pbuf ${PLUGIN_PROTOBUFS}) SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) + SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.proto) SET(PLUGIN_PROTOCPP ${PLUGIN_PROTOCPP} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) ENDFOREACH() From fbaf2697cb10c7ae20b575ac87c533baba00b490 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 8 Nov 2016 15:10:43 +0530 Subject: [PATCH 0303/1012] fixed MD5 hash of win64 ruby lib. --- plugins/ruby/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 6b5e62c66..f7d35433a 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -38,7 +38,7 @@ ELSE() ${RUBYLIB}.gz "81db54a8b8b3090c94c6ae2147d30b8f" ${RUBYLIB} - "8a8564418aebddef3dfee1e96690e713") + "8568db86a202b803ad7616c53b25bf75") ELSE() DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" "gz" From 20b9aab8dbf4577e7d7d489aaacaf2b338512c13 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 8 Nov 2016 15:11:27 +0530 Subject: [PATCH 0304/1012] moved remotefortressreader.cpp to its own directory. --- plugins/CMakeLists.txt | 2 +- plugins/remotefortressreader/CMakeLists.txt | 36 +++++++++++++++++++ .../remotefortressreader.cpp | 0 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 plugins/remotefortressreader/CMakeLists.txt rename plugins/{ => remotefortressreader}/remotefortressreader.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8546c6e89..0e4c8f999 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -141,7 +141,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(prospector prospector.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(regrass regrass.cpp) - DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp proto/RemoteFortressReader.proto PROTOBUFS RemoteFortressReader) + add_subdirectory(remotefortressreader) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) DFHACK_PLUGIN(resume resume.cpp) diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt new file mode 100644 index 000000000..be03e604f --- /dev/null +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -0,0 +1,36 @@ +PROJECT (remotefortressreader) +# A list of source files +SET(PROJECT_SRCS + remotefortressreader.cpp +) +# A list of headers +SET(PROJECT_HDRS + +) +#proto files to include. +SET(PROJECT_PROTO + ../../proto/RemoteFortressReader +) + +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra windows libs here + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack +DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS} PROTOBUFS ${PROJECT_PROTO} ) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp similarity index 100% rename from plugins/remotefortressreader.cpp rename to plugins/remotefortressreader/remotefortressreader.cpp From 5e204069ce0ffbd6717cf2d91c980d45fb56e773 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 8 Nov 2016 15:13:06 +0530 Subject: [PATCH 0305/1012] update stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 642e493ef..416bc28f2 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 642e493efcf8e8d803186950cd11eef3982d496e +Subproject commit 416bc28f2e25d06e7da28a5643b8a849aefddbf5 From 6895f3fbedeaec90a9492e0de786e7f88344cf13 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 8 Nov 2016 15:13:43 +0530 Subject: [PATCH 0306/1012] remove tabs --- plugins/remotefortressreader/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index be03e604f..0b84d8905 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -9,7 +9,7 @@ SET(PROJECT_HDRS ) #proto files to include. SET(PROJECT_PROTO - ../../proto/RemoteFortressReader + ../../proto/RemoteFortressReader ) SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) From 8fec45696d9f09c5ef060a0c4ac9450e89fedce0 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 8 Nov 2016 12:01:24 -0600 Subject: [PATCH 0307/1012] Better job assignment algorithm for labormanager. --- plugins/labormanager.cpp | 266 ++++++++++++++++++++++----------------- 1 file changed, 149 insertions(+), 117 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 5a2774cd2..298568a41 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -414,23 +414,23 @@ struct labor_default static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINEa */ {200, 0, TOOL_PICK}, + /* MINE */ {100, 0, TOOL_PICK}, /* HAUL_STONE */ {100, 0, TOOL_NONE}, /* HAUL_WOOD */ {100, 0, TOOL_NONE}, - /* HAUL_BODY */ {200, 0, TOOL_NONE}, - /* HAUL_FOOD */ {300, 0, TOOL_NONE}, - /* HAUL_REFUSE */ {100, 0, TOOL_NONE}, + /* HAUL_BODY */ {1000, 0, TOOL_NONE}, + /* HAUL_FOOD */ {500, 0, TOOL_NONE}, + /* HAUL_REFUSE */ {200, 0, TOOL_NONE}, /* HAUL_ITEM */ {100, 0, TOOL_NONE}, /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, - /* CLEAN */ {200, 0, TOOL_NONE}, - /* CUTWOOD */ {200, 0, TOOL_AXE}, - /* CARPENTER */ {200, 0, TOOL_NONE}, - /* DETAIL */ {200, 0, TOOL_NONE}, - /* MASON */ {200, 0, TOOL_NONE}, - /* ARCHITECT */ {400, 0, TOOL_NONE}, - /* ANIMALTRAIN */ {200, 0, TOOL_NONE}, - /* ANIMALCARE */ {200, 0, TOOL_NONE}, + /* CLEAN */ {100, 0, TOOL_NONE}, + /* CUTWOOD */ {100, 0, TOOL_AXE}, + /* CARPENTER */ {100, 0, TOOL_NONE}, + /* DETAIL */ {100, 0, TOOL_NONE}, + /* MASON */ {100, 0, TOOL_NONE}, + /* ARCHITECT */ {100, 0, TOOL_NONE}, + /* ANIMALTRAIN */ {100, 0, TOOL_NONE}, + /* ANIMALCARE */ {100, 0, TOOL_NONE}, /* DIAGNOSE */ {1000, 0, TOOL_NONE}, /* SURGERY */ {1000, 0, TOOL_NONE}, /* BONE_SETTING */ {1000, 0, TOOL_NONE}, @@ -438,65 +438,65 @@ static const struct labor_default default_labor_infos[] = { /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, - /* BUTCHER */ {200, 0, TOOL_NONE}, - /* TRAPPER */ {200, 0, TOOL_NONE}, - /* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, - /* LEATHER */ {200, 0, TOOL_NONE}, - /* TANNER */ {200, 0, TOOL_NONE}, - /* BREWER */ {200, 0, TOOL_NONE}, - /* ALCHEMIST */ {200, 0, TOOL_NONE}, - /* SOAP_MAKER */ {200, 0, TOOL_NONE}, - /* WEAVER */ {200, 0, TOOL_NONE}, - /* CLOTHESMAKER */ {200, 0, TOOL_NONE}, - /* MILLER */ {200, 0, TOOL_NONE}, - /* PROCESS_PLANT */ {200, 0, TOOL_NONE}, - /* MAKE_CHEESE */ {200, 0, TOOL_NONE}, - /* MILK */ {200, 0, TOOL_NONE}, - /* COOK */ {200, 0, TOOL_NONE}, - /* PLANT */ {200, 0, TOOL_NONE}, - /* HERBALIST */ {200, 0, TOOL_NONE}, + /* BUTCHER */ {500, 0, TOOL_NONE}, + /* TRAPPER */ {100, 0, TOOL_NONE}, + /* DISSECT_VERMIN */ {100, 0, TOOL_NONE}, + /* LEATHER */ {100, 0, TOOL_NONE}, + /* TANNER */ {100, 0, TOOL_NONE}, + /* BREWER */ {100, 0, TOOL_NONE}, + /* ALCHEMIST */ {100, 0, TOOL_NONE}, + /* SOAP_MAKER */ {100, 0, TOOL_NONE}, + /* WEAVER */ {100, 0, TOOL_NONE}, + /* CLOTHESMAKER */ {100, 0, TOOL_NONE}, + /* MILLER */ {100, 0, TOOL_NONE}, + /* PROCESS_PLANT */ {100, 0, TOOL_NONE}, + /* MAKE_CHEESE */ {100, 0, TOOL_NONE}, + /* MILK */ {100, 0, TOOL_NONE}, + /* COOK */ {100, 0, TOOL_NONE}, + /* PLANT */ {100, 0, TOOL_NONE}, + /* HERBALIST */ {100, 0, TOOL_NONE}, /* FISH */ {100, 0, TOOL_NONE}, - /* CLEAN_FISH */ {200, 0, TOOL_NONE}, - /* DISSECT_FISH */ {200, 0, TOOL_NONE}, + /* CLEAN_FISH */ {100, 0, TOOL_NONE}, + /* DISSECT_FISH */ {100, 0, TOOL_NONE}, /* HUNT */ {100, 0, TOOL_CROSSBOW}, - /* SMELT */ {200, 0, TOOL_NONE}, - /* FORGE_WEAPON */ {200, 0, TOOL_NONE}, - /* FORGE_ARMOR */ {200, 0, TOOL_NONE}, - /* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, - /* METAL_CRAFT */ {200, 0, TOOL_NONE}, - /* CUT_GEM */ {200, 0, TOOL_NONE}, - /* ENCRUST_GEM */ {200, 0, TOOL_NONE}, - /* WOOD_CRAFT */ {200, 0, TOOL_NONE}, - /* STONE_CRAFT */ {200, 0, TOOL_NONE}, - /* BONE_CARVE */ {200, 0, TOOL_NONE}, - /* GLASSMAKER */ {200, 0, TOOL_NONE}, - /* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, - /* SIEGECRAFT */ {200, 0, TOOL_NONE}, - /* SIEGEOPERATE */ {200, 0, TOOL_NONE}, - /* BOWYER */ {200, 0, TOOL_NONE}, - /* MECHANIC */ {200, 0, TOOL_NONE}, - /* POTASH_MAKING */ {200, 0, TOOL_NONE}, - /* LYE_MAKING */ {200, 0, TOOL_NONE}, - /* DYER */ {200, 0, TOOL_NONE}, - /* BURN_WOOD */ {200, 0, TOOL_NONE}, - /* OPERATE_PUMP */ {200, 0, TOOL_NONE}, - /* SHEARER */ {200, 0, TOOL_NONE}, - /* SPINNER */ {200, 0, TOOL_NONE}, - /* POTTERY */ {200, 0, TOOL_NONE}, - /* GLAZING */ {200, 0, TOOL_NONE}, - /* PRESSING */ {200, 0, TOOL_NONE}, - /* BEEKEEPING */ {200, 0, TOOL_NONE}, - /* WAX_WORKING */ {200, 0, TOOL_NONE}, - /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE}, - /* HAUL_TRADE */ {200, 0, TOOL_NONE}, - /* PULL_LEVER */ {200, 0, TOOL_NONE}, - /* REMOVE_CONSTRUCTION */ {200, 0, TOOL_NONE}, - /* HAUL_WATER */ {200, 0, TOOL_NONE}, - /* GELD */ {200, 0, TOOL_NONE}, - /* BUILD_ROAD */ {200, 0, TOOL_NONE}, - /* BUILD_CONSTRUCTION */ {200, 0, TOOL_NONE}, - /* PAPERMAKING */ {200, 0, TOOL_NONE}, - /* BOOKBINDING */ {200, 0, TOOL_NONE} + /* SMELT */ {100, 0, TOOL_NONE}, + /* FORGE_WEAPON */ {100, 0, TOOL_NONE}, + /* FORGE_ARMOR */ {100, 0, TOOL_NONE}, + /* FORGE_FURNITURE */ {100, 0, TOOL_NONE}, + /* METAL_CRAFT */ {100, 0, TOOL_NONE}, + /* CUT_GEM */ {100, 0, TOOL_NONE}, + /* ENCRUST_GEM */ {100, 0, TOOL_NONE}, + /* WOOD_CRAFT */ {100, 0, TOOL_NONE}, + /* STONE_CRAFT */ {100, 0, TOOL_NONE}, + /* BONE_CARVE */ {100, 0, TOOL_NONE}, + /* GLASSMAKER */ {100, 0, TOOL_NONE}, + /* EXTRACT_STRAND */ {100, 0, TOOL_NONE}, + /* SIEGECRAFT */ {100, 0, TOOL_NONE}, + /* SIEGEOPERATE */ {100, 0, TOOL_NONE}, + /* BOWYER */ {100, 0, TOOL_NONE}, + /* MECHANIC */ {100, 0, TOOL_NONE}, + /* POTASH_MAKING */ {100, 0, TOOL_NONE}, + /* LYE_MAKING */ {100, 0, TOOL_NONE}, + /* DYER */ {100, 0, TOOL_NONE}, + /* BURN_WOOD */ {100, 0, TOOL_NONE}, + /* OPERATE_PUMP */ {100, 0, TOOL_NONE}, + /* SHEARER */ {100, 0, TOOL_NONE}, + /* SPINNER */ {100, 0, TOOL_NONE}, + /* POTTERY */ {100, 0, TOOL_NONE}, + /* GLAZING */ {100, 0, TOOL_NONE}, + /* PRESSING */ {100, 0, TOOL_NONE}, + /* BEEKEEPING */ {100, 0, TOOL_NONE}, + /* WAX_WORKING */ {100, 0, TOOL_NONE}, + /* PUSH_HAUL_VEHICLES */ {100, 0, TOOL_NONE}, + /* HAUL_TRADE */ {1000, 0, TOOL_NONE}, + /* PULL_LEVER */ {1000, 0, TOOL_NONE}, + /* REMOVE_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* HAUL_WATER */ {100, 0, TOOL_NONE}, + /* GELD */ {100, 0, TOOL_NONE}, + /* BUILD_ROAD */ {100, 0, TOOL_NONE}, + /* BUILD_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* PAPERMAKING */ {100, 0, TOOL_NONE}, + /* BOOKBINDING */ {100, 0, TOOL_NONE} }; void debug (char* fmt, ...); @@ -852,12 +852,13 @@ private: return df::unit_labor::TRAPPER; case df::building_type::Civzone: case df::building_type::Nest: - case df::building_type::RoadDirt: case df::building_type::Stockpile: case df::building_type::Weapon: return df::unit_labor::NONE; case df::building_type::SiegeEngine: return df::unit_labor::SIEGECRAFT; + case df::building_type::RoadDirt: + return df::unit_labor::BUILD_ROAD; } debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", @@ -1167,13 +1168,13 @@ public: job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1676,20 +1677,24 @@ private: if (bld != -1) { df::building* b = binsearch_in_vector(world->buildings.all, bld); - int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) - { - if (b->jobs[jn]->flags.bits.suspend) - continue; - fjid = b->jobs[jn]->id; - break; - } + // check if this job is the first nonsuspended job on this building; if not, ignore it // (except for farms and trade depots) - if (fjid != j->id && - b->getType() != df::building_type::FarmPlot && - b->getType() != df::building_type::TradeDepot) - return; + + if (b->getType() != df::building_type::FarmPlot && + b->getType() != df::building_type::TradeDepot) + { + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + if (fjid != j->id) + return; + } } df::unit_labor labor = labor_mapper->find_job_labor (j); @@ -2056,7 +2061,8 @@ private: out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); // determine if dwarf has medical needs - if (dwarf->dwarf->health) + // babies cannot currently receive health care even if they need it + if (dwarf->dwarf->profession != profession::BABY && dwarf->dwarf->health) { if (dwarf->dwarf->health->flags.bits.needs_recovery) cnt_recover_wounded++; @@ -2160,13 +2166,10 @@ private: if (labor == df::unit_labor::OPERATE_PUMP) score += 50000; else - score += 1000; + score += 25000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 30000; - if (default_labor_infos[labor].tool != TOOL_NONE && - !d->has_tool[default_labor_infos[labor].tool]) - score -= 30000; + score += 10000000; if (d->has_children && labor_outside[labor]) score -= 15000; if (d->armed && labor_outside[labor]) @@ -2361,8 +2364,6 @@ public: } - labor_needed[df::unit_labor::CLEAN] = 1; - if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -2372,7 +2373,9 @@ public: } } + std::map base_priority; priority_queue> pq; + priority_queue> pq2; for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { @@ -2383,15 +2386,16 @@ public: if (labor_infos[l].maximum_dwarfs() > 0 && i->second > labor_infos[l].maximum_dwarfs()) i->second = labor_infos[l].maximum_dwarfs(); - if (i->second > 0) - { - int priority = labor_infos[l].priority(); - if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) - priority += labor_infos[l].time_since_last_assigned()/12; + int priority = labor_infos[l].priority(); + + priority += labor_infos[l].time_since_last_assigned()/12; + priority -= labor_infos[l].busy_dwarfs; + + base_priority[l] = priority; - for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) - priority /= 2; + if (i->second > 0) + { pq.push(make_pair(priority, l)); } } @@ -2418,9 +2422,22 @@ public: if (--labor_needed[labor] > 0) { - priority /= 2; - pq.push(make_pair(priority, labor)); + priority-=10; + pq2.push(make_pair(priority, labor)); } + + if (pq.empty()) + while(!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); + } + } + + while (!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); } int canary = (1 << df::unit_labor::HAUL_STONE) | @@ -2475,7 +2492,7 @@ public: if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) { set_labor(*bestdwarf, l, true); - if (t != TOOL_NONE && (*bestdwarf)->has_tool[t]) + if (t != TOOL_NONE && !((*bestdwarf)->has_tool[t])) { df::job_type j; j = df::job_type::NONE; @@ -2526,8 +2543,7 @@ public: continue; int score = score_labor (*d, l); - if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) - score += labor_infos[l].time_since_last_assigned()/12; + if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) score += 1000000; @@ -2538,7 +2554,9 @@ public: { set_labor(*d, l, true); } - if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } } @@ -2579,18 +2597,23 @@ public: /* Assign any leftover dwarfs to "standard" labors */ + if (print_debug) + out.print ("After assignment, %d dwarfs left over\n", available_dwarfs.size()); + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) { FOR_ENUM_ITEMS (unit_labor, l) { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && - canary & (1 << l)) - set_labor(*d, l, true); - else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER) - set_labor(*d, l, true); - else - set_labor(*d, l, false); - } + if (l == df::unit_labor::NONE) + continue; + + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::REMOVE_CONSTRUCTION || + l == df::unit_labor::PULL_LEVER || + l == df::unit_labor::HAUL_TRADE); + } } /* check for dwarfs assigned no labors and assign them the bucket list if there are */ @@ -2648,7 +2671,8 @@ public: bool has_tool = (*d)->has_tool[t]; bool needs_tool = (*d)->dwarf->status.labors[l]; - if (has_tool != needs_tool) + if ((needs_tool && !has_tool) || + (has_tool && !needs_tool && tool_in_use[t] >= tool_count[t])) { df::job_type j = df::job_type::NONE; @@ -2675,6 +2699,10 @@ public: *df::global::process_jobs = true; } + if (print_debug) { + *df::global::pause_state = true; + } + print_debug = 0; } @@ -2709,8 +2737,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - if (++step_count < 60) +// if (++step_count < 60) +// return CR_OK; + + if (*df::global::process_jobs) return CR_OK; + step_count = 0; debug_stream = &out; From ae59b4f5ad91000995bdc5dc0b5b480e1e18b892 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 10 Nov 2016 14:25:05 -0500 Subject: [PATCH 0308/1012] Update xml, scripts --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 81e2cf023..84f6e968a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 81e2cf023422ad6f01061db12586a0589ef6eac5 +Subproject commit 84f6e968a9ec5515f9dbef96b445e3fc83f83e8b diff --git a/scripts b/scripts index b285334a8..bcd7ed20d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b285334a8a91c0b43bac9d3e362b95fcbfa472c7 +Subproject commit bcd7ed20d5c096beabd58b0c7ec3f90ce08a67f4 From 8d9b888410ac49d34395f2004ea76c286ae7878e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 14 Nov 2016 14:16:43 -0500 Subject: [PATCH 0309/1012] Revert "fixed MD5 hash of win64 ruby lib." Unable to get anything other than 8a856 for the extracted library on my end This reverts commit fbaf2697cb10c7ae20b575ac87c533baba00b490. --- plugins/ruby/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index f7d35433a..6b5e62c66 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -38,7 +38,7 @@ ELSE() ${RUBYLIB}.gz "81db54a8b8b3090c94c6ae2147d30b8f" ${RUBYLIB} - "8568db86a202b803ad7616c53b25bf75") + "8a8564418aebddef3dfee1e96690e713") ELSE() DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" "gz" From c05f3d7d30d16a80666bfbe71354133a97b962c1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 14 Nov 2016 14:18:34 -0500 Subject: [PATCH 0310/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index bcd7ed20d..8dc528f1b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit bcd7ed20d5c096beabd58b0c7ec3f90ce08a67f4 +Subproject commit 8dc528f1b02b14afd3ad6267d9e350b6840def6b From 1e41cdc7f60d5c415b3554073456a07a9c1e2b20 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 16 Nov 2016 15:20:22 -0500 Subject: [PATCH 0311/1012] Fix labormanager docs --- docs/Plugins.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 85b6ba58d..fac50d254 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -860,9 +860,11 @@ any particular type of job. Simple usage: :enable labormanager: Enables the plugin with default settings. -(Persistent per fortress) :disable labormanager: Disables the plugin. + (Persistent per fortress) -Anything beyond this is optional - autolabor works fairly well on the +:disable labormanager: Disables the plugin. + +Anything beyond this is optional - labormanager works fairly well on the default settings. The default priorities for each labor vary (some labors are higher @@ -924,7 +926,7 @@ the dfhack console, followed by the message "LABORMANAGER: Game paused so you can investigate the above message.". If this happens, please open an issue on github, reporting the lines that immediately preceded this message. You can tell labormanager to ignore this error and carry on by -typing "autolabor pause-on-error no", but be warned that some job may go +typing ``labormanager pause-on-error no``, but be warned that some job may go undone in this situation. Advanced usage: From dce00a5034e04f0606eac006f19e6e839e2930d3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 16 Nov 2016 15:21:13 -0500 Subject: [PATCH 0312/1012] Partial compilation fixes for advtools, misery --- plugins/advtools.cpp | 11 ++++++----- plugins/misery.cpp | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 77cc02934..50868560d 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -17,6 +17,7 @@ #include "df/item.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/unit_relationship_type.h" #include "df/map_block.h" #include "df/nemesis_record.h" #include "df/historical_figure.h" @@ -139,7 +140,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) switch (ui_advmode->menu) { case Travel: - case Sleep: + // was also Sleep, now equivalent revert = true; break; default: @@ -218,7 +219,7 @@ df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) out.print("Returned into the body of %s.\n", name.c_str()); } - real_nemesis->unit->relations.group_leader_id = -1; + real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; in_transient_swap = false; } @@ -237,7 +238,7 @@ void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_ // Update follow new_nemesis->group_leader_id = -1; - new_nemesis->unit->relations.group_leader_id = -1; + new_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; for (unsigned i = 0; i < cvec.size(); i++) { @@ -247,7 +248,7 @@ void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_ nm->group_leader_id = new_nemesis->id; if (nm->unit) - nm->unit->relations.group_leader_id = new_nemesis->unit_id; + nm->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; } } @@ -721,7 +722,7 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par // Make the player unit follow around to avoid bad consequences // if it is unloaded before the transient swap is reverted. - real_nemesis->unit->relations.group_leader_id = new_nemesis->unit_id; + real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; } return CR_OK; diff --git a/plugins/misery.cpp b/plugins/misery.cpp index a4468079c..59b745871 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -5,7 +5,6 @@ #include "df/world.h" #include "df/ui.h" #include "df/unit.h" -#include "df/unit_thought.h" #include "df/unit_thought_type.h" #include From df9b5bca7311489748ab100dfc51080841c3f196 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 Nov 2016 10:31:48 -0500 Subject: [PATCH 0313/1012] Allow ruby plugin to try more than one library path, including libruby.so on Linux --- plugins/ruby/ruby.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 0470541e1..5aa04da04 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -318,22 +318,28 @@ DFHack::DFLibrary *libruby_handle; // load the ruby library, initialize function pointers static int df_loadruby(void) { - const char *libpath = + const char *libpaths[] = { #if defined(WIN32) - "./libruby.dll"; + "./libruby.dll", #elif defined(__APPLE__) - #ifdef DFHACK64 - "/System/Library/Frameworks/Ruby.framework/Ruby"; - #else - "hack/libruby.dylib"; - #endif + "hack/libruby.dylib", + "/System/Library/Frameworks/Ruby.framework/Ruby", #else - "hack/libruby.so"; + "hack/libruby.so", + "libruby.so", #endif + NULL + }; + + for (const char **path = libpaths; *path; path++) { + if ((libruby_handle = OpenPlugin(*path))) + break; + else + fprintf(stderr, "ruby: warning: Failed to load %s\n", *path); + } - libruby_handle = OpenPlugin(libpath); if (!libruby_handle) { - fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath); + Core::printerr("Cannot initialize ruby plugin: failed to load ruby library\n"); return 0; } From 74f5df99db12bd111785fd6e3cfc49ca5b460ab9 Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 17 Nov 2016 00:11:15 -0800 Subject: [PATCH 0314/1012] Add job remove method Job remove eliminates a job's worker & holder references, if any, puts the worker on cd, if appropriate, removes the job's postings, eliminates the job from the global linked list, and then finally deletes it. This code was tested by incorporating it into autochop and it does make the plugin work. However, chop jobs don't have holder building references, and anyway, with DF being 90% edge case by volume, this could use a heck of a lot more testing. I saw elsewhere code that prevented worker removal if the job was a special job, and that made me feel funny so I made the job remove method not work if the job is a special job. --- library/include/modules/Job.h | 5 +++ library/modules/Job.cpp | 65 ++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index fbbed9ff3..78a694b6f 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -66,6 +66,11 @@ namespace DFHack DFHACK_EXPORT void setJobCooldown(df::building *workshop, df::unit *worker, int cooldown = 100); DFHACK_EXPORT bool removeWorker(df::job *job, int cooldown = 100); + // Delete a job & remove all refs from everywhere. + // This method DELETES the job object! Everything related to it will be wiped + // clean from the earth, so make sure you pull what you need out before calling this! + DFHACK_EXPORT void removeJob(df::job *job); + // Instruct the game to check and assign workers DFHACK_EXPORT void checkBuildingsNow(); DFHACK_EXPORT void checkDesignationsNow(); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 2678eadba..d187989a0 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -212,7 +212,7 @@ void DFHack::Job::printJobDetails(color_ostream &out, df::job *job) out.color(job->flags.bits.suspend ? COLOR_DARKGREY : COLOR_GREY); out << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type); if (job->flags.whole) - out << " (" << bitfield_to_string(job->flags) << ")"; + out << " (" << bitfield_to_string(job->flags) << ")"; out << endl; out.reset_color(); @@ -304,6 +304,68 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } +void DFHack::Job::removeJob(df::job *job) { + using df::global::world; + CHECK_NULL_POINTER(job); + + if (job->flags.bits.special) //I don't think you can cancel these, because DF wasn't build to expect it? + return; + + //As far as I know there are only two general refs jobs have, the unit assigned to work it (if any) + //and the workshop it was created at (if any). It's possible there are others, so we go ahead and wipe all + //refs, but these two are the only ones that we really handle with any intelligence. If other refs + //exist that might have return-references that need to be cleared, that needs to be implemented!!!! + auto holderRef = getGeneralRef(job, general_ref_type::BUILDING_HOLDER); + auto workerRef = getGeneralRef(job, general_ref_type::UNIT_WORKER); + df::building *holder = NULL; + df::unit *worker = NULL; + + if (holderRef) holder = holderRef->getBuilding(); + if (workerRef) worker = workerRef->getUnit(); + + //removeWorker() adds a job cd about right now, but I chose not to do that because I'm pretty sure + //that's only to stop removed workers from immediately reclaiming the job before doing something + //else, and this job is gonna be dead in a second. + + //Remove return-refs from the holder & worker + if (holder) { + int jobIndex = linear_index(holder->jobs, job); + if (jobIndex >= 0) + vector_erase_at(holder->jobs, jobIndex); + } + + if (worker) { + if (worker->job.current_job == job) + worker->job.current_job = NULL; + } + + //Wipe all refs out + while (job->general_refs.size() > 0) { + auto ref = job->general_refs[0]; + vector_erase_at(job->general_refs, 0); + delete ref; + } + + //Remove job from job board + Job::removePostings(job, true); + + //Remove job from global list + if (job->list_link) { + auto prev = job->list_link->prev; + auto next = job->list_link->next; + + if (prev) + prev->next = next; + + if (next) + next->prev = prev; + + delete job->list_link; + } + + delete job; +} + bool DFHack::Job::removeWorker(df::job *job, int cooldown) { CHECK_NULL_POINTER(job); @@ -397,6 +459,7 @@ bool DFHack::Job::removePostings(df::job *job, bool remove_all) { if ((**it).job == job) { + (**it).job = NULL; (**it).flags.bits.dead = true; removed = true; } From 67af9f5e82ff0c45043aa2508f8346b17e77d5a9 Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 17 Nov 2016 23:04:48 -0800 Subject: [PATCH 0315/1012] Add lua bindings for removeJob --- library/LuaApi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 712ccb541..4416a9066 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1469,6 +1469,7 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,getName), WRAPM(Job,linkIntoWorld), WRAPM(Job,removePostings), + WRAPM(Job,removeJob), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } From fba32f2e2fb2256037a358e2d0170c0607b65f6c Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 17 Nov 2016 23:25:48 -0800 Subject: [PATCH 0316/1012] Also disconnect the job from its items. --- library/modules/Job.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index d187989a0..f8bf00240 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -346,6 +346,33 @@ void DFHack::Job::removeJob(df::job *job) { delete ref; } + //Detach all items from the job + while (job->items.size() > 0) { + auto itemRef = job->items[0]; + df::item *item = NULL; + + if (itemRef) { + item = itemRef->item; + + if (item) { + item->flags.bits.in_job = false; + + //Work backward through the specific refs & remove/delete all specific refs to this job + int refCount = item->specific_refs.size(); + for(int refIndex = refCount-1; refIndex >= 0; refIndex--) { + auto ref = item->specific_refs[refIndex]; + if (ref->type == df::specific_ref_type::JOB && ref->job == job) { + vector_erase_at(item->specific_refs, refIndex); + delete ref; + } + } + } + + delete itemRef; + } + vector_erase_at(job->items, 0); + } + //Remove job from job board Job::removePostings(job, true); From 8b964ca2dcdd7bbf33d099d4916a185fde8dd8ca Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Mon, 21 Nov 2016 06:51:21 -0800 Subject: [PATCH 0317/1012] Wipe job_items vector --- library/modules/Job.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index f8bf00240..0ee17ddd1 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -376,6 +376,15 @@ void DFHack::Job::removeJob(df::job *job) { //Remove job from job board Job::removePostings(job, true); + //Clean up job_items + while (job->job_items.size() > 0) { + auto jobItem = job->job_items[0]; + vector_erase_at(job->job_items, 0); + if (jobItem) { + delete jobItem; + } + } + //Remove job from global list if (job->list_link) { auto prev = job->list_link->prev; From 80e0a91670f09a84b5e15d0ac6ec2d9befd680f2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 16:54:50 -0500 Subject: [PATCH 0318/1012] Update .travis.yml for 0.43.05 --- .travis.yml | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e60a1461..59faf012d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,6 @@ addons: - zlib1g-dev:i386 matrix: include: - - env: GCC_VERSION=4.5 - addons: - apt: - packages: - - *default_packages - - gcc-4.5-multilib - - g++-4.5-multilib - env: GCC_VERSION=4.8 addons: apt: @@ -41,10 +34,10 @@ script: - cd build-travis - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON - make -j3 -notifications: - email: false - irc: - channels: - - "chat.freenode.net#dfhack" - on_success: change - on_failure: always +# notifications: +# email: false +# irc: +# channels: +# - "chat.freenode.net#dfhack" +# on_success: change +# on_failure: always From 647ef4d556b295ebbb14c80eb2ce72b013f7704b Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 17:17:56 -0500 Subject: [PATCH 0319/1012] Fix labormanager whitespace --- plugins/labormanager.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index ede083488..35454e6cb 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1683,7 +1683,7 @@ private: // (except for farms and trade depots) if (b->getType() != df::building_type::FarmPlot && - b->getType() != df::building_type::TradeDepot) + b->getType() != df::building_type::TradeDepot) { int fjid = -1; for (int jn = 0; jn < b->jobs.size(); jn++) @@ -2389,10 +2389,10 @@ public: i->second = labor_infos[l].maximum_dwarfs(); int priority = labor_infos[l].priority(); - + priority += labor_infos[l].time_since_last_assigned()/12; priority -= labor_infos[l].busy_dwarfs; - + base_priority[l] = priority; if (i->second > 0) @@ -2427,8 +2427,8 @@ public: pq2.push(make_pair(priority, labor)); } - if (pq.empty()) - while(!pq2.empty()) + if (pq.empty()) + while(!pq2.empty()) { pq.push(pq2.top()); pq2.pop(); @@ -2555,8 +2555,8 @@ public: { set_labor(*d, l, true); } - if ((*d)->using_labor != df::unit_labor::NONE && - (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } @@ -2608,10 +2608,10 @@ public: if (l == df::unit_labor::NONE) continue; - set_labor(*d, l, - (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || - l == df::unit_labor::CLEAN || - l == df::unit_labor::REMOVE_CONSTRUCTION || + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER || l == df::unit_labor::HAUL_TRADE); } @@ -2741,7 +2741,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // if (++step_count < 60) // return CR_OK; - if (*df::global::process_jobs) + if (*df::global::process_jobs) return CR_OK; step_count = 0; From c7a35d7ecec4d75888359a2ade1a67d05edca4db Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 17:24:36 -0500 Subject: [PATCH 0320/1012] fix remotefortressreader protobuf error in clean builds --- plugins/remotefortressreader/proto/readme.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 plugins/remotefortressreader/proto/readme.txt diff --git a/plugins/remotefortressreader/proto/readme.txt b/plugins/remotefortressreader/proto/readme.txt new file mode 100644 index 000000000..8cd56aefd --- /dev/null +++ b/plugins/remotefortressreader/proto/readme.txt @@ -0,0 +1 @@ +placeholder to fix protobufs in plugins/remotefortressreader/CMakeLists.txt From 206e9ef993dbd7602d12655662b1ccff3b32e313 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 17:31:36 -0500 Subject: [PATCH 0321/1012] Re-enable Travis IRC notifications --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59faf012d..22fe49e2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,10 +34,10 @@ script: - cd build-travis - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON - make -j3 -# notifications: -# email: false -# irc: -# channels: -# - "chat.freenode.net#dfhack" -# on_success: change -# on_failure: always +notifications: + email: false + irc: + channels: + - "chat.freenode.net#dfhack" + on_success: change + on_failure: always From b41ace2f8fa1709463709545b89d2c44829ae3fe Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 20:49:38 -0500 Subject: [PATCH 0322/1012] Update scripts (travis) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 8dc528f1b..25f85f8c7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8dc528f1b02b14afd3ad6267d9e350b6840def6b +Subproject commit 25f85f8c7b7a2f0b5a635a237985952337dcfef7 From 13eb5e702beb6d8e40c0e17be64cda9a8d9d1efb Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 20:50:19 -0500 Subject: [PATCH 0323/1012] bump to alpha2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a47d0721..654553f6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "alpha1") +SET(DFHACK_RELEASE "alpha2") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From dd28079f30e671f32a2341f7d5de2fcc4e50a411 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 Nov 2016 22:43:48 -0500 Subject: [PATCH 0324/1012] Fix ruby.cpp compilation on win64 --- plugins/ruby/ruby.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 5aa04da04..006d63402 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -596,7 +596,7 @@ static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) #if defined(_WIN64) // win64 char *rtti = *(char**)(ptr - 0x8); - char *typeinfo = Core::getInstance().p->getBase() + *(uint32_t*)(rtti + 0xC); + char *typeinfo = (char*)Core::getInstance().p->getBase() + *(uint32_t*)(rtti + 0xC); // skip the .?AV, trim @@ from end return rb_str_new(typeinfo+0x14, strlen(typeinfo+0x14)-2); #elif defined(WIN32) From 08840ccabf60f8e7295cb9f1bfc524a8f7824942 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 23 Nov 2016 19:20:20 -0500 Subject: [PATCH 0325/1012] Add abbradar to Authors.rst (da3c6404f) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 195b90c44..242f6a7d3 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -72,6 +72,7 @@ mizipzor mizipzor moversti moversti Neil Little nmlittle Nick Rart nickrart comestible +Nikolay Amiantov abbradar Omniclasm PeridexisErrant PeridexisErrant Petr Mrázek peterix From e490afdf0058da782f6d9b8c1c09f559fcd2c81f Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 24 Nov 2016 22:36:11 -0800 Subject: [PATCH 0326/1012] Rebuilt slightly to offer bool return val We fail on unknown general ref types now, without modifying the job at all yet --- library/LuaApi.cpp | 2 + library/include/modules/Job.h | 12 +++- library/modules/Job.cpp | 126 ++++++++++++++++++++-------------- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4416a9066..7f0e629a7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1469,6 +1469,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,getName), WRAPM(Job,linkIntoWorld), WRAPM(Job,removePostings), + WRAPM(Job,disconnectJobItem), + WRAPM(Job,disconnectJobGeneralRef), WRAPM(Job,removeJob), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 78a694b6f..1448b6656 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -66,10 +66,20 @@ namespace DFHack DFHACK_EXPORT void setJobCooldown(df::building *workshop, df::unit *worker, int cooldown = 100); DFHACK_EXPORT bool removeWorker(df::job *job, int cooldown = 100); + // This helpful method only removes the backref from the item to the job, but it doesn't + // remove the item ref from the job's vector, or delete it or anything. Think of it as a method + // that does all the needful to make an item ref ready to delete. + DFHACK_EXPORT void disconnectJobItem(df::job_item_ref *item, df::job *job); + // This helpful method only removes the backref from whatever the general_ref points to, + // it doesn't remove the general_ref from the job's vector, or delete it or anything. + // Think of it as a method that does all the needful to make a ref ready to delete. + // If it returns false, you've found a ref that the method doesn't know how to handle. Congratulations! + // You should report that and/or check in a fix. + DFHACK_EXPORT bool disconnectJobGeneralRef(df::general_ref *ref, df::job *job); // Delete a job & remove all refs from everywhere. // This method DELETES the job object! Everything related to it will be wiped // clean from the earth, so make sure you pull what you need out before calling this! - DFHACK_EXPORT void removeJob(df::job *job); + DFHACK_EXPORT bool removeJob(df::job *job); // Instruct the game to check and assign workers DFHACK_EXPORT void checkBuildingsNow(); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 0ee17ddd1..b1f262f09 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -304,73 +304,98 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } -void DFHack::Job::removeJob(df::job *job) { +void DFHack::Job::disconnectJobItem(df::job_item_ref *ref, df::job *job) { + if (!ref) return; + + auto item = ref->item; + if (!item) return; + + //Work backward through the specific refs & remove/delete all specific refs to this job + int refCount = item->specific_refs.size(); + bool stillHasJobs = false; + for(int refIndex = refCount-1; refIndex >= 0; refIndex--) { + auto ref = item->specific_refs[refIndex]; + + if (ref->type == df::specific_ref_type::JOB) { + if (ref->job == job) { + vector_erase_at(item->specific_refs, refIndex); + delete ref; + } else { + stillHasJobs = true; + } + } + } + + if (!stillHasJobs) item->flags.bits.in_job = false; +} + +bool DFHack::Job::disconnectJobGeneralRef(df::general_ref *ref, df::job *job) { + if (ref == NULL) return true; + + switch (ref->getType()) { + case general_ref_type::BUILDING_HOLDER: + auto building = ref->getBuilding(); + + if (building != NULL) { + int jobIndex = linear_index(building->jobs, job); + if (jobIndex >= 0) { + vector_erase_at(building->jobs, jobIndex); + } + } + break; + case general_ref_type::UNIT_WORKER: + auto unit = ref->getUnit(); + + if (unit != NULL) { + if (unit->job.current_job == job) { + unit->job.current_job = NULL; + } + } + break; + default: + return false; + } + + return true; +} + +bool DFHack::Job::removeJob(df::job *job) { using df::global::world; CHECK_NULL_POINTER(job); if (job->flags.bits.special) //I don't think you can cancel these, because DF wasn't build to expect it? - return; + return false; - //As far as I know there are only two general refs jobs have, the unit assigned to work it (if any) - //and the workshop it was created at (if any). It's possible there are others, so we go ahead and wipe all - //refs, but these two are the only ones that we really handle with any intelligence. If other refs - //exist that might have return-references that need to be cleared, that needs to be implemented!!!! - auto holderRef = getGeneralRef(job, general_ref_type::BUILDING_HOLDER); - auto workerRef = getGeneralRef(job, general_ref_type::UNIT_WORKER); - df::building *holder = NULL; - df::unit *worker = NULL; - - if (holderRef) holder = holderRef->getBuilding(); - if (workerRef) worker = workerRef->getUnit(); - - //removeWorker() adds a job cd about right now, but I chose not to do that because I'm pretty sure - //that's only to stop removed workers from immediately reclaiming the job before doing something - //else, and this job is gonna be dead in a second. - - //Remove return-refs from the holder & worker - if (holder) { - int jobIndex = linear_index(holder->jobs, job); - if (jobIndex >= 0) - vector_erase_at(holder->jobs, jobIndex); - } + //We actually only know how to handle BUILDING_HOLDER and UNIT_WORKER refs- there's probably a great + //way to handle them, but until we have a good example, we'll just fail to remove jobs that have other sorts + //of refs, or any specific refs + if (job->specific_refs.size() > 0) + return false; - if (worker) { - if (worker->job.current_job == job) - worker->job.current_job = NULL; + for (auto genRefItr = job->general_refs.begin(); genRefItr != job->general_refs.end(); ++genRefItr) { + auto ref = *genRefItr; + if (ref != NULL && (ref->getType() != general_ref_type::BUILDING_HOLDER && ref->getType() != general_ref_type::UNIT_WORKER)) + return false; } - //Wipe all refs out + //Disconnect, delete, and wipe all general refs while (job->general_refs.size() > 0) { auto ref = job->general_refs[0]; + + //Our code above should have ensured that this won't return false- if it does, there's not + //a great way of recovering since we can't properly destroy the job & we can't leave it + //around. Better to know the moment that becomes a problem. + assert(disconnectJobGeneralRef(ref, job)); vector_erase_at(job->general_refs, 0); - delete ref; + if (ref != NULL) delete ref; } //Detach all items from the job while (job->items.size() > 0) { auto itemRef = job->items[0]; - df::item *item = NULL; - - if (itemRef) { - item = itemRef->item; - - if (item) { - item->flags.bits.in_job = false; - - //Work backward through the specific refs & remove/delete all specific refs to this job - int refCount = item->specific_refs.size(); - for(int refIndex = refCount-1; refIndex >= 0; refIndex--) { - auto ref = item->specific_refs[refIndex]; - if (ref->type == df::specific_ref_type::JOB && ref->job == job) { - vector_erase_at(item->specific_refs, refIndex); - delete ref; - } - } - } - - delete itemRef; - } + disconnectJobItem(itemRef, job); vector_erase_at(job->items, 0); + if (itemRef != NULL) delete itemRef; } //Remove job from job board @@ -400,6 +425,7 @@ void DFHack::Job::removeJob(df::job *job) { } delete job; + return true; } bool DFHack::Job::removeWorker(df::job *job, int cooldown) From de0e211e07dbc0e044096efbaf783d31d9759046 Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 24 Nov 2016 23:35:03 -0800 Subject: [PATCH 0327/1012] Figured I could like test my code. --- library/modules/Job.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index b1f262f09..531c85773 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -332,9 +332,12 @@ void DFHack::Job::disconnectJobItem(df::job_item_ref *ref, df::job *job) { bool DFHack::Job::disconnectJobGeneralRef(df::general_ref *ref, df::job *job) { if (ref == NULL) return true; + df::building *building = NULL; + df::unit *unit = NULL; + switch (ref->getType()) { case general_ref_type::BUILDING_HOLDER: - auto building = ref->getBuilding(); + building = ref->getBuilding(); if (building != NULL) { int jobIndex = linear_index(building->jobs, job); @@ -344,7 +347,7 @@ bool DFHack::Job::disconnectJobGeneralRef(df::general_ref *ref, df::job *job) { } break; case general_ref_type::UNIT_WORKER: - auto unit = ref->getUnit(); + unit = ref->getUnit(); if (unit != NULL) { if (unit->job.current_job == job) { @@ -385,7 +388,9 @@ bool DFHack::Job::removeJob(df::job *job) { //Our code above should have ensured that this won't return false- if it does, there's not //a great way of recovering since we can't properly destroy the job & we can't leave it //around. Better to know the moment that becomes a problem. - assert(disconnectJobGeneralRef(ref, job)); + bool success = disconnectJobGeneralRef(ref, job); + assert(success); + vector_erase_at(job->general_refs, 0); if (ref != NULL) delete ref; } From 7920f71517622ef2f28cdd1cab6be2d0ec25d191 Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Fri, 25 Nov 2016 00:25:18 -0800 Subject: [PATCH 0328/1012] Bad formatting --- library/include/modules/Job.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 1448b6656..439a4484d 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -68,16 +68,16 @@ namespace DFHack // This helpful method only removes the backref from the item to the job, but it doesn't // remove the item ref from the job's vector, or delete it or anything. Think of it as a method - // that does all the needful to make an item ref ready to delete. + // that does all the needful to make an item ref ready to delete. DFHACK_EXPORT void disconnectJobItem(df::job_item_ref *item, df::job *job); // This helpful method only removes the backref from whatever the general_ref points to, // it doesn't remove the general_ref from the job's vector, or delete it or anything. - // Think of it as a method that does all the needful to make a ref ready to delete. + // Think of it as a method that does all the needful to make a ref ready to delete. // If it returns false, you've found a ref that the method doesn't know how to handle. Congratulations! // You should report that and/or check in a fix. DFHACK_EXPORT bool disconnectJobGeneralRef(df::general_ref *ref, df::job *job); // Delete a job & remove all refs from everywhere. - // This method DELETES the job object! Everything related to it will be wiped + // This method DELETES the job object! Everything related to it will be wiped // clean from the earth, so make sure you pull what you need out before calling this! DFHACK_EXPORT bool removeJob(df::job *job); From f71d19578c43e605bbcb00417b01ab4e08e62520 Mon Sep 17 00:00:00 2001 From: nocico Date: Sat, 26 Nov 2016 13:34:56 +0200 Subject: [PATCH 0329/1012] make labormanager know building instruments is furniture hauling --- plugins/labormanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 35454e6cb..7e2bf9584 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -842,6 +842,7 @@ private: case df::building_type::BarsVertical: case df::building_type::GrateWall: case df::building_type::Bookcase: + case df::building_type::Instrument: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: From b723378a627d8e344f70f793fe47808663a412e0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 26 Nov 2016 14:06:31 -0500 Subject: [PATCH 0330/1012] Add nocico to Authors.rst (f71d195) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 242f6a7d3..c606fbe38 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -73,6 +73,7 @@ moversti moversti Neil Little nmlittle Nick Rart nickrart comestible Nikolay Amiantov abbradar +nocico nocico Omniclasm PeridexisErrant PeridexisErrant Petr Mrázek peterix From 8eb4f17b239ed6ccc4c6162df1eb9bc2d48bb351 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 26 Nov 2016 17:37:26 -0600 Subject: [PATCH 0331/1012] Use attributes in calculating assignment weight --- plugins/labormanager.cpp | 156 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index ede083488..ed3390868 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1505,9 +1505,152 @@ static void generate_labor_to_skill_map() labor_to_skill[labor] = skill; } } - } +struct skill_attr_weight { + int phys_attr_weights [6]; + int mental_attr_weights [13]; +}; + +static struct skill_attr_weight skill_attr_weights[ENUM_LAST_ITEM(job_skill) + 1] = +{ + // S A T E R D AA F W C I P M LA SS M KS E SA + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MINING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCUTTING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CARPENTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DETAILSTONE */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MASONRY */, + { { 0, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 } } /* ANIMALTRAIN */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 } } /* ANIMALCARE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_FISH */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_VERMIN */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSFISH */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BUTCHER */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* TRAPPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TANNER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WEAVING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BREWING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ALCHEMY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CLOTHESMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILLING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSPLANTS */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEESEMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILK */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COOK */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLANT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* HERBALISM */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* FISH */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SMELT */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* EXTRACT_STRAND */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_WEAPON */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_FURNITURE */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CUTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ENCRUSTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCRAFT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STONECRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* METALCRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLASSMAKER */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* LEATHERWORK */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BONECARVE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* AXE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWORD */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DAGGER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MACE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* HAMMER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPEAR */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CROSSBOW */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SHIELD */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SIEGECRAFT */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SIEGEOPERATE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOWYER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* PIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WHIP */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BOW */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BLOWGUN */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* THROW */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MECHANICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAGIC_NATURE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SNEAK */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DESIGNBUILDING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0 } } /* DRESS_WOUNDS */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* DIAGNOSE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SURGERY */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SET_BONE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SUTURE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CRUTCH_WALK */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WOOD_BURNING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LYE_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SOAP_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POTASH_MAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DYER */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPERATE_PUMP */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWIMMING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PERSUASION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* NEGOTIATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1 } } /* JUDGING_INTENT */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* APPRAISAL */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 } } /* ORGANIZATION */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* RECORD_KEEPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 } } /* LYING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* INTIMIDATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* CONVERSATION */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* COMEDY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* FLATTERY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 } } /* CONSOLE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PACIFY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TRACKING */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* KNOWLEDGE_ACQUISITION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* CONCENTRATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISCIPLINE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SITUATIONAL_AWARENESS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WRITING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROSE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POETRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* READING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SPEAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COORDINATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BALANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* LEADERSHIP */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* TEACHING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MELEE_COMBAT */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* RANGED_COMBAT */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WRESTLING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BITE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GRASP_STRIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STANCE_STRIKE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DODGING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MISC_WEAPON */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* KNAPPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILITARY_TACTICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SHEARING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPINNING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* POTTERY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLAZING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PRESSING */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BEEKEEPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WAX_WORKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CLIMBING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GELD */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAKE_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SING_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_KEYBOARD_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_STRINGED_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_WIND_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_PERCUSSION_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CRITICAL_THINKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LOGIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MATHEMATICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ASTRONOMY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEMISTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GEOGRAPHY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPTICS_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FLUID_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PAPERMAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOOKBINDING */ +}; static void enable_plugin(color_ostream &out) { @@ -2148,6 +2291,7 @@ private: { int skill_level = 0; int xp = 0; + int attr_weight = 0; if (labor != df::unit_labor::NONE) { @@ -2156,10 +2300,16 @@ private: { skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); + + for (int pa = 0; pa < 6; pa++) + attr_weight += (skill_attr_weights[skill].phys_attr_weights[pa]) * (d->dwarf->body.physical_attrs[pa].value - 1000); + + for (int ma = 0; ma < 13; ma++) + attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); } } - int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10) + attr_weight; if (labor != df::unit_labor::NONE) { @@ -2177,6 +2327,8 @@ private: score += 5000; } + score -= Units::computeMovementSpeed(d->dwarf); + return score; } From fe1dd01535bf6f7ee0ad384780906886a6034206 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 8 Nov 2016 12:01:24 -0600 Subject: [PATCH 0332/1012] Better job assignment algorithm for labormanager. --- plugins/devel/labormanager.cpp | 266 ++++++++++++++++++--------------- 1 file changed, 149 insertions(+), 117 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index 7d41c7814..ae1f2c17c 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -414,23 +414,23 @@ struct labor_default static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINEa */ {200, 0, TOOL_PICK}, + /* MINE */ {100, 0, TOOL_PICK}, /* HAUL_STONE */ {100, 0, TOOL_NONE}, /* HAUL_WOOD */ {100, 0, TOOL_NONE}, - /* HAUL_BODY */ {200, 0, TOOL_NONE}, - /* HAUL_FOOD */ {300, 0, TOOL_NONE}, - /* HAUL_REFUSE */ {100, 0, TOOL_NONE}, + /* HAUL_BODY */ {1000, 0, TOOL_NONE}, + /* HAUL_FOOD */ {500, 0, TOOL_NONE}, + /* HAUL_REFUSE */ {200, 0, TOOL_NONE}, /* HAUL_ITEM */ {100, 0, TOOL_NONE}, /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, - /* CLEAN */ {200, 0, TOOL_NONE}, - /* CUTWOOD */ {200, 0, TOOL_AXE}, - /* CARPENTER */ {200, 0, TOOL_NONE}, - /* DETAIL */ {200, 0, TOOL_NONE}, - /* MASON */ {200, 0, TOOL_NONE}, - /* ARCHITECT */ {400, 0, TOOL_NONE}, - /* ANIMALTRAIN */ {200, 0, TOOL_NONE}, - /* ANIMALCARE */ {200, 0, TOOL_NONE}, + /* CLEAN */ {100, 0, TOOL_NONE}, + /* CUTWOOD */ {100, 0, TOOL_AXE}, + /* CARPENTER */ {100, 0, TOOL_NONE}, + /* DETAIL */ {100, 0, TOOL_NONE}, + /* MASON */ {100, 0, TOOL_NONE}, + /* ARCHITECT */ {100, 0, TOOL_NONE}, + /* ANIMALTRAIN */ {100, 0, TOOL_NONE}, + /* ANIMALCARE */ {100, 0, TOOL_NONE}, /* DIAGNOSE */ {1000, 0, TOOL_NONE}, /* SURGERY */ {1000, 0, TOOL_NONE}, /* BONE_SETTING */ {1000, 0, TOOL_NONE}, @@ -438,65 +438,65 @@ static const struct labor_default default_labor_infos[] = { /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, - /* BUTCHER */ {200, 0, TOOL_NONE}, - /* TRAPPER */ {200, 0, TOOL_NONE}, - /* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, - /* LEATHER */ {200, 0, TOOL_NONE}, - /* TANNER */ {200, 0, TOOL_NONE}, - /* BREWER */ {200, 0, TOOL_NONE}, - /* ALCHEMIST */ {200, 0, TOOL_NONE}, - /* SOAP_MAKER */ {200, 0, TOOL_NONE}, - /* WEAVER */ {200, 0, TOOL_NONE}, - /* CLOTHESMAKER */ {200, 0, TOOL_NONE}, - /* MILLER */ {200, 0, TOOL_NONE}, - /* PROCESS_PLANT */ {200, 0, TOOL_NONE}, - /* MAKE_CHEESE */ {200, 0, TOOL_NONE}, - /* MILK */ {200, 0, TOOL_NONE}, - /* COOK */ {200, 0, TOOL_NONE}, - /* PLANT */ {200, 0, TOOL_NONE}, - /* HERBALIST */ {200, 0, TOOL_NONE}, + /* BUTCHER */ {500, 0, TOOL_NONE}, + /* TRAPPER */ {100, 0, TOOL_NONE}, + /* DISSECT_VERMIN */ {100, 0, TOOL_NONE}, + /* LEATHER */ {100, 0, TOOL_NONE}, + /* TANNER */ {100, 0, TOOL_NONE}, + /* BREWER */ {100, 0, TOOL_NONE}, + /* ALCHEMIST */ {100, 0, TOOL_NONE}, + /* SOAP_MAKER */ {100, 0, TOOL_NONE}, + /* WEAVER */ {100, 0, TOOL_NONE}, + /* CLOTHESMAKER */ {100, 0, TOOL_NONE}, + /* MILLER */ {100, 0, TOOL_NONE}, + /* PROCESS_PLANT */ {100, 0, TOOL_NONE}, + /* MAKE_CHEESE */ {100, 0, TOOL_NONE}, + /* MILK */ {100, 0, TOOL_NONE}, + /* COOK */ {100, 0, TOOL_NONE}, + /* PLANT */ {100, 0, TOOL_NONE}, + /* HERBALIST */ {100, 0, TOOL_NONE}, /* FISH */ {100, 0, TOOL_NONE}, - /* CLEAN_FISH */ {200, 0, TOOL_NONE}, - /* DISSECT_FISH */ {200, 0, TOOL_NONE}, + /* CLEAN_FISH */ {100, 0, TOOL_NONE}, + /* DISSECT_FISH */ {100, 0, TOOL_NONE}, /* HUNT */ {100, 0, TOOL_CROSSBOW}, - /* SMELT */ {200, 0, TOOL_NONE}, - /* FORGE_WEAPON */ {200, 0, TOOL_NONE}, - /* FORGE_ARMOR */ {200, 0, TOOL_NONE}, - /* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, - /* METAL_CRAFT */ {200, 0, TOOL_NONE}, - /* CUT_GEM */ {200, 0, TOOL_NONE}, - /* ENCRUST_GEM */ {200, 0, TOOL_NONE}, - /* WOOD_CRAFT */ {200, 0, TOOL_NONE}, - /* STONE_CRAFT */ {200, 0, TOOL_NONE}, - /* BONE_CARVE */ {200, 0, TOOL_NONE}, - /* GLASSMAKER */ {200, 0, TOOL_NONE}, - /* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, - /* SIEGECRAFT */ {200, 0, TOOL_NONE}, - /* SIEGEOPERATE */ {200, 0, TOOL_NONE}, - /* BOWYER */ {200, 0, TOOL_NONE}, - /* MECHANIC */ {200, 0, TOOL_NONE}, - /* POTASH_MAKING */ {200, 0, TOOL_NONE}, - /* LYE_MAKING */ {200, 0, TOOL_NONE}, - /* DYER */ {200, 0, TOOL_NONE}, - /* BURN_WOOD */ {200, 0, TOOL_NONE}, - /* OPERATE_PUMP */ {200, 0, TOOL_NONE}, - /* SHEARER */ {200, 0, TOOL_NONE}, - /* SPINNER */ {200, 0, TOOL_NONE}, - /* POTTERY */ {200, 0, TOOL_NONE}, - /* GLAZING */ {200, 0, TOOL_NONE}, - /* PRESSING */ {200, 0, TOOL_NONE}, - /* BEEKEEPING */ {200, 0, TOOL_NONE}, - /* WAX_WORKING */ {200, 0, TOOL_NONE}, - /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE}, - /* HAUL_TRADE */ {200, 0, TOOL_NONE}, - /* PULL_LEVER */ {200, 0, TOOL_NONE}, - /* REMOVE_CONSTRUCTION */ {200, 0, TOOL_NONE}, - /* HAUL_WATER */ {200, 0, TOOL_NONE}, - /* GELD */ {200, 0, TOOL_NONE}, - /* BUILD_ROAD */ {200, 0, TOOL_NONE}, - /* BUILD_CONSTRUCTION */ {200, 0, TOOL_NONE}, - /* PAPERMAKING */ {200, 0, TOOL_NONE}, - /* BOOKBINDING */ {200, 0, TOOL_NONE} + /* SMELT */ {100, 0, TOOL_NONE}, + /* FORGE_WEAPON */ {100, 0, TOOL_NONE}, + /* FORGE_ARMOR */ {100, 0, TOOL_NONE}, + /* FORGE_FURNITURE */ {100, 0, TOOL_NONE}, + /* METAL_CRAFT */ {100, 0, TOOL_NONE}, + /* CUT_GEM */ {100, 0, TOOL_NONE}, + /* ENCRUST_GEM */ {100, 0, TOOL_NONE}, + /* WOOD_CRAFT */ {100, 0, TOOL_NONE}, + /* STONE_CRAFT */ {100, 0, TOOL_NONE}, + /* BONE_CARVE */ {100, 0, TOOL_NONE}, + /* GLASSMAKER */ {100, 0, TOOL_NONE}, + /* EXTRACT_STRAND */ {100, 0, TOOL_NONE}, + /* SIEGECRAFT */ {100, 0, TOOL_NONE}, + /* SIEGEOPERATE */ {100, 0, TOOL_NONE}, + /* BOWYER */ {100, 0, TOOL_NONE}, + /* MECHANIC */ {100, 0, TOOL_NONE}, + /* POTASH_MAKING */ {100, 0, TOOL_NONE}, + /* LYE_MAKING */ {100, 0, TOOL_NONE}, + /* DYER */ {100, 0, TOOL_NONE}, + /* BURN_WOOD */ {100, 0, TOOL_NONE}, + /* OPERATE_PUMP */ {100, 0, TOOL_NONE}, + /* SHEARER */ {100, 0, TOOL_NONE}, + /* SPINNER */ {100, 0, TOOL_NONE}, + /* POTTERY */ {100, 0, TOOL_NONE}, + /* GLAZING */ {100, 0, TOOL_NONE}, + /* PRESSING */ {100, 0, TOOL_NONE}, + /* BEEKEEPING */ {100, 0, TOOL_NONE}, + /* WAX_WORKING */ {100, 0, TOOL_NONE}, + /* PUSH_HAUL_VEHICLES */ {100, 0, TOOL_NONE}, + /* HAUL_TRADE */ {1000, 0, TOOL_NONE}, + /* PULL_LEVER */ {1000, 0, TOOL_NONE}, + /* REMOVE_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* HAUL_WATER */ {100, 0, TOOL_NONE}, + /* GELD */ {100, 0, TOOL_NONE}, + /* BUILD_ROAD */ {100, 0, TOOL_NONE}, + /* BUILD_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* PAPERMAKING */ {100, 0, TOOL_NONE}, + /* BOOKBINDING */ {100, 0, TOOL_NONE} }; void debug (char* fmt, ...); @@ -852,12 +852,13 @@ private: return df::unit_labor::TRAPPER; case df::building_type::Civzone: case df::building_type::Nest: - case df::building_type::RoadDirt: case df::building_type::Stockpile: case df::building_type::Weapon: return df::unit_labor::NONE; case df::building_type::SiegeEngine: return df::unit_labor::SIEGECRAFT; + case df::building_type::RoadDirt: + return df::unit_labor::BUILD_ROAD; } debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", @@ -1165,13 +1166,13 @@ public: job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1674,20 +1675,24 @@ private: if (bld != -1) { df::building* b = binsearch_in_vector(world->buildings.all, bld); - int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) - { - if (b->jobs[jn]->flags.bits.suspend) - continue; - fjid = b->jobs[jn]->id; - break; - } + // check if this job is the first nonsuspended job on this building; if not, ignore it // (except for farms and trade depots) - if (fjid != j->id && - b->getType() != df::building_type::FarmPlot && - b->getType() != df::building_type::TradeDepot) - return; + + if (b->getType() != df::building_type::FarmPlot && + b->getType() != df::building_type::TradeDepot) + { + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + if (fjid != j->id) + return; + } } df::unit_labor labor = labor_mapper->find_job_labor (j); @@ -2054,7 +2059,8 @@ private: out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); // determine if dwarf has medical needs - if (dwarf->dwarf->health) + // babies cannot currently receive health care even if they need it + if (dwarf->dwarf->profession != profession::BABY && dwarf->dwarf->health) { if (dwarf->dwarf->health->flags.bits.needs_recovery) cnt_recover_wounded++; @@ -2158,13 +2164,10 @@ private: if (labor == df::unit_labor::OPERATE_PUMP) score += 50000; else - score += 1000; + score += 25000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 30000; - if (default_labor_infos[labor].tool != TOOL_NONE && - !d->has_tool[default_labor_infos[labor].tool]) - score -= 30000; + score += 10000000; if (d->has_children && labor_outside[labor]) score -= 15000; if (d->armed && labor_outside[labor]) @@ -2359,8 +2362,6 @@ public: } - labor_needed[df::unit_labor::CLEAN] = 1; - if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -2370,7 +2371,9 @@ public: } } + std::map base_priority; priority_queue> pq; + priority_queue> pq2; for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { @@ -2381,15 +2384,16 @@ public: if (labor_infos[l].maximum_dwarfs() > 0 && i->second > labor_infos[l].maximum_dwarfs()) i->second = labor_infos[l].maximum_dwarfs(); - if (i->second > 0) - { - int priority = labor_infos[l].priority(); - if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) - priority += labor_infos[l].time_since_last_assigned()/12; + int priority = labor_infos[l].priority(); + + priority += labor_infos[l].time_since_last_assigned()/12; + priority -= labor_infos[l].busy_dwarfs; + + base_priority[l] = priority; - for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) - priority /= 2; + if (i->second > 0) + { pq.push(make_pair(priority, l)); } } @@ -2416,9 +2420,22 @@ public: if (--labor_needed[labor] > 0) { - priority /= 2; - pq.push(make_pair(priority, labor)); + priority-=10; + pq2.push(make_pair(priority, labor)); } + + if (pq.empty()) + while(!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); + } + } + + while (!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); } int canary = (1 << df::unit_labor::HAUL_STONE) | @@ -2473,7 +2490,7 @@ public: if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) { set_labor(*bestdwarf, l, true); - if (t != TOOL_NONE && (*bestdwarf)->has_tool[t]) + if (t != TOOL_NONE && !((*bestdwarf)->has_tool[t])) { df::job_type j; j = df::job_type::NONE; @@ -2524,8 +2541,7 @@ public: continue; int score = score_labor (*d, l); - if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) - score += labor_infos[l].time_since_last_assigned()/12; + if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) score += 1000000; @@ -2536,7 +2552,9 @@ public: { set_labor(*d, l, true); } - if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } } @@ -2577,18 +2595,23 @@ public: /* Assign any leftover dwarfs to "standard" labors */ + if (print_debug) + out.print ("After assignment, %d dwarfs left over\n", available_dwarfs.size()); + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) { FOR_ENUM_ITEMS (unit_labor, l) { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && - canary & (1 << l)) - set_labor(*d, l, true); - else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER) - set_labor(*d, l, true); - else - set_labor(*d, l, false); - } + if (l == df::unit_labor::NONE) + continue; + + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::REMOVE_CONSTRUCTION || + l == df::unit_labor::PULL_LEVER || + l == df::unit_labor::HAUL_TRADE); + } } /* check for dwarfs assigned no labors and assign them the bucket list if there are */ @@ -2646,7 +2669,8 @@ public: bool has_tool = (*d)->has_tool[t]; bool needs_tool = (*d)->dwarf->status.labors[l]; - if (has_tool != needs_tool) + if ((needs_tool && !has_tool) || + (has_tool && !needs_tool && tool_in_use[t] >= tool_count[t])) { df::job_type j = df::job_type::NONE; @@ -2673,6 +2697,10 @@ public: *df::global::process_jobs = true; } + if (print_debug) { + *df::global::pause_state = true; + } + print_debug = 0; } @@ -2707,8 +2735,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - if (++step_count < 60) +// if (++step_count < 60) +// return CR_OK; + + if (*df::global::process_jobs) return CR_OK; + step_count = 0; debug_stream = &out; From 338f6029a742def578beb6cc9302cb48e2a33428 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 26 Nov 2016 17:37:26 -0600 Subject: [PATCH 0333/1012] Use attributes in calculating assignment weight --- plugins/devel/labormanager.cpp | 156 ++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) diff --git a/plugins/devel/labormanager.cpp b/plugins/devel/labormanager.cpp index ae1f2c17c..7ba1578b2 100644 --- a/plugins/devel/labormanager.cpp +++ b/plugins/devel/labormanager.cpp @@ -1502,9 +1502,152 @@ static void generate_labor_to_skill_map() labor_to_skill[labor] = skill; } } - } +struct skill_attr_weight { + int phys_attr_weights [6]; + int mental_attr_weights [13]; +}; + +static struct skill_attr_weight skill_attr_weights[ENUM_LAST_ITEM(job_skill) + 1] = +{ + // S A T E R D AA F W C I P M LA SS M KS E SA + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MINING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCUTTING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CARPENTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DETAILSTONE */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MASONRY */, + { { 0, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 } } /* ANIMALTRAIN */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 } } /* ANIMALCARE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_FISH */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_VERMIN */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSFISH */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BUTCHER */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* TRAPPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TANNER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WEAVING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BREWING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ALCHEMY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CLOTHESMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILLING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSPLANTS */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEESEMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILK */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COOK */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLANT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* HERBALISM */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* FISH */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SMELT */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* EXTRACT_STRAND */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_WEAPON */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_FURNITURE */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CUTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ENCRUSTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCRAFT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STONECRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* METALCRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLASSMAKER */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* LEATHERWORK */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BONECARVE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* AXE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWORD */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DAGGER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MACE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* HAMMER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPEAR */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CROSSBOW */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SHIELD */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SIEGECRAFT */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SIEGEOPERATE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOWYER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* PIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WHIP */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BOW */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BLOWGUN */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* THROW */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MECHANICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAGIC_NATURE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SNEAK */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DESIGNBUILDING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0 } } /* DRESS_WOUNDS */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* DIAGNOSE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SURGERY */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SET_BONE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SUTURE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CRUTCH_WALK */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WOOD_BURNING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LYE_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SOAP_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POTASH_MAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DYER */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPERATE_PUMP */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWIMMING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PERSUASION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* NEGOTIATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1 } } /* JUDGING_INTENT */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* APPRAISAL */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 } } /* ORGANIZATION */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* RECORD_KEEPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 } } /* LYING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* INTIMIDATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* CONVERSATION */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* COMEDY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* FLATTERY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 } } /* CONSOLE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PACIFY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TRACKING */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* KNOWLEDGE_ACQUISITION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* CONCENTRATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISCIPLINE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SITUATIONAL_AWARENESS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WRITING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROSE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POETRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* READING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SPEAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COORDINATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BALANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* LEADERSHIP */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* TEACHING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MELEE_COMBAT */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* RANGED_COMBAT */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WRESTLING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BITE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GRASP_STRIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STANCE_STRIKE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DODGING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MISC_WEAPON */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* KNAPPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILITARY_TACTICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SHEARING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPINNING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* POTTERY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLAZING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PRESSING */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BEEKEEPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WAX_WORKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CLIMBING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GELD */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAKE_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SING_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_KEYBOARD_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_STRINGED_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_WIND_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_PERCUSSION_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CRITICAL_THINKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LOGIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MATHEMATICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ASTRONOMY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEMISTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GEOGRAPHY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPTICS_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FLUID_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PAPERMAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOOKBINDING */ +}; static void enable_plugin(color_ostream &out) { @@ -2145,6 +2288,7 @@ private: { int skill_level = 0; int xp = 0; + int attr_weight = 0; if (labor != df::unit_labor::NONE) { @@ -2153,10 +2297,16 @@ private: { skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); + + for (int pa = 0; pa < 6; pa++) + attr_weight += (skill_attr_weights[skill].phys_attr_weights[pa]) * (d->dwarf->body.physical_attrs[pa].value - 1000); + + for (int ma = 0; ma < 13; ma++) + attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); } } - int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10) + attr_weight; if (labor != df::unit_labor::NONE) { @@ -2174,6 +2324,8 @@ private: score += 5000; } + score -= Units::computeMovementSpeed(d->dwarf); + return score; } From 491d53b76f8bc0c9667235b2483e97f9248e4b1a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 26 Nov 2016 18:08:48 -0600 Subject: [PATCH 0334/1012] fix white space to make travis happy --- plugins/labormanager.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index ed3390868..e7ab6ce31 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1826,7 +1826,7 @@ private: // (except for farms and trade depots) if (b->getType() != df::building_type::FarmPlot && - b->getType() != df::building_type::TradeDepot) + b->getType() != df::building_type::TradeDepot) { int fjid = -1; for (int jn = 0; jn < b->jobs.size(); jn++) @@ -2301,10 +2301,10 @@ private: skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); - for (int pa = 0; pa < 6; pa++) + for (int pa = 0; pa < 6; pa++) attr_weight += (skill_attr_weights[skill].phys_attr_weights[pa]) * (d->dwarf->body.physical_attrs[pa].value - 1000); - for (int ma = 0; ma < 13; ma++) + for (int ma = 0; ma < 13; ma++) attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); } } @@ -2541,10 +2541,10 @@ public: i->second = labor_infos[l].maximum_dwarfs(); int priority = labor_infos[l].priority(); - + priority += labor_infos[l].time_since_last_assigned()/12; priority -= labor_infos[l].busy_dwarfs; - + base_priority[l] = priority; if (i->second > 0) @@ -2579,8 +2579,8 @@ public: pq2.push(make_pair(priority, labor)); } - if (pq.empty()) - while(!pq2.empty()) + if (pq.empty()) + while(!pq2.empty()) { pq.push(pq2.top()); pq2.pop(); @@ -2707,8 +2707,8 @@ public: { set_labor(*d, l, true); } - if ((*d)->using_labor != df::unit_labor::NONE && - (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } @@ -2760,10 +2760,10 @@ public: if (l == df::unit_labor::NONE) continue; - set_labor(*d, l, - (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || - l == df::unit_labor::CLEAN || - l == df::unit_labor::REMOVE_CONSTRUCTION || + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER || l == df::unit_labor::HAUL_TRADE); } @@ -2893,7 +2893,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // if (++step_count < 60) // return CR_OK; - if (*df::global::process_jobs) + if (*df::global::process_jobs) return CR_OK; step_count = 0; From cbcb148182b0491283d0840d36b8dcadc941f8d0 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 8 Nov 2016 12:01:24 -0600 Subject: [PATCH 0335/1012] Better job assignment algorithm for labormanager. --- plugins/labormanager.cpp | 266 ++++++++++++++++++++++----------------- 1 file changed, 149 insertions(+), 117 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 5a2774cd2..298568a41 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -414,23 +414,23 @@ struct labor_default static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINEa */ {200, 0, TOOL_PICK}, + /* MINE */ {100, 0, TOOL_PICK}, /* HAUL_STONE */ {100, 0, TOOL_NONE}, /* HAUL_WOOD */ {100, 0, TOOL_NONE}, - /* HAUL_BODY */ {200, 0, TOOL_NONE}, - /* HAUL_FOOD */ {300, 0, TOOL_NONE}, - /* HAUL_REFUSE */ {100, 0, TOOL_NONE}, + /* HAUL_BODY */ {1000, 0, TOOL_NONE}, + /* HAUL_FOOD */ {500, 0, TOOL_NONE}, + /* HAUL_REFUSE */ {200, 0, TOOL_NONE}, /* HAUL_ITEM */ {100, 0, TOOL_NONE}, /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, - /* CLEAN */ {200, 0, TOOL_NONE}, - /* CUTWOOD */ {200, 0, TOOL_AXE}, - /* CARPENTER */ {200, 0, TOOL_NONE}, - /* DETAIL */ {200, 0, TOOL_NONE}, - /* MASON */ {200, 0, TOOL_NONE}, - /* ARCHITECT */ {400, 0, TOOL_NONE}, - /* ANIMALTRAIN */ {200, 0, TOOL_NONE}, - /* ANIMALCARE */ {200, 0, TOOL_NONE}, + /* CLEAN */ {100, 0, TOOL_NONE}, + /* CUTWOOD */ {100, 0, TOOL_AXE}, + /* CARPENTER */ {100, 0, TOOL_NONE}, + /* DETAIL */ {100, 0, TOOL_NONE}, + /* MASON */ {100, 0, TOOL_NONE}, + /* ARCHITECT */ {100, 0, TOOL_NONE}, + /* ANIMALTRAIN */ {100, 0, TOOL_NONE}, + /* ANIMALCARE */ {100, 0, TOOL_NONE}, /* DIAGNOSE */ {1000, 0, TOOL_NONE}, /* SURGERY */ {1000, 0, TOOL_NONE}, /* BONE_SETTING */ {1000, 0, TOOL_NONE}, @@ -438,65 +438,65 @@ static const struct labor_default default_labor_infos[] = { /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, - /* BUTCHER */ {200, 0, TOOL_NONE}, - /* TRAPPER */ {200, 0, TOOL_NONE}, - /* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, - /* LEATHER */ {200, 0, TOOL_NONE}, - /* TANNER */ {200, 0, TOOL_NONE}, - /* BREWER */ {200, 0, TOOL_NONE}, - /* ALCHEMIST */ {200, 0, TOOL_NONE}, - /* SOAP_MAKER */ {200, 0, TOOL_NONE}, - /* WEAVER */ {200, 0, TOOL_NONE}, - /* CLOTHESMAKER */ {200, 0, TOOL_NONE}, - /* MILLER */ {200, 0, TOOL_NONE}, - /* PROCESS_PLANT */ {200, 0, TOOL_NONE}, - /* MAKE_CHEESE */ {200, 0, TOOL_NONE}, - /* MILK */ {200, 0, TOOL_NONE}, - /* COOK */ {200, 0, TOOL_NONE}, - /* PLANT */ {200, 0, TOOL_NONE}, - /* HERBALIST */ {200, 0, TOOL_NONE}, + /* BUTCHER */ {500, 0, TOOL_NONE}, + /* TRAPPER */ {100, 0, TOOL_NONE}, + /* DISSECT_VERMIN */ {100, 0, TOOL_NONE}, + /* LEATHER */ {100, 0, TOOL_NONE}, + /* TANNER */ {100, 0, TOOL_NONE}, + /* BREWER */ {100, 0, TOOL_NONE}, + /* ALCHEMIST */ {100, 0, TOOL_NONE}, + /* SOAP_MAKER */ {100, 0, TOOL_NONE}, + /* WEAVER */ {100, 0, TOOL_NONE}, + /* CLOTHESMAKER */ {100, 0, TOOL_NONE}, + /* MILLER */ {100, 0, TOOL_NONE}, + /* PROCESS_PLANT */ {100, 0, TOOL_NONE}, + /* MAKE_CHEESE */ {100, 0, TOOL_NONE}, + /* MILK */ {100, 0, TOOL_NONE}, + /* COOK */ {100, 0, TOOL_NONE}, + /* PLANT */ {100, 0, TOOL_NONE}, + /* HERBALIST */ {100, 0, TOOL_NONE}, /* FISH */ {100, 0, TOOL_NONE}, - /* CLEAN_FISH */ {200, 0, TOOL_NONE}, - /* DISSECT_FISH */ {200, 0, TOOL_NONE}, + /* CLEAN_FISH */ {100, 0, TOOL_NONE}, + /* DISSECT_FISH */ {100, 0, TOOL_NONE}, /* HUNT */ {100, 0, TOOL_CROSSBOW}, - /* SMELT */ {200, 0, TOOL_NONE}, - /* FORGE_WEAPON */ {200, 0, TOOL_NONE}, - /* FORGE_ARMOR */ {200, 0, TOOL_NONE}, - /* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, - /* METAL_CRAFT */ {200, 0, TOOL_NONE}, - /* CUT_GEM */ {200, 0, TOOL_NONE}, - /* ENCRUST_GEM */ {200, 0, TOOL_NONE}, - /* WOOD_CRAFT */ {200, 0, TOOL_NONE}, - /* STONE_CRAFT */ {200, 0, TOOL_NONE}, - /* BONE_CARVE */ {200, 0, TOOL_NONE}, - /* GLASSMAKER */ {200, 0, TOOL_NONE}, - /* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, - /* SIEGECRAFT */ {200, 0, TOOL_NONE}, - /* SIEGEOPERATE */ {200, 0, TOOL_NONE}, - /* BOWYER */ {200, 0, TOOL_NONE}, - /* MECHANIC */ {200, 0, TOOL_NONE}, - /* POTASH_MAKING */ {200, 0, TOOL_NONE}, - /* LYE_MAKING */ {200, 0, TOOL_NONE}, - /* DYER */ {200, 0, TOOL_NONE}, - /* BURN_WOOD */ {200, 0, TOOL_NONE}, - /* OPERATE_PUMP */ {200, 0, TOOL_NONE}, - /* SHEARER */ {200, 0, TOOL_NONE}, - /* SPINNER */ {200, 0, TOOL_NONE}, - /* POTTERY */ {200, 0, TOOL_NONE}, - /* GLAZING */ {200, 0, TOOL_NONE}, - /* PRESSING */ {200, 0, TOOL_NONE}, - /* BEEKEEPING */ {200, 0, TOOL_NONE}, - /* WAX_WORKING */ {200, 0, TOOL_NONE}, - /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE}, - /* HAUL_TRADE */ {200, 0, TOOL_NONE}, - /* PULL_LEVER */ {200, 0, TOOL_NONE}, - /* REMOVE_CONSTRUCTION */ {200, 0, TOOL_NONE}, - /* HAUL_WATER */ {200, 0, TOOL_NONE}, - /* GELD */ {200, 0, TOOL_NONE}, - /* BUILD_ROAD */ {200, 0, TOOL_NONE}, - /* BUILD_CONSTRUCTION */ {200, 0, TOOL_NONE}, - /* PAPERMAKING */ {200, 0, TOOL_NONE}, - /* BOOKBINDING */ {200, 0, TOOL_NONE} + /* SMELT */ {100, 0, TOOL_NONE}, + /* FORGE_WEAPON */ {100, 0, TOOL_NONE}, + /* FORGE_ARMOR */ {100, 0, TOOL_NONE}, + /* FORGE_FURNITURE */ {100, 0, TOOL_NONE}, + /* METAL_CRAFT */ {100, 0, TOOL_NONE}, + /* CUT_GEM */ {100, 0, TOOL_NONE}, + /* ENCRUST_GEM */ {100, 0, TOOL_NONE}, + /* WOOD_CRAFT */ {100, 0, TOOL_NONE}, + /* STONE_CRAFT */ {100, 0, TOOL_NONE}, + /* BONE_CARVE */ {100, 0, TOOL_NONE}, + /* GLASSMAKER */ {100, 0, TOOL_NONE}, + /* EXTRACT_STRAND */ {100, 0, TOOL_NONE}, + /* SIEGECRAFT */ {100, 0, TOOL_NONE}, + /* SIEGEOPERATE */ {100, 0, TOOL_NONE}, + /* BOWYER */ {100, 0, TOOL_NONE}, + /* MECHANIC */ {100, 0, TOOL_NONE}, + /* POTASH_MAKING */ {100, 0, TOOL_NONE}, + /* LYE_MAKING */ {100, 0, TOOL_NONE}, + /* DYER */ {100, 0, TOOL_NONE}, + /* BURN_WOOD */ {100, 0, TOOL_NONE}, + /* OPERATE_PUMP */ {100, 0, TOOL_NONE}, + /* SHEARER */ {100, 0, TOOL_NONE}, + /* SPINNER */ {100, 0, TOOL_NONE}, + /* POTTERY */ {100, 0, TOOL_NONE}, + /* GLAZING */ {100, 0, TOOL_NONE}, + /* PRESSING */ {100, 0, TOOL_NONE}, + /* BEEKEEPING */ {100, 0, TOOL_NONE}, + /* WAX_WORKING */ {100, 0, TOOL_NONE}, + /* PUSH_HAUL_VEHICLES */ {100, 0, TOOL_NONE}, + /* HAUL_TRADE */ {1000, 0, TOOL_NONE}, + /* PULL_LEVER */ {1000, 0, TOOL_NONE}, + /* REMOVE_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* HAUL_WATER */ {100, 0, TOOL_NONE}, + /* GELD */ {100, 0, TOOL_NONE}, + /* BUILD_ROAD */ {100, 0, TOOL_NONE}, + /* BUILD_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* PAPERMAKING */ {100, 0, TOOL_NONE}, + /* BOOKBINDING */ {100, 0, TOOL_NONE} }; void debug (char* fmt, ...); @@ -852,12 +852,13 @@ private: return df::unit_labor::TRAPPER; case df::building_type::Civzone: case df::building_type::Nest: - case df::building_type::RoadDirt: case df::building_type::Stockpile: case df::building_type::Weapon: return df::unit_labor::NONE; case df::building_type::SiegeEngine: return df::unit_labor::SIEGECRAFT; + case df::building_type::RoadDirt: + return df::unit_labor::BUILD_ROAD; } debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", @@ -1167,13 +1168,13 @@ public: job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1676,20 +1677,24 @@ private: if (bld != -1) { df::building* b = binsearch_in_vector(world->buildings.all, bld); - int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) - { - if (b->jobs[jn]->flags.bits.suspend) - continue; - fjid = b->jobs[jn]->id; - break; - } + // check if this job is the first nonsuspended job on this building; if not, ignore it // (except for farms and trade depots) - if (fjid != j->id && - b->getType() != df::building_type::FarmPlot && - b->getType() != df::building_type::TradeDepot) - return; + + if (b->getType() != df::building_type::FarmPlot && + b->getType() != df::building_type::TradeDepot) + { + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + if (fjid != j->id) + return; + } } df::unit_labor labor = labor_mapper->find_job_labor (j); @@ -2056,7 +2061,8 @@ private: out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); // determine if dwarf has medical needs - if (dwarf->dwarf->health) + // babies cannot currently receive health care even if they need it + if (dwarf->dwarf->profession != profession::BABY && dwarf->dwarf->health) { if (dwarf->dwarf->health->flags.bits.needs_recovery) cnt_recover_wounded++; @@ -2160,13 +2166,10 @@ private: if (labor == df::unit_labor::OPERATE_PUMP) score += 50000; else - score += 1000; + score += 25000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 30000; - if (default_labor_infos[labor].tool != TOOL_NONE && - !d->has_tool[default_labor_infos[labor].tool]) - score -= 30000; + score += 10000000; if (d->has_children && labor_outside[labor]) score -= 15000; if (d->armed && labor_outside[labor]) @@ -2361,8 +2364,6 @@ public: } - labor_needed[df::unit_labor::CLEAN] = 1; - if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -2372,7 +2373,9 @@ public: } } + std::map base_priority; priority_queue> pq; + priority_queue> pq2; for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { @@ -2383,15 +2386,16 @@ public: if (labor_infos[l].maximum_dwarfs() > 0 && i->second > labor_infos[l].maximum_dwarfs()) i->second = labor_infos[l].maximum_dwarfs(); - if (i->second > 0) - { - int priority = labor_infos[l].priority(); - if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) - priority += labor_infos[l].time_since_last_assigned()/12; + int priority = labor_infos[l].priority(); + + priority += labor_infos[l].time_since_last_assigned()/12; + priority -= labor_infos[l].busy_dwarfs; + + base_priority[l] = priority; - for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) - priority /= 2; + if (i->second > 0) + { pq.push(make_pair(priority, l)); } } @@ -2418,9 +2422,22 @@ public: if (--labor_needed[labor] > 0) { - priority /= 2; - pq.push(make_pair(priority, labor)); + priority-=10; + pq2.push(make_pair(priority, labor)); } + + if (pq.empty()) + while(!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); + } + } + + while (!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); } int canary = (1 << df::unit_labor::HAUL_STONE) | @@ -2475,7 +2492,7 @@ public: if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) { set_labor(*bestdwarf, l, true); - if (t != TOOL_NONE && (*bestdwarf)->has_tool[t]) + if (t != TOOL_NONE && !((*bestdwarf)->has_tool[t])) { df::job_type j; j = df::job_type::NONE; @@ -2526,8 +2543,7 @@ public: continue; int score = score_labor (*d, l); - if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS) - score += labor_infos[l].time_since_last_assigned()/12; + if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) score += 1000000; @@ -2538,7 +2554,9 @@ public: { set_labor(*d, l, true); } - if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } } @@ -2579,18 +2597,23 @@ public: /* Assign any leftover dwarfs to "standard" labors */ + if (print_debug) + out.print ("After assignment, %d dwarfs left over\n", available_dwarfs.size()); + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) { FOR_ENUM_ITEMS (unit_labor, l) { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && - canary & (1 << l)) - set_labor(*d, l, true); - else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER) - set_labor(*d, l, true); - else - set_labor(*d, l, false); - } + if (l == df::unit_labor::NONE) + continue; + + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::REMOVE_CONSTRUCTION || + l == df::unit_labor::PULL_LEVER || + l == df::unit_labor::HAUL_TRADE); + } } /* check for dwarfs assigned no labors and assign them the bucket list if there are */ @@ -2648,7 +2671,8 @@ public: bool has_tool = (*d)->has_tool[t]; bool needs_tool = (*d)->dwarf->status.labors[l]; - if (has_tool != needs_tool) + if ((needs_tool && !has_tool) || + (has_tool && !needs_tool && tool_in_use[t] >= tool_count[t])) { df::job_type j = df::job_type::NONE; @@ -2675,6 +2699,10 @@ public: *df::global::process_jobs = true; } + if (print_debug) { + *df::global::pause_state = true; + } + print_debug = 0; } @@ -2709,8 +2737,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - if (++step_count < 60) +// if (++step_count < 60) +// return CR_OK; + + if (*df::global::process_jobs) return CR_OK; + step_count = 0; debug_stream = &out; From 61baaa0c374d252bc72645af01dd527c91c8fd7b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 18:02:07 -0500 Subject: [PATCH 0336/1012] Docs for labormanager --- docs/Plugins.rst | 104 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index ba502c38e..adf4d1206 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -810,6 +810,110 @@ Examples: ``autolabor CUTWOOD disable`` Turn off autolabor for wood cutting. +.. _labormanager: + +labormanager +============ +Automatically manage dwarf labors to efficiently complete jobs. +Labormanager is derived from autolabor (above) but uses a completely +different approach to assigning jobs to dwarves. While autolabor tries +to keep as many dwarves busy as possible, labormanager instead strives +to get jobs done as quickly as possible. + +Labormanager frequently scans the current job list, current list of +dwarfs, and the map to determine how many dwarves need to be assigned to +what labors in order to meet all current labor needs without starving +any particular type of job. + +.. warning:: + + *As with autolabor, labormanager will override any manual changes you + make to labors while it is enabled, including through other tools such + as Dwarf Therapist* + +Simple usage: + +:enable labormanager: Enables the plugin with default settings. +(Persistent per fortress) :disable labormanager: Disables the plugin. + +Anything beyond this is optional - autolabor works fairly well on the +default settings. + +The default priorities for each labor vary (some labors are higher +priority by default than others). The way the plugin works is that, once +it determines how many of each labor is needed, it then sorts them by +adjusted priority. (Labors other than hauling have a bias added to them +based on how long it's been since they were last used, to prevent job +starvation.) The labor with the highest priority is selected, the "best +fit" dwarf for that labor is assigned to that labor, and then its +priority is *halved*. This process is repeated until either dwarfs or +labors run out. + +Because there is no easy way to detect how many haulers are actually +needed at any moment, the plugin always ensures that at least one dwarf +is assigned to each of the hauling labors, even if no hauling jobs are +detected. At least one dwarf is always assigned to construction removing +and cleaning because these jobs also cannot be easily detected. Lever +pulling is always assigned to everyone. Any dwarfs for which there are +no jobs will be assigned hauling, lever pulling, and cleaning labors. If +you use animal trainers, note that labormanager + +Labormanager also sometimes assigns extra labors to currently busy +dwarfs so that when they finish their current job, they will go off and +do something useful instead of standing around waiting for a job. + +There is special handling to ensure that at least one dwarf is assigned +to haul food whenever food is detected left in a place where it will rot +if not stored. This will cause a dwarf to go idle if you have no +storepiles to haul food to. + +Dwarfs who are unable to work (child, in the military, wounded, +handless, asleep, in a meeting) are entirely excluded from labor +assignment. Any dwarf explicitly assigned to a burrow will also be +completely ignored by labormanager. + +The fitness algorithm for assigning jobs to dwarfs generally attempts to +favor dwarfs who are more skilled over those who are less skilled. It +also tries to avoid assigning female dwarfs with children to jobs that +are "outside", favors assigning "outside" jobs to dwarfs who are +carrying a tool that could be used as a weapon, and tries to minimize +how often dwarfs have to reequip. + +Labormanager automatically determines medical needs and reserves health +care providers as needed. Note that this may cause idling if you have +injured dwarfs but no or inadequate hospital facilities. + +Hunting is never assigned without a butchery, and fishing is never +assigned without a fishery, and neither of these labors is assigned +unless specifically enabled. + +The method by which labormanager determines what labor is needed for a +particular job is complicated and, in places, incomplete. In some +situations, labormanager will detect that it cannot determine what labor +is required. It will, by default, pause and print an error message on +the dfhack console, followed by the message "LABORMANAGER: Game paused +so you can investigate the above message.". If this happens, please open +an issue on github, reporting the lines that immediately preceded this +message. You can tell labormanager to ignore this error and carry on by +typing "autolabor pause-on-error no", but be warned that some job may go +undone in this situation. + +Advanced usage: + +:autolabor enable: Turn plugin on. +:autolabor disable: Turn plugin off. +:autolabor priority : Set the priority value (see above) for labor to . +:autolabor reset : Reset the priority value of labor to its default. +:autolabor reset-all: Reset all priority values to their defaults. +:autolabor allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. +:autolabor forbid-fishing: Forbid dwarfs from fishing. Default behavior. +:autolabor allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. +:autolabor forbid-hunting: Forbid dwarfs from hunting. Default behavior. +:autolabor list: Show current priorities and current allocation stats. +:autolabor pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. +:autolabor pause-on-error no: Allow labormanager to continue past a labor inference engine failure. + + .. _autohauler: autohauler From a28c4db69ae8441d9e6d38a080dea8673e0a7a4a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 25 Oct 2016 18:10:29 -0500 Subject: [PATCH 0337/1012] Finish clipped sentence. (erk.) --- docs/Plugins.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index adf4d1206..c82d68389 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -856,7 +856,10 @@ detected. At least one dwarf is always assigned to construction removing and cleaning because these jobs also cannot be easily detected. Lever pulling is always assigned to everyone. Any dwarfs for which there are no jobs will be assigned hauling, lever pulling, and cleaning labors. If -you use animal trainers, note that labormanager +you use animal trainers, note that labormanager will misbehave if you +assign specific trainers to specific animals; results are only guaranteed +if you use "any trainer", and animal trainers will probably be +overallocated in any case. Labormanager also sometimes assigns extra labors to currently busy dwarfs so that when they finish their current job, they will go off and @@ -900,18 +903,18 @@ undone in this situation. Advanced usage: -:autolabor enable: Turn plugin on. -:autolabor disable: Turn plugin off. -:autolabor priority : Set the priority value (see above) for labor to . -:autolabor reset : Reset the priority value of labor to its default. -:autolabor reset-all: Reset all priority values to their defaults. -:autolabor allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. -:autolabor forbid-fishing: Forbid dwarfs from fishing. Default behavior. -:autolabor allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. -:autolabor forbid-hunting: Forbid dwarfs from hunting. Default behavior. -:autolabor list: Show current priorities and current allocation stats. -:autolabor pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. -:autolabor pause-on-error no: Allow labormanager to continue past a labor inference engine failure. +:labormanager enable: Turn plugin on. +:labormanager disable: Turn plugin off. +:labormanager priority : Set the priority value (see above) for labor to . +:labormanager reset : Reset the priority value of labor to its default. +:labormanager reset-all: Reset all priority values to their defaults. +:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. +:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. +:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. +:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. +:labormanager list: Show current priorities and current allocation stats. +:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. +:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. .. _autohauler: From 47426e3e002356980f632c91ade9ded5073656f3 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 26 Nov 2016 17:37:26 -0600 Subject: [PATCH 0338/1012] Use attributes in calculating assignment weight --- plugins/labormanager.cpp | 156 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 298568a41..eedcffe7e 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1504,9 +1504,152 @@ static void generate_labor_to_skill_map() labor_to_skill[labor] = skill; } } - } +struct skill_attr_weight { + int phys_attr_weights [6]; + int mental_attr_weights [13]; +}; + +static struct skill_attr_weight skill_attr_weights[ENUM_LAST_ITEM(job_skill) + 1] = +{ + // S A T E R D AA F W C I P M LA SS M KS E SA + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MINING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCUTTING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CARPENTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DETAILSTONE */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MASONRY */, + { { 0, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 } } /* ANIMALTRAIN */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 } } /* ANIMALCARE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_FISH */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_VERMIN */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSFISH */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BUTCHER */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* TRAPPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TANNER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WEAVING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BREWING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ALCHEMY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CLOTHESMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILLING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSPLANTS */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEESEMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILK */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COOK */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLANT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* HERBALISM */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* FISH */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SMELT */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* EXTRACT_STRAND */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_WEAPON */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_FURNITURE */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CUTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ENCRUSTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCRAFT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STONECRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* METALCRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLASSMAKER */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* LEATHERWORK */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BONECARVE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* AXE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWORD */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DAGGER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MACE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* HAMMER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPEAR */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CROSSBOW */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SHIELD */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SIEGECRAFT */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SIEGEOPERATE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOWYER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* PIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WHIP */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BOW */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BLOWGUN */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* THROW */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MECHANICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAGIC_NATURE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SNEAK */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DESIGNBUILDING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0 } } /* DRESS_WOUNDS */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* DIAGNOSE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SURGERY */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SET_BONE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SUTURE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CRUTCH_WALK */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WOOD_BURNING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LYE_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SOAP_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POTASH_MAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DYER */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPERATE_PUMP */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWIMMING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PERSUASION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* NEGOTIATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1 } } /* JUDGING_INTENT */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* APPRAISAL */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 } } /* ORGANIZATION */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* RECORD_KEEPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 } } /* LYING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* INTIMIDATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* CONVERSATION */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* COMEDY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* FLATTERY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 } } /* CONSOLE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PACIFY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TRACKING */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* KNOWLEDGE_ACQUISITION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* CONCENTRATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISCIPLINE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SITUATIONAL_AWARENESS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WRITING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROSE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POETRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* READING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SPEAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COORDINATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BALANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* LEADERSHIP */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* TEACHING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MELEE_COMBAT */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* RANGED_COMBAT */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WRESTLING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BITE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GRASP_STRIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STANCE_STRIKE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DODGING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MISC_WEAPON */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* KNAPPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILITARY_TACTICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SHEARING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPINNING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* POTTERY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLAZING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PRESSING */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BEEKEEPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WAX_WORKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CLIMBING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GELD */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAKE_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SING_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_KEYBOARD_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_STRINGED_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_WIND_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_PERCUSSION_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CRITICAL_THINKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LOGIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MATHEMATICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ASTRONOMY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEMISTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GEOGRAPHY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPTICS_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FLUID_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PAPERMAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOOKBINDING */ +}; static void enable_plugin(color_ostream &out) { @@ -2147,6 +2290,7 @@ private: { int skill_level = 0; int xp = 0; + int attr_weight = 0; if (labor != df::unit_labor::NONE) { @@ -2155,10 +2299,16 @@ private: { skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); + + for (int pa = 0; pa < 6; pa++) + attr_weight += (skill_attr_weights[skill].phys_attr_weights[pa]) * (d->dwarf->body.physical_attrs[pa].value - 1000); + + for (int ma = 0; ma < 13; ma++) + attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); } } - int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10) + attr_weight; if (labor != df::unit_labor::NONE) { @@ -2176,6 +2326,8 @@ private: score += 5000; } + score -= Units::computeMovementSpeed(d->dwarf); + return score; } From ad84217687fa84ef9e0375a705a14b35290c2cf9 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 26 Nov 2016 18:08:48 -0600 Subject: [PATCH 0339/1012] fix white space to make travis happy --- plugins/labormanager.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index eedcffe7e..e0c26bb61 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1825,7 +1825,7 @@ private: // (except for farms and trade depots) if (b->getType() != df::building_type::FarmPlot && - b->getType() != df::building_type::TradeDepot) + b->getType() != df::building_type::TradeDepot) { int fjid = -1; for (int jn = 0; jn < b->jobs.size(); jn++) @@ -2300,10 +2300,10 @@ private: skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); - for (int pa = 0; pa < 6; pa++) + for (int pa = 0; pa < 6; pa++) attr_weight += (skill_attr_weights[skill].phys_attr_weights[pa]) * (d->dwarf->body.physical_attrs[pa].value - 1000); - for (int ma = 0; ma < 13; ma++) + for (int ma = 0; ma < 13; ma++) attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); } } @@ -2540,10 +2540,10 @@ public: i->second = labor_infos[l].maximum_dwarfs(); int priority = labor_infos[l].priority(); - + priority += labor_infos[l].time_since_last_assigned()/12; priority -= labor_infos[l].busy_dwarfs; - + base_priority[l] = priority; if (i->second > 0) @@ -2578,8 +2578,8 @@ public: pq2.push(make_pair(priority, labor)); } - if (pq.empty()) - while(!pq2.empty()) + if (pq.empty()) + while(!pq2.empty()) { pq.push(pq2.top()); pq2.pop(); @@ -2706,8 +2706,8 @@ public: { set_labor(*d, l, true); } - if ((*d)->using_labor != df::unit_labor::NONE && - (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) set_labor(*d, (*d)->using_labor, false); } @@ -2759,10 +2759,10 @@ public: if (l == df::unit_labor::NONE) continue; - set_labor(*d, l, - (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || - l == df::unit_labor::CLEAN || - l == df::unit_labor::REMOVE_CONSTRUCTION || + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER || l == df::unit_labor::HAUL_TRADE); } @@ -2892,7 +2892,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // if (++step_count < 60) // return CR_OK; - if (*df::global::process_jobs) + if (*df::global::process_jobs) return CR_OK; step_count = 0; From 10384fe720d739c787cdc99dd55544fc1c6b6bdd Mon Sep 17 00:00:00 2001 From: nocico Date: Sat, 26 Nov 2016 13:34:56 +0200 Subject: [PATCH 0340/1012] make labormanager know building instruments is furniture hauling --- plugins/labormanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index e0c26bb61..362b6a4f5 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -841,6 +841,7 @@ private: case df::building_type::BarsVertical: case df::building_type::GrateWall: case df::building_type::Bookcase: + case df::building_type::Instrument: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: From 0acd1c9059d985591525ca96599a5e444790749c Mon Sep 17 00:00:00 2001 From: nocico Date: Sat, 26 Nov 2016 13:34:56 +0200 Subject: [PATCH 0341/1012] make labormanager know building instruments is furniture hauling --- plugins/labormanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index e7ab6ce31..12bac7227 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -842,6 +842,7 @@ private: case df::building_type::BarsVertical: case df::building_type::GrateWall: case df::building_type::Bookcase: + case df::building_type::Instrument: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: From 5405c7674616ad4ddb13dfa0a9281ba29980a89e Mon Sep 17 00:00:00 2001 From: nocico Date: Mon, 28 Nov 2016 05:25:38 +0200 Subject: [PATCH 0342/1012] labormanager: support for jobs in bowyer's shop --- plugins/labormanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 7e2bf9584..635eadaa7 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1021,6 +1021,8 @@ private: return df::unit_labor::LEATHER; case df::workshop_type::Clothiers: return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::Bowyers: + return df::unit_labor::BOWYER; case df::workshop_type::MagmaForge: case df::workshop_type::MetalsmithsForge: return metaltype; From ae48612ebd59d67a5c5082503f6cce31355a7b21 Mon Sep 17 00:00:00 2001 From: nocico Date: Mon, 28 Nov 2016 05:25:38 +0200 Subject: [PATCH 0343/1012] labormanager: support for jobs in bowyer's shop --- plugins/labormanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 362b6a4f5..ce8e18a6d 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1020,6 +1020,8 @@ private: return df::unit_labor::LEATHER; case df::workshop_type::Clothiers: return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::Bowyers: + return df::unit_labor::BOWYER; case df::workshop_type::MagmaForge: case df::workshop_type::MetalsmithsForge: return metaltype; From ba4952491a3b62a1817148ecdc5fdaac2e141391 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Nov 2016 00:02:59 -0500 Subject: [PATCH 0344/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 25f85f8c7..35cd74b06 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 25f85f8c7b7a2f0b5a635a237985952337dcfef7 +Subproject commit 35cd74b0688565e8b37275d20d99911c7f5b2c7e From 15b7ba552d42e47ab2bc3cd96fb8386f04bdcc2d Mon Sep 17 00:00:00 2001 From: nocico Date: Tue, 29 Nov 2016 01:12:40 +0200 Subject: [PATCH 0345/1012] labormanager: trap components --- plugins/labormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 635eadaa7..a582ce2d1 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1273,7 +1273,7 @@ public: job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); - job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_make_weapon; job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC) ; From 442a12a9b28580a96fb44bb6aaf7a1f7d859107f Mon Sep 17 00:00:00 2001 From: nocico Date: Tue, 29 Nov 2016 04:24:25 +0200 Subject: [PATCH 0346/1012] labormanager: multimaterial constructions --- plugins/labormanager.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index a582ce2d1..b90b1656c 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -673,8 +673,15 @@ static df::building* get_building_from_job(df::job* j) return 0; } -static df::unit_labor construction_build_labor (df::item* i) +static df::unit_labor construction_build_labor (df::building_actual* b) { + if (b->getType() == df::building_type::RoadPaved) + return df::unit_labor::BUILD_ROAD; + auto a = (df::building_actual *) b; + // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block + // For wells 0 mechanism, 1 rope, 2 bucket, 3 block + // Trade depots and bridges use the last one too + df::item* i = a->contained_items.back()->item; MaterialInfo matinfo; if (i && matinfo.decode(i)) { @@ -813,7 +820,7 @@ private: df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) return df::unit_labor::ARCHITECT; - return construction_build_labor(j->items[0]->item); + return construction_build_labor(b); } break; case df::building_type::FarmPlot: @@ -911,8 +918,8 @@ private: case df::building_type::Well: case df::building_type::Windmill: { - df::building_actual* b = (df::building_actual*) bld; - return construction_build_labor(b->contained_items[0]->item); + auto b = (df::building_actual*) bld; + return construction_build_labor(b); } break; case df::building_type::FarmPlot: From d626990ac850936ffaec2bee36ae13a7b8fd4059 Mon Sep 17 00:00:00 2001 From: nocico Date: Tue, 29 Nov 2016 01:12:40 +0200 Subject: [PATCH 0347/1012] labormanager: trap components --- plugins/labormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index ce8e18a6d..cf84faf8b 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1272,7 +1272,7 @@ public: job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); - job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_make_weapon; job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC) ; job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC) ; From 5c16b5a2b730959a44f6f712c98ef5e40ee6c133 Mon Sep 17 00:00:00 2001 From: nocico Date: Tue, 29 Nov 2016 04:24:25 +0200 Subject: [PATCH 0348/1012] labormanager: multimaterial constructions --- plugins/labormanager.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index cf84faf8b..d263c1c66 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -672,8 +672,15 @@ static df::building* get_building_from_job(df::job* j) return 0; } -static df::unit_labor construction_build_labor (df::item* i) +static df::unit_labor construction_build_labor (df::building_actual* b) { + if (b->getType() == df::building_type::RoadPaved) + return df::unit_labor::BUILD_ROAD; + auto a = (df::building_actual *) b; + // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block + // For wells 0 mechanism, 1 rope, 2 bucket, 3 block + // Trade depots and bridges use the last one too + df::item* i = a->contained_items.back()->item; MaterialInfo matinfo; if (i && matinfo.decode(i)) { @@ -812,7 +819,7 @@ private: df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) return df::unit_labor::ARCHITECT; - return construction_build_labor(j->items[0]->item); + return construction_build_labor(b); } break; case df::building_type::FarmPlot: @@ -910,8 +917,8 @@ private: case df::building_type::Well: case df::building_type::Windmill: { - df::building_actual* b = (df::building_actual*) bld; - return construction_build_labor(b->contained_items[0]->item); + auto b = (df::building_actual*) bld; + return construction_build_labor(b); } break; case df::building_type::FarmPlot: From d5d0775f1ad3888ea671df1f9f2de80324996672 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 29 Nov 2016 00:22:34 -0600 Subject: [PATCH 0349/1012] Remove redundant cast --- plugins/labormanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index d263c1c66..53fce05ea 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -676,11 +676,10 @@ static df::unit_labor construction_build_labor (df::building_actual* b) { if (b->getType() == df::building_type::RoadPaved) return df::unit_labor::BUILD_ROAD; - auto a = (df::building_actual *) b; // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block // For wells 0 mechanism, 1 rope, 2 bucket, 3 block // Trade depots and bridges use the last one too - df::item* i = a->contained_items.back()->item; + df::item* i = b->contained_items.back()->item; MaterialInfo matinfo; if (i && matinfo.decode(i)) { From 77a1264a947f3201639b5991f57b36e31f69b125 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 1 Dec 2016 11:06:40 -0600 Subject: [PATCH 0350/1012] Fix deconstruction of buildings containing items --- plugins/labormanager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 53fce05ea..49645234a 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -676,10 +676,15 @@ static df::unit_labor construction_build_labor (df::building_actual* b) { if (b->getType() == df::building_type::RoadPaved) return df::unit_labor::BUILD_ROAD; + // Find last item in building with use mode 2 // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block // For wells 0 mechanism, 1 rope, 2 bucket, 3 block // Trade depots and bridges use the last one too - df::item* i = b->contained_items.back()->item; + // Must check use mode b/c buildings may have items in them that are not part of the building + + if ((*p)->use_mode == 2) + i = (*p)->item; + MaterialInfo matinfo; if (i && matinfo.decode(i)) { From 1d976385794a7050b2c5c9a4db169653080842fe Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 1 Dec 2016 11:10:52 -0600 Subject: [PATCH 0351/1012] Mysteriously disappearing code reinserted --- plugins/labormanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 49645234a..12e8aa0c3 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -682,6 +682,8 @@ static df::unit_labor construction_build_labor (df::building_actual* b) // Trade depots and bridges use the last one too // Must check use mode b/c buildings may have items in them that are not part of the building + df::item* i = 0; + for (auto p = b->contained_items.begin(); p != b->contained_items.end(); p++) if ((*p)->use_mode == 2) i = (*p)->item; From ad6d6fbaa6907c5ca482fff3eb0589ea0d704322 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 1 Dec 2016 14:36:46 -0600 Subject: [PATCH 0352/1012] further tweak building construction labor identification It's always more complicated than you expect.... --- plugins/labormanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 12e8aa0c3..2a97b7d58 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -676,7 +676,7 @@ static df::unit_labor construction_build_labor (df::building_actual* b) { if (b->getType() == df::building_type::RoadPaved) return df::unit_labor::BUILD_ROAD; - // Find last item in building with use mode 2 + // Find last item in building with use mode appropriate to the building's constructions state // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block // For wells 0 mechanism, 1 rope, 2 bucket, 3 block // Trade depots and bridges use the last one too @@ -684,7 +684,8 @@ static df::unit_labor construction_build_labor (df::building_actual* b) df::item* i = 0; for (auto p = b->contained_items.begin(); p != b->contained_items.end(); p++) - if ((*p)->use_mode == 2) + if (b->construction_stage > 0 && (*p)->use_mode == 2 || + b->construction_stage == 0 && (*p)->use_mode == 0) i = (*p)->item; MaterialInfo matinfo; From 595f3857b6b11cc42adc518fce787e914e54536c Mon Sep 17 00:00:00 2001 From: Stephen Baynham Date: Thu, 1 Dec 2016 20:13:49 -0800 Subject: [PATCH 0353/1012] Reverse the param order of these two methods The current way doesn't match other Job module methods --- library/include/modules/Job.h | 4 ++-- library/modules/Job.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 439a4484d..814f1e062 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -69,13 +69,13 @@ namespace DFHack // This helpful method only removes the backref from the item to the job, but it doesn't // remove the item ref from the job's vector, or delete it or anything. Think of it as a method // that does all the needful to make an item ref ready to delete. - DFHACK_EXPORT void disconnectJobItem(df::job_item_ref *item, df::job *job); + DFHACK_EXPORT void disconnectJobItem(df::job *job, df::job_item_ref *item); // This helpful method only removes the backref from whatever the general_ref points to, // it doesn't remove the general_ref from the job's vector, or delete it or anything. // Think of it as a method that does all the needful to make a ref ready to delete. // If it returns false, you've found a ref that the method doesn't know how to handle. Congratulations! // You should report that and/or check in a fix. - DFHACK_EXPORT bool disconnectJobGeneralRef(df::general_ref *ref, df::job *job); + DFHACK_EXPORT bool disconnectJobGeneralRef(df::job *job, df::general_ref *ref); // Delete a job & remove all refs from everywhere. // This method DELETES the job object! Everything related to it will be wiped // clean from the earth, so make sure you pull what you need out before calling this! diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 531c85773..3f12ea2c2 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -304,7 +304,7 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } -void DFHack::Job::disconnectJobItem(df::job_item_ref *ref, df::job *job) { +void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *ref) { if (!ref) return; auto item = ref->item; @@ -329,7 +329,7 @@ void DFHack::Job::disconnectJobItem(df::job_item_ref *ref, df::job *job) { if (!stillHasJobs) item->flags.bits.in_job = false; } -bool DFHack::Job::disconnectJobGeneralRef(df::general_ref *ref, df::job *job) { +bool DFHack::Job::disconnectJobGeneralRef(df::job *job, df::general_ref *ref) { if (ref == NULL) return true; df::building *building = NULL; @@ -388,7 +388,7 @@ bool DFHack::Job::removeJob(df::job *job) { //Our code above should have ensured that this won't return false- if it does, there's not //a great way of recovering since we can't properly destroy the job & we can't leave it //around. Better to know the moment that becomes a problem. - bool success = disconnectJobGeneralRef(ref, job); + bool success = disconnectJobGeneralRef(job, ref); assert(success); vector_erase_at(job->general_refs, 0); @@ -398,7 +398,7 @@ bool DFHack::Job::removeJob(df::job *job) { //Detach all items from the job while (job->items.size() > 0) { auto itemRef = job->items[0]; - disconnectJobItem(itemRef, job); + disconnectJobItem(job, itemRef); vector_erase_at(job->items, 0); if (itemRef != NULL) delete itemRef; } From 8488b1a95396260566791c3f386e4770b1fadd57 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 5 Dec 2016 10:29:16 +0100 Subject: [PATCH 0354/1012] ruby: use correct raw string length with encodings --- plugins/ruby/ruby.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 006d63402..83463b870 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -624,7 +624,7 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) static VALUE rb_dfhack_run(VALUE self, VALUE cmd) { std::string s; - int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(cmd, rb_intern("bytesize"), 0)); s.assign(rb_string_value_ptr(&cmd), strlen); dfhack_run_queue->push_back(s); return Qtrue; @@ -686,7 +686,7 @@ static VALUE rb_dfmemory_read_double(VALUE self, VALUE addr) static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw) { // no stable api for raw.length between rb1.8/rb1.9 ... - int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(raw, rb_intern("bytesize"), 0)); memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen); @@ -752,7 +752,7 @@ static VALUE rb_dfmemory_check(VALUE self, VALUE addr) // memory write (tmp override page permissions, eg patch code) static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) { - int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(raw, rb_intern("bytesize"), 0)); bool ret; ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr), @@ -831,7 +831,7 @@ static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr) static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val) { std::string *s = (std::string*)rb_num2ulong(addr); - int strlen = FIX2INT(rb_funcall(val, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(val, rb_intern("bytesize"), 0)); s->assign(rb_string_value_ptr(&val), strlen); return Qtrue; } From a5afd9886390f11929c1f51b641cb9e3e25b3ee0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Dec 2016 12:14:08 -0500 Subject: [PATCH 0355/1012] travis: install requests[security] to silence sphinx warning --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 22fe49e2d..38d053570 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ matrix: - gcc-4.8-multilib - g++-4.8-multilib before_install: - pip install --user sphinx + pip install --user sphinx "requests[security]" script: - git tag tmp-travis-build - sh travis/git-info.sh From 672410bea72e04c4ddada7a6ece68820be1bd5c6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Dec 2016 12:21:27 -0500 Subject: [PATCH 0356/1012] travis: Use sphinx 1.4 only for now The sphinx.domains.cpp extension (which I'm not sure we're explicitly using) generates a warning with parallel docs builds, which causes the Travis build to fail. This may be something that needs to be fixed upstream. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 38d053570..932375656 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ matrix: - gcc-4.8-multilib - g++-4.8-multilib before_install: - pip install --user sphinx "requests[security]" + pip install --user "sphinx==1.4" "requests[security]" script: - git tag tmp-travis-build - sh travis/git-info.sh From 144675c74b1a5a03d218537c77e697b9dee1514d Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Tue, 6 Dec 2016 21:40:30 -0500 Subject: [PATCH 0357/1012] Update bundled tinyxml version from 2.5.3 to 2.6.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream has moved onto tinyxml2, so this is likely to be the last release of the original tinyxml (2.6.2 came out in 2011). dfhack should therefore probably ship 2.6.2, since it's unlikely there will ever be future fixes to this code. The upstream changelog (taken from "changes.txt" in upstream tarball) contains many bugfixes and is included in the commit message below. 2.5.4 - A TiXMLDocument can't be a sub-node. Block this from happening in the 'replace'. Thanks Noam. - [ 1714831 ] TiXmlBase::location is not copied by copy-ctors, fix reported and suggested by Nicola Civran. - Fixed possible memory overrun in the comment reading code - thanks gcarlton77 2.5.5 - Alex van der Wal spotted incorrect types (lf) being used in print and scan. robertnestor pointed out some problems with the simple solution. Types updated. - Johannes Hillert pointed out some bug typos. - Christian Mueller identified inconsistent error handling with Attributes. - olivier barthelemy also reported a problem with double truncation, also related to the %lf issue. - zaelsius came up with a great (and simple) suggestion to fix QueryValueAttribute truncating strings. - added some null pointer checks suggested by hansenk - Sami V�is�nen found a (rare) buffer overrun that could occur in parsing. - vi tri filed a bug that led to a refactoring of the attribute setting mess (as well as adding a missing SetDoubleAttribute() ) - removed TIXML_ERROR_OUT_OF_MEMORY. TinyXML does not systematically address OOO, and the notion it does is misleading. - vanneto, keithmarshall, others all reported the warning from IsWhiteSpace() usage. Cleaned this up - many thanks to everyone who reported this one. - tibur found a bug in end tag parsing 2.6.2 - Switched over to VC 2010 - Fixed up all the build issues arising from that. (Lots of latent build problems.) - Removed the old, now unmaintained and likely not working, build files. - Fixed some static analysis issues reported by orbitcowboy from cppcheck. - Bayard 95 sent in analysis from a different analyzer - fixes applied from that as well. - Tim Kosse sent a patch fixing an infinite loop. - Ma Anguo identified a doc issue. - Eddie Cohen identified a missing qualifier resulting in a compilation error on some systems. - Fixed a line ending bug. (What year is this? Can we all agree on a format for text files? Please? ...oh well.) --- depends/tinyxml/tinystr.cpp | 5 - depends/tinyxml/tinystr.h | 14 - depends/tinyxml/tinyxml.cpp | 434 +++++++++++++++--------------- depends/tinyxml/tinyxml.h | 127 ++++----- depends/tinyxml/tinyxmlerror.cpp | 3 +- depends/tinyxml/tinyxmlparser.cpp | 60 ++--- 6 files changed, 312 insertions(+), 331 deletions(-) diff --git a/depends/tinyxml/tinystr.cpp b/depends/tinyxml/tinystr.cpp index 681250714..066576820 100644 --- a/depends/tinyxml/tinystr.cpp +++ b/depends/tinyxml/tinystr.cpp @@ -1,6 +1,5 @@ /* www.sourceforge.net/projects/tinyxml -Original file by Yves Berquin. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -22,10 +21,6 @@ must not be misrepresented as being the original software. distribution. */ -/* - * THIS FILE WAS ALTERED BY Tyge Løvset, 7. April 2005. - */ - #ifndef TIXML_USE_STL diff --git a/depends/tinyxml/tinystr.h b/depends/tinyxml/tinystr.h index 3c2aa9d54..89cca3341 100644 --- a/depends/tinyxml/tinystr.h +++ b/depends/tinyxml/tinystr.h @@ -1,6 +1,5 @@ /* www.sourceforge.net/projects/tinyxml -Original file by Yves Berquin. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -22,17 +21,6 @@ must not be misrepresented as being the original software. distribution. */ -/* - * THIS FILE WAS ALTERED BY Tyge Lovset, 7. April 2005. - * - * - completely rewritten. compact, clean, and fast implementation. - * - sizeof(TiXmlString) = pointer size (4 bytes on 32-bit systems) - * - fixed reserve() to work as per specification. - * - fixed buggy compares operator==(), operator<(), and operator>() - * - fixed operator+=() to take a const ref argument, following spec. - * - added "copy" constructor with length, and most compare operators. - * - added swap(), clear(), size(), capacity(), operator+(). - */ #ifndef TIXML_USE_STL @@ -106,13 +94,11 @@ class TiXmlString quit(); } - // = operator TiXmlString& operator = (const char * copy) { return assign( copy, (size_type)strlen(copy)); } - // = operator TiXmlString& operator = (const TiXmlString & copy) { return assign(copy.start(), copy.length()); diff --git a/depends/tinyxml/tinyxml.cpp b/depends/tinyxml/tinyxml.cpp index 5de21f6de..9c161dfcb 100644 --- a/depends/tinyxml/tinyxml.cpp +++ b/depends/tinyxml/tinyxml.cpp @@ -1,6 +1,6 @@ /* www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) +Original code by Lee Thomason (www.grinninglizard.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -31,6 +31,7 @@ distribution. #include "tinyxml.h" +FILE* TiXmlFOpen( const char* filename, const char* mode ); bool TiXmlBase::condenseWhiteSpace = true; @@ -161,6 +162,7 @@ void TiXmlNode::CopyTo( TiXmlNode* target ) const { target->SetValue (value.c_str() ); target->userData = userData; + target->location = location; } @@ -186,10 +188,11 @@ TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) assert( node->parent == 0 || node->parent == this ); assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); - if ( node->Type() == TiXmlNode::DOCUMENT ) + if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) { delete node; - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } @@ -210,9 +213,10 @@ TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) { - if ( addThis.Type() == TiXmlNode::DOCUMENT ) + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) { - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } TiXmlNode* node = addThis.Clone(); @@ -228,9 +232,10 @@ TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& if ( !beforeThis || beforeThis->parent != this ) { return 0; } - if ( addThis.Type() == TiXmlNode::DOCUMENT ) + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) { - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } @@ -260,9 +265,10 @@ TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& a if ( !afterThis || afterThis->parent != this ) { return 0; } - if ( addThis.Type() == TiXmlNode::DOCUMENT ) + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) { - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } @@ -289,9 +295,20 @@ TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& a TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) { + if ( !replaceThis ) + return 0; + if ( replaceThis->parent != this ) return 0; + if ( withThis.ToDocument() ) { + // A document can never be a child. Thanks to Noam. + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = withThis.Clone(); if ( !node ) return 0; @@ -317,6 +334,10 @@ TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& wit bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) { + if ( !removeThis ) { + return false; + } + if ( removeThis->parent != this ) { assert( 0 ); @@ -502,7 +523,7 @@ const TiXmlDocument* TiXmlNode::GetDocument() const TiXmlElement::TiXmlElement (const char * _value) - : TiXmlNode( TiXmlNode::ELEMENT ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) { firstChild = lastChild = 0; value = _value; @@ -511,7 +532,7 @@ TiXmlElement::TiXmlElement (const char * _value) #ifdef TIXML_USE_STL TiXmlElement::TiXmlElement( const std::string& _value ) - : TiXmlNode( TiXmlNode::ELEMENT ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) { firstChild = lastChild = 0; value = _value; @@ -520,17 +541,18 @@ TiXmlElement::TiXmlElement( const std::string& _value ) TiXmlElement::TiXmlElement( const TiXmlElement& copy) - : TiXmlNode( TiXmlNode::ELEMENT ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) { firstChild = lastChild = 0; copy.CopyTo( this ); } -void TiXmlElement::operator=( const TiXmlElement& base ) +TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) { ClearThis(); base.CopyTo( this ); + return *this; } @@ -564,9 +586,9 @@ const char* TiXmlElement::Attribute( const char* name ) const #ifdef TIXML_USE_STL const std::string* TiXmlElement::Attribute( const std::string& name ) const { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( node ) - return &node->ValueStr(); + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( attrib ) + return &attrib->ValueStr(); return 0; } #endif @@ -574,195 +596,202 @@ const std::string* TiXmlElement::Attribute( const std::string& name ) const const char* TiXmlElement::Attribute( const char* name, int* i ) const { - const char* s = Attribute( name ); - if ( i ) - { - if ( s ) { - *i = atoi( s ); - } - else { - *i = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( i ) { + attrib->QueryIntValue( i ); } } - return s; + return result; } #ifdef TIXML_USE_STL const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const { - const std::string* s = Attribute( name ); - if ( i ) - { - if ( s ) { - *i = atoi( s->c_str() ); - } - else { - *i = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( i ) { + attrib->QueryIntValue( i ); } } - return s; + return result; } #endif const char* TiXmlElement::Attribute( const char* name, double* d ) const { - const char* s = Attribute( name ); - if ( d ) - { - if ( s ) { - *d = atof( s ); - } - else { - *d = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( d ) { + attrib->QueryDoubleValue( d ); } } - return s; + return result; } #ifdef TIXML_USE_STL const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const { - const std::string* s = Attribute( name ); - if ( d ) - { - if ( s ) { - *d = atof( s->c_str() ); - } - else { - *d = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( d ) { + attrib->QueryDoubleValue( d ); } } - return s; + return result; } #endif int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} + + +int TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const { const TiXmlAttribute* node = attributeSet.Find( name ); if ( !node ) return TIXML_NO_ATTRIBUTE; - return node->QueryIntValue( ival ); + + int ival = 0; + int result = node->QueryIntValue( &ival ); + *value = (unsigned)ival; + return result; } -#ifdef TIXML_USE_STL -int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +int TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const { const TiXmlAttribute* node = attributeSet.Find( name ); if ( !node ) return TIXML_NO_ATTRIBUTE; - return node->QueryIntValue( ival ); + + int result = TIXML_WRONG_TYPE; + if ( StringEqual( node->Value(), "true", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "yes", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "1", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = true; + result = TIXML_SUCCESS; + } + else if ( StringEqual( node->Value(), "false", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "no", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "0", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = false; + result = TIXML_SUCCESS; + } + return result; +} + + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); } #endif int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) return TIXML_NO_ATTRIBUTE; - return node->QueryDoubleValue( dval ); + return attrib->QueryDoubleValue( dval ); } #ifdef TIXML_USE_STL int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) return TIXML_NO_ATTRIBUTE; - return node->QueryDoubleValue( dval ); + return attrib->QueryDoubleValue( dval ); } #endif void TiXmlElement::SetAttribute( const char * name, int val ) { - char buf[64]; - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%d", val ); - #else - sprintf( buf, "%d", val ); - #endif - SetAttribute( name, buf ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } } #ifdef TIXML_USE_STL void TiXmlElement::SetAttribute( const std::string& name, int val ) { - std::ostringstream oss; - oss << val; - SetAttribute( name, oss.str() ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } } #endif void TiXmlElement::SetDoubleAttribute( const char * name, double val ) { - char buf[256]; - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%f", val ); - #else - sprintf( buf, "%f", val ); - #endif - SetAttribute( name, buf ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } } -void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) -{ - #ifdef TIXML_USE_STL - TIXML_STRING _name( cname ); - TIXML_STRING _value( cvalue ); - #else - const char* _name = cname; - const char* _value = cvalue; - #endif - - TiXmlAttribute* node = attributeSet.Find( _name ); - if ( node ) - { - node->SetValue( _value ); - return; +#ifdef TIXML_USE_STL +void TiXmlElement::SetDoubleAttribute( const std::string& name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); } +} +#endif - TiXmlAttribute* attrib = new TiXmlAttribute( cname, cvalue ); - if ( attrib ) - { - attributeSet.Add( attrib ); - } - else - { - TiXmlDocument* document = GetDocument(); - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + +void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( cname ); + if ( attrib ) { + attrib->SetValue( cvalue ); } } #ifdef TIXML_USE_STL -void TiXmlElement::SetAttribute( const std::string& name, const std::string& _value ) +void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) { - TiXmlAttribute* node = attributeSet.Find( name ); - if ( node ) - { - node->SetValue( _value ); - return; - } - - TiXmlAttribute* attrib = new TiXmlAttribute( name, _value ); - if ( attrib ) - { - attributeSet.Add( attrib ); - } - else - { - TiXmlDocument* document = GetDocument(); - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); + if ( attrib ) { + attrib->SetValue( _value ); } } #endif @@ -881,14 +910,14 @@ const char* TiXmlElement::GetText() const } -TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { tabsize = 4; useMicrosoftBOM = false; ClearError(); } -TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { tabsize = 4; useMicrosoftBOM = false; @@ -898,7 +927,7 @@ TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode #ifdef TIXML_USE_STL -TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { tabsize = 4; useMicrosoftBOM = false; @@ -908,49 +937,33 @@ TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiX #endif -TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { copy.CopyTo( this ); } -void TiXmlDocument::operator=( const TiXmlDocument& copy ) +TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) { Clear(); copy.CopyTo( this ); + return *this; } bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) { - // See STL_STRING_BUG below. - //StringToBuffer buf( value ); - return LoadFile( Value(), encoding ); } bool TiXmlDocument::SaveFile() const { - // See STL_STRING_BUG below. -// StringToBuffer buf( value ); -// -// if ( buf.buffer && SaveFile( buf.buffer ) ) -// return true; -// -// return false; return SaveFile( Value() ); } bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) { - // There was a really terrifying little bug here. The code: - // value = filename - // in the STL case, cause the assignment method of the std::string to - // be called. What is strange, is that the std::string had the same - // address as it's c_str() method, and so bad things happen. Looks - // like a bug in the Microsoft STL implementation. - // Add an extra string to avoid the crash. TIXML_STRING filename( _filename ); value = filename; @@ -995,11 +1008,6 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) return false; } - // If we have a file, assume it is all one big XML file, and read it in. - // The document parser may decide the document ends sooner than the entire file, however. - TIXML_STRING data; - data.reserve( length ); - // Subtle bug here. TinyXml did use fgets. But from the XML spec: // 2.11 End-of-Line Handling // @@ -1030,58 +1038,46 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) return false; } - const char* lastPos = buf; - const char* p = buf; + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; buf[length] = 0; while( *p ) { assert( p < (buf+length) ); - if ( *p == 0xa ) { - // Newline character. No special rules for this. Append all the characters - // since the last string, and include the newline. - data.append( lastPos, (p-lastPos+1) ); // append, include the newline - ++p; // move past the newline - lastPos = p; // and point to the new buffer (may be 0) - assert( p <= (buf+length) ); - } - else if ( *p == 0xd ) { - // Carriage return. Append what we have so far, then - // handle moving forward in the buffer. - if ( (p-lastPos) > 0 ) { - data.append( lastPos, p-lastPos ); // do not add the CR - } - data += (char)0xa; // a proper newline - - if ( *(p+1) == 0xa ) { - // Carriage return - new line sequence - p += 2; - lastPos = p; - assert( p <= (buf+length) ); - } - else { - // it was followed by something else...that is presumably characters again. - ++p; - lastPos = p; - assert( p <= (buf+length) ); + assert( q <= (buf+length) ); + assert( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; } } else { - ++p; + *q++ = *p++; } } - // Handle any left over characters. - if ( p-lastPos ) { - data.append( lastPos, p-lastPos ); - } - delete [] buf; - buf = 0; + assert( q <= (buf+length) ); + *q = 0; - Parse( data.c_str(), 0, encoding ); + Parse( buf, 0, encoding ); - if ( Error() ) - return false; - else - return true; + delete [] buf; + return !Error(); } @@ -1220,7 +1216,7 @@ void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) cons if (value.find ('\"') == TIXML_STRING::npos) { if ( cfile ) { - fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); } if ( str ) { (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; @@ -1228,7 +1224,7 @@ void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) cons } else { if ( cfile ) { - fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); } if ( str ) { (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; @@ -1266,9 +1262,9 @@ void TiXmlAttribute::SetDoubleValue( double _value ) { char buf [256]; #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%lf", _value); + TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); #else - sprintf (buf, "%lf", _value); + sprintf (buf, "%g", _value); #endif SetValue (buf); } @@ -1284,16 +1280,17 @@ double TiXmlAttribute::DoubleValue() const } -TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::COMMENT ) +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { copy.CopyTo( this ); } -void TiXmlComment::operator=( const TiXmlComment& base ) +TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) { Clear(); base.CopyTo( this ); + return *this; } @@ -1382,7 +1379,7 @@ TiXmlNode* TiXmlText::Clone() const TiXmlDeclaration::TiXmlDeclaration( const char * _version, const char * _encoding, const char * _standalone ) - : TiXmlNode( TiXmlNode::DECLARATION ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) { version = _version; encoding = _encoding; @@ -1394,7 +1391,7 @@ TiXmlDeclaration::TiXmlDeclaration( const char * _version, TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, const std::string& _encoding, const std::string& _standalone ) - : TiXmlNode( TiXmlNode::DECLARATION ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) { version = _version; encoding = _encoding; @@ -1404,16 +1401,17 @@ TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) - : TiXmlNode( TiXmlNode::DECLARATION ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) { copy.CopyTo( this ); } -void TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) { Clear(); copy.CopyTo( this ); + return *this; } @@ -1548,9 +1546,9 @@ void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) #ifdef TIXML_USE_STL -const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const { - for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) { if ( node->name == name ) return node; @@ -1558,23 +1556,22 @@ const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const return 0; } -/* -TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) { - for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( node->name == name ) - return node; + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); } - return 0; + return attrib; } -*/ #endif -const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const { - for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) { if ( strcmp( node->name.c_str(), name ) == 0 ) return node; @@ -1582,17 +1579,18 @@ const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const return 0; } -/* -TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) { - for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( strcmp( node->name.c_str(), name ) == 0 ) - return node; + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); } - return 0; + return attrib; } -*/ + #ifdef TIXML_USE_STL std::istream& operator>> (std::istream & in, TiXmlNode & base) diff --git a/depends/tinyxml/tinyxml.h b/depends/tinyxml/tinyxml.h index c6f40cc27..a3589e5b2 100644 --- a/depends/tinyxml/tinyxml.h +++ b/depends/tinyxml/tinyxml.h @@ -1,6 +1,6 @@ /* www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) +Original code by Lee Thomason (www.grinninglizard.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -63,21 +63,19 @@ distribution. #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) // Microsoft visual studio, version 2005 and higher. #define TIXML_SNPRINTF _snprintf_s - #define TIXML_SNSCANF _snscanf_s #define TIXML_SSCANF sscanf_s #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) // Microsoft visual studio, version 6 and higher. //#pragma message( "Using _sn* functions." ) #define TIXML_SNPRINTF _snprintf - #define TIXML_SNSCANF _snscanf #define TIXML_SSCANF sscanf #elif defined(__GNUC__) && (__GNUC__ >= 3 ) // GCC version 3 and higher.s //#warning( "Using sn* functions." ) #define TIXML_SNPRINTF snprintf - #define TIXML_SNSCANF snscanf #define TIXML_SSCANF sscanf #else + #define TIXML_SNPRINTF snprintf #define TIXML_SSCANF sscanf #endif #endif @@ -92,8 +90,8 @@ class TiXmlDeclaration; class TiXmlParsingData; const int TIXML_MAJOR_VERSION = 2; -const int TIXML_MINOR_VERSION = 5; -const int TIXML_PATCH_VERSION = 3; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 2; /* Internal structure for tracking location of items in the XML file. @@ -109,10 +107,11 @@ struct TiXmlCursor /** + Implements the interface to the "Visitor pattern" (see the Accept() method.) If you call the Accept() method, it requires being passed a TiXmlVisitor class to handle callbacks. For nodes that contain other nodes (Document, Element) you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves - are simple called with Visit(). + are simply called with Visit(). If you return 'true' from a Visit method, recursive parsing will continue. If you return false, no children of this node or its sibilings will be Visited. @@ -147,7 +146,7 @@ public: virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } /// Visit a comment node virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } - /// Visit an unknow node + /// Visit an unknown node virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } }; @@ -267,7 +266,6 @@ public: TIXML_NO_ERROR = 0, TIXML_ERROR, TIXML_ERROR_OPENING_FILE, - TIXML_ERROR_OUT_OF_MEMORY, TIXML_ERROR_PARSING_ELEMENT, TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, TIXML_ERROR_READING_ELEMENT_VALUE, @@ -288,6 +286,7 @@ public: protected: static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + inline static bool IsWhiteSpace( char c ) { return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); @@ -462,13 +461,13 @@ public: */ enum NodeType { - DOCUMENT, - ELEMENT, - COMMENT, - UNKNOWN, - TEXT, - DECLARATION, - TYPECOUNT + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT }; virtual ~TiXmlNode(); @@ -679,8 +678,8 @@ public: #endif /** Query the type (as an enumerated value, above) of this node. - The possible types are: DOCUMENT, ELEMENT, COMMENT, - UNKNOWN, TEXT, and DECLARATION. + The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, + TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. */ int Type() const { return type; } @@ -915,17 +914,14 @@ public: const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } - const TiXmlAttribute* Find( const char* _name ) const; - TiXmlAttribute* Find( const char* _name ) { - return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) ); - } - #ifdef TIXML_USE_STL - const TiXmlAttribute* Find( const std::string& _name ) const; - TiXmlAttribute* Find( const std::string& _name ) { - return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) ); - } + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif - #endif private: //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), @@ -954,7 +950,7 @@ public: TiXmlElement( const TiXmlElement& ); - void operator=( const TiXmlElement& base ); + TiXmlElement& operator=( const TiXmlElement& base ); virtual ~TiXmlElement(); @@ -987,6 +983,13 @@ public: does not exist, then TIXML_NO_ATTRIBUTE is returned. */ int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute(). + int QueryUnsignedAttribute( const char* name, unsigned* _value ) const; + /** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). + Note that '1', 'true', or 'yes' are considered true, while '0', 'false' + and 'no' are considered false. + */ + int QueryBoolAttribute( const char* name, bool* _value ) const; /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). int QueryDoubleAttribute( const char* name, double* _value ) const; /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). @@ -1000,11 +1003,21 @@ public: } #ifdef TIXML_USE_STL + /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). + int QueryStringAttribute( const char* name, std::string* _value ) const { + const char* cstr = Attribute( name ); + if ( cstr ) { + *_value = std::string( cstr ); + return TIXML_SUCCESS; + } + return TIXML_NO_ATTRIBUTE; + } + /** Template form of the attribute query which will try to read the attribute into the specified type. Very easy, very powerful, but be careful to make sure to call this with the correct type. - NOTE: This method doesn't work correctly for 'string' types. + NOTE: This method doesn't work correctly for 'string' types that contain spaces. @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE */ @@ -1020,13 +1033,8 @@ public: return TIXML_SUCCESS; return TIXML_WRONG_TYPE; } - /* - This is - in theory - a bug fix for "QueryValueAtribute returns truncated std::string" - but template specialization is hard to get working cross-compiler. Leaving the bug for now. - - // The above will fail for std::string because the space character is used as a seperator. - // Specialize for strings. Bug [ 1695429 ] QueryValueAtribute returns truncated std::string - template<> int QueryValueAttribute( const std::string& name, std::string* outValue ) const + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const { const TiXmlAttribute* node = attributeSet.Find( name ); if ( !node ) @@ -1034,7 +1042,6 @@ public: *outValue = node->ValueStr(); return TIXML_SUCCESS; } - */ #endif /** Sets an attribute of name to a given value. The attribute @@ -1053,6 +1060,8 @@ public: void SetAttribute( const std::string& name, const std::string& _value ); ///< STL std::string form. void SetAttribute( const std::string& name, int _value ); + ///< STL std::string form. + void SetDoubleAttribute( const std::string& name, double value ); #endif /** Sets an attribute of name to a given value. The attribute @@ -1144,7 +1153,6 @@ protected: const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); private: - TiXmlAttributeSet attributeSet; }; @@ -1155,13 +1163,13 @@ class TiXmlComment : public TiXmlNode { public: /// Constructs an empty comment. - TiXmlComment() : TiXmlNode( TiXmlNode::COMMENT ) {} + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} /// Construct a comment from text. - TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::COMMENT ) { + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { SetValue( _value ); } TiXmlComment( const TiXmlComment& ); - void operator=( const TiXmlComment& base ); + TiXmlComment& operator=( const TiXmlComment& base ); virtual ~TiXmlComment() {} @@ -1175,8 +1183,8 @@ public: */ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. /** Walk the XML tree visiting this node and all of its children. */ @@ -1209,7 +1217,7 @@ public: normal, encoded text. If you want it be output as a CDATA text element, set the parameter _cdata to 'true' */ - TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TEXT) + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) { SetValue( initValue ); cdata = false; @@ -1218,15 +1226,15 @@ public: #ifdef TIXML_USE_STL /// Constructor. - TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TEXT) + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) { SetValue( initValue ); cdata = false; } #endif - TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TEXT ) { copy.CopyTo( this ); } - void operator=( const TiXmlText& base ) { base.CopyTo( this ); } + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } // Write this text object to a FILE stream. virtual void Print( FILE* cfile, int depth ) const; @@ -1278,7 +1286,7 @@ class TiXmlDeclaration : public TiXmlNode { public: /// Construct an empty declaration. - TiXmlDeclaration() : TiXmlNode( TiXmlNode::DECLARATION ) {} + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} #ifdef TIXML_USE_STL /// Constructor. @@ -1293,7 +1301,7 @@ public: const char* _standalone ); TiXmlDeclaration( const TiXmlDeclaration& copy ); - void operator=( const TiXmlDeclaration& copy ); + TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); virtual ~TiXmlDeclaration() {} @@ -1346,11 +1354,11 @@ private: class TiXmlUnknown : public TiXmlNode { public: - TiXmlUnknown() : TiXmlNode( TiXmlNode::UNKNOWN ) {} + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} virtual ~TiXmlUnknown() {} - TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::UNKNOWN ) { copy.CopyTo( this ); } - void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); } + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } /// Creates a copy of this Unknown and returns it. virtual TiXmlNode* Clone() const; @@ -1359,8 +1367,8 @@ public: virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. /** Walk the XML tree visiting this node and all of its children. */ @@ -1396,7 +1404,7 @@ public: #endif TiXmlDocument( const TiXmlDocument& copy ); - void operator=( const TiXmlDocument& copy ); + TiXmlDocument& operator=( const TiXmlDocument& copy ); virtual ~TiXmlDocument() {} @@ -1423,14 +1431,10 @@ public: #ifdef TIXML_USE_STL bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. { -// StringToBuffer f( filename ); -// return ( f.buffer && LoadFile( f.buffer, encoding )); return LoadFile( filename.c_str(), encoding ); } bool SaveFile( const std::string& filename ) const ///< STL std::string version. { -// StringToBuffer f( filename ); -// return ( f.buffer && SaveFile( f.buffer )); return SaveFile( filename.c_str() ); } #endif @@ -1638,7 +1642,7 @@ public: TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } /// Copy constructor TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } - TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } /// Return a handle to the first child node. TiXmlHandle FirstChild() const; @@ -1799,4 +1803,3 @@ private: #endif #endif - diff --git a/depends/tinyxml/tinyxmlerror.cpp b/depends/tinyxml/tinyxmlerror.cpp index d24f63b2e..538c21d0b 100644 --- a/depends/tinyxml/tinyxmlerror.cpp +++ b/depends/tinyxml/tinyxmlerror.cpp @@ -31,12 +31,11 @@ distribution. // It also cleans up the code a bit. // -const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] = +const char* TiXmlBase::errorString[ TiXmlBase::TIXML_ERROR_STRING_COUNT ] = { "No error", "Error", "Failed to open file", - "Memory allocation failed.", "Error parsing Element.", "Failed to read Element name", "Error reading Element value.", diff --git a/depends/tinyxml/tinyxmlparser.cpp b/depends/tinyxml/tinyxmlparser.cpp index 5793f0528..81b7eae96 100644 --- a/depends/tinyxml/tinyxmlparser.cpp +++ b/depends/tinyxml/tinyxmlparser.cpp @@ -1,6 +1,6 @@ /* www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) +Original code by Lee Thomason (www.grinninglizard.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -40,7 +40,7 @@ distribution. // Note tha "PutString" hardcodes the same list. This // is less flexible than it appears. Changing the entries // or order will break putstring. -TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = +TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = { { "&", 5, '&' }, { "<", 4, '<' }, @@ -174,7 +174,7 @@ class TiXmlParsingData public: void Stamp( const char* now, TiXmlEncoding encoding ); - const TiXmlCursor& Cursor() { return cursor; } + const TiXmlCursor& Cursor() const { return cursor; } private: // Only used by the document! @@ -346,7 +346,7 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) continue; } - if ( IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) // Still using old rules for white space. + if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. ++p; else break; @@ -354,7 +354,7 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) } else { - while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' ) + while ( *p && IsWhiteSpace( *p ) ) ++p; } @@ -631,9 +631,9 @@ const char* TiXmlBase::ReadText( const char* p, } } } - if ( p ) + if ( p && *p ) p += strlen( endTag ); - return p; + return ( p && *p ) ? p : 0; } #ifdef TIXML_USE_STL @@ -825,7 +825,6 @@ TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) return 0; } - TiXmlDocument* doc = GetDocument(); p = SkipWhiteSpace( p, encoding ); if ( !p || !*p ) @@ -896,11 +895,6 @@ TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) // Set the parent, so it can report errors returnNode->parent = this; } - else - { - if ( doc ) - doc->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); - } return returnNode; } @@ -1083,7 +1077,6 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc TIXML_STRING endTag (""; // Check for and read attributes. Also look for an empty // tag or an end tag. @@ -1122,10 +1115,20 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc } // We should find the end tag now + // note that: + // and + // + // are both valid end tags. if ( StringEqual( p, endTag.c_str(), false, encoding ) ) { p += endTag.length(); - return p; + p = SkipWhiteSpace( p, encoding ); + if ( p && *p && *p == '>' ) { + ++p; + return p; + } + if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding ); + return 0; } else { @@ -1139,7 +1142,6 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc TiXmlAttribute* attrib = new TiXmlAttribute(); if ( !attrib ) { - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, pErr, data, encoding ); return 0; } @@ -1162,7 +1164,7 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc #endif if ( node ) { - node->SetValue( attrib->Value() ); + if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding ); delete attrib; return 0; } @@ -1191,8 +1193,7 @@ const char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXm if ( !textNode ) { - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, encoding ); - return 0; + return 0; } if ( TiXmlBase::IsWhiteSpaceCondensed() ) @@ -1297,9 +1298,10 @@ const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc if ( !p ) { - if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); + if ( document ) + document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); } - if ( *p == '>' ) + if ( p && *p == '>' ) return p+1; return p; } @@ -1349,7 +1351,8 @@ const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc if ( !StringEqual( p, startTag, false, encoding ) ) { - document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + if ( document ) + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); return 0; } p += strlen( startTag ); @@ -1379,7 +1382,7 @@ const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc value.append( p, 1 ); ++p; } - if ( p ) + if ( p && *p ) p += strlen( endTag ); return p; @@ -1391,10 +1394,6 @@ const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlE p = SkipWhiteSpace( p, encoding ); if ( !p || !*p ) return 0; -// int tabsize = 4; -// if ( document ) -// tabsize = document->TabSize(); - if ( data ) { data->Stamp( p, encoding ); @@ -1446,7 +1445,7 @@ const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlE // its best, even without them. value = ""; while ( p && *p // existence - && !IsWhiteSpace( *p ) && *p != '\n' && *p != '\r' // whitespace + && !IsWhiteSpace( *p ) // whitespace && *p != '/' && *p != '>' ) // tag end { if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { @@ -1515,7 +1514,8 @@ const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncodi if ( !StringEqual( p, startTag, false, encoding ) ) { - document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + if ( document ) + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); return 0; } p += strlen( startTag ); @@ -1539,7 +1539,7 @@ const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncodi const char* end = "<"; p = ReadText( p, &value, ignoreWhite, end, false, encoding ); - if ( p ) + if ( p && *p ) return p-1; // don't truncate the '<' return 0; } From 50ce9942f443c4a51d65f31edf5d9984a1ece718 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 8 Dec 2016 16:01:14 -0600 Subject: [PATCH 0358/1012] labormanager: add deconstruct labor for constructed instruments (#7) --- plugins/labormanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index bb971ebd2..fed0b1abe 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -961,6 +961,7 @@ private: case df::building_type::GrateFloor: case df::building_type::GrateWall: case df::building_type::Bookcase: + case df::building_type::Instrument: return df::unit_labor::HAUL_FURNITURE; case df::building_type::AnimalTrap: return df::unit_labor::TRAPPER; From 1a9d8629c555ea2cdfb48468561f90b98bd0f938 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 9 Dec 2016 09:54:20 -0600 Subject: [PATCH 0359/1012] labormanager: fix StoreItemInVehicle labor inference (#8) * labormanager: add deconstruct labor for constructed instruments * labormanager: fix StoreItemInVehicle labor inference StoreItemInVehicle requires the labor appropriate to the item being stashed --- plugins/labormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index fed0b1abe..94a22acb3 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1375,7 +1375,7 @@ public: job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); - job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_hauling; job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; From 6e16a7a20c5d5c3e75aa0964f0dd3cc5011b30ea Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Dec 2016 11:27:44 -0500 Subject: [PATCH 0360/1012] Update xml, scripts --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 84f6e968a..666083d1c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 84f6e968a9ec5515f9dbef96b445e3fc83f83e8b +Subproject commit 666083d1c705f65e2865759e27ead5ff32cd6005 diff --git a/scripts b/scripts index 35cd74b06..611e119cd 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 35cd74b0688565e8b37275d20d99911c7f5b2c7e +Subproject commit 611e119cdbbfa1559f6c11de16f773ef9babe0a2 From acdb369aa8479edbfbd4e30327876ad7592ff870 Mon Sep 17 00:00:00 2001 From: David Corbett Date: Fri, 9 Dec 2016 13:41:14 -0500 Subject: [PATCH 0361/1012] Avoid non-trivial bitfield constructors --- library/include/modules/MapCache.h | 4 ++-- library/modules/Gui.cpp | 8 ++++---- library/modules/Maps.cpp | 2 +- plugins/building-hacks.cpp | 4 +++- plugins/burrows.cpp | 4 ++-- plugins/stocks.cpp | 2 +- plugins/workflow.cpp | 4 ++-- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index b64cc7859..0048f5bd9 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -542,7 +542,7 @@ class DFHACK_EXPORT MapCache df::tile_designation designationAt (DFCoord tilecoord) { Block * b= BlockAtTile(tilecoord); - return b ? b->DesignationAt(tilecoord) : df::tile_designation(0); + return b ? b->DesignationAt(tilecoord) : df::tile_designation(); } bool setDesignationAt (DFCoord tilecoord, df::tile_designation des) { @@ -554,7 +554,7 @@ class DFHACK_EXPORT MapCache df::tile_occupancy occupancyAt (DFCoord tilecoord) { Block * b= BlockAtTile(tilecoord); - return b ? b->OccupancyAt(tilecoord) : df::tile_occupancy(0); + return b ? b->OccupancyAt(tilecoord) : df::tile_occupancy(); } bool setOccupancyAt (DFCoord tilecoord, df::tile_occupancy occ) { diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 2cc955d63..9a86e69ec 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1303,16 +1303,16 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r void Gui::showAnnouncement(std::string message, int color, bool bright) { - df::announcement_flags mode(0); + df::announcement_flags mode; mode.bits.D_DISPLAY = mode.bits.A_DISPLAY = true; - makeAnnouncement(df::announcement_type(0), mode, df::coord(), message, color, bright); + makeAnnouncement(df::announcement_type(), mode, df::coord(), message, color, bright); } void Gui::showZoomAnnouncement( df::announcement_type type, df::coord pos, std::string message, int color, bool bright ) { - df::announcement_flags mode(0); + df::announcement_flags mode; mode.bits.D_DISPLAY = mode.bits.A_DISPLAY = true; makeAnnouncement(type, mode, pos, message, color, bright); @@ -1335,7 +1335,7 @@ void Gui::showAutoAnnouncement( ) { using df::global::announcements; - df::announcement_flags flags(0); + df::announcement_flags flags; flags.bits.D_DISPLAY = flags.bits.A_DISPLAY = true; if (is_valid_enum_item(type) && announcements) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index e56ae3109..e9674437a 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -195,7 +195,7 @@ df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z) slot->map_pos.z = z; // Assume sky - df::tile_designation dsgn(0); + df::tile_designation dsgn; dsgn.bits.light = true; dsgn.bits.outside = true; diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index 483082c67..bc989df30 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -391,7 +391,9 @@ static int addBuilding(lua_State* L) int y=lua_tonumber(L,-1); lua_pop(L,1); - newDefinition.connections.can_connect.push_back(-1);//TODO add this too... + df::machine_conn_modes modes; + modes.whole = -1; + newDefinition.connections.can_connect.push_back(modes);//TODO add this too... newDefinition.connections.tiles.push_back(df::coord(x,y,0)); lua_pop(L,1); diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 87adbe734..69325331e 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -547,8 +547,8 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) { CHECK_NULL_POINTER(target); - df::tile_designation mask(0); - df::tile_designation value(0); + df::tile_designation mask; + df::tile_designation value; if (name == "ABOVE_GROUND") mask.bits.subterranean = true; diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 8565c703f..3c7193701 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1198,7 +1198,7 @@ private: if (state_to_apply == -1) state_to_apply = (item->flags.whole & flags.whole) ? 0 : 1; - grouped_entry->setFlags(flags.whole, state_to_apply); + grouped_entry->setFlags(flags, state_to_apply); } } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 18090afc6..55f1dc504 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -793,7 +793,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (item.subtype >= 0) weight += 10000; - df::dfhack_material_category mat_mask(0); + df::dfhack_material_category mat_mask; std::string maskstr = vector_get(tokens,1); if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { out.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); @@ -1031,7 +1031,7 @@ static int cbEnumJobOutputs(lua_State *L) lua_settop(L, 6); - df::dfhack_material_category mat_mask(0); + df::dfhack_material_category mat_mask; if (!lua_isnil(L, 3)) Lua::CheckDFAssign(L, &mat_mask, 3); From 3bae9d9d656172faea64f29b69dfcf6c6d8053af Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Dec 2016 12:27:43 -0500 Subject: [PATCH 0362/1012] Lua API: Make bitfield.whole return an integer, not a decimal --- library/LuaTypes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 8d61906b9..f281afe86 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -945,7 +945,7 @@ static int meta_bitfield_index(lua_State *state) { size_t intv = 0; memcpy(&intv, ptr, std::min(sizeof(intv), size_t(id->byte_size()))); - lua_pushnumber(state, intv); + lua_pushinteger(state, intv); return 1; } From e8c8953cbbce77cff69c9f1b49bc37bb27e9681d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Dec 2016 18:22:28 -0500 Subject: [PATCH 0363/1012] Re-add and update misery plugin Closes #1037 Ref #1011 --- NEWS.rst | 1 + docs/Plugins.rst | 15 ++++ library/xml | 2 +- plugins/CMakeLists.txt | 2 +- plugins/misery.cpp | 200 ++++++++++++++++++++--------------------- 5 files changed, 113 insertions(+), 107 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index b7e80cf77..c011f6df2 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -58,6 +58,7 @@ New Plugins ----------- - `dwarfvet` enables animal caretaking - `labormanager` (formerly autolabor2): a more advanced alternative to `autolabor` +- `misery`: re-added and updated for the 0.4x series - `title-folder`: shows DF folder name in window title bar when enabled New Scripts diff --git a/docs/Plugins.rst b/docs/Plugins.rst index fac50d254..6e9dd566e 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2469,6 +2469,21 @@ Options: .. _mode: +misery +====== +When enabled, fake bad thoughts will be added to all dwarves. + +Usage: + +:misery enable n: enable misery with optional magnitude n. If specified, n must + be positive. +:misery n: same as "misery enable n" +:misery enable: same as "misery enable 1" +:misery disable: stop adding new negative thoughts. This will not remove + existing negative thoughts. Equivalent to "misery 0". +:misery clear: remove fake thoughts, even after saving and reloading. Does + not change factor. + mode ==== This command lets you see and change the game mode directly. diff --git a/library/xml b/library/xml index 666083d1c..589e71541 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 666083d1c705f65e2865759e27ead5ff32cd6005 +Subproject commit 589e71541f54fac4ee3c575530317a67affac9e6 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c83f14e60..4611009c3 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,7 +132,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) - #DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) diff --git a/plugins/misery.cpp b/plugins/misery.cpp index 59b745871..6a67dc90f 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -1,64 +1,107 @@ -#include "PluginManager.h" -#include "Export.h" +#include +#include +#include +#include #include "DataDefs.h" -#include "df/world.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Units.h" + +#include "df/emotion_type.h" #include "df/ui.h" #include "df/unit.h" +#include "df/unit_personality.h" +#include "df/unit_soul.h" #include "df/unit_thought_type.h" - -#include -#include -#include +#include "df/world.h" using namespace std; using namespace DFHack; -/* -misery -====== -When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default). - -Usage: - -:misery enable n: enable misery with optional magnitude n. If specified, n must be positive. -:misery n: same as "misery enable n" -:misery enable: same as "misery enable 2" -:misery disable: stop adding new negative thoughts. This will not remove existing - duplicated thoughts. Equivalent to "misery 1" -:misery clear: remove fake thoughts added in this session of DF. Saving makes them - permanent! Does not change factor. -*/ DFHACK_PLUGIN("misery"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(cur_year); +REQUIRE_GLOBAL(cur_year_tick); -static int factor = 1; -static map processedThoughtCountTable; +typedef df::unit_personality::T_emotions Emotion; -//keep track of fake thoughts so you can remove them if requested -static vector > fakeThoughts; -static int count; -const int maxCount = 1000; +static int factor = 1; +static int tick = 0; +const int INTERVAL = 1000; command_result misery(color_ostream& out, vector& parameters); +void add_misery(df::unit *unit); +void clear_misery(df::unit *unit); + +const int FAKE_EMOTION_FLAG = (1 << 30); +const int STRENGTH_MULTIPLIER = 100; + +bool is_valid_unit (df::unit *unit) { + if (!Units::isOwnRace(unit) || !Units::isOwnCiv(unit)) + return false; + if (Units::isDead(unit)) + return false; + return true; +} + +inline bool is_fake_emotion (Emotion *e) { + return e->flags.whole & FAKE_EMOTION_FLAG; +} + +void add_misery (df::unit *unit) { + // Add a fake miserable thought + // Remove any fake ones that already exist + if (!unit || !unit->status.current_soul) + return; + clear_misery(unit); + auto &emotions = unit->status.current_soul->personality.emotions; + Emotion *e = new Emotion; + e->type = df::emotion_type::MISERY; + e->thought = df::unit_thought_type::SoapyBath; + e->flags.whole |= FAKE_EMOTION_FLAG; + emotions.push_back(e); + + for (Emotion *e : emotions) { + if (is_fake_emotion(e)) { + e->year = *cur_year; + e->year_tick = *cur_year_tick; + e->strength = STRENGTH_MULTIPLIER * factor; + e->severity = STRENGTH_MULTIPLIER * factor; + } + } +} + +void clear_misery (df::unit *unit) { + if (!unit || !unit->status.current_soul) + return; + auto &emotions = unit->status.current_soul->personality.emotions; + auto it = remove_if(emotions.begin(), emotions.end(), [](Emotion *e) { + if (is_fake_emotion(e)) { + delete e; + return true; + } + return false; + }); + emotions.erase(it, emotions.end()); +} DFhackCExport command_result plugin_shutdown(color_ostream& out) { - factor = 1; + factor = 0; return CR_OK; } DFhackCExport command_result plugin_onupdate(color_ostream& out) { static bool wasLoaded = false; - if ( factor == 1 || !world || !world->map.block_index ) { + if ( factor == 0 || !world || !world->map.block_index ) { if ( wasLoaded ) { //we just unloaded the game: clear all data - factor = 1; + factor = 0; is_enabled = false; - processedThoughtCountTable.clear(); - fakeThoughts.clear(); wasLoaded = false; } return CR_OK; @@ -68,71 +111,17 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { wasLoaded = true; } - if ( count < maxCount ) { - count++; + if ( tick < INTERVAL ) { + tick++; return CR_OK; } - count = 0; - - int32_t race_id = ui->race_id; - int32_t civ_id = ui->civ_id; - for ( size_t a = 0; a < world->units.all.size(); a++ ) { - df::unit* unit = world->units.all[a]; //TODO: consider units.active - //living, native units only - if ( unit->race != race_id || unit->civ_id != civ_id ) - continue; - if ( unit->flags1.bits.dead ) - continue; - - int processedThoughtCount; - map::iterator i = processedThoughtCountTable.find(unit->id); - if ( i == processedThoughtCountTable.end() ) { - processedThoughtCount = unit->status.recent_events.size(); - processedThoughtCountTable[unit->id] = processedThoughtCount; - } else { - processedThoughtCount = (*i).second; - } - - if ( processedThoughtCount == unit->status.recent_events.size() ) { - continue; - } else if ( processedThoughtCount > unit->status.recent_events.size() ) { - processedThoughtCount = unit->status.recent_events.size(); - } + tick = 0; - //don't reprocess any old thoughts - vector newThoughts; - for ( size_t b = processedThoughtCount; b < unit->status.recent_events.size(); b++ ) { - df::unit_thought* oldThought = unit->status.recent_events[b]; - const char* bob = ENUM_ATTR(unit_thought_type, value, oldThought->type); - if ( bob[0] != '-' ) { - //out.print("unit %4d: old thought value = %s\n", unit->id, bob); - continue; - } - /*out.print("unit %4d: Duplicating thought type %d (%s), value %s, age %d, subtype %d, severity %d\n", - unit->id, - oldThought->type.value, - ENUM_ATTR(unit_thought_type, caption, (oldThought->type)), - //df::enum_traits::attr_table[oldThought->type].caption - bob, - oldThought->age, - oldThought->subtype, - oldThought->severity - );*/ - //add duplicate thoughts to the temp list - for ( size_t c = 0; c < factor; c++ ) { - df::unit_thought* thought = new df::unit_thought; - thought->type = unit->status.recent_events[b]->type; - thought->age = unit->status.recent_events[b]->age; - thought->subtype = unit->status.recent_events[b]->subtype; - thought->severity = unit->status.recent_events[b]->severity; - newThoughts.push_back(thought); - } + //TODO: consider units.active + for (df::unit *unit : world->units.all) { + if (is_valid_unit(unit)) { + add_misery(unit); } - for ( size_t b = 0; b < newThoughts.size(); b++ ) { - fakeThoughts.push_back(std::pair(a, unit->status.recent_events.size())); - unit->status.recent_events.push_back(newThoughts[b]); - } - processedThoughtCountTable[unit->id] = unit->status.recent_events.size(); } return CR_OK; @@ -162,7 +151,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { is_enabled = enable; - factor = enable ? 2 : 1; + factor = enable ? 1 : 0; + tick = INTERVAL; } return CR_OK; @@ -182,34 +172,34 @@ command_result misery(color_ostream &out, vector& parameters) { if ( parameters.size() > 1 ) { return CR_WRONG_USAGE; } - factor = 1; + factor = 0; is_enabled = false; return CR_OK; } else if ( parameters[0] == "enable" ) { is_enabled = true; - factor = 2; + factor = 1; if ( parameters.size() == 2 ) { int a = atoi(parameters[1].c_str()); - if ( a <= 1 ) { + if ( a < 1 ) { out.printerr("Second argument must be a positive integer.\n"); return CR_WRONG_USAGE; } factor = a; } + tick = INTERVAL; } else if ( parameters[0] == "clear" ) { - for ( size_t a = 0; a < fakeThoughts.size(); a++ ) { - int dorfIndex = fakeThoughts[a].first; - int thoughtIndex = fakeThoughts[a].second; - world->units.all[dorfIndex]->status.recent_events[thoughtIndex]->age = 1000000; + for (df::unit *unit : world->units.all) { + if (is_valid_unit(unit)) { + clear_misery(unit); + } } - fakeThoughts.clear(); } else { int a = atoi(parameters[0].c_str()); - if ( a < 1 ) { + if ( a < 0 ) { return CR_WRONG_USAGE; } factor = a; - is_enabled = factor > 1; + is_enabled = factor > 0; } return CR_OK; From 92c01edf98f8103f3fdf06759f6bacfcc3ac2a4a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Dec 2016 18:23:17 -0500 Subject: [PATCH 0364/1012] Update scripts (fix export-dt-ini) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 611e119cd..ad5b365f3 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 611e119cdbbfa1559f6c11de16f773ef9babe0a2 +Subproject commit ad5b365f3ec1db4648152b00af13b0cf32340425 From 4b91d3799ee358fb4124ec6c0d9c6c0d0b27858d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Dec 2016 18:30:34 -0500 Subject: [PATCH 0365/1012] Fix misery docs anchor --- docs/Plugins.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 6e9dd566e..ccba07973 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2467,7 +2467,7 @@ Options: :lair: Mark the map as monster lair :lair reset: Mark the map as ordinary (not lair) -.. _mode: +.. _misery: misery ====== @@ -2484,6 +2484,8 @@ Usage: :misery clear: remove fake thoughts, even after saving and reloading. Does not change factor. +.. _mode: + mode ==== This command lets you see and change the game mode directly. From 4c0cb6854f3ef87212bb43a46300130bcc1b0108 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Dec 2016 00:23:16 -0500 Subject: [PATCH 0366/1012] Change minimum GCC version to 4.8 --- CMakeLists.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 654553f6b..9a0bffb84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,17 +24,14 @@ project(dfhack) macro(CHECK_GCC COMPILER_PATH) execute_process(COMMAND ${COMPILER_PATH} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT) string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT) - if (${GCC_VERSION_OUT} VERSION_LESS "4.5") - message(SEND_ERROR "${COMPILER_PATH} version ${GCC_VERSION_OUT} cannot be used - use GCC 4.5 or later") + if (${GCC_VERSION_OUT} VERSION_LESS "4.8") + message(SEND_ERROR "${COMPILER_PATH} version ${GCC_VERSION_OUT} cannot be used - use GCC 4.8 or later") elseif (${GCC_VERSION_OUT} VERSION_GREATER "4.9.9") # GCC 5 changes ABI name mangling to enable C++11 changes. # This must be disabled to enable linking against DF. # http://developerblog.redhat.com/2015/02/05/gcc5-and-the-c11-abi/ add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) endif() - if(${GCC_VERSION_OUT} VERSION_LESS "4.8") - message(WARNING "You are using GCC < 4.8 (detected version: ${GCC_VERSION_OUT}). Support for GCC versions before 4.8 may be removed in the future.") - endif() endmacro() if(UNIX) From f3e2e5e7d45b7edd4f38ba0d175cbf04b252ec46 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Dec 2016 18:04:00 -0500 Subject: [PATCH 0367/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 589e71541..d08339d3c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 589e71541f54fac4ee3c575530317a67affac9e6 +Subproject commit d08339d3ce144152cdbb703462d53231b282757d From 697d35a583405c68b70c71353c1718ee6996c45f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Dec 2016 18:04:15 -0500 Subject: [PATCH 0368/1012] Update scripts: Merge dfhack/scripts#8 --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ad5b365f3..619d5b311 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ad5b365f3ec1db4648152b00af13b0cf32340425 +Subproject commit 619d5b3116061fdfffa06b9e7cd266367bb42c0a From c8b7dbd2553baa2a98f2ef11e76f3a4ebe855be3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Dec 2016 21:16:21 -0500 Subject: [PATCH 0369/1012] Fix manipulator crash when selecting profession from empty list Also add ListColumn::hasSelection() Fixes #1040 --- plugins/listcolumn.h | 12 ++++++++++++ plugins/manipulator.cpp | 15 +++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 5c96b4594..9914c8ea7 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -317,6 +317,18 @@ public: return results[0]; } + bool hasSelection() + { + for (auto item : list) + { + if (item.selected) + { + return true; + } + } + return false; + } + void clearSelection() { for_each_(list, clear_fn); diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index dfc7c1f0d..2bbb9d8c3 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -954,6 +954,7 @@ public: viewscreen_unitprofessionset(vector &base_units, bool filter_selected = true ) + :menu_options(-1) // default { menu_options.multiselect = false; menu_options.auto_select = true; @@ -967,7 +968,7 @@ public: std::string name = manager.templates[i].name; if (manager.templates[i].mask) name += " (mask)"; - ListEntry elem(name, i+1); + ListEntry elem(name, i); menu_options.add(elem); } menu_options.filterDisplay(); @@ -1001,20 +1002,22 @@ public: } if (events->count(interface_key::SELECT)) { - select_profession(menu_options.getFirstSelectedElem()); + if (menu_options.hasSelection()) + { + select_profession(menu_options.getFirstSelectedElem()); + } Screen::dismiss(this); return; } } void select_profession(size_t selected) { - if (selected > manager.templates.size()) + if (manager.templates.empty() || selected >= manager.templates.size()) return; - ProfessionTemplate prof = manager.templates[selected - 1]; + ProfessionTemplate prof = manager.templates[selected]; - for (auto it = units.begin(); it != units.end(); ++it) + for (UnitInfo *u : units) { - UnitInfo* u = (*it); if (!u || !u->unit || !u->allowEdit) continue; prof.apply(u); } From 439e13643c0458fcf0dd9c9ce14cdf44d409506a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 13 Dec 2016 23:55:20 -0600 Subject: [PATCH 0370/1012] better hack to make ruby work on win64 --- plugins/ruby/ruby.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 83463b870..ed38868bd 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -345,7 +345,8 @@ static int df_loadruby(void) // ruby_sysinit is optional (ruby1.9 only) ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit"); -#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0 +#define rbloadsyma(s,a) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #a))) return 0 +#define rbloadsym(s) rbloadsyma(s,s) rbloadsym(ruby_init_stack); rbloadsym(ruby_init); rbloadsym(ruby_init_loadpath); @@ -362,8 +363,14 @@ static int df_loadruby(void) rbloadsym(rb_ary_shift); rbloadsym(rb_num2dbl); rbloadsym(rb_int2inum); +#if defined(_WIN64) + rbloadsyma(rb_uint2inum, rb_ull2inum); + rbloadsyma(rb_num2ulong, rb_num2ull); +#else rbloadsym(rb_uint2inum); rbloadsym(rb_num2ulong); +#endif + #undef rbloadsym // rb_float_new_in_heap in ruby 2 if (!((rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new"))) || From 4c3515cc6b189a075acc57e867f65bcd511b0753 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Dec 2016 12:56:54 -0500 Subject: [PATCH 0371/1012] Update NEWS.rst, add NEWS-dev.rst for prerelease changes --- NEWS.rst | 16 +++++++- docs/NEWS-dev.rst | 101 ++++++++++++++++++++++++++++++++++++++++++++++ docs/Plugins.rst | 5 ++- index.rst | 1 + 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 docs/NEWS-dev.rst diff --git a/NEWS.rst b/NEWS.rst index c011f6df2..1c7517adf 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -4,6 +4,11 @@ Items within each section are listed in alphabetical order to minimise merge conflicts. Try to match the style and level of detail of the other entries. + This file should not contain details specific to prereleases, but it should + contain changes from previous stable releases. For example, if a bug was + introduced in one alpha version and fixed in another, do not include it + here. + Sections for each release are added as required, and consist solely of the following in order as subheadings:: @@ -37,8 +42,10 @@ Internals --------- - 64-bit support on all platforms - Visual Studio 2015 now required on Windows instead of 2010 -- GCC 4.8 recommended on Linux and OS X (and now supported on OS X) +- GCC 4.8 or newer required on Linux and OS X (and now supported on OS X) - Several structure fixes to match 64-bit DF's memory layout +- Added ``DFHack::Job::removeJob()`` function +- Updated TinyXML from 2.5.3 to 2.6.2 Lua --- @@ -52,7 +59,6 @@ Lua Ruby ---- - Added support for loading ruby 2.x libraries -- Fixed some layouts on x64 (incomplete) New Plugins ----------- @@ -64,12 +70,18 @@ New Plugins New Scripts ----------- - `load-save`: loads a save non-interactively +- `modtools/change-build-menu`: Edit the build mode sidebar menus +- `modtools/if-entity`: Run a command if the current entity matches a given ID +- `season-palette`: Swap color palettes with the changes of the seasons Fixes ----- - The DF path on OS X can now contain spaces and ``:`` characters - Buildings::setOwner() changes now persist properly when saved +- `add-thought`: fixed support for emotion names +- `autofarm`: Made surface farms detect local biome - `devel/find-offsets`: fixed a crash when vtables used by globals aren't available +- `manipulator`: Fixed crash when selecting a profession from an empty list Misc Improvements ----------------- diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst new file mode 100644 index 000000000..6a247294b --- /dev/null +++ b/docs/NEWS-dev.rst @@ -0,0 +1,101 @@ +.. comment + This is the development changelog file for DFHack. If you add or change + anything, note it here under the heading "DFHack Future", in the appropriate + section. Items within each section are listed in alphabetical order to + minimise merge conflicts. Try to match the style and level of detail of the + other entries. + + This file contains changes that are relevant to users of prereleases. These + changes should include changes from just the previous release, whether that + release was stable or not. For instance, a feature added in 0.43.05-alpha1 + should go under "0.43.05-alpha1" here *and* "0.43.05-r1" (or "future") in + NEWS.rst. A fix in one prerelease for an issue in the previous prerelease + should just go here in the appropriate section, not in NEWS.rst. + + Sections for each release are added as required, and consist solely of the + following in order as subheadings:: + + Fixes + Structures + API Changes + Additions/Removals + Other Changes + + When referring to a script, plugin, or command, use backticks (```) to + create a link to the relevant documentation - and check that the docs are + still up to date! + + When adding a new release, change "DFHack future" to the appropriate title + before releasing, and then add a new "DFHack future" section after releasing. + +.. _dev-changelog: + +##################### +Development Changelog +##################### + +.. contents:: + :depth: 2 + +DFHack 0.43.05-alpha3 +===================== + +Fixes +----- +- `add-thought`: fixed support for emotion names +- `autofarm`: Made surface farms detect local biome +- `devel/export-dt-ini`: fixed squad_schedule_entry size +- `labormanager`: + + - Now accounts for unit attributes + - Made instrument-building jobs work (constructed instruments) + - Fixed deconstructing constructed instruments + - Fixed jobs in bowyer's shops + - Fixed trap component jobs + - Fixed multi-material construction jobs + - Fixed deconstruction of buildings containing items + - Fixed interference caused by "store item in vehicle" jobs + +- `manipulator`: Fixed crash when selecting a profession from an empty list +- `ruby`: + + - Fixed crash on Win64 due to truncated global addresses + - Fixed compilation on Win64 + - Use correct raw string length with encodings + +Structures +---------- +- Changed many ``comment`` XML attributes with version numbers to use new + ``since`` attribute instead +- ``activity_event_conflictst.sides``: named many fields +- ``building_def.build_key``: fixed size on 64-bit Linux and OS X +- ``historical_kills``: + + - ``unk_30`` -> ``killed_underground_region`` + - ``unk_40`` -> ``killed_region`` + +- ``historical_kills.killed_undead``: removed ``skeletal`` flag +- ``ui_advmode``: aligned enough so that it doesn't crash (64-bit OS X/Linux) +- ``ui_advmode.show_menu``: changed from bool to enum +- ``unit_personality.emotions.flags``: now a bitfield + +API Changes +----------- +- Added ``DFHack::Job::removeJob()`` function +- C++: Removed bitfield constructors that take an initial value. These kept + bitfields from being used in unions. Set ``bitfield.whole`` directly instead. +- Lua: ``bitfield.whole`` now returns an integer, not a decimal + +Additions/Removals +------------------ +- Removed source for treefarm plugin (wasn't built) +- Added `modtools/change-build-menu`: Edit the build mode sidebar menus +- Added `modtools/if-entity`: Run a command if the current entity matches a + given ID +- Added `season-palette`: Swap color palettes with the changes of the seasons + +Other changes +------------- +- Changed minimum GCC version to 4.8 on OS X and Linux (earlier versions + wouldn't have worked on Linux anyway) +- Updated TinyXML from 2.5.3 to 2.6.2 diff --git a/docs/Plugins.rst b/docs/Plugins.rst index ccba07973..b3e68459d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -380,9 +380,10 @@ active in the current context. See also `hotkey-notes`. :dfhack-keybind:`hotkeys` .. _rb: +.. _ruby: -rb -== +ruby +==== Ruby language plugin, which evaluates the following arguments as a ruby string. Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. diff --git a/index.rst b/index.rst index 88d3ca6b1..1f647907b 100644 --- a/index.rst +++ b/index.rst @@ -59,6 +59,7 @@ For Developers /Contributing /docs/Compile + /docs/NEWS-dev /docs/Lua API /library/xml/SYNTAX /library/xml/how-to-update From 7b2e6f299f7f7dfb1b9a99562c47b38dbf04df2a Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Dec 2016 13:18:37 -0500 Subject: [PATCH 0372/1012] Bump version to 0.43.05-alpha3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a0bffb84..488dd323d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "alpha2") +SET(DFHACK_RELEASE "alpha3") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From a5eb5d25028eaea2e2ad4454e0961676597d99d4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 15 Dec 2016 19:18:07 -0500 Subject: [PATCH 0373/1012] manipulator: Sort custom professions Also use == instead of string::compare() --- plugins/manipulator.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 2bbb9d8c3..76b8251b5 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -647,7 +647,7 @@ struct ProfessionTemplate name = line.substr(nextInd + 1); continue; } - if (line.compare("MASK")==0) + if (line == "MASK") { mask = true; continue; @@ -655,7 +655,7 @@ struct ProfessionTemplate for (int i = 0; i < NUM_COLUMNS; i++) { - if (line.compare(ENUM_KEY_STR(unit_labor, columns[i].labor)) == 0) + if (line == ENUM_KEY_STR(unit_labor, columns[i].labor)) { labors.push_back(columns[i].labor); } @@ -743,9 +743,10 @@ public: return; } Filesystem::listdir(professions_folder, files); + std::sort(files.begin(), files.end()); for(size_t i = 0; i < files.size(); i++) { - if (files[i].compare(".") == 0 || files[i].compare("..") == 0) + if (files[i] == "." || files[i] == "..") continue; ProfessionTemplate t; From 5be77fa63dcdba88e1418eeea50d81fa462a52fe Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 16 Dec 2016 08:09:53 -0600 Subject: [PATCH 0374/1012] stockflow: Fix "integer expected" problem in stockflow --- plugins/lua/stockflow.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 6027bb455..510718ff1 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1137,6 +1137,7 @@ function check_stockpiles(verbose) local filled, empty = check_pile(spec.stockpile, verbose) local amount = trigger.filled and filled or empty amount = (amount - (amount % trigger.divisor)) / trigger.divisor + amount = math.floor(amount) result[reaction] = (result[reaction] or 0) + amount end end From 1419d58b9a6d074edb0d56deda62f5f2e4f12852 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 16 Dec 2016 13:04:51 -0600 Subject: [PATCH 0375/1012] stockflow: Use "floor division" Per suggestion by @dscorbett --- plugins/lua/stockflow.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 510718ff1..32fc342fb 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1136,8 +1136,7 @@ function check_stockpiles(verbose) local reaction = spec.entry.ints[entry_ints.order_number] local filled, empty = check_pile(spec.stockpile, verbose) local amount = trigger.filled and filled or empty - amount = (amount - (amount % trigger.divisor)) / trigger.divisor - amount = math.floor(amount) + amount = (amount - (amount % trigger.divisor)) // trigger.divisor result[reaction] = (result[reaction] or 0) + amount end end From ba48afe9080fdba05a94fc7b66f31bda4264c749 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Fri, 16 Dec 2016 14:22:00 -0500 Subject: [PATCH 0376/1012] stockflow: Simplify calculation See #1046 Use math.floor() for now until we can get Lua 5.3 working on Travis --- plugins/lua/stockflow.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 32fc342fb..dd83e65d9 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1135,8 +1135,7 @@ function check_stockpiles(verbose) if trigger and trigger.divisor then local reaction = spec.entry.ints[entry_ints.order_number] local filled, empty = check_pile(spec.stockpile, verbose) - local amount = trigger.filled and filled or empty - amount = (amount - (amount % trigger.divisor)) // trigger.divisor + local amount = math.floor((trigger.filled and filled or empty) / trigger.divisor) result[reaction] = (result[reaction] or 0) + amount end end From 3c7d2626e250a4128e2fa1a98f1480e40235ab33 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 18 Dec 2016 17:57:23 -0600 Subject: [PATCH 0377/1012] df::dfhack_material_category has no constructor, must be manually initialized see #1047 --- plugins/buildingplan-lib.h | 4 +++- plugins/workflow.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 7ec0c41d4..6975960d2 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -114,7 +114,9 @@ struct ItemFilter bool decorated_only; ItemFilter() : min_quality(df::item_quality::Ordinary), decorated_only(false), valid(true) - { } + { + clear(); // mat_mask is not cleared by default (see issue #1047) + } bool matchesMask(DFHack::MaterialInfo &mat); diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 55f1dc504..f8f9e8231 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -357,7 +357,9 @@ public: : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), is_active(false), cant_resume_reported(false), low_stock_reported(-1) - {} + { + mat_mask.whole = 0; // see https://github.com/DFHack/dfhack/issues/1047 + } int goalCount() { return config.ival(0); } void setGoalCount(int v) { config.ival(0) = v; } From 506d124a98693cd7bad859b5429dbd660423d797 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 26 Dec 2016 19:41:25 -0500 Subject: [PATCH 0378/1012] Update xml Ref #1047, #1050 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d08339d3c..666f4c7f5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d08339d3ce144152cdbb703462d53231b282757d +Subproject commit 666f4c7f5fc578240d2a2ab2aa097bfc5c60d6ad From 8bd92b6a083d729e580811d5050e2f68414469ba Mon Sep 17 00:00:00 2001 From: Milo Christiansen Date: Tue, 27 Dec 2016 16:49:46 -0500 Subject: [PATCH 0379/1012] Add a Lua module for getting a tile's material (#1031) --- library/lua/tile-material.lua | 400 ++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 library/lua/tile-material.lua diff --git a/library/lua/tile-material.lua b/library/lua/tile-material.lua new file mode 100644 index 000000000..5661de73e --- /dev/null +++ b/library/lua/tile-material.lua @@ -0,0 +1,400 @@ +-- tile-material: Functions to help retrieve the material for a tile. + +--[[ +Copyright 2015-2016 Milo Christiansen + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim +that you wrote the original software. If you use this software in a product, an +acknowledgment in the product documentation would be appreciated but is not +required. + +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. +]] + +local _ENV = mkmodule("tile-material") + +--[====[ +tile-material +============= + +This module contains functions for finding the material of a tile. + +There is a function that will find the material of the tile based on it's type (in other words +it will return the material DF is using for that tile), and there are functions that will attempt +to return only a certain class of materials. + +Most users will be most interested in the generic "GetTileMat" function, but the other functions +should be useful in certain cases. For example "GetLayerMat" will always return the material of +the stone (or soil) in the current layer, ignoring any veins or other inclusions. + +Some tile types/materials have special behavior with the "GetTileMat" function. + +* Open space and other "material-less" tiles (such as semi-molten rock or eerie glowing pits) + will return nil. +* Ice will return the hard-coded water material ("WATER:NONE"). + +The specialized functions will return nil if a material of their type is not possible for a tile. +For example calling "GetVeinMat" for a tile that does not have (and has never had) a mineral vein +will always return nil. + +There are two functions for dealing with constructions, one to get the material of the construction +and one that gets the material of the tile the construction was built over. + +All the functions take coordinates as either three arguments (x, y, z) or one argument containing +a table with numeric x, y, and z keys. + +I am not sure how caved in tiles are handled, but after some quick testing it appears that the +game creates mineral veins for them. I am not 100% sure if these functions will reliably work +with all caved in tiles, but I can confirm that they do in at least some cases... +]====] + +-- Since there isn't any consistent style for module documentation I documented every function in +-- the style used by GoDoc (which is what I am most used to). + +-- Internal +local function prepPos(x, y, z) + if x ~= nil and y == nil and z == nil then + if type(x) ~= "table" or type(x.x) ~= "number" or type(x.y) ~= "number" or type(x.z) ~= "number" or x.x == -30000 then + error "Invalid coordinate argument(s)." + end + return x + else + if type(x) ~= "number" or type(y) ~= "number" or type(z) ~= "number" or x == -30000 then + error "Invalid coordinate argument(s)." + end + return {x = x, y = y, z = z} + end +end + +-- Internal +local function fixedMat(id) + local mat = dfhack.matinfo.find(id) + return function(x, y, z) + return mat + end +end + +-- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table covers the common case of returning plant materials for plant tiles and other +-- materials for the remaining tiles. +BasicMats = { + [df.tiletype_material.AIR] = nil, -- Empty + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.MAGMA] = nil, -- SMR + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, + [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? +} + +-- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will ignore plants, returning layer materials (or nil for trees) instead. +NoPlantMats = { + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, + [df.tiletype_material.GRASS_DARK] = GetLayerMat, + [df.tiletype_material.GRASS_DRY] = GetLayerMat, + [df.tiletype_material.GRASS_DEAD] = GetLayerMat, + [df.tiletype_material.PLANT] = GetLayerMat, + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, +} + +-- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. +OnlyPlantMats = { + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, +} + +-- GetLayerMat returns the layer material for the given tile. +-- AFAIK this will never return nil. +function GetLayerMat(x, y, z) + local pos = prepPos(x, y, z) + + local region_info = dfhack.maps.getRegionBiome(dfhack.maps.getTileBiomeRgn(pos)) + local map_block = dfhack.maps.ensureTileBlock(pos) + + local biome = df.world_geo_biome.find(region_info.geo_index) + + local layer_index = map_block.designation[pos.x%16][pos.y%16].geolayer_index + local layer_mat_index = biome.layers[layer_index].mat_index + + return dfhack.matinfo.decode(0, layer_mat_index) +end + +-- GetLavaStone returns the biome lava stone material (generally obsidian). +function GetLavaStone(x, y, z) + local pos = prepPos(x, y, z) + + local regions = df.global.world.world_data.region_details + + local rx, ry = dfhack.maps.getTileBiomeRgn(pos) + + for _, region in ipairs(regions) do + if region.pos.x == rx and region.pos.y == ry then + return dfhack.matinfo.decode(0, region.lava_stone) + end + end + return nil +end + +-- GetVeinMat returns the vein material of the given tile or nil if the tile has no veins. +-- Multiple veins in one tile should be handled properly (smallest vein type, last in the list wins, +-- which seems to be the rule DF uses). +function GetVeinMat(x, y, z) + local pos = prepPos(x, y, z) + + local map_block = dfhack.maps.ensureTileBlock(pos) + + local events = {} + for _, event in ipairs(map_block.block_events) do + if getmetatable(event) == "block_square_event_mineralst" then + if dfhack.maps.getTileAssignment(event.tile_bitmask, pos.x, pos.y) then + table.insert(events, event) + end + end + end + + if #events == 0 then + return nil + end + + local event_priority = function(event) + if event.flags.cluster then + return 1 + elseif event.flags.vein then + return 2 + elseif event.flags.cluster_small then + return 3 + elseif event.flags.cluster_one then + return 4 + else + return 5 + end + end + + local priority = events[1] + for _, event in ipairs(events) do + if event_priority(event) >= event_priority(priority) then + priority = event + end + end + + return dfhack.matinfo.decode(0, priority.inorganic_mat) +end + +-- GetConstructionMat returns the material of the construction at the given tile or nil if the tile +-- has no construction. +function GetConstructionMat(x, y, z) + local pos = prepPos(x, y, z) + + for _, construction in ipairs(df.global.world.constructions) do + if construction.pos.x == pos.x and construction.pos.y == pos.y and construction.pos.z == pos.z then + return dfhack.matinfo.decode(construction) + end + end + return nil +end + +-- GetConstructOriginalTileMat returns the material of the tile under the construction at the given +-- tile or nil if the tile has no construction. +function GetConstructOriginalTileMat(x, y, z) + local pos = prepPos(x, y, z) + + for _, construction in ipairs(df.global.world.constructions) do + if construction.pos.x == pos.x and construction.pos.y == pos.y and construction.pos.z == pos.z then + return GetTileTypeMat(construction.original_tile, BasicMats, pos) + end + end + return nil +end + +-- GetTreeMat returns the material of the tree at the given tile or nil if the tile does not have a +-- tree or giant mushroom. +-- Currently roots are ignored. +function GetTreeMat(x, y, z) + local pos = prepPos(x, y, z) + + local function coordInTree(pos, tree) + local x1 = tree.pos.x - math.floor(tree.tree_info.dim_x / 2) + local x2 = tree.pos.x + math.floor(tree.tree_info.dim_x / 2) + local y1 = tree.pos.y - math.floor(tree.tree_info.dim_y / 2) + local y2 = tree.pos.y + math.floor(tree.tree_info.dim_y / 2) + local z1 = tree.pos.z + local z2 = tree.pos.z + tree.tree_info.body_height + + if not ((pos.x >= x1 and pos.x <= x2) and (pos.y >= y1 and pos.y <= y2) and (pos.z >= z1 and pos.z <= z2)) then + return false + end + + return not tree.tree_info.body[pos.z - tree.pos.z]:_displace((pos.y - y1) * tree.tree_info.dim_x + (pos.x - x1)).blocked + end + + for _, tree in ipairs(df.global.world.plants.all) do + if tree.tree_info ~= nil then + if coordInTree(pos, tree) then + return dfhack.matinfo.decode(419, tree.material) + end + end + end + return nil +end + +-- GetShrubMat returns the material of the shrub at the given tile or nil if the tile does not +-- contain a shrub or sapling. +function GetShrubMat(x, y, z) + local pos = prepPos(x, y, z) + + for _, shrub in ipairs(df.global.world.plants.all) do + if shrub.tree_info == nil then + if shrub.pos.x == pos.x and shrub.pos.y == pos.y and shrub.pos.z == pos.z then + return dfhack.matinfo.decode(419, shrub.material) + end + end + end + return nil +end + +-- GetGrassMat returns the material of the grass at the given tile or nil if the tile is not +-- covered in grass. +function GetGrassMat(x, y, z) + local pos = prepPos(x, y, z) + + local map_block = dfhack.maps.ensureTileBlock(pos) + + for _, event in ipairs(map_block.block_events) do + if getmetatable(event) == "block_square_event_grassst" then + local amount = event.amount[pos.x%16][pos.y%16] + if amount > 0 then + return df.plant_raw.find(event.plant_index).material + end + end + end + return nil +end + +-- GetFeatureMat returns the material of the feature (adamantine tube, underworld surface, etc) at +-- the given tile or nil if the tile is not made of a feature stone. +function GetFeatureMat(x, y, z) + local pos = prepPos(x, y, z) + + local map_block = dfhack.maps.ensureTileBlock(pos) + + if df.tiletype.attrs[map_block.tiletype[pos.x%16][pos.y%16]].material ~= df.tiletype_material.FEATURE then + return nil + end + + if map_block.designation[pos.x%16][pos.y%16].feature_local then + -- adamantine tube, etc + for id, idx in ipairs(df.global.world.features.feature_local_idx) do + if idx == map_block.local_feature then + return dfhack.matinfo.decode(df.global.world.features.map_features[id]) + end + end + elseif map_block.designation[pos.x%16][pos.y%16].feature_global then + -- cavern, magma sea, underworld, etc + for id, idx in ipairs(df.global.world.features.feature_global_idx) do + if idx == map_block.global_feature then + return dfhack.matinfo.decode(df.global.world.features.map_features[id]) + end + end + end + + return nil +end + +-- GetTileMat will return the material of the specified tile as determined by its tile type and the +-- world geology data, etc. +-- The returned material should exactly match the material reported by DF except in cases where is +-- is impossible to get a material. +-- This is equivalent to calling GetTileMatSpec with the BasicMats matspec table. +function GetTileMat(x, y, z) + return GetTileMatSpec(BasicMats, x, y, z) +end + +-- GetTileMatSpec is exactly like GetTileMat except you may specify an explicit matspec table. +-- +-- "matspec" tables are simply tables with tiletype material classes as keys and functions +-- taking a coordinate table and returning a material as values. These tables are used to +-- determine how a specific material for a given tiletype material classification is determined. +-- Any tiletype material class that is unset (left nil) in a matspec table will result in tiles +-- of that type returning nil for their material. +function GetTileMatSpec(matspec, x, y, z) + local pos = prepPos(x, y, z) + + local typ = dfhack.maps.getTileType(pos) + if typ == nil then + return nil + end + + return GetTileTypeMat(typ, matspec, pos) +end + +-- GetTileTypeMat returns the material of the given tile assuming it is the given tiletype. +-- +-- Use this function when you want to check to see what material a given tile would be if it +-- was a specific tiletype. For example you can check to see if the tile used to be part of +-- a mineral vein or similar. Note that you can do the same basic thing by calling the individual +-- material finders directly, but this is sometimes simpler. +-- +-- Unless the tile could be the given type this function will probably return nil. +function GetTileTypeMat(typ, matspec, x, y, z) + local pos = prepPos(x, y, z) + + local type_mat = df.tiletype.attrs[typ].material + + local mat_getter = matspec[type_mat] + if mat_getter == nil then + return nil + end + return mat_getter(pos) +end + +return _ENV + From cd0d9fed18a5c8597834506c8bb0630e5c195c43 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 27 Dec 2016 17:40:54 -0500 Subject: [PATCH 0380/1012] stocks: Support getSelectedItem() --- plugins/stocks.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 3c7193701..1d3d04d6f 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1062,6 +1062,18 @@ public: std::string getFocusString() { return "stocks_view"; } + df::item *getSelectedItem() override + { + if (is_grouped) + return nullptr; + vector items = getSelectedItems(); + if (items.size() != 1) + return nullptr; + if (items[0]->entries.size() != 1) + return nullptr; + return items[0]->entries[0]; + } + private: StockListColumn items_column; int selected_column; From 538b07d214e26db4f390c2347fdefe54c6a7f88d Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 27 Dec 2016 23:24:22 -0500 Subject: [PATCH 0381/1012] Update xml and fix knowledge_scholar_category_flag::value() custom method Allows bitfields within unions (#1047, dfhack/df-structures#169) --- .../df/custom/knowledge_scholar_category_flag.methods.inc | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc index f5c4e9a76..c1fe1a564 100644 --- a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc +++ b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc @@ -3,7 +3,7 @@ df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() int32_t value = category * 32; for (int32_t i = 0; i < 32; i++) { - if (flags & (1 << i)) + if (flags.whole & (1 << i)) { value += i; break; diff --git a/library/xml b/library/xml index 666f4c7f5..e84bdcd02 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 666f4c7f5fc578240d2a2ab2aa097bfc5c60d6ad +Subproject commit e84bdcd0280495aeb49f12c124b4e52c62aaafc9 From c99780aafd5294cc64feb18a14214182187327f8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 15 Dec 2016 01:38:19 -0600 Subject: [PATCH 0382/1012] Add support for shell crafts. --- plugins/labormanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 94a22acb3..699e04605 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -1007,7 +1007,8 @@ private: case df::item_type::NONE: if (j->material_category.bits.bone || j->material_category.bits.horn || - j->material_category.bits.tooth) + j->material_category.bits.tooth || + j->material_category.bits.shell) return df::unit_labor::BONE_CARVE; else { From 408dba9da7ab2c04f983b6ee08d1df5807373d9b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 28 Dec 2016 22:12:22 -0500 Subject: [PATCH 0383/1012] Update NEWS(-dev) and scripts (add fix/tile-occupancy) --- NEWS.rst | 7 ++++++- docs/NEWS-dev.rst | 22 ++++++++++++++++++++++ scripts | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 1c7517adf..2fdbfc561 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -55,6 +55,7 @@ Lua - ``df.new()`` supports more types: ``char``, ``intptr_t``, ``uintptr_t``, ``long``, ``unsigned long`` - String representations of vectors and a few other containers now include their lengths +- Added a ``tile-material`` module Ruby ---- @@ -69,6 +70,7 @@ New Plugins New Scripts ----------- +- `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. - `load-save`: loads a save non-interactively - `modtools/change-build-menu`: Edit the build mode sidebar menus - `modtools/if-entity`: Run a command if the current entity matches a given ID @@ -81,7 +83,10 @@ Fixes - `add-thought`: fixed support for emotion names - `autofarm`: Made surface farms detect local biome - `devel/find-offsets`: fixed a crash when vtables used by globals aren't available -- `manipulator`: Fixed crash when selecting a profession from an empty list +- `manipulator`: + + - Fixed crash when selecting a profession from an empty list + - Custom professions are now sorted alphabetically more reliably Misc Improvements ----------------- diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 6a247294b..1ff96f7bb 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,28 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.43.05-alpha4 +===================== + +Fixes +----- +- Fixed an issue with uninitialized bitfields that was causing several issues + (disappearing buildings in `buildingplan`'s planning mode, strange behavior in + the extended `stocks` screen, and likely other problems). This issue was + introduced in 0.43.05-alpha3. +- `stockflow`: Fixed an "integer expected" error + +Additions/Removals +------------------ +- Added `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. + Useful for fixing blocked tiles introduced by the above buildingplan issue. +- Added a Lua ``tile-material`` module + +Other Changes +------------- +- `labormanager`: Add support for shell crafts +- `manipulator`: Custom professions are now sorted alphabetically more reliably + DFHack 0.43.05-alpha3 ===================== diff --git a/scripts b/scripts index 619d5b311..1cbbced78 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 619d5b3116061fdfffa06b9e7cd266367bb42c0a +Subproject commit 1cbbced78fe8433f78cd2b236c9d4e53e6493e60 From 6b3a5ac7848cfffe8a8b467655967dbb3fea5f19 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 29 Dec 2016 00:40:43 -0500 Subject: [PATCH 0384/1012] Bump to alpha4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 488dd323d..3602655b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "alpha3") +SET(DFHACK_RELEASE "alpha4") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From d18beb8dafb575cb8cb135910cf56e5a127965ff Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 29 Dec 2016 15:33:09 -0500 Subject: [PATCH 0385/1012] xml: Add some linux64 offsets, fix ui_sidebar_menus alignment Fixes #1053 Fixes #1054 Fixes #1055 --- docs/NEWS-dev.rst | 8 ++++++++ library/xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 1ff96f7bb..9920e3433 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -48,6 +48,14 @@ Fixes introduced in 0.43.05-alpha3. - `stockflow`: Fixed an "integer expected" error +Structures +---------- +- Located several globals on 64-bit Linux: flows, timed_events, ui_advmode, + ui_building_assign_type, ui_building_assign_is_marked, + ui_building_assign_units, ui_building_assign_items, and ui_look_list. This + fixes `search-plugin`, `zone`, and `force`, among others. +- ``ui_sidebar_menus``: Fixed some x64 alignment issues + Additions/Removals ------------------ - Added `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. diff --git a/library/xml b/library/xml index e84bdcd02..bb4228f58 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e84bdcd0280495aeb49f12c124b4e52c62aaafc9 +Subproject commit bb4228f58b1601c4868c95be6763f5ff2e5d0a08 From 61d081849e76a2b5a60c63fc31ec4c193e3b295e Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 30 Dec 2016 18:02:51 -0500 Subject: [PATCH 0386/1012] xml: ui_sidebar_menus.command_line fix --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index bb4228f58..6ff5d183f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit bb4228f58b1601c4868c95be6763f5ff2e5d0a08 +Subproject commit 6ff5d183f5029cba683b4160b09438a2bbe3646f From cda4e7a30023dab81f4d1f52f2e901846aa3aba1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 30 Dec 2016 18:03:28 -0500 Subject: [PATCH 0387/1012] title-version: add prerelease indicator --- plugins/title-version.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp index 78a694211..127a1bd9f 100644 --- a/plugins/title-version.cpp +++ b/plugins/title-version.cpp @@ -36,10 +36,15 @@ struct title_version_hook : df::viewscreen_titlest { if (!DFHACK_IS_RELEASE) { OutputString(COLOR_WHITE, x, y, " (dev)"); - x = 0; y = 1; + x = 0; y++; OutputString(COLOR_WHITE, x, y, "Git: "); OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION); } + if (DFHACK_IS_PRERELEASE) + { + x = 0; y++; + OutputString(COLOR_LIGHTRED, x, y, "Pre-release build"); + } } }; From 92f890d3c6256ff62ecd393f2628eee3b931a4bf Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Jan 2017 12:30:42 -0500 Subject: [PATCH 0388/1012] Update xml, scripts (#994 and file_compressor fix) --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 6ff5d183f..34331ef67 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 6ff5d183f5029cba683b4160b09438a2bbe3646f +Subproject commit 34331ef67f3d163f4ab66121449a8217eb1cfb47 diff --git a/scripts b/scripts index 1cbbced78..20eb2146f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1cbbced78fe8433f78cd2b236c9d4e53e6493e60 +Subproject commit 20eb2146fb93912b07034f3192dde1cf95a9ec7f From affcd658dc77cf16489439a0876c16fca784d2a8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Jan 2017 12:29:21 -0500 Subject: [PATCH 0389/1012] title-version: Hide when loading a game (e.g. arena) --- plugins/title-version.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp index 127a1bd9f..be7bc7e79 100644 --- a/plugins/title-version.cpp +++ b/plugins/title-version.cpp @@ -31,6 +31,9 @@ struct title_version_hook : df::viewscreen_titlest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); + if (loading) + return; + int x = 0, y = 0; OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION); if (!DFHACK_IS_RELEASE) From e2fc7d3e00affe847de41278c42379d6b68a6191 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Jan 2017 18:32:12 -0500 Subject: [PATCH 0390/1012] Update OS X compilation instructions - GCC 4.8+ is now required - remove references to 4.5, XCode 7, etc. - Homebrew's formula has been renamed to "gcc@4.8". "gcc48" still works as well, so applying this change to the master branch isn't urgent. --- docs/Compile.rst | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index 4cea7713b..ac324a9f7 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -219,41 +219,22 @@ DFHack functions similarly on OS X and Linux, and the majority of the information above regarding the build process (cmake and make) applies here as well. -DFHack can officially be built on OS X with anything from GCC 4.5 to 4.8, so 4.8 -is recommended, as 4.5 has issues on newer systems, but 4.5-4.7 should also -work. Anything newer than 4.8 will require you to perform extra steps to get -DFHack to run (see `osx-new-gcc-notes`), and your build will likely not be -redistributable. +DFHack can officially be built on OS X with GCC 4.8. Anything newer than 4.8 +will require you to perform extra steps to get DFHack to run (see `osx-new-gcc-notes`), +and your build will likely not be redistributable. .. _osx-new-gcc-notes: -Notes for GCC 4.9+, OS X 10.10+, or XCode 7 users -------------------------------------------------- +Notes for GCC 4.9+ or OS X 10.10+ users +--------------------------------------- If none of these situations apply to you, skip to `osx-setup`. -If you have issues building on OS X 10.10 (Yosemite) or above, try definining the -following environment variable:: +If you have issues building on OS X 10.10 (Yosemite) or above, try definining +the following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 - -If you try to build with GCC 4.5, you will probably find that GCC 4.5 will fail -to install on OS X 10.11 and newer, or any older OS X that is using XCode 7 or -newer. There are two workarounds: - -* Install a newer version of GCC instead (e.g. ``brew install gcc48`` or ``brew - install gcc5``) and follow the instructions for linking libstdc++ below. - -* Install XCode 6, which is available as a free download from the Apple - Developer Center. - - * Either install this as your only XCode, or install it additionally - to XCode 7 and then switch between them using ``xcode-select`` - * Ensure XCode 6 is active before attempting to install GCC 4.5 and - whenever you are compiling DFHack with GCC 4.5. - - If you build with a GCC version newer than 4.8, DFHack will probably crash immediately on startup, or soon after. To fix this, you will need to replace ``hack/libstdc++.6.dylib`` with a symlink to the ``libstdc++.6.dylib`` included @@ -271,7 +252,7 @@ For example, with GCC 5.2.0, ``PATH_TO_LIBSTDC++`` would be:: build will *not* be redistributable. (Even if you copy the ``libstdc++.6.dylib`` from your GCC version and distribute that too, it will fail on older OS X versions.) For this reason, if you plan on distributing DFHack, it is highly -recommended to use GCC 4.5-4.8. +recommended to use GCC 4.8. .. _osx-setup: @@ -298,7 +279,7 @@ Dependencies and system set-up brew tap homebrew/versions brew install git brew install cmake - brew install gcc48 + brew install gcc@4.8 Using `MacPorts `_:: From f0d4a1f563b81e9a3c6a85b9467eaf01ad9335d0 Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Fri, 20 Jan 2017 17:16:19 -0500 Subject: [PATCH 0391/1012] Add DOWNLOAD_RUBY option to Ruby plugin cmake, defaulting to ON If DOWNLOAD_RUBY is set to OFF, the build will not attempt to download a prebuilt libruby.so and not fire a warning that one is not present. This may be desired if one wants to link dfhack against a newer system libruby.so, now that dfhack supports ruby 2.x and also supports linking against system libruby (at least, on Linux), as per the following commit: https://github.com/DFHack/dfhack/commit/df9b5bca7311489748ab100dfc51080841c3f196 This also allows the dfhack build to proceed without network access (once all submodules have been fetched), which at least some Linux distributions, like Fedora, require. By default DOWNLOAD_RUBY is set to ON, so this should not change the behavior of default builds. I tested that when DOWNLOAD_RUBY is set to OFF, dfhack still launches and that the Ruby plugin still functions, e.g. by running ```rb_eval puts('Hello world.')``` in the dfhack console. --- plugins/ruby/CMakeLists.txt | 104 +++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 6b5e62c66..f1ef12ac5 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,52 +1,59 @@ -IF (APPLE) - SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib) - SET(RUBYLIB_INSTALL_NAME "libruby.dylib") - IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - # MESSAGE("No ruby lib for 64-bit OS X yet") - ELSE() - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libruby187.dylib.gz" - "gz" - ${RUBYLIB}.gz - "e9bc4263557e652121b055a46abb4f97" - ${RUBYLIB} - "3ee5356759f764a440be5b5b44649826") - ENDIF() -ELSEIF(UNIX) - SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) - SET(RUBYLIB_INSTALL_NAME "libruby.so") - IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux64-libruby187.so.gz" - "gz" - ${RUBYLIB}.gz - "8eb757bb9ada08608914d8ca8906c427" - ${RUBYLIB} - "e8c36a06f031cfbf02def28169bb5f1f") - ELSE() - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux32-libruby187.so.gz" - "gz" - ${RUBYLIB}.gz - "2d06f5069ff07ea934ecd40db55a4ac5" - ${RUBYLIB} - "b00d8d7086cb39f6fde793f9d89cb2d7") - ENDIF() -ELSE() - SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) - SET(RUBYLIB_INSTALL_NAME "libruby.dll") - IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-libruby200.dll.gz" - "gz" - ${RUBYLIB}.gz - "81db54a8b8b3090c94c6ae2147d30b8f" - ${RUBYLIB} - "8a8564418aebddef3dfee1e96690e713") +# Allow build system to turn off downloading of libruby.so. +OPTION(DOWNLOAD_RUBY "Download prebuilt libruby.so for ruby plugin." ON) + +IF (DOWNLOAD_RUBY) + + IF (APPLE) + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib) + SET(RUBYLIB_INSTALL_NAME "libruby.dylib") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + # MESSAGE("No ruby lib for 64-bit OS X yet") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libruby187.dylib.gz" + "gz" + ${RUBYLIB}.gz + "e9bc4263557e652121b055a46abb4f97" + ${RUBYLIB} + "3ee5356759f764a440be5b5b44649826") + ENDIF() + ELSEIF(UNIX) + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) + SET(RUBYLIB_INSTALL_NAME "libruby.so") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux64-libruby187.so.gz" + "gz" + ${RUBYLIB}.gz + "8eb757bb9ada08608914d8ca8906c427" + ${RUBYLIB} + "e8c36a06f031cfbf02def28169bb5f1f") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux32-libruby187.so.gz" + "gz" + ${RUBYLIB}.gz + "2d06f5069ff07ea934ecd40db55a4ac5" + ${RUBYLIB} + "b00d8d7086cb39f6fde793f9d89cb2d7") + ENDIF() ELSE() - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" - "gz" - ${RUBYLIB}.gz - "ffc0f1b5b33748e2a36128e90c97f6b2" - ${RUBYLIB} - "482c1c418f4ee1a5f04203eee1cda0ef") + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) + SET(RUBYLIB_INSTALL_NAME "libruby.dll") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-libruby200.dll.gz" + "gz" + ${RUBYLIB}.gz + "81db54a8b8b3090c94c6ae2147d30b8f" + ${RUBYLIB} + "8a8564418aebddef3dfee1e96690e713") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" + "gz" + ${RUBYLIB}.gz + "ffc0f1b5b33748e2a36128e90c97f6b2" + ${RUBYLIB} + "482c1c418f4ee1a5f04203eee1cda0ef") + ENDIF() ENDIF() + ENDIF() IF (APPLE OR UNIX) @@ -73,7 +80,8 @@ ADD_DEPENDENCIES(ruby ruby-autogen-rb) IF(EXISTS ${RUBYLIB}) INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) ELSE() - IF(NOT(APPLE AND ${DFHACK_BUILD_ARCH} STREQUAL 64)) + # Only fire this warning if DOWNLOAD_RUBY was set. + IF(NOT(APPLE AND ${DFHACK_BUILD_ARCH} STREQUAL 64) AND DOWNLOAD_RUBY) MESSAGE(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed") ENDIF() ENDIF() From cc02ced0ac854ce60d3320b426af4fa9b97d5629 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 21 Jan 2017 15:16:06 -0600 Subject: [PATCH 0392/1012] Fix RTTI in Win64 - offsets are relative to the current module (e.g. the DLL), so it needs to be looked up explicitly --- library/Process-windows.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index cb2e16bd2..3a1da0ac4 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -342,12 +342,14 @@ int Process::adjustOffset(int offset, bool to_file) return -1; } - string Process::doReadClassName (void * vptr) { char * rtti = readPtr((char *)vptr - sizeof(void*)); #ifdef DFHACK64 - char * typeinfo = d->base + readDWord(rtti + 0xC); + void *base; + if (!RtlPcToFileHeader(rtti, &base)) + return "dummy"; + char * typeinfo = (char *)base + readDWord(rtti + 0xC); string raw = readCString(typeinfo + 0x10+4); // skips the .?AV #else char * typeinfo = readPtr(rtti + 0xC); From c659b885b6ab5d6e71a0e5650b7882f99066445e Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 25 Jan 2017 23:06:03 +0530 Subject: [PATCH 0393/1012] Start a plugin to rename generated creatures to have sensible IDs --- plugins/CMakeLists.txt | 1 + plugins/generated-creature-renamer.cpp | 79 ++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 plugins/generated-creature-renamer.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4611009c3..6b1f80634 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -122,6 +122,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib) + DFHACK_PLUGIN(generated-creature-renamer generated-creature-renamer.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(hotkeys hotkeys.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp new file mode 100644 index 000000000..8c2fe20ef --- /dev/null +++ b/plugins/generated-creature-renamer.cpp @@ -0,0 +1,79 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "df/world.h" +#include "df/world_raws.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" + +//#include "df/world.h" + +using namespace DFHack; + +DFHACK_PLUGIN("rename_creatures"); +REQUIRE_GLOBAL(world); + +command_result rename_creatures (color_ostream &out, std::vector & parameters); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "rename_creatures", + "Renames generated creature tags to something friendlier to modders", + rename_creatures, + false, //allow non-interactive use + "longHelpString" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +std::string descriptors[] = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", +"damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", +"mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", +"ladybug", "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", +"dung beetle", "rhinoceros beetle", "rove beetle", "snakefly", "lacewing", "antlion larva", +"mosquito", "flea", "scorpionfly", "caddisfly", "butterfly", "moth", "caterpillar", "maggot", +"spider", "tarantula", "scorpion", "tick", "mite", "shrimp", "lobster", "crab", "nematode", +"snail", "slug", "earthworm", "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", +"salamander", "newt", "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", +"gila monster", "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", +"tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", +"ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", "quail", "pheasant", +"gull", "loon", "grebe", "albatross", "petrel", "penguin", "pelican", "stork", "vulture", "flamingo", +"falcon", "kestrel", "condor", "osprey", "buzzard", "eagle", "harrier", "kite", "crane", "dove", +"pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", +"hornbill", "quetzal", "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", +"fantail", "shrike", "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", +"warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", "tanager", +"cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", "opossum", "koala", +"wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", "marmot", "beaver", "gopher", +"mouse", "porcupine", "chinchilla", "cavy", "capybara", "rabbit", "hare", "lemur", "loris", "monkey", +"hedgehog", "shrew", "mole", "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", +"weasel", "otter", "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", +"walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", "warthog", +"hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", +"bison", "buffalo", "bull" }; + +command_result rename_creatures (color_ostream &out, std::vector & parameters) +{ + if (!parameters.empty()) + return CR_WRONG_USAGE; + CoreSuspender suspend; + for (int i = 0; i < world->raws.creatures.all.size(); i++) + { + auto creatureRaw = world->raws.creatures.all[i]; + if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) + continue; + out.print(creatureRaw->creature_id.c_str()); + out.print(creatureRaw->caste[0]->description.c_str()); + } + return CR_OK; +} + From 934d5b32bce6f880d9e3ff4b2f1bbc134421c5bb Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 26 Jan 2017 10:00:38 +0530 Subject: [PATCH 0394/1012] Fix creature listing and plugin name --- plugins/generated-creature-renamer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 8c2fe20ef..81816d061 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -12,7 +12,7 @@ using namespace DFHack; -DFHACK_PLUGIN("rename_creatures"); +DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); command_result rename_creatures (color_ostream &out, std::vector & parameters); @@ -72,7 +72,9 @@ command_result rename_creatures (color_ostream &out, std::vector & if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) continue; out.print(creatureRaw->creature_id.c_str()); + out.print("\n"); out.print(creatureRaw->caste[0]->description.c_str()); + out.print("\n"); } return CR_OK; } From 365624453e3ee0f6b9e953ff15a523fe902a900f Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Thu, 26 Jan 2017 12:45:40 +0530 Subject: [PATCH 0395/1012] Finish up the generated-creature-renamer plugin. --- plugins/generated-creature-renamer.cpp | 93 +++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 81816d061..c6c7cdfec 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -7,6 +7,7 @@ #include "df/world_raws.h" #include "df/creature_raw.h" #include "df/caste_raw.h" +#include "modules/World.h" //#include "df/world.h" @@ -15,26 +16,34 @@ using namespace DFHack; DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); -command_result rename_creatures (color_ostream &out, std::vector & parameters); +command_result rename_creatures(color_ostream &out, std::vector & parameters); +command_result list_creatures(color_ostream &out, std::vector & parameters); -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "rename_creatures", + "rename-generated", "Renames generated creature tags to something friendlier to modders", rename_creatures, false, //allow non-interactive use - "longHelpString" + "Renames generated creature tags to something friendlier to modders" + )); + commands.push_back(PluginCommand( + "list-generated", + "Prints a list of generated creature tokens. Use \"list-generated detailed\" to show descriptions.", + list_creatures, + false, //allow non-interactive use + "Prints a list of generated creature tokens. Use \"list-generated detailed\" to show descriptions." )); return CR_OK; } -DFhackCExport command_result plugin_shutdown (color_ostream &out) +DFhackCExport command_result plugin_shutdown(color_ostream &out) { return CR_OK; } -std::string descriptors[] = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", +std::string descriptors[221] = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", "damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", "mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", "ladybug", "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", @@ -48,7 +57,7 @@ std::string descriptors[] = { "blob", "quadruped", "humanoid", "silverfish", "ma "ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", "quail", "pheasant", "gull", "loon", "grebe", "albatross", "petrel", "penguin", "pelican", "stork", "vulture", "flamingo", "falcon", "kestrel", "condor", "osprey", "buzzard", "eagle", "harrier", "kite", "crane", "dove", -"pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", +"pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", "hornbill", "quetzal", "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", "fantail", "shrike", "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", "warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", "tanager", @@ -61,21 +70,83 @@ std::string descriptors[] = { "blob", "quadruped", "humanoid", "silverfish", "ma "hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", "bison", "buffalo", "bull" }; -command_result rename_creatures (color_ostream &out, std::vector & parameters) +command_result rename_creatures(color_ostream &out, std::vector & parameters) { if (!parameters.empty()) return CR_WRONG_USAGE; CoreSuspender suspend; + + int descriptorCount[221] = { 0 }; + + if (World::GetPersistentData("AlreadyRenamedCreatures").isValid()) + { + return CR_OK; + } + + for (int i = 0; i < world->raws.creatures.all.size(); i++) + { + auto creatureRaw = world->raws.creatures.all[i]; + if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) + continue; + size_t minPos = std::string::npos; + size_t foundIndex = std::string::npos; + + for (size_t j = 0; j < 221; j++) + { + size_t pos = creatureRaw->caste[0]->description.find(descriptors[j]); + if (pos < minPos) + { + minPos = pos; + foundIndex = j; + } + } + + if (foundIndex < std::string::npos) + { + size_t digitPos = creatureRaw->creature_id.find_first_of("0123456789"); + if (digitPos > creatureRaw->creature_id.length()) + digitPos = creatureRaw->creature_id.length(); + + creatureRaw->creature_id.replace(digitPos, std::string::npos, descriptors[foundIndex]); + + if (descriptorCount[foundIndex] > 0) + { + creatureRaw->creature_id.append(std::to_string(descriptorCount[foundIndex])); + } + + descriptorCount[foundIndex]++; + } + } + World::AddPersistentData("AlreadyRenamedCreatures"); + + return CR_OK; +} + +command_result list_creatures(color_ostream &out, std::vector & parameters) +{ + bool detailed = false; + if (!parameters.empty()) + { + if (parameters.size() > 1) + return CR_WRONG_USAGE; + if(parameters[0].compare("detailed") != 0) + return CR_WRONG_USAGE; + detailed = true; + } + + CoreSuspender suspend; for (int i = 0; i < world->raws.creatures.all.size(); i++) { auto creatureRaw = world->raws.creatures.all[i]; if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) continue; out.print(creatureRaw->creature_id.c_str()); - out.print("\n"); - out.print(creatureRaw->caste[0]->description.c_str()); + if (detailed) + { + out.print("\t"); + out.print(creatureRaw->caste[0]->description.c_str()); + } out.print("\n"); } - return CR_OK; } From 24a653f77be87aa83a8efece28245e282c7b73a8 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Thu, 26 Jan 2017 12:50:37 +0530 Subject: [PATCH 0396/1012] added ants and apes to the list. --- plugins/generated-creature-renamer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index c6c7cdfec..20e861c83 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -43,7 +43,8 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) return CR_OK; } -std::string descriptors[221] = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", +#define NUM_DESC 223 +std::string descriptors[NUM_DESC] = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", "damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", "mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", "ladybug", "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", @@ -68,7 +69,7 @@ std::string descriptors[221] = { "blob", "quadruped", "humanoid", "silverfish", "weasel", "otter", "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", "walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", "warthog", "hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", -"bison", "buffalo", "bull" }; +"bison", "buffalo", "bull", "ape", "ant" }; command_result rename_creatures(color_ostream &out, std::vector & parameters) { @@ -76,7 +77,7 @@ command_result rename_creatures(color_ostream &out, std::vector & return CR_WRONG_USAGE; CoreSuspender suspend; - int descriptorCount[221] = { 0 }; + int descriptorCount[NUM_DESC] = { 0 }; if (World::GetPersistentData("AlreadyRenamedCreatures").isValid()) { @@ -91,7 +92,7 @@ command_result rename_creatures(color_ostream &out, std::vector & size_t minPos = std::string::npos; size_t foundIndex = std::string::npos; - for (size_t j = 0; j < 221; j++) + for (size_t j = 0; j < NUM_DESC; j++) { size_t pos = creatureRaw->caste[0]->description.find(descriptors[j]); if (pos < minPos) From c3c3f37b0660d301a6008d85ef3a6db9247b2030 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Thu, 26 Jan 2017 15:58:43 +0530 Subject: [PATCH 0397/1012] Added more descriptor types, and made it run on world load. --- dfhack.init-example | 3 + plugins/generated-creature-renamer.cpp | 144 +++++++++++++------------ 2 files changed, 78 insertions(+), 69 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 169d40046..a0dd220e1 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -233,6 +233,9 @@ enable \ # enable mouse controls and sand indicator in embark screen embark-tools enable sticky sand mouse +# Renames generated creatures to reflect the template used to generate them. +enable generated-creature-renamer + ########### # Scripts # ########### diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 20e861c83..1a738cce4 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -16,18 +16,10 @@ using namespace DFHack; DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); -command_result rename_creatures(color_ostream &out, std::vector & parameters); command_result list_creatures(color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "rename-generated", - "Renames generated creature tags to something friendlier to modders", - rename_creatures, - false, //allow non-interactive use - "Renames generated creature tags to something friendlier to modders" - )); commands.push_back(PluginCommand( "list-generated", "Prints a list of generated creature tokens. Use \"list-generated detailed\" to show descriptions.", @@ -43,82 +35,96 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) return CR_OK; } -#define NUM_DESC 223 -std::string descriptors[NUM_DESC] = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", -"damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", -"mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", -"ladybug", "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", -"dung beetle", "rhinoceros beetle", "rove beetle", "snakefly", "lacewing", "antlion larva", -"mosquito", "flea", "scorpionfly", "caddisfly", "butterfly", "moth", "caterpillar", "maggot", -"spider", "tarantula", "scorpion", "tick", "mite", "shrimp", "lobster", "crab", "nematode", -"snail", "slug", "earthworm", "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", -"salamander", "newt", "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", -"gila monster", "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", -"tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", -"ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", "quail", "pheasant", -"gull", "loon", "grebe", "albatross", "petrel", "penguin", "pelican", "stork", "vulture", "flamingo", -"falcon", "kestrel", "condor", "osprey", "buzzard", "eagle", "harrier", "kite", "crane", "dove", -"pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", -"hornbill", "quetzal", "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", -"fantail", "shrike", "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", -"warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", "tanager", -"cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", "opossum", "koala", -"wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", "marmot", "beaver", "gopher", -"mouse", "porcupine", "chinchilla", "cavy", "capybara", "rabbit", "hare", "lemur", "loris", "monkey", -"hedgehog", "shrew", "mole", "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", -"weasel", "otter", "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", -"walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", "warthog", -"hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", -"bison", "buffalo", "bull", "ape", "ant" }; - -command_result rename_creatures(color_ostream &out, std::vector & parameters) -{ - if (!parameters.empty()) - return CR_WRONG_USAGE; - CoreSuspender suspend; +DFHACK_PLUGIN_IS_ENABLED(is_enabled); - int descriptorCount[NUM_DESC] = { 0 }; +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + is_enabled = enable; + return CR_OK; +} - if (World::GetPersistentData("AlreadyRenamedCreatures").isValid()) - { +#define NUM_DESC 227 +std::string descriptors[NUM_DESC] = { " blob", " quadruped", " humanoid", " silverfish", " mayfly", " dragonfly", +" damselfly", " stonefly", " earwig", " grasshopper", " cricket", " stick insect", " cockroach", " termite", +" mantis", " louse", " thrips", " aphid", " cicada", " assassin bug", " wasp", " hornet", " tiger beetle", +" ladybug", " weevil", " darkling beetle", " click beetle", " firefly", " scarab beetle", " stag beetle", +" dung beetle", " rhinoceros beetle", " rove beetle", " snakefly", " lacewing", " antlion larva", +" mosquito", " flea", " scorpionfly", " caddisfly", " butterfly", " moth", " caterpillar", " maggot", +" spider", " tarantula", " scorpion", " tick", " mite", " shrimp", " lobster", " crab", " nematode", +" snail", " slug", " earthworm", " leech", " bristleworm", " ribbon worm", " flat worm", " toad", " frog", +" salamander", " newt", " alligator", " crocodile", " lizard", " chameleon", " iguana", " gecko", " skink", +" gila monster", " monitor", " serpent", " viper", " rattlesnake", " cobra", " python", " anaconda", " turtle", +" tortoise", " pterosaur", " dimetrodon", " sauropod", " theropod", " iguanodont", " hadrosaurid", " stegosaurid", +" ceratopsid", " ankylosaurid", " duck", " goose", " swan", " turkey", " grouse", " chicken", " quail", " pheasant", +" gull", " loon", " grebe", " albatross", " petrel", " penguin", " pelican", " stork", " vulture", " flamingo", +" falcon", " kestrel", " condor", " osprey", " buzzard", " eagle", " harrier", " kite", " crane", " dove", +" pigeon", " parrot", " cockatoo", " cuckoo", " nightjar", " swift", " hummingbird", " kingfisher", +" hornbill", " quetzal", " toucan", " woodpecker", " lyrebird", " thornbill", " honeyeater", " oriole", +" fantail", " shrike", " crow", " raven", " magpie", " kinglet", " lark", " swallow", " martin", " bushtit", +" warbler", " thrush", " oxpecker", " starling", " mockingbird", " wren", " nuthatch", " sparrow", " tanager", +" cardinal", " bunting", " finch", " titmouse", " chickadee", " waxwing", " flycatcher", " opossum", " koala", +" wombat", " kangaroo", " sloth", " anteater", " armadillo", " squirrel", " marmot", " beaver", " gopher", +" mouse", " porcupine", " chinchilla", " cavy", " capybara", " rabbit", " hare", " lemur", " loris", " monkey", +" hedgehog", " shrew", " mole", " fruit bat", " wolf", " coyote", " jackal", " raccoon", " coati", +" weasel", " otter", " badger", " skunk", " bear", " panda", " panther", " mongoose", " hyena", " civet", +" walrus", " pangolin", " elephant", " mammoth", " horse", " zebra", " tapir", " rhinoceros", " warthog", +" hippopotamus", " camel", " llama", " giraffe", " deer", " moose", " antelope", " sheep", " goat", +" bison", " buffalo", " bull", " ape", " ant", " bat", " owl", " pig", " bee" }; + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + if (!is_enabled) return CR_OK; - } - - for (int i = 0; i < world->raws.creatures.all.size(); i++) + switch (event) { - auto creatureRaw = world->raws.creatures.all[i]; - if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) - continue; - size_t minPos = std::string::npos; - size_t foundIndex = std::string::npos; + case DFHack::SC_WORLD_LOADED: + CoreSuspender suspend; - for (size_t j = 0; j < NUM_DESC; j++) + int descriptorCount[NUM_DESC] = { 0 }; + + if (World::GetPersistentData("AlreadyRenamedCreatures").isValid()) { - size_t pos = creatureRaw->caste[0]->description.find(descriptors[j]); - if (pos < minPos) - { - minPos = pos; - foundIndex = j; - } + return CR_OK; } - if (foundIndex < std::string::npos) + for (int i = 0; i < world->raws.creatures.all.size(); i++) { - size_t digitPos = creatureRaw->creature_id.find_first_of("0123456789"); - if (digitPos > creatureRaw->creature_id.length()) - digitPos = creatureRaw->creature_id.length(); + auto creatureRaw = world->raws.creatures.all[i]; + if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) + continue; + size_t minPos = std::string::npos; + size_t foundIndex = std::string::npos; - creatureRaw->creature_id.replace(digitPos, std::string::npos, descriptors[foundIndex]); - - if (descriptorCount[foundIndex] > 0) + for (size_t j = 0; j < NUM_DESC; j++) { - creatureRaw->creature_id.append(std::to_string(descriptorCount[foundIndex])); + size_t pos = creatureRaw->caste[0]->description.find(descriptors[j]); + if (pos < minPos) + { + minPos = pos; + foundIndex = j; + } } - descriptorCount[foundIndex]++; + if (foundIndex < std::string::npos) + { + size_t digitPos = creatureRaw->creature_id.find_first_of("0123456789"); + if (digitPos > creatureRaw->creature_id.length()) + digitPos = creatureRaw->creature_id.length(); + + creatureRaw->creature_id.replace(digitPos, std::string::npos, descriptors[foundIndex], 1, std::string::npos); + + if (descriptorCount[foundIndex] > 0) + { + creatureRaw->creature_id.append(std::to_string(descriptorCount[foundIndex])); + } + + descriptorCount[foundIndex]++; + } } + World::AddPersistentData("AlreadyRenamedCreatures"); + break; } - World::AddPersistentData("AlreadyRenamedCreatures"); return CR_OK; } From d97b0497b354927bc4f1ed79c52c9cf1d1dba002 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Thu, 2 Feb 2017 12:12:28 +0530 Subject: [PATCH 0398/1012] Add prosession and noble position info to units. --- plugins/proto/RemoteFortressReader.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index c0ed000c7..fdfc25d99 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -322,6 +322,8 @@ message UnitDefinition optional int32 blood_max = 14; optional int32 blood_count = 15; optional UnitAppearance appearance = 16; + optional int32 profession_id = 17; + repeated string noble_positions = 18; } message UnitList From c8e6845a7fceec83cfc0d69928762c43e1673d5a Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Fri, 3 Feb 2017 10:08:35 +0530 Subject: [PATCH 0399/1012] Send noble positions and professions along with Units. --- .../remotefortressreader/remotefortressreader.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index d1803dc0b..3ebd2ea35 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -96,6 +96,7 @@ #include "df/world_region_details.h" #include "df/world_site.h" #include "df/world_site_realization.h" +#include "df/entity_position.h" #if DF_VERSION > 40001 #include "df/plant_growth.h" @@ -1800,6 +1801,19 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, for (int j = 0; j < unit->appearance.colors.size(); j++) appearance->add_colors(unit->appearance.colors[j]); appearance->set_size_modifier(unit->appearance.size_modifier); + + send_unit->set_profession_id(unit->profession); + + std::vector pvec; + + if (Units::getNoblePositions(&pvec, unit)) + { + for (int j = 0; j < pvec.size(); j++) + { + auto noble_positon = pvec[j]; + send_unit->add_noble_positions(noble_positon.position->code); + } + } } return CR_OK; } From f65a9810996d9d20d2423ab534cb04c495c905d5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Feb 2017 14:46:38 -0500 Subject: [PATCH 0400/1012] Fix indentation --- plugins/proto/RemoteFortressReader.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index fdfc25d99..af25c9a4b 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -322,8 +322,8 @@ message UnitDefinition optional int32 blood_max = 14; optional int32 blood_count = 15; optional UnitAppearance appearance = 16; - optional int32 profession_id = 17; - repeated string noble_positions = 18; + optional int32 profession_id = 17; + repeated string noble_positions = 18; } message UnitList @@ -756,4 +756,4 @@ message DigCommand message SingleBool { optional bool Value = 1; -} \ No newline at end of file +} From a8f48331886d2f2327fddb1c88a8e04ef54d2979 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Feb 2017 14:49:09 -0500 Subject: [PATCH 0401/1012] internal_memscan: add extra check --- library/LuaApi.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b8a6fe53d..39404e9be 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2557,6 +2557,9 @@ static int internal_memscan(lua_State *L) for (int i = 0; i <= hcount; i++) { uint8_t *p = haystack + i*hstep; + if (p + nsize > haystack + (hcount * hstep)) { + break; + } if (memcmp(p, needle, nsize) == 0) { lua_pushinteger(L, i); lua_pushinteger(L, (lua_Integer)p); From 7823d78cd91210ae1e648be038b6a92a6f658391 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Feb 2017 14:49:21 -0500 Subject: [PATCH 0402/1012] Update xml and embark-tools field name --- library/xml | 2 +- plugins/embark-tools.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 34331ef67..fc2570c6c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 34331ef67f3d163f4ab66121449a8217eb1cfb47 +Subproject commit fc2570c6c0179fcdf48610f0e3eaef7f84908da7 diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index d2bdf4909..274f8cad7 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -128,7 +128,7 @@ public: if (input->count(df::interface_key::SETUP_EMBARK)) { cancel = true; - screen->in_embark_normal = 1; + screen->in_embark_only_warning = 1; } }; }; From 34988e53b9022e27c1f4412f403b57b987561270 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 5 Feb 2017 22:03:06 -0500 Subject: [PATCH 0403/1012] Update supported keys in keybinding help text --- library/Core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 6afe907aa..b1bf67973 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1043,7 +1043,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << endl << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << endl << "Later adds, and earlier items within one command have priority." << endl - << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, or F1-F9, or Enter)." << endl + << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, or Enter)." << endl << "Context may be used to limit the scope of the binding, by" << endl << "requiring the current context to have a certain prefix." << endl << "Current UI context is: " From f2164d27c9d939407a4f814c1da2d5e39aceb72b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 5 Feb 2017 22:11:35 -0500 Subject: [PATCH 0404/1012] ls: List scripts outside of hack/scripts Closes #412 --- library/Core.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index b1bf67973..642da61ea 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -279,19 +279,24 @@ static void listScripts(PluginManager *plug_mgr, std::map &pset, std::vector files; Filesystem::listdir(path, files); + path += '/'; for (size_t i = 0; i < files.size(); i++) { if (hasEnding(files[i], ".lua")) { - std::string help = getScriptHelp(path + files[i], "--"); - - pset[prefix + files[i].substr(0, files[i].size()-4)] = help; + string help = getScriptHelp(path + files[i], "--"); + string key = prefix + files[i].substr(0, files[i].size()-4); + if (pset.find(key) == pset.end()) { + pset[key] = help; + } } else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && hasEnding(files[i], ".rb")) { - std::string help = getScriptHelp(path + files[i], "#"); - - pset[prefix + files[i].substr(0, files[i].size()-3)] = help; + string help = getScriptHelp(path + files[i], "#"); + string key = prefix + files[i].substr(0, files[i].size()-3); + if (pset.find(key) == pset.end()) { + pset[key] = help; + } } else if (all && !files[i].empty() && files[i][0] != '.') { @@ -300,6 +305,14 @@ static void listScripts(PluginManager *plug_mgr, std::map &pset, } } +static void listAllScripts(map &pset, bool all) +{ + vector paths; + Core::getInstance().getScriptPaths(&paths); + for (string path : paths) + listScripts(Core::getInstance().getPluginManager(), pset, path, all); +} + namespace { struct ScriptArgs { const string *pcmd; @@ -418,7 +431,7 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: bool all = (first.find('/') != std::string::npos); std::map scripts; - listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all); + listAllScripts(scripts, all); for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) if (iter->first.substr(0, first.size()) == first) possible.push_back(iter->first); @@ -907,7 +920,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v con.reset_color(); } std::map scripts; - listScripts(plug_mgr, scripts, getHackPath() + "scripts/", all); + listAllScripts(scripts, all); if (!scripts.empty()) { con.print("\nscripts:\n"); From f94cc47be0ec9257ac9bd245590e9a7f6dc21b3f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 5 Feb 2017 22:12:10 -0500 Subject: [PATCH 0405/1012] Update scripts (devel/find-offsets, season-palette) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 20eb2146f..76ee1ec38 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 20eb2146fb93912b07034f3192dde1cf95a9ec7f +Subproject commit 76ee1ec38bf56fce0e48404520ce5e0ebe22702b From b18bd72c05db76d3a33742fec10488986a246cd2 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Mon, 6 Feb 2017 10:12:20 +0530 Subject: [PATCH 0406/1012] Replace tab with spaces. --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6b1f80634..f7586f03d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -122,7 +122,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib) - DFHACK_PLUGIN(generated-creature-renamer generated-creature-renamer.cpp) + DFHACK_PLUGIN(generated-creature-renamer generated-creature-renamer.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(hotkeys hotkeys.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) From 6e75840d911f2f481ad498d13730deffa6a56418 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Mon, 6 Feb 2017 10:18:14 +0530 Subject: [PATCH 0407/1012] Don't start creature-renamer automatically. --- dfhack.init-example | 3 --- 1 file changed, 3 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index a0dd220e1..169d40046 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -233,9 +233,6 @@ enable \ # enable mouse controls and sand indicator in embark screen embark-tools enable sticky sand mouse -# Renames generated creatures to reflect the template used to generate them. -enable generated-creature-renamer - ########### # Scripts # ########### From 1aef1d1b98f0b2e78a1bc519d0f952ea7472c51b Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Mon, 6 Feb 2017 10:42:10 +0530 Subject: [PATCH 0408/1012] use STD::Vector instead of a C array, and set version properly. --- plugins/generated-creature-renamer.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 1a738cce4..8f82f0e63 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -16,6 +16,8 @@ using namespace DFHack; DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); +#define RENAMER_VERSION 1 + command_result list_creatures(color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) @@ -43,8 +45,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) return CR_OK; } -#define NUM_DESC 227 -std::string descriptors[NUM_DESC] = { " blob", " quadruped", " humanoid", " silverfish", " mayfly", " dragonfly", +std::vector descriptors = { " blob", " quadruped", " humanoid", " silverfish", " mayfly", " dragonfly", " damselfly", " stonefly", " earwig", " grasshopper", " cricket", " stick insect", " cockroach", " termite", " mantis", " louse", " thrips", " aphid", " cicada", " assassin bug", " wasp", " hornet", " tiger beetle", " ladybug", " weevil", " darkling beetle", " click beetle", " firefly", " scarab beetle", " stag beetle", @@ -81,9 +82,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case DFHack::SC_WORLD_LOADED: CoreSuspender suspend; - int descriptorCount[NUM_DESC] = { 0 }; + std::vector descriptorCount = std::vector(descriptors.size()); - if (World::GetPersistentData("AlreadyRenamedCreatures").isValid()) + auto version = World::GetPersistentData("AlreadyRenamedCreatures"); + if (version.isValid() && version.ival(1) >= RENAMER_VERSION) { return CR_OK; } @@ -96,7 +98,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan size_t minPos = std::string::npos; size_t foundIndex = std::string::npos; - for (size_t j = 0; j < NUM_DESC; j++) + for (size_t j = 0; j < descriptors.size(); j++) { size_t pos = creatureRaw->caste[0]->description.find(descriptors[j]); if (pos < minPos) @@ -122,7 +124,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan descriptorCount[foundIndex]++; } } - World::AddPersistentData("AlreadyRenamedCreatures"); + version = World::AddPersistentData("AlreadyRenamedCreatures"); + version.ival(1) = RENAMER_VERSION; break; } From 5b83c6fe685dc82dc35c228fea9014942949ff5d Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Mon, 6 Feb 2017 10:49:22 +0530 Subject: [PATCH 0409/1012] Add spaces to search string programmatically. --- plugins/generated-creature-renamer.cpp | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 8f82f0e63..caace087a 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -45,32 +45,32 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) return CR_OK; } -std::vector descriptors = { " blob", " quadruped", " humanoid", " silverfish", " mayfly", " dragonfly", -" damselfly", " stonefly", " earwig", " grasshopper", " cricket", " stick insect", " cockroach", " termite", -" mantis", " louse", " thrips", " aphid", " cicada", " assassin bug", " wasp", " hornet", " tiger beetle", -" ladybug", " weevil", " darkling beetle", " click beetle", " firefly", " scarab beetle", " stag beetle", -" dung beetle", " rhinoceros beetle", " rove beetle", " snakefly", " lacewing", " antlion larva", -" mosquito", " flea", " scorpionfly", " caddisfly", " butterfly", " moth", " caterpillar", " maggot", -" spider", " tarantula", " scorpion", " tick", " mite", " shrimp", " lobster", " crab", " nematode", -" snail", " slug", " earthworm", " leech", " bristleworm", " ribbon worm", " flat worm", " toad", " frog", -" salamander", " newt", " alligator", " crocodile", " lizard", " chameleon", " iguana", " gecko", " skink", -" gila monster", " monitor", " serpent", " viper", " rattlesnake", " cobra", " python", " anaconda", " turtle", -" tortoise", " pterosaur", " dimetrodon", " sauropod", " theropod", " iguanodont", " hadrosaurid", " stegosaurid", -" ceratopsid", " ankylosaurid", " duck", " goose", " swan", " turkey", " grouse", " chicken", " quail", " pheasant", -" gull", " loon", " grebe", " albatross", " petrel", " penguin", " pelican", " stork", " vulture", " flamingo", -" falcon", " kestrel", " condor", " osprey", " buzzard", " eagle", " harrier", " kite", " crane", " dove", -" pigeon", " parrot", " cockatoo", " cuckoo", " nightjar", " swift", " hummingbird", " kingfisher", -" hornbill", " quetzal", " toucan", " woodpecker", " lyrebird", " thornbill", " honeyeater", " oriole", -" fantail", " shrike", " crow", " raven", " magpie", " kinglet", " lark", " swallow", " martin", " bushtit", -" warbler", " thrush", " oxpecker", " starling", " mockingbird", " wren", " nuthatch", " sparrow", " tanager", -" cardinal", " bunting", " finch", " titmouse", " chickadee", " waxwing", " flycatcher", " opossum", " koala", -" wombat", " kangaroo", " sloth", " anteater", " armadillo", " squirrel", " marmot", " beaver", " gopher", -" mouse", " porcupine", " chinchilla", " cavy", " capybara", " rabbit", " hare", " lemur", " loris", " monkey", -" hedgehog", " shrew", " mole", " fruit bat", " wolf", " coyote", " jackal", " raccoon", " coati", -" weasel", " otter", " badger", " skunk", " bear", " panda", " panther", " mongoose", " hyena", " civet", -" walrus", " pangolin", " elephant", " mammoth", " horse", " zebra", " tapir", " rhinoceros", " warthog", -" hippopotamus", " camel", " llama", " giraffe", " deer", " moose", " antelope", " sheep", " goat", -" bison", " buffalo", " bull", " ape", " ant", " bat", " owl", " pig", " bee" }; +std::vector descriptors = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", +"damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", +"mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", +"ladybug", "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", +"dung beetle", "rhinoceros beetle", "rove beetle", "snakefly", "lacewing", "antlion larva", +"mosquito", "flea", "scorpionfly", "caddisfly", "butterfly", "moth", "caterpillar", "maggot", +"spider", "tarantula", "scorpion", "tick", "mite", "shrimp", "lobster", "crab", "nematode", +"snail", "slug", "earthworm", "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", +"salamander", "newt", "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", +"gila monster", "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", +"tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", +"ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", "quail", "pheasant", +"gull", "loon", "grebe", "albatross", "petrel", "penguin", "pelican", "stork", "vulture", "flamingo", +"falcon", "kestrel", "condor", "osprey", "buzzard", "eagle", "harrier", "kite", "crane", "dove", +"pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", +"hornbill", "quetzal", "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", +"fantail", "shrike", "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", +"warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", "tanager", +"cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", "opossum", "koala", +"wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", "marmot", "beaver", "gopher", +"mouse", "porcupine", "chinchilla", "cavy", "capybara", "rabbit", "hare", "lemur", "loris", "monkey", +"hedgehog", "shrew", "mole", "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", +"weasel", "otter", "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", +"walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", "warthog", +"hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", +"bison", "buffalo", "bull", "ape", "ant", "bat", "owl", "pig", "bee"}; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) @@ -100,7 +100,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan for (size_t j = 0; j < descriptors.size(); j++) { - size_t pos = creatureRaw->caste[0]->description.find(descriptors[j]); + size_t pos = creatureRaw->caste[0]->description.find(" " + descriptors[j]); if (pos < minPos) { minPos = pos; From 64e217132e41957c51347280bece3697e02a81d9 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Mon, 6 Feb 2017 10:50:18 +0530 Subject: [PATCH 0410/1012] Don't offset the raplacement by 1 since there's no space now. --- plugins/generated-creature-renamer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index caace087a..7e0f2c309 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -114,7 +114,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan if (digitPos > creatureRaw->creature_id.length()) digitPos = creatureRaw->creature_id.length(); - creatureRaw->creature_id.replace(digitPos, std::string::npos, descriptors[foundIndex], 1, std::string::npos); + creatureRaw->creature_id.replace(digitPos, std::string::npos, descriptors[foundIndex]); if (descriptorCount[foundIndex] > 0) { From 68faca09ee03726e8408253dcc08a02d6a9a3baf Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 7 Feb 2017 10:27:57 +0530 Subject: [PATCH 0411/1012] Added missing base types given by toady, and reorganized the list to look better. --- plugins/generated-creature-renamer.cpp | 58 ++++++++++++++------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 7e0f2c309..1d0b98f58 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -45,32 +45,38 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) return CR_OK; } -std::vector descriptors = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", -"damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", -"mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", -"ladybug", "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", -"dung beetle", "rhinoceros beetle", "rove beetle", "snakefly", "lacewing", "antlion larva", -"mosquito", "flea", "scorpionfly", "caddisfly", "butterfly", "moth", "caterpillar", "maggot", -"spider", "tarantula", "scorpion", "tick", "mite", "shrimp", "lobster", "crab", "nematode", -"snail", "slug", "earthworm", "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", -"salamander", "newt", "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", -"gila monster", "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", -"tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", -"ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", "quail", "pheasant", -"gull", "loon", "grebe", "albatross", "petrel", "penguin", "pelican", "stork", "vulture", "flamingo", -"falcon", "kestrel", "condor", "osprey", "buzzard", "eagle", "harrier", "kite", "crane", "dove", -"pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", -"hornbill", "quetzal", "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", -"fantail", "shrike", "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", -"warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", "tanager", -"cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", "opossum", "koala", -"wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", "marmot", "beaver", "gopher", -"mouse", "porcupine", "chinchilla", "cavy", "capybara", "rabbit", "hare", "lemur", "loris", "monkey", -"hedgehog", "shrew", "mole", "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", -"weasel", "otter", "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", -"walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", "warthog", -"hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", -"bison", "buffalo", "bull", "ape", "ant", "bat", "owl", "pig", "bee"}; +std::vector descriptors = { + "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", "damselfly", "stonefly", + "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", "mantis", "louse", + "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", "ladybug", + "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", "dung beetle", "rhinoceros beetle", + "rove beetle", "snakefly", "lacewing", "antlion larva", "mosquito", "flea", "scorpionfly", "caddisfly", + "butterfly", "moth", "caterpillar", "maggot", "spider", "tarantula", "scorpion", "tick", + "mite", "shrimp", "lobster", "crab", "nematode", "snail", "slug", "earthworm", + "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", "salamander", "newt", + "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", "gila monster", + "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", + "tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", + "ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", + "quail", "pheasant", "gull", "loon", "grebe", "albatross", "petrel", "penguin", + "pelican", "stork", "vulture", "flamingo", "falcon", "kestrel", "condor", "osprey", + "buzzard", "eagle", "harrier", "kite", "crane", "dove", "pigeon", "parrot", + "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", "hornbill", "quetzal", + "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", "fantail", "shrike", + "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", + "warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", + "tanager", "cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", + "opossum", "koala", "wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", + "marmot", "beaver", "gopher", "mouse", "porcupine", "chinchilla", "cavy", "capybara", + "rabbit", "hare", "lemur", "loris", "monkey", "hedgehog", "shrew", "mole", + "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", "weasel", "otter", + "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", + "walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", + "warthog", "hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", + "sheep", "goat", "bison", "buffalo", "bull", "ape", "ant", "bat", + "owl", "pig", "bee", "fly", "hawk", "jay", "rat", "fox", + "cat", "ass", "elk" +}; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) From f9b296884c728ea416a799505e4d30d1e7ee2ce1 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 7 Feb 2017 11:01:42 +0530 Subject: [PATCH 0412/1012] Made the creature renamer work more than once on the same save, and changed the format of the resulting names. --- plugins/generated-creature-renamer.cpp | 51 +++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 1d0b98f58..9463773b2 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -16,7 +16,7 @@ using namespace DFHack; DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); -#define RENAMER_VERSION 1 +#define RENAMER_VERSION 2 command_result list_creatures(color_ostream &out, std::vector & parameters); @@ -78,6 +78,14 @@ std::vector descriptors = { "cat", "ass", "elk" }; +std::vector prefixes = { + "FORGOTTEN_BEAST_", + "TITAN_", + "DEMON_", + "NIGHT_CREATURE_", + "HF" +}; + DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { @@ -102,7 +110,19 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) continue; size_t minPos = std::string::npos; - size_t foundIndex = std::string::npos; + size_t foundIndex = -1; + size_t prefixIndex = -1; + + for (rsize_t j = 0; j < prefixes.size(); j++) + { + if (creatureRaw->creature_id.compare(0, prefixes[j].length(), prefixes[j]) == 0) + { + prefixIndex = j; + } + } + + if (prefixIndex < 0) + continue; //unrecognized generaed type. for (size_t j = 0; j < descriptors.size(); j++) { @@ -114,21 +134,26 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } } - if (foundIndex < std::string::npos) - { - size_t digitPos = creatureRaw->creature_id.find_first_of("0123456789"); - if (digitPos > creatureRaw->creature_id.length()) - digitPos = creatureRaw->creature_id.length(); + if (foundIndex < 0) + continue; //can't find a match. - creatureRaw->creature_id.replace(digitPos, std::string::npos, descriptors[foundIndex]); + auto descriptor = descriptors[foundIndex]; - if (descriptorCount[foundIndex] > 0) - { - creatureRaw->creature_id.append(std::to_string(descriptorCount[foundIndex])); - } + for (int j = 0; j < descriptor.size(); j++) + { + if (descriptor[j] == ' ') + descriptor[j] = '_'; + else + descriptor[j] = toupper(descriptor[j]); + } + + creatureRaw->creature_id = prefixes[prefixIndex] + descriptor; - descriptorCount[foundIndex]++; + if (descriptorCount[foundIndex] > 0) + { + creatureRaw->creature_id.append("_" + std::to_string(descriptorCount[foundIndex])); } + descriptorCount[foundIndex]++; } version = World::AddPersistentData("AlreadyRenamedCreatures"); version.ival(1) = RENAMER_VERSION; From b151ad7c750f4bdc4e0b69240a98d854843615c6 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 7 Feb 2017 11:09:39 +0530 Subject: [PATCH 0413/1012] always make sure the name prefix has an underscore at the end of it. --- plugins/generated-creature-renamer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 9463773b2..df7b5c0d3 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -16,7 +16,7 @@ using namespace DFHack; DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); -#define RENAMER_VERSION 2 +#define RENAMER_VERSION 3 command_result list_creatures(color_ostream &out, std::vector & parameters); @@ -147,6 +147,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan descriptor[j] = toupper(descriptor[j]); } + auto prefix = prefixes[prefixIndex]; + if (prefix[prefix.length() - 1] != '_') + prefix.append("_"); + creatureRaw->creature_id = prefixes[prefixIndex] + descriptor; if (descriptorCount[foundIndex] > 0) From 10bbd3cb39ff1cf97f14329f60de9bed0a735ff7 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 7 Feb 2017 11:19:45 +0530 Subject: [PATCH 0414/1012] Added a function to spit out a generated graphics pack file. Not done yet. --- plugins/generated-creature-renamer.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index df7b5c0d3..e6cf8723f 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -19,6 +19,7 @@ REQUIRE_GLOBAL(world); #define RENAMER_VERSION 3 command_result list_creatures(color_ostream &out, std::vector & parameters); +command_result save_generated_raw(color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -29,7 +30,13 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector & pa } } +command_result save_generated_raw(color_ostream &out, std::vector & parameters) +{ + + return CR_OK; +} From 873feaee2b04e1d27a48253b5ee616ced0521a82 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 7 Feb 2017 15:57:35 +0530 Subject: [PATCH 0415/1012] Added a function to the creature renamer to save a graphics pack file to set graphics for all the generated creatures. --- plugins/generated-creature-renamer.cpp | 177 +++++++++++++++++-------- 1 file changed, 121 insertions(+), 56 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index e6cf8723f..5eb030d44 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -8,6 +8,7 @@ #include "df/creature_raw.h" #include "df/caste_raw.h" #include "modules/World.h" +#include "MemAccess.h" //#include "df/world.h" @@ -25,17 +26,17 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector descriptorCount = std::vector(descriptors.size()); + + auto version = World::GetPersistentData("AlreadyRenamedCreatures"); + if (version.isValid() && version.ival(1) >= RENAMER_VERSION) { - case DFHack::SC_WORLD_LOADED: - CoreSuspender suspend; + return CR_OK; + } - std::vector descriptorCount = std::vector(descriptors.size()); + int creatureCount = 0; - auto version = World::GetPersistentData("AlreadyRenamedCreatures"); - if (version.isValid() && version.ival(1) >= RENAMER_VERSION) - { - return CR_OK; - } + for (int i = 0; i < world->raws.creatures.all.size(); i++) + { + auto creatureRaw = world->raws.creatures.all[i]; + if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) + continue; + size_t minPos = std::string::npos; + size_t foundIndex = -1; + size_t prefixIndex = -1; - for (int i = 0; i < world->raws.creatures.all.size(); i++) + for (rsize_t j = 0; j < prefixes.size(); j++) { - auto creatureRaw = world->raws.creatures.all[i]; - if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) - continue; - size_t minPos = std::string::npos; - size_t foundIndex = -1; - size_t prefixIndex = -1; - - for (rsize_t j = 0; j < prefixes.size(); j++) + if (creatureRaw->creature_id.compare(0, prefixes[j].length(), prefixes[j]) == 0) { - if (creatureRaw->creature_id.compare(0, prefixes[j].length(), prefixes[j]) == 0) - { - prefixIndex = j; - } + prefixIndex = j; } + } - if (prefixIndex < 0) - continue; //unrecognized generaed type. + if (prefixIndex < 0) + continue; //unrecognized generaed type. - for (size_t j = 0; j < descriptors.size(); j++) + for (size_t j = 0; j < descriptors.size(); j++) + { + size_t pos = creatureRaw->caste[0]->description.find(" " + descriptors[j]); + if (pos < minPos) { - size_t pos = creatureRaw->caste[0]->description.find(" " + descriptors[j]); - if (pos < minPos) - { - minPos = pos; - foundIndex = j; - } + minPos = pos; + foundIndex = j; } + } - if (foundIndex < 0) - continue; //can't find a match. + if (foundIndex < 0) + continue; //can't find a match. - auto descriptor = descriptors[foundIndex]; + auto descriptor = descriptors[foundIndex]; - for (int j = 0; j < descriptor.size(); j++) - { - if (descriptor[j] == ' ') - descriptor[j] = '_'; - else - descriptor[j] = toupper(descriptor[j]); - } + for (int j = 0; j < descriptor.size(); j++) + { + if (descriptor[j] == ' ') + descriptor[j] = '_'; + else + descriptor[j] = toupper(descriptor[j]); + } - auto prefix = prefixes[prefixIndex]; - if (prefix[prefix.length() - 1] != '_') - prefix.append("_"); + auto prefix = prefixes[prefixIndex]; + if (prefix[prefix.length() - 1] != '_') + prefix.append("_"); - creatureRaw->creature_id = prefixes[prefixIndex] + descriptor; + creatureRaw->creature_id = prefixes[prefixIndex] + descriptor; - if (descriptorCount[foundIndex] > 0) - { - creatureRaw->creature_id.append("_" + std::to_string(descriptorCount[foundIndex])); - } - descriptorCount[foundIndex]++; + if (descriptorCount[foundIndex] > 0) + { + creatureRaw->creature_id.append("_" + std::to_string(descriptorCount[foundIndex])); } - version = World::AddPersistentData("AlreadyRenamedCreatures"); - version.ival(1) = RENAMER_VERSION; - break; + descriptorCount[foundIndex]++; + creatureCount++; } + version = World::AddPersistentData("AlreadyRenamedCreatures"); + version.ival(1) = RENAMER_VERSION; + + out << "Renamed " << creatureCount << " generated creatures to have sensible names." << endl; + + return CR_OK; } @@ -200,10 +207,68 @@ command_result list_creatures(color_ostream &out, std::vector & pa } out.print("\n"); } + return CR_OK; } command_result save_generated_raw(color_ostream &out, std::vector & parameters) { +#ifdef LINUX_BUILD + std::string pathSep = "/"; +#else + std::string pathSep = "\\"; +#endif + int pageWidth = 16; + int pageHeight = (descriptors.size() / pageWidth) + ((descriptors.size() % pageWidth > 0) ? 1 : 0); + int tileWidth = 24; + int tileHeight = 24; + std::string fileName = "graphics_procedural_creatures"; + std::string pageName = "PROCEDURAL_FRIENDLY"; + int repeats = 128; + + std::ofstream outputFile(fileName + ".txt", std::ios::out | std::ios::trunc); + + outputFile << fileName << endl << endl; + + outputFile << "[OBJECT:GRAPHICS]" << endl << endl; + + outputFile << "[TILE_PAGE:" << pageName << "]" << endl; + outputFile << " [FILE:procedural_friendly.png]" << endl; + outputFile << " [TILE_DIM:" << tileWidth << ":" << tileHeight << "]" << endl; + outputFile << " [PAGE_DIM:" << pageWidth << ":" << pageHeight << "]" << endl << endl; + + for (size_t descIndex = 0; descIndex < descriptors.size(); descIndex++) + { + for (size_t prefIndex = 0; prefIndex < prefixes.size(); prefIndex++) + { + for (size_t rep = 0; rep < repeats; rep++) + { + auto descriptor = descriptors[descIndex]; + + for (int j = 0; j < descriptor.size(); j++) + { + if (descriptor[j] == ' ') + descriptor[j] = '_'; + else + descriptor[j] = toupper(descriptor[j]); + } + + auto prefix = prefixes[prefIndex]; + if (prefix[prefix.length() - 1] != '_') + prefix.append("_"); + + std::string token = prefix + descriptor; + if (rep > 0) + token.append("_" + std::to_string(rep)); + + outputFile << "[CREATURE_GRAPHICS:" << token << "]" << endl; + outputFile << " [DEFAULT:" << pageName << ":" << descIndex % pageWidth << ":" << descIndex / pageWidth << ":ADD_COLOR]" << endl; + } + outputFile << endl; + } + outputFile << endl; + } + + outputFile.close(); return CR_OK; } From 2c19f6b237f555aaf5de0abf6a08043aa8bf1024 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Wed, 8 Feb 2017 10:34:11 +0530 Subject: [PATCH 0416/1012] remove rsize_t --- plugins/generated-creature-renamer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 5eb030d44..13f0f9a6f 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -124,7 +124,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan size_t foundIndex = -1; size_t prefixIndex = -1; - for (rsize_t j = 0; j < prefixes.size(); j++) + for (size_t j = 0; j < prefixes.size(); j++) { if (creatureRaw->creature_id.compare(0, prefixes[j].length(), prefixes[j]) == 0) { From 0d8decd7eccbab575334646bcd9f67a2e795b0f4 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Wed, 8 Feb 2017 10:54:42 +0530 Subject: [PATCH 0417/1012] Remove trailing whitespaces. --- plugins/generated-creature-renamer.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 13f0f9a6f..49444eaf4 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -57,32 +57,32 @@ std::vector descriptors = { "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", "damselfly", "stonefly", "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", "mantis", "louse", "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", "ladybug", - "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", "dung beetle", "rhinoceros beetle", - "rove beetle", "snakefly", "lacewing", "antlion larva", "mosquito", "flea", "scorpionfly", "caddisfly", + "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", "dung beetle", "rhinoceros beetle", + "rove beetle", "snakefly", "lacewing", "antlion larva", "mosquito", "flea", "scorpionfly", "caddisfly", "butterfly", "moth", "caterpillar", "maggot", "spider", "tarantula", "scorpion", "tick", "mite", "shrimp", "lobster", "crab", "nematode", "snail", "slug", "earthworm", "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", "salamander", "newt", - "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", "gila monster", + "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", "gila monster", "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", "tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", - "ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", + "ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", "quail", "pheasant", "gull", "loon", "grebe", "albatross", "petrel", "penguin", "pelican", "stork", "vulture", "flamingo", "falcon", "kestrel", "condor", "osprey", "buzzard", "eagle", "harrier", "kite", "crane", "dove", "pigeon", "parrot", "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", "hornbill", "quetzal", - "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", "fantail", "shrike", + "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", "fantail", "shrike", "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", "warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", "tanager", "cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", "opossum", "koala", "wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", - "marmot", "beaver", "gopher", "mouse", "porcupine", "chinchilla", "cavy", "capybara", - "rabbit", "hare", "lemur", "loris", "monkey", "hedgehog", "shrew", "mole", + "marmot", "beaver", "gopher", "mouse", "porcupine", "chinchilla", "cavy", "capybara", + "rabbit", "hare", "lemur", "loris", "monkey", "hedgehog", "shrew", "mole", "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", "weasel", "otter", "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", "walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", - "warthog", "hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", + "warthog", "hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", "sheep", "goat", "bison", "buffalo", "bull", "ape", "ant", "bat", - "owl", "pig", "bee", "fly", "hawk", "jay", "rat", "fox", + "owl", "pig", "bee", "fly", "hawk", "jay", "rat", "fox", "cat", "ass", "elk" }; From c68faaae378ba5fec6dbe7127b1ec79a38584536 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Fri, 10 Feb 2017 15:30:48 +0530 Subject: [PATCH 0418/1012] Add basic documentation for the generated-creature-renamer plugin. --- docs/Plugins.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index b3e68459d..b34b41da9 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2453,6 +2453,24 @@ armor onto a war animal or to add unusual items (such as crowns) to any unit. For more information run ``forceequip help``. See also `modtools/equip-item`. +.. _generated-creature-renamer: + +generated-creature-renamer +========================== +Automatically renames generated creatures, such as forgotten beasts, titans, +etc, to have raw token names that match the description given in-game. + +The `list-generated` command can be used to list the token names of all +generated creatures in a given save, with an optional `detailed` argument +to show the accompanying description. + +The `save-generated-raws` command will save a sample creature graphics file in +the Dwarf Fortress root directory, to use as a start for making a graphics set +for generated creatures using the new names that they get with this plugin. + +The new names are saved with the save, and the plugin, when enabled, only runs once +per save, unless there's an update. + .. _lair: lair From f89678c47259ac3b06c15af8f5165f7d86f12816 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Feb 2017 15:07:48 -0500 Subject: [PATCH 0419/1012] Fix backticks --- docs/Plugins.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index b34b41da9..9e7498cee 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2460,11 +2460,11 @@ generated-creature-renamer Automatically renames generated creatures, such as forgotten beasts, titans, etc, to have raw token names that match the description given in-game. -The `list-generated` command can be used to list the token names of all -generated creatures in a given save, with an optional `detailed` argument +The ``list-generated`` command can be used to list the token names of all +generated creatures in a given save, with an optional ``detailed`` argument to show the accompanying description. -The `save-generated-raws` command will save a sample creature graphics file in +The ``save-generated-raws`` command will save a sample creature graphics file in the Dwarf Fortress root directory, to use as a start for making a graphics set for generated creatures using the new names that they get with this plugin. From b110355b53778d2ffa5a509bcb84ab433ea40502 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Feb 2017 15:09:46 -0500 Subject: [PATCH 0420/1012] Add missing space --- docs/Plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 9e7498cee..e2b829020 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2540,7 +2540,7 @@ Options: :-unit: Make the strange mood strike the selected unit instead of picking one randomly. Unit eligibility is still enforced. :-type : Force the mood to be of a particular type instead of choosing randomly based on happiness. - Valid values for Tare "fey", "secretive", "possessed", "fell", and "macabre". + Valid values for T are "fey", "secretive", "possessed", "fell", and "macabre". :-skill S: Force the mood to use a specific skill instead of choosing the highest moodable skill. Valid values are "miner", "carpenter", "engraver", "mason", "tanner", "weaver", "clothier", "weaponsmith", "armorsmith", "metalsmith", "gemcutter", "gemsetter", From a10d4ae226db6f1f7c562f06b2cfd1804db03b81 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Feb 2017 16:53:50 -0500 Subject: [PATCH 0421/1012] Add viewscreen::feed_key method --- library/include/df/custom/viewscreen.methods.inc | 5 +++++ library/xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/library/include/df/custom/viewscreen.methods.inc b/library/include/df/custom/viewscreen.methods.inc index c5d277716..b1f07242e 100644 --- a/library/include/df/custom/viewscreen.methods.inc +++ b/library/include/df/custom/viewscreen.methods.inc @@ -1 +1,6 @@ friend struct df::interfacest; +void feed_key(df::interface_key key) { + std::set input; + input.insert(key); + feed(&input); +} diff --git a/library/xml b/library/xml index fc2570c6c..e60c4259a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fc2570c6c0179fcdf48610f0e3eaef7f84908da7 +Subproject commit e60c4259ab9ea9326af42173024a3b3e40fe6688 From 8b27ed5f1c5ae0b6f991621caaa69545d1f134eb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Feb 2017 17:07:49 -0500 Subject: [PATCH 0422/1012] Update changelog --- NEWS.rst | 8 ++++++++ docs/NEWS-dev.rst | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 2fdbfc561..4229da17f 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -64,6 +64,7 @@ Ruby New Plugins ----------- - `dwarfvet` enables animal caretaking +- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs - `labormanager` (formerly autolabor2): a more advanced alternative to `autolabor` - `misery`: re-added and updated for the 0.4x series - `title-folder`: shows DF folder name in window title bar when enabled @@ -80,6 +81,7 @@ Fixes ----- - The DF path on OS X can now contain spaces and ``:`` characters - Buildings::setOwner() changes now persist properly when saved +- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable - `add-thought`: fixed support for emotion names - `autofarm`: Made surface farms detect local biome - `devel/find-offsets`: fixed a crash when vtables used by globals aren't available @@ -88,6 +90,10 @@ Fixes - Fixed crash when selecting a profession from an empty list - Custom professions are now sorted alphabetically more reliably +- `modtools/create-unit`: stopped permanently overwriting the creature creation + menu in arena mode +- `title-version`: now hidden when loading an arena + Misc Improvements ----------------- - Documented all default keybindings (from :file:`dfhack.init-example`) in the @@ -101,6 +107,8 @@ Misc Improvements - site towers, world buildings - surface material +- `title-version`: Added a prerelease indicator + DFHack 0.43.03-r1 ================= diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 9920e3433..2efc9bf2a 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,35 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.43.05-beta1 +==================== + +Fixes +----- +- Fixed various crashes on 64-bit Windows related to DFHack screens, notably `manipulator` +- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable +- `modtools/create-unit`: stopped permanently overwriting the creature creation + menu in arena mode +- `season-palette`: fixed an issue where only part of the screen was redrawn + after changing the color scheme +- `title-version`: now hidden when loading an arena + +Structures +---------- +- ``file_compressorst``: fixed field sizes on x64 +- ``ui_sidebar_menus.command_line``: fixed field sizes on x64 +- ``viewscreen_layer_arena_creaturest``: identified more fields +- ``world.math``: identified +- ``world.murky_pools``: identified + +Additions/Removals +------------------ +- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs + +Other Changes +------------- +- `title-version`: Added a prerelease indicator + DFHack 0.43.05-alpha4 ===================== From 2fdd4d96b623611c92a66cb2fe9b8b28e8ab8309 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Feb 2017 20:24:51 -0500 Subject: [PATCH 0423/1012] Add more XML changes to NEWS-dev --- docs/NEWS-dev.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 2efc9bf2a..df74735c0 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -43,6 +43,7 @@ DFHack 0.43.05-beta1 Fixes ----- - Fixed various crashes on 64-bit Windows related to DFHack screens, notably `manipulator` +- Fixed addresses of next_id globals on 64-bit Linux (fixes an `automaterial`/box-select crash) - ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable - `modtools/create-unit`: stopped permanently overwriting the creature creation menu in arena mode @@ -53,7 +54,9 @@ Fixes Structures ---------- - ``file_compressorst``: fixed field sizes on x64 +- ``historical_entity``: fixed alignment on x64 - ``ui_sidebar_menus.command_line``: fixed field sizes on x64 +- ``viewscreen_choose_start_sitest``: added 3 missing fields, renamed ``in_embark_only_warning`` - ``viewscreen_layer_arena_creaturest``: identified more fields - ``world.math``: identified - ``world.murky_pools``: identified From 33060c7d0e2432fe31c62f35234bd96250055388 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 Feb 2017 20:48:37 -0500 Subject: [PATCH 0424/1012] Bump to beta1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3602655b4..c7c0835a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "alpha4") +SET(DFHACK_RELEASE "beta1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 19695b4ee7e8b370c8ce53aa2dcd399e1e9ca94d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 17 Feb 2017 22:53:48 -0500 Subject: [PATCH 0425/1012] EventManager/eventful: Pass building ID pointers to event handlers Previously, there was some disagreement over whether event handlers such as Buildings::updateBuildings() took building pointers or building IDs shoved into pointers. It turned out to be the latter, which, unfortunately, did not compile on x64. Passing building IDs isn't possible in all cases, because building event handlers can be called for recently-deleted buildings too. Pointers to building IDs do work reliably, though. Fixes #1003 --- library/modules/Buildings.cpp | 92 ++++++++++++++++---------------- library/modules/EventManager.cpp | 6 +-- plugins/eventful.cpp | 6 +-- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 9d672cd58..55f0869a4 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1182,52 +1182,52 @@ void Buildings::clearBuildings(color_ostream& out) { void Buildings::updateBuildings(color_ostream& out, void* ptr) { - // int32_t id = (int32_t)ptr; - // auto building = df::building::find(id); - - // if (building) - // { - // // Already cached -> weird, so bail out - // if (corner1.count(id)) - // return; - // // Civzones cannot be cached because they can - // // overlap each other and normal buildings. - // if (!building->isSettingOccupancy()) - // return; - - // df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); - // df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); - - // corner1[id] = p1; - // corner2[id] = p2; - - // for ( int32_t x = p1.x; x <= p2.x; x++ ) { - // for ( int32_t y = p1.y; y <= p2.y; y++ ) { - // df::coord pt(x,y,building->z); - // if (containsTile(building, pt, false)) - // locationToBuilding[pt] = id; - // } - // } - // } - // else if (corner1.count(id)) - // { - // //existing building: destroy it - // df::coord p1 = corner1[id]; - // df::coord p2 = corner2[id]; - - // for ( int32_t x = p1.x; x <= p2.x; x++ ) { - // for ( int32_t y = p1.y; y <= p2.y; y++ ) { - // df::coord pt(x,y,p1.z); - - // auto cur = locationToBuilding.find(pt); - // if (cur != locationToBuilding.end() && cur->second == id) - // locationToBuilding.erase(cur); - // } - // } - - // corner1.erase(id); - // corner2.erase(id); - // } + int32_t id = *((int32_t*)ptr); + auto building = df::building::find(id); + + if (building) + { + // Already cached -> weird, so bail out + if (corner1.count(id)) + return; + // Civzones cannot be cached because they can + // overlap each other and normal buildings. + if (!building->isSettingOccupancy()) + return; + + df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); + df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); + + corner1[id] = p1; + corner2[id] = p2; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,building->z); + if (containsTile(building, pt, false)) + locationToBuilding[pt] = id; + } + } + } + else if (corner1.count(id)) + { + //existing building: destroy it + df::coord p1 = corner1[id]; + df::coord p2 = corner2[id]; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,p1.z); + + auto cur = locationToBuilding.find(pt); + if (cur != locationToBuilding.end() && cur->second == id) + locationToBuilding.erase(cur); + } + } + + corner1.erase(id); + corner2.erase(id); + } } void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector *items) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index dfb978022..e2baf9bfb 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -273,7 +273,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } for ( size_t a = 0; a < df::global::world->buildings.all.size(); a++ ) { df::building* b = df::global::world->buildings.all[a]; - Buildings::updateBuildings(out, (void*)b); + Buildings::updateBuildings(out, (void*)&(b->id)); buildings.insert(b->id); } lastSyndromeTime = -1; @@ -609,7 +609,7 @@ static void manageBuildingEvent(color_ostream& out) { buildings.insert(a); for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)intptr_t(a)); + bob.eventHandler(out, (void*)&a); } } nextBuilding = *df::global::building_next_id; @@ -625,7 +625,7 @@ static void manageBuildingEvent(color_ostream& out) { for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)intptr_t(id)); + bob.eventHandler(out, (void*)&id); } a = buildings.erase(a); } diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 14f1ec26e..365542314 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -183,8 +183,8 @@ void ev_mng_invasion(color_ostream& out, void* ptr) } static void ev_mng_building(color_ostream& out, void* ptr) { - int32_t myId=*(int32_t*)&ptr; - onBuildingCreatedDestroyed(out,myId); + int32_t id = *((int32_t*)ptr); + onBuildingCreatedDestroyed(out, id); } static void ev_mng_inventory(color_ostream& out, void* ptr) { @@ -289,7 +289,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(furnace_hook, fillSidebarMenu); struct product_hook : item_product { typedef item_product interpose_base; - + DEFINE_VMETHOD_INTERPOSE( void, produce, (df::unit *unit, From c21b7bf9419d98b7adcbf156fe226dd16eec01de Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 1 Mar 2017 15:56:50 -0500 Subject: [PATCH 0426/1012] Add a Painter:key_string() method --- library/lua/gui.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 603c7ab44..b83691acf 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -354,6 +354,10 @@ function Painter:key(code,pen,...) ) end +function Painter:key_string(code, text, ...) + return self:key(code):string(': '):string(text, ...) +end + -------------------------- -- Abstract view object -- -------------------------- From 13cdc38b2f462efadee204b51fe541ca8683fd68 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 1 Mar 2017 15:57:09 -0500 Subject: [PATCH 0427/1012] Update scripts/gui/extended-status (new bed queueing feature) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 76ee1ec38..a05e51095 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 76ee1ec38bf56fce0e48404520ce5e0ebe22702b +Subproject commit a05e51095d9d571213aa33caffb5912468a3e6dc From 43a58f8d85aa61d4994f0bc7352d7bf6e3356de0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 1 Mar 2017 20:29:46 -0500 Subject: [PATCH 0428/1012] Remove obsolete settings from Windows build scripts, plus misc. updates - Added BUILD_DOCS=1 to release scripts - Removed "breakfast" scripts, which used the same settings as "all", plus a release version override, which is rarely used (if at all) and can be done separately. --- build/win32/generate-MSVC-all-breakfast.bat | 9 --------- build/win32/generate-MSVC-all.bat | 2 +- build/win32/generate-MSVC-minimal.bat | 2 +- build/win32/generate-MSVC-release.bat | 2 +- build/win64/generate-MSVC-all-breakfast.bat | 9 --------- build/win64/generate-MSVC-all.bat | 2 +- build/win64/generate-MSVC-minimal.bat | 2 +- build/win64/generate-MSVC-release.bat | 2 +- 8 files changed, 6 insertions(+), 24 deletions(-) delete mode 100644 build/win32/generate-MSVC-all-breakfast.bat delete mode 100644 build/win64/generate-MSVC-all-breakfast.bat diff --git a/build/win32/generate-MSVC-all-breakfast.bat b/build/win32/generate-MSVC-all-breakfast.bat deleted file mode 100644 index ac26c4f75..000000000 --- a/build/win32/generate-MSVC-all-breakfast.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -IF EXIST DF_PATH.txt SET /P _DF_PATH= Date: Wed, 1 Mar 2017 20:35:27 -0500 Subject: [PATCH 0429/1012] Update Windows build instructions for 0.43.05 --- docs/Compile.rst | 60 ++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index ac324a9f7..ebf6936c5 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -100,6 +100,9 @@ change, so specifying it explicitly is a good idea. cmake .. -DDFHACK_BUILD_ARCH=64 +Note that the scripts in the "build" folder on Windows will set the architecture +automatically. + Other settings -------------- There are a variety of other settings which you can find in CMakeCache.txt in @@ -355,47 +358,34 @@ Dependencies ------------ You will need the following: -* Microsoft Visual Studio 2010 SP1, with the C++ language +* Microsoft Visual Studio 2015, with the C++ language * Git * CMake * Perl with XML::LibXML and XML::LibXSLT * It is recommended to install StrawberryPerl, which includes both. -Microsoft Visual Studio 2010 SP1 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -DFHack has to be compiled with the Microsoft Visual C++ 2010 SP1 toolchain; later -versions won't work against Dwarf Fortress due to ABI and STL incompatibilities. - -At present, the only way to obtain the MSVC C++ 2010 toolchain is to install a -full copy of Microsoft Visual Studio 2010 SP1. The free Express version is sufficient. - -You can grab it from `Microsoft's site `_. - -You should also install the Visual Studio 2010 SP1 update. +* Python (for documentation; optional, except for release builds) -You can confirm whether you have SP1 by opening the Visual Studio 2010 IDE -and selecting About from the Help menu. If you have SP1 it will have *SP1Rel* -at the end of the version number, for example: *Version 10.0.40219.1 SP1Rel* - -Use of pre-SP1 releases has been reported to cause issues and is therefore not -supported by DFHack. Please ensure you are using SP1 before raising any Issues. - -If your Windows Update is configured to receive updates for all Microsoft -Products, not just Windows, you will receive the SP1 update automatically -through Windows Update (you will probably need to trigger a manual check.) +Microsoft Visual Studio 2015 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +DFHack has to be compiled with the Microsoft Visual C++ 2015 toolchain; other +versions won't work against Dwarf Fortress due to ABI and STL incompatibilities. -If not, you can download it directly `from this Microsoft Download link `_. +At present, the only way to obtain the MSVC C++ 2015 toolchain is to install a +full copy of Microsoft Visual Studio 2015. The free Community version is +sufficient. Additional dependencies: installing with the Chocolatey Package Manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The remainder of dependencies - Git, CMake and StrawberryPerl - can be most -easily installed using the Chocolatey Package Manger. Chocolatey is a + +The remainder of dependencies - Git, CMake, StrawberryPerl, and Python - can be +most easily installed using the Chocolatey Package Manger. Chocolatey is a \*nix-style package manager for Windows. It's fast, small (8-20MB on disk) and very capable. Think "``apt-get`` for Windows." -Chocolatey is a preferred way of installing the required dependencies -as it's quicker, less effort and will install known-good utilities +Chocolatey is a recommended way of installing the required dependencies +as it's quicker, requires less effort, and will install known-good utilities guaranteed to have the correct setup (especially PATH). To install Chocolatey and the required dependencies: @@ -482,8 +472,10 @@ install XML::LibXML and XML::LibXSLT for it using CPAN. Build ----- -There are several different batch files in the ``build`` folder along -with a script that's used for picking the DF path. +There are several different batch files in the ``win32`` and ``win64`` +subfolders in the ``build`` folder, along with a script that's used for picking +the DF path. Use the subfolder corresponding to the architecture that you want +to build for. First, run ``set_df_path.vbs`` and point the dialog that pops up at a suitable DF installation which is of the appropriate version for the DFHack @@ -501,6 +493,9 @@ solution file(s): in, then hit configure, then generate. More options can appear after the configure step. * ``minimal`` will create a minimal solution with just the bare necessities - the main library and standard plugins. +* ``release`` will create a solution with everything that should be included in + release builds of DFHack. Note that this includes documentation, which requires + Python. Then you can either open the solution with MSVC or use one of the msbuild scripts: @@ -548,9 +543,10 @@ files as detailed above. Building/installing from the Visual Studio IDE: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -After running the CMake generate script you will have a new folder called VC2010. -Open the file ``dfhack.sln`` inside that folder. If you have multiple versions of -Visual Studio installed, make sure you open with Visual Studio 2010. +After running the CMake generate script you will have a new folder called VC2015 +or VC2015_32, depending on the architecture you specified. Open the file +``dfhack.sln`` inside that folder. If you have multiple versions of Visual +Studio installed, make sure you open with Visual Studio 2015. The first thing you must then do is change the build type. It defaults to Debug, but this cannot be used on Windows. Debug is not binary-compatible with DF. From b675f0fd3c62e26b90a44619da696001f2c1ad52 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 1 Mar 2017 20:44:47 -0500 Subject: [PATCH 0430/1012] Move old git note to bottom of compilation docs --- docs/Compile.rst | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index ebf6936c5..647dbb30b 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -31,6 +31,8 @@ To get the latest development code (develop branch), clone as above and then:: git checkout develop git submodule update +Generally, you should only need to clone DFHack once. + **Important note regarding submodule update and changing branches**: You must run ``git submodule update`` every time you change branches, @@ -39,17 +41,9 @@ If a submodule only exists on the newer branch, you also need to run ``git submodule update --init``. Failure to do this may result in strange build errors or "not a known DF version" errors. -**Important note regarding very old git versions** +**More notes**: -If you are using git 1.8.0 or older, and cloned DFHack before commit 85a920d -(around DFHack v0.43.03-alpha1), you may run into fatal git errors when updating -submodules after switching branches. This is due to those versions of git being -unable to handle our change from "scripts/3rdparty/name" submodules to a single -"scripts" submodule. This may be fixable by renaming .git/modules/scripts to -something else and re-running ``git submodule update --init`` on the branch with -the single scripts submodule (and running it again when switching back to the -one with multiple submodules, if necessary), but it is usually much simpler to -upgrade your git version. +* `note-old-git-and-dfhack` Contributing to DFHack ====================== @@ -670,3 +664,21 @@ Chocolatey as outlined in the `Windows section `:: Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run:: pip install sphinx + +Misc. Notes +=========== + +.. _note-old-git-and-dfhack: + +Note on using very old git versions with pre-0.43.03 DFHack versions +-------------------------------------------------------------------- + +If you are using git 1.8.0 or older, and cloned DFHack before commit 85a920d +(around DFHack v0.43.03-alpha1), you may run into fatal git errors when updating +submodules after switching branches. This is due to those versions of git being +unable to handle our change from "scripts/3rdparty/name" submodules to a single +"scripts" submodule. This may be fixable by renaming .git/modules/scripts to +something else and re-running ``git submodule update --init`` on the branch with +the single scripts submodule (and running it again when switching back to the +one with multiple submodules, if necessary), but it is usually much simpler to +upgrade your git version. From 0cc51f8d69f299a2170eb2385ceb18d4df4afb6a Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 1 Mar 2017 21:29:17 -0500 Subject: [PATCH 0431/1012] Add support for downloading files in advance, useful for offline builds Also document this in Compile.rst - there have been complaints about the fact that DFHack downloads files at build time not being documented at all. --- CMake/DownloadFile.cmake | 21 +++++++++++++++++++++ CMake/downloads/.gitignore | 3 +++ CMake/downloads/README.txt | 3 +++ docs/Compile.rst | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 CMake/downloads/.gitignore create mode 100644 CMake/downloads/README.txt diff --git a/CMake/DownloadFile.cmake b/CMake/DownloadFile.cmake index 8f9938f90..fe9e4e05e 100644 --- a/CMake/DownloadFile.cmake +++ b/CMake/DownloadFile.cmake @@ -9,10 +9,31 @@ function(file_md5_if_exists FILE VAR) endif() endfunction() +function(search_downloads FILE_MD5 VAR) + set(${VAR} "" PARENT_SCOPE) + file(GLOB FILES ${CMAKE_SOURCE_DIR}/CMake/downloads/*) + foreach(FILE ${FILES}) + file(MD5 "${FILE}" CUR_MD5) + if("${CUR_MD5}" STREQUAL "${FILE_MD5}") + set(${VAR} ${FILE} PARENT_SCOPE) + return() + endif() + endforeach() +endfunction() + function(download_file URL DEST EXPECTED_MD5) get_filename_component(FILENAME "${URL}" NAME) file_md5_if_exists("${DEST}" CUR_MD5) + search_downloads(${EXPECTED_MD5} DLPATH) + if(NOT("${DLPATH}" STREQUAL "")) + message("* Copying ${FILENAME} from ${DLPATH}") + execute_process(COMMAND "${CMAKE_COMMAND}" -E copy + "${DLPATH}" + "${DEST}") + return() + endif() + if(NOT "${EXPECTED_MD5}" STREQUAL "${CUR_MD5}") message("* Downloading ${FILENAME}") file(DOWNLOAD "${URL}" "${DEST}" EXPECTED_MD5 "${EXPECTED_MD5}" SHOW_PROGRESS) diff --git a/CMake/downloads/.gitignore b/CMake/downloads/.gitignore new file mode 100644 index 000000000..ff7b252fe --- /dev/null +++ b/CMake/downloads/.gitignore @@ -0,0 +1,3 @@ +* +!README.txt +!.gitignore diff --git a/CMake/downloads/README.txt b/CMake/downloads/README.txt new file mode 100644 index 000000000..40b504725 --- /dev/null +++ b/CMake/downloads/README.txt @@ -0,0 +1,3 @@ +This folder exists as an alternate location for downloaded files. Files will +ordinarily not be downloaded here, but CMake will look for them here anyway to +facilitate offline builds. diff --git a/docs/Compile.rst b/docs/Compile.rst index 647dbb30b..c69d1de29 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -43,6 +43,7 @@ build errors or "not a known DF version" errors. **More notes**: +* `note-offline-builds` - read this if your build machine may not have an internet connection! * `note-old-git-and-dfhack` Contributing to DFHack @@ -668,6 +669,37 @@ Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run:: Misc. Notes =========== +.. _note-offline-builds: + +Note on building DFHack offline +------------------------------- + +As of 0.43.05, DFHack downloads several files during the build process, depending +on your target OS and architecture. If your build machine's internet connection +is unreliable, or nonexistent, you can download these files in advance. + +First, you must locate the files you will need. These can be found in the +`dfhack-bin repo `_. Look for the +most recent version number *before or equal to* the DF version which you are +building for. For example, suppose "0.43.05" and "0.43.07" are listed. You should +choose "0.43.05" if you are building for 0.43.05 or 0.43.06, and "0.43.07" if +you are building for 0.43.07 or 0.43.08. + +Then, download all of the files you need, and save them to ``/CMake/downloads/``. The destination filename you choose +does not matter, as long as the files end up in the ``CMake/downloads`` folder. +You need to download all of the files for the architecture(s) you are building +for. For example, if you are building for 32-bit Linux and 64-bit Windows, +download all files starting with ``linux32`` and ``win64``. GitHub should sort +files alphabetically, so all the files you need should be next to each other. + +It is recommended that you create a build folder and run CMake to verify that +you have downloaded everything at this point, assuming your download machine has +CMake installed. This involves running a "generate" batch script on Windows, or +a command starting with ``cmake ..`` on Linux and OS X. CMake should +automatically locate files that you placed in ``CMake/downloads``, and use them +instead of attempting to download them. + .. _note-old-git-and-dfhack: Note on using very old git versions with pre-0.43.03 DFHack versions From 15759808e5a33da4fff2521a51039f2e4abf8da7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 2 Mar 2017 14:04:14 -0500 Subject: [PATCH 0432/1012] Fix "plug" output width for generated-creature-renamer --- library/Core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 642da61ea..198e49df4 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -931,8 +931,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else if (builtin == "plug") { - const char *header_format = "%25s %10s %4s %8s\n"; - const char *row_format = "%25s %10s %4i %8s\n"; + const char *header_format = "%30s %10s %4s %8s\n"; + const char *row_format = "%30s %10s %4i %8s\n"; con.print(header_format, "Name", "State", "Cmds", "Enabled"); plug_mgr->refresh(); From 334817fa1be79a9f529433ade677a39e08b34c30 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 2 Mar 2017 14:04:34 -0500 Subject: [PATCH 0433/1012] Add Lua API functions to determine architecture --- library/LuaApi.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 39404e9be..a2446c78a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1386,6 +1386,16 @@ static std::string getOSType() } } +static int getArchitecture() +{ + return sizeof(void*) * 8; +} + +static std::string getArchitectureName() +{ + return getArchitecture() == 64 ? "x86_64" : "x86"; +} + static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); } static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); } @@ -1403,6 +1413,8 @@ static std::string df2console(std::string s) { return DF2CONSOLE(s); } static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), + WRAP(getArchitecture), + WRAP(getArchitectureName), WRAP(getDFVersion), WRAP(getDFPath), WRAP(getTickCount), From d53c00d726811ed2649d754f42be679a52b09162 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 2 Mar 2017 14:05:08 -0500 Subject: [PATCH 0434/1012] Update scripts (add install-info) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a05e51095..7e8daa168 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a05e51095d9d571213aa33caffb5912468a3e6dc +Subproject commit 7e8daa16822cc67f8e84adab7607694b622fe434 From 67f39316ce6e105fc99efd957693782b02ed199c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 2 Mar 2017 14:05:34 -0500 Subject: [PATCH 0435/1012] Update xml (assorted viewscreen changes) --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index e60c4259a..a292304c6 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e60c4259ab9ea9326af42173024a3b3e40fe6688 +Subproject commit a292304c6097a499dfa749d5cc8ab24e69e004f5 From 38702febf7077842158c376a4b088288c616f3c4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Mar 2017 00:24:05 -0400 Subject: [PATCH 0436/1012] Use LUA_(C)PATH_VAR instead of LUA_(C)PATH in luaconf.h This was apparently changed at some point around Lua 5.3, so a custom LUA_PATH environment variable could prevent DFHack from initializing. --- depends/lua/include/luaconf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index ce96b4747..051d2a944 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -200,8 +200,8 @@ LUA_CDIR"?.so;" "./?.so" #endif /* } */ -#define LUA_PATH "DFHACK_LUA_PATH" -#define LUA_CPATH "DFHACK_LUA_CPATH" +#define LUA_PATH_VAR "DFHACK_LUA_PATH" +#define LUA_CPATH_VAR "DFHACK_LUA_CPATH" /* @@ LUA_DIRSEP is the directory separator (for submodules). From e60d8de5fe04459120eb1c511dbf8f362f03257e Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 18 Mar 2017 00:25:46 +0530 Subject: [PATCH 0437/1012] Fix array out of bounds errors in remotefortressreader that were crashing linux. --- .../remotefortressreader.cpp | 78 ++++++++++++++----- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 3ebd2ea35..1f23cf0e1 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -2584,25 +2584,65 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w auto south = riverTile->mutable_south(); auto west = riverTile->mutable_west(); - north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); - north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); - north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); - north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); - - south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); - south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); - south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); - south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); - - west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); - west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); - west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); - west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); - - east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); - east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); - east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); - east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + if (xx < 16) + { + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + } + else + { + north->set_active(0); + north->set_elevation(100); + north->set_min_pos(-30000); + north->set_max_pos(-30000); + } + + if (yy < 16 && xx < 16) + { + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + } + else + { + south->set_active(0); + south->set_elevation(100); + south->set_min_pos(-30000); + south->set_max_pos(-30000); + } + + if (yy < 16) + { + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + } + else + { + west->set_active(0); + west->set_elevation(100); + west->set_min_pos(-30000); + west->set_max_pos(-30000); + } + + if (xx < 16 && yy < 16) + { + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + else + { + east->set_active(0); + east->set_elevation(100); + east->set_min_pos(-30000); + east->set_max_pos(-30000); + } } auto regionMap = worldData->region_map[pos_x][pos_y]; From 50a022d81bee885bb7181bc96e74236ae0604bc9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 00:31:15 -0400 Subject: [PATCH 0438/1012] download_file(): avoid copying files if the correct file already exists --- CMake/DownloadFile.cmake | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CMake/DownloadFile.cmake b/CMake/DownloadFile.cmake index fe9e4e05e..5a10cca2f 100644 --- a/CMake/DownloadFile.cmake +++ b/CMake/DownloadFile.cmake @@ -25,16 +25,16 @@ function(download_file URL DEST EXPECTED_MD5) get_filename_component(FILENAME "${URL}" NAME) file_md5_if_exists("${DEST}" CUR_MD5) - search_downloads(${EXPECTED_MD5} DLPATH) - if(NOT("${DLPATH}" STREQUAL "")) - message("* Copying ${FILENAME} from ${DLPATH}") - execute_process(COMMAND "${CMAKE_COMMAND}" -E copy - "${DLPATH}" - "${DEST}") - return() - endif() - if(NOT "${EXPECTED_MD5}" STREQUAL "${CUR_MD5}") + search_downloads(${EXPECTED_MD5} DLPATH) + if(NOT("${DLPATH}" STREQUAL "")) + message("* Copying ${FILENAME} from ${DLPATH}") + execute_process(COMMAND "${CMAKE_COMMAND}" -E copy + "${DLPATH}" + "${DEST}") + return() + endif() + message("* Downloading ${FILENAME}") file(DOWNLOAD "${URL}" "${DEST}" EXPECTED_MD5 "${EXPECTED_MD5}" SHOW_PROGRESS) endif() From f17e9d36620978793a5daa08ac747ca7ea45373c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 00:31:25 -0400 Subject: [PATCH 0439/1012] title-folder: fix SDL path on OS X The Stonesense install script appears to do strange things which cause DF to load SDL.framework/Versions/A/SDL instead of SDL.framework/SDL. Once the former is loaded, loading the latter and calling SDL_WM_* functions from it will fail. A better solution would be to remove parts of the Stonesense fix-libs-*.sh script(s) that are causing this, since they don't appear to be needed, or use dlsym() to find the already-opened library/symbols. --- plugins/title-folder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/title-folder.cpp b/plugins/title-folder.cpp index 205f7225a..eae381b95 100644 --- a/plugins/title-folder.cpp +++ b/plugins/title-folder.cpp @@ -16,6 +16,7 @@ static std::string original_title; static DFLibrary *sdl_handle = NULL; static const std::vector sdl_libs { "SDLreal.dll", + "SDL.framework/Versions/A/SDL", "SDL.framework/SDL", "libSDL-1.2.so.0" }; From 85e0daf15efd60b7b9dd0fc43ac2ea3ab5b1c02a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 00:34:07 -0400 Subject: [PATCH 0440/1012] stonesense: basic 64-bit OS X support --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 416bc28f2..1925760b2 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 416bc28f2e25d06e7da28a5643b8a849aefddbf5 +Subproject commit 1925760b2f611d246d1715a2e3cfb591a02ef00b From 400d22cb760d2f41b011184d6acd2324848e03fd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 00:35:16 -0400 Subject: [PATCH 0441/1012] Update xml (ui_advmode_menu and ui_unit_view_mode) --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index a292304c6..8727ebd74 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a292304c6097a499dfa749d5cc8ab24e69e004f5 +Subproject commit 8727ebd74a3f5d90e34a08266a3719e0cd5817d9 From a35d5493a44ce8e64093a8065b57f8bbad6e9d77 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 17:49:54 -0400 Subject: [PATCH 0442/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 7e8daa168..cf367974b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7e8daa16822cc67f8e84adab7607694b622fe434 +Subproject commit cf367974b5e1513d454b2988d45122e98cd28f52 From fa1adbbac4b3c1312587440684729652f89b3b73 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 17:55:39 -0400 Subject: [PATCH 0443/1012] Allow lua integer writes to accept anything that can be converted to an integer May address #1044 --- library/LuaTypes.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index f281afe86..5b5a174fa 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -122,10 +122,11 @@ void df::integer_identity_base::lua_read(lua_State *state, int fname_idx, void * void df::integer_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) { - if (!lua_isinteger(state, val_index)) + int is_num = 0; + auto value = lua_tointegerx(state, val_index, &is_num); + if (!is_num) field_error(state, fname_idx, "integer expected", "write"); - - write(ptr, lua_tointeger(state, val_index)); + write(ptr, value); } void df::float_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) From aea76b7ef3c895f36a90ac7d64e7b592a3612ba5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Mar 2017 17:56:06 -0400 Subject: [PATCH 0444/1012] stockflow: make sure that manager order amounts are integers Fixes #1044 --- plugins/lua/stockflow.lua | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index dd83e65d9..fcaca0ecd 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -140,12 +140,12 @@ function collect_orders() stockpile = stockpile, entry = entry, } - + found = true break end end - + if not found then print("Unmatched stockflow entry for stockpile #"..stockpile.stockpile_number..": "..entry.value.." ("..order_number..")") end @@ -174,7 +174,7 @@ function reaction_entry(reactions, job_type, values, name) -- We could warn about it; in any case, don't add it to the list. return end - + local order = df.manager_order:new() -- These defaults differ from the newly created order's. order:assign{ @@ -420,7 +420,7 @@ function collect_reactions() local name = string.gsub(reaction.name, "^.", string.upper) reaction_entry(result, job_types.CustomReaction, {reaction_name = reaction.code}, name) end - + -- Reactions generated by the game. for _, reaction in ipairs(df.global.world.raws.reactions) do if reaction.source_enid == entity.id then @@ -751,7 +751,7 @@ function collect_reactions() {job_types.MakeBracelet, "Make", "Bracelet"}, {job_types.MakeEarring, "Make", "Earring"}, }, mat) - + if not mat.cloth then material_reactions(result, { {job_types.MakeCrown, "Make", "Crown"}, @@ -759,7 +759,7 @@ function collect_reactions() {job_types.MakeRing, "Make", "Ring"}, {job_types.MakeGem, "Make Large", "Gem"}, }, mat) - + if not mat.short then material_reactions(result, { {job_types.MakeScepter, "Make", "Scepter"}, @@ -798,29 +798,29 @@ screen = gui.FramedScreen { function screen:onRenderBody(dc) -- Emulates the built-in manager screen. - + if not (self.page_size == self.frame_rect.height - ExtraLines) then -- The screen size has changed. self:refilter() end - + -- Top instruction line. dc:seek(1, 1):string("Type in parts of the name to narrow your search. ", COLOR_WHITE) dc:key("LEAVESCREEN"):string(" to abort.", COLOR_WHITE) - + -- Search term, if any. dc:seek(1, FirstRow + self.page_size + 1):string(self.search_string, COLOR_LIGHTCYAN) - + -- Bottom instruction line. dc:seek(1, FirstRow + self.page_size + 2) dc:key("STANDARDSCROLL_UP"):key("STANDARDSCROLL_DOWN") dc:key("STANDARDSCROLL_PAGEUP"):key("STANDARDSCROLL_PAGEDOWN") dc:key("STANDARDSCROLL_LEFT"):key("STANDARDSCROLL_RIGHT") dc:string(": Select", COLOR_WHITE) - + dc:seek(CenterCol, FirstRow + self.page_size + 2) dc:key("SETUPGAME_SAVE_PROFILE_ABORT"):string(": No order", COLOR_WHITE) - + -- Reaction lines. for _, item in ipairs(self.displayed) do dc:seek(item.x, item.y):string(item.name, item.color) @@ -954,7 +954,7 @@ function screen:refilter() -- * Yellow: At least one word starts with at least one search term -- * Grey: Each search term is found in the middle of a word self.page_size = self.frame_rect.height - ExtraLines - + local filtered = {} local needles = splitstring(self.search_string, " ") for key, value in ipairs(reaction_list) do @@ -1094,6 +1094,7 @@ end -- Place a new copy of the order onto the manager's queue. function create_orders(order, amount) local new_order = order:new() + amount = math.floor(amount) new_order.amount_left = amount new_order.amount_total = amount -- Todo: Create in a validated state if the fortress is small enough? From 5ea964b9cf15c970d09dd9b05bdd40e83b93b287 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 25 Mar 2017 22:23:40 +0530 Subject: [PATCH 0445/1012] Send building items with buildings. --- plugins/proto/RemoteFortressReader.proto | 7 + plugins/remotefortressreader/CMakeLists.txt | 3 +- .../remotefortressreader/building_reader.cpp | 529 ++++++++++++++++++ .../remotefortressreader/building_reader.h | 9 + .../remotefortressreader.cpp | 527 +---------------- 5 files changed, 554 insertions(+), 521 deletions(-) create mode 100644 plugins/remotefortressreader/building_reader.cpp create mode 100644 plugins/remotefortressreader/building_reader.h diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index af25c9a4b..d8a0b0a61 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -150,6 +150,12 @@ message BuildingExtents repeated int32 extents = 5; } +message BuildingItem +{ + optional Item item = 1; + optional int32 mode = 2; +} + message BuildingInstance { required int32 index = 1; @@ -165,6 +171,7 @@ message BuildingInstance optional bool is_room = 11; optional BuildingExtents room = 12; optional BuildingDirection direction = 13; //Doesn't mean anything for most buildings + repeated BuildingItem items = 14; } message RiverEdge diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 0b84d8905..b9f120059 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -2,10 +2,11 @@ PROJECT (remotefortressreader) # A list of source files SET(PROJECT_SRCS remotefortressreader.cpp + building_reader.cpp ) # A list of headers SET(PROJECT_HDRS - + building_reader.h ) #proto files to include. SET(PROJECT_PROTO diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp new file mode 100644 index 000000000..1a49a7c02 --- /dev/null +++ b/plugins/remotefortressreader/building_reader.cpp @@ -0,0 +1,529 @@ +#include "building_reader.h" +#include "DataDefs.h" + +#include "df/building_axle_horizontalst.h" +#include "df/building_bridgest.h" +#include "df/building_def_furnacest.h" +#include "df/building_def_workshopst.h" +#include "df/building_rollersst.h" +#include "df/building_screw_pumpst.h" +#include "df/building_siegeenginest.h" +#include "df/building_water_wheelst.h" +#include "df/building_wellst.h" +#include "df/building_windmillst.h" +#include "df/world.h" + + +#include "modules/Buildings.h" + + +using namespace DFHack; +using namespace df::enums; +using namespace RemoteFortressReader; +using namespace std; + +DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::BuildingList *out) +{ + FOR_ENUM_ITEMS(building_type, bt) + { + BuildingDefinition * bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(-1); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt)); + + switch (bt) + { + case df::enums::building_type::NONE: + break; + case df::enums::building_type::Chair: + break; + case df::enums::building_type::Bed: + break; + case df::enums::building_type::Table: + break; + case df::enums::building_type::Coffin: + break; + case df::enums::building_type::FarmPlot: + break; + case df::enums::building_type::Furnace: + FOR_ENUM_ITEMS(furnace_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(furnace_type, st)); + + if (st == furnace_type::Custom) + { + for (int i = 0; i < df::global::world->raws.buildings.furnaces.size(); i++) + { + auto cust = df::global::world->raws.buildings.furnaces[i]; + + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(cust->id); + bld->set_id(cust->code); + bld->set_name(cust->name); + } + } + } + break; + case df::enums::building_type::TradeDepot: + break; + case df::enums::building_type::Shop: + FOR_ENUM_ITEMS(shop_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(shop_type, st)); + + } + break; + case df::enums::building_type::Door: + break; + case df::enums::building_type::Floodgate: + break; + case df::enums::building_type::Box: + break; + case df::enums::building_type::Weaponrack: + break; + case df::enums::building_type::Armorstand: + break; + case df::enums::building_type::Workshop: + FOR_ENUM_ITEMS(workshop_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(workshop_type, st)); + + if (st == workshop_type::Custom) + { + for (int i = 0; i < df::global::world->raws.buildings.workshops.size(); i++) + { + auto cust = df::global::world->raws.buildings.workshops[i]; + + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(cust->id); + bld->set_id(cust->code); + bld->set_name(cust->name); + } + } + } + break; + case df::enums::building_type::Cabinet: + break; + case df::enums::building_type::Statue: + break; + case df::enums::building_type::WindowGlass: + break; + case df::enums::building_type::WindowGem: + break; + case df::enums::building_type::Well: + break; + case df::enums::building_type::Bridge: + break; + case df::enums::building_type::RoadDirt: + break; + case df::enums::building_type::RoadPaved: + break; + case df::enums::building_type::SiegeEngine: + FOR_ENUM_ITEMS(siegeengine_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(siegeengine_type, st)); + + } + break; + case df::enums::building_type::Trap: + FOR_ENUM_ITEMS(trap_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(trap_type, st)); + + } + break; + case df::enums::building_type::AnimalTrap: + break; + case df::enums::building_type::Support: + break; + case df::enums::building_type::ArcheryTarget: + break; + case df::enums::building_type::Chain: + break; + case df::enums::building_type::Cage: + break; + case df::enums::building_type::Stockpile: + break; + case df::enums::building_type::Civzone: + FOR_ENUM_ITEMS(civzone_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(civzone_type, st)); + + } + break; + case df::enums::building_type::Weapon: + break; + case df::enums::building_type::Wagon: + break; + case df::enums::building_type::ScrewPump: + break; + case df::enums::building_type::Construction: + FOR_ENUM_ITEMS(construction_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(construction_type, st)); + + } + break; + case df::enums::building_type::Hatch: + break; + case df::enums::building_type::GrateWall: + break; + case df::enums::building_type::GrateFloor: + break; + case df::enums::building_type::BarsVertical: + break; + case df::enums::building_type::BarsFloor: + break; + case df::enums::building_type::GearAssembly: + break; + case df::enums::building_type::AxleHorizontal: + break; + case df::enums::building_type::AxleVertical: + break; + case df::enums::building_type::WaterWheel: + break; + case df::enums::building_type::Windmill: + break; + case df::enums::building_type::TractionBench: + break; + case df::enums::building_type::Slab: + break; + case df::enums::building_type::Nest: + break; + case df::enums::building_type::NestBox: + break; + case df::enums::building_type::Hive: + break; + case df::enums::building_type::Rollers: + break; + default: + break; + } + } + return CR_OK; +} + + +void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build) +{ + df::building * local_build = df::global::world->buildings.all[buildingIndex]; + remote_build->set_index(buildingIndex); + int minZ = local_build->z; + if (local_build->getType() == df::enums::building_type::Well) + { + df::building_wellst * well_building = virtual_cast(local_build); + if (well_building) + minZ = well_building->bucket_z; + } + remote_build->set_pos_x_min(local_build->x1); + remote_build->set_pos_y_min(local_build->y1); + remote_build->set_pos_z_min(minZ); + + remote_build->set_pos_x_max(local_build->x2); + remote_build->set_pos_y_max(local_build->y2); + remote_build->set_pos_z_max(local_build->z); + + auto buildingType = remote_build->mutable_building_type(); + auto type = local_build->getType(); + buildingType->set_building_type(type); + buildingType->set_building_subtype(local_build->getSubtype()); + buildingType->set_building_custom(local_build->getCustomType()); + + auto material = remote_build->mutable_material(); + material->set_mat_type(local_build->mat_type); + material->set_mat_index(local_build->mat_index); + + remote_build->set_building_flags(local_build->flags.whole); + remote_build->set_is_room(local_build->is_room); + if (local_build->is_room || local_build->getType() == df::enums::building_type::Civzone || local_build->getType() == df::enums::building_type::Stockpile) + { + auto room = remote_build->mutable_room(); + room->set_pos_x(local_build->room.x); + room->set_pos_y(local_build->room.y); + room->set_width(local_build->room.width); + room->set_height(local_build->room.height); + for (int i = 0; i < (local_build->room.width * local_build->room.height); i++) + { + room->add_extents(local_build->room.extents[i]); + } + } + + + switch (type) + { + case df::enums::building_type::NONE: + break; + case df::enums::building_type::Chair: + break; + case df::enums::building_type::Bed: + break; + case df::enums::building_type::Table: + break; + case df::enums::building_type::Coffin: + break; + case df::enums::building_type::FarmPlot: + break; + case df::enums::building_type::Furnace: + break; + case df::enums::building_type::TradeDepot: + break; + case df::enums::building_type::Shop: + break; + case df::enums::building_type::Door: + break; + case df::enums::building_type::Floodgate: + break; + case df::enums::building_type::Box: + break; + case df::enums::building_type::Weaponrack: + break; + case df::enums::building_type::Armorstand: + break; + case df::enums::building_type::Workshop: + break; + case df::enums::building_type::Cabinet: + break; + case df::enums::building_type::Statue: + break; + case df::enums::building_type::WindowGlass: + break; + case df::enums::building_type::WindowGem: + break; + case df::enums::building_type::Well: + break; + case df::enums::building_type::Bridge: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto direction = actual->direction; + switch (direction) + { + case df::building_bridgest::Retracting: + break; + case df::building_bridgest::Left: + remote_build->set_direction(WEST); + break; + case df::building_bridgest::Right: + remote_build->set_direction(EAST); + break; + case df::building_bridgest::Up: + remote_build->set_direction(NORTH); + break; + case df::building_bridgest::Down: + remote_build->set_direction(SOUTH); + break; + default: + break; + } + } + } + break; + case df::enums::building_type::RoadDirt: + break; + case df::enums::building_type::RoadPaved: + break; + case df::enums::building_type::SiegeEngine: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto facing = actual->facing; + switch (facing) + { + case df::building_siegeenginest::Left: + remote_build->set_direction(WEST); + break; + case df::building_siegeenginest::Up: + remote_build->set_direction(NORTH); + break; + case df::building_siegeenginest::Right: + remote_build->set_direction(EAST); + break; + case df::building_siegeenginest::Down: + remote_build->set_direction(SOUTH); + break; + default: + break; + } + } + } + break; + case df::enums::building_type::Trap: + break; + case df::enums::building_type::AnimalTrap: + break; + case df::enums::building_type::Support: + break; + case df::enums::building_type::ArcheryTarget: + break; + case df::enums::building_type::Chain: + break; + case df::enums::building_type::Cage: + break; + case df::enums::building_type::Stockpile: + break; + case df::enums::building_type::Civzone: + break; + case df::enums::building_type::Weapon: + break; + case df::enums::building_type::Wagon: + break; + case df::enums::building_type::ScrewPump: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto direction = actual->direction; + switch (direction) + { + case df::enums::screw_pump_direction::FromNorth: + remote_build->set_direction(NORTH); + break; + case df::enums::screw_pump_direction::FromEast: + remote_build->set_direction(EAST); + break; + case df::enums::screw_pump_direction::FromSouth: + remote_build->set_direction(SOUTH); + break; + case df::enums::screw_pump_direction::FromWest: + remote_build->set_direction(WEST); + break; + default: + break; + } + } + } + break; + case df::enums::building_type::Construction: + break; + case df::enums::building_type::Hatch: + break; + case df::enums::building_type::GrateWall: + break; + case df::enums::building_type::GrateFloor: + break; + case df::enums::building_type::BarsVertical: + break; + case df::enums::building_type::BarsFloor: + break; + case df::enums::building_type::GearAssembly: + break; + case df::enums::building_type::AxleHorizontal: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->is_vertical) + remote_build->set_direction(NORTH); + else + remote_build->set_direction(EAST); + } + } + break; + case df::enums::building_type::AxleVertical: + break; + case df::enums::building_type::WaterWheel: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->is_vertical) + remote_build->set_direction(NORTH); + else + remote_build->set_direction(EAST); + } + } + break; + case df::enums::building_type::Windmill: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->orient_x < 0) + remote_build->set_direction(WEST); + else if (actual->orient_x > 0) + remote_build->set_direction(EAST); + else if (actual->orient_y < 0) + remote_build->set_direction(NORTH); + else if (actual->orient_y > 0) + remote_build->set_direction(SOUTH); + else + remote_build->set_direction(WEST); + } + } + break; + case df::enums::building_type::TractionBench: + break; + case df::enums::building_type::Slab: + break; + case df::enums::building_type::Nest: + break; + case df::enums::building_type::NestBox: + break; + case df::enums::building_type::Hive: + break; + case df::enums::building_type::Rollers: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto direction = actual->direction; + switch (direction) + { + case df::enums::screw_pump_direction::FromNorth: + remote_build->set_direction(NORTH); + break; + case df::enums::screw_pump_direction::FromEast: + remote_build->set_direction(EAST); + break; + case df::enums::screw_pump_direction::FromSouth: + remote_build->set_direction(SOUTH); + break; + case df::enums::screw_pump_direction::FromWest: + remote_build->set_direction(WEST); + break; + default: + break; + } + } + } + break; + default: + break; + } +} + diff --git a/plugins/remotefortressreader/building_reader.h b/plugins/remotefortressreader/building_reader.h new file mode 100644 index 000000000..6b789359b --- /dev/null +++ b/plugins/remotefortressreader/building_reader.h @@ -0,0 +1,9 @@ +#ifndef BUILDING_READER_H +#define BUILDING_READER_H +#include "RemoteClient.h" +#include "RemoteFortressReader.pb.h" + +DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::BuildingList *out); +void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build); + +#endif \ No newline at end of file diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 1f23cf0e1..8274b7db5 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -17,7 +17,6 @@ #include "SDL_keyboard.h" #include "TileTypes.h" -#include "modules/Buildings.h" #include "modules/Gui.h" #include "modules/Items.h" #include "modules/MapCache.h" @@ -35,17 +34,9 @@ #include "df/body_part_layer_raw.h" #include "df/body_part_raw.h" #include "df/bp_appearance_modifier.h" -#include "df/building_axle_horizontalst.h" -#include "df/building_bridgest.h" -#include "df/building_def_furnacest.h" -#include "df/building_def_workshopst.h" -#include "df/building_rollersst.h" -#include "df/building_screw_pumpst.h" -#include "df/building_siegeenginest.h" -#include "df/building_water_wheelst.h" -#include "df/building_wellst.h" -#include "df/building_windmillst.h" #include "df/builtin_mats.h" +#include "df/building_wellst.h" + #include "df/caste_raw.h" #include "df/caste_raw.h" #include "df/color_modifier_raw.h" @@ -105,6 +96,8 @@ #include "df/plant_tree_tile.h" #endif +#include "building_reader.h" + using namespace DFHack; using namespace df::enums; using namespace RemoteFortressReader; @@ -135,7 +128,6 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out); static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); -static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out); @@ -312,296 +304,6 @@ void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefiniti out->set_blue(color->blue * 255); } -void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build) -{ - df::building * local_build = df::global::world->buildings.all[buildingIndex]; - remote_build->set_index(buildingIndex); - int minZ = local_build->z; - if (local_build->getType() == df::enums::building_type::Well) - { - df::building_wellst * well_building = virtual_cast(local_build); - if (well_building) - minZ = well_building->bucket_z; - } - remote_build->set_pos_x_min(local_build->x1); - remote_build->set_pos_y_min(local_build->y1); - remote_build->set_pos_z_min(minZ); - - remote_build->set_pos_x_max(local_build->x2); - remote_build->set_pos_y_max(local_build->y2); - remote_build->set_pos_z_max(local_build->z); - - auto buildingType = remote_build->mutable_building_type(); - auto type = local_build->getType(); - buildingType->set_building_type(type); - buildingType->set_building_subtype(local_build->getSubtype()); - buildingType->set_building_custom(local_build->getCustomType()); - - auto material = remote_build->mutable_material(); - material->set_mat_type(local_build->mat_type); - material->set_mat_index(local_build->mat_index); - - remote_build->set_building_flags(local_build->flags.whole); - remote_build->set_is_room(local_build->is_room); - if (local_build->is_room || local_build->getType() == df::enums::building_type::Civzone || local_build->getType() == df::enums::building_type::Stockpile) - { - auto room = remote_build->mutable_room(); - room->set_pos_x(local_build->room.x); - room->set_pos_y(local_build->room.y); - room->set_width(local_build->room.width); - room->set_height(local_build->room.height); - for (int i = 0; i < (local_build->room.width * local_build->room.height); i++) - { - room->add_extents(local_build->room.extents[i]); - } - } - - - switch (type) - { - case df::enums::building_type::NONE: - break; - case df::enums::building_type::Chair: - break; - case df::enums::building_type::Bed: - break; - case df::enums::building_type::Table: - break; - case df::enums::building_type::Coffin: - break; - case df::enums::building_type::FarmPlot: - break; - case df::enums::building_type::Furnace: - break; - case df::enums::building_type::TradeDepot: - break; - case df::enums::building_type::Shop: - break; - case df::enums::building_type::Door: - break; - case df::enums::building_type::Floodgate: - break; - case df::enums::building_type::Box: - break; - case df::enums::building_type::Weaponrack: - break; - case df::enums::building_type::Armorstand: - break; - case df::enums::building_type::Workshop: - break; - case df::enums::building_type::Cabinet: - break; - case df::enums::building_type::Statue: - break; - case df::enums::building_type::WindowGlass: - break; - case df::enums::building_type::WindowGem: - break; - case df::enums::building_type::Well: - break; - case df::enums::building_type::Bridge: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto direction = actual->direction; - switch (direction) - { - case df::building_bridgest::Retracting: - break; - case df::building_bridgest::Left: - remote_build->set_direction(WEST); - break; - case df::building_bridgest::Right: - remote_build->set_direction(EAST); - break; - case df::building_bridgest::Up: - remote_build->set_direction(NORTH); - break; - case df::building_bridgest::Down: - remote_build->set_direction(SOUTH); - break; - default: - break; - } - } - } - break; - case df::enums::building_type::RoadDirt: - break; - case df::enums::building_type::RoadPaved: - break; - case df::enums::building_type::SiegeEngine: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto facing = actual->facing; - switch (facing) - { - case df::building_siegeenginest::Left: - remote_build->set_direction(WEST); - break; - case df::building_siegeenginest::Up: - remote_build->set_direction(NORTH); - break; - case df::building_siegeenginest::Right: - remote_build->set_direction(EAST); - break; - case df::building_siegeenginest::Down: - remote_build->set_direction(SOUTH); - break; - default: - break; - } - } - } - break; - case df::enums::building_type::Trap: - break; - case df::enums::building_type::AnimalTrap: - break; - case df::enums::building_type::Support: - break; - case df::enums::building_type::ArcheryTarget: - break; - case df::enums::building_type::Chain: - break; - case df::enums::building_type::Cage: - break; - case df::enums::building_type::Stockpile: - break; - case df::enums::building_type::Civzone: - break; - case df::enums::building_type::Weapon: - break; - case df::enums::building_type::Wagon: - break; - case df::enums::building_type::ScrewPump: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto direction = actual->direction; - switch (direction) - { - case df::enums::screw_pump_direction::FromNorth: - remote_build->set_direction(NORTH); - break; - case df::enums::screw_pump_direction::FromEast: - remote_build->set_direction(EAST); - break; - case df::enums::screw_pump_direction::FromSouth: - remote_build->set_direction(SOUTH); - break; - case df::enums::screw_pump_direction::FromWest: - remote_build->set_direction(WEST); - break; - default: - break; - } - } - } - break; - case df::enums::building_type::Construction: - break; - case df::enums::building_type::Hatch: - break; - case df::enums::building_type::GrateWall: - break; - case df::enums::building_type::GrateFloor: - break; - case df::enums::building_type::BarsVertical: - break; - case df::enums::building_type::BarsFloor: - break; - case df::enums::building_type::GearAssembly: - break; - case df::enums::building_type::AxleHorizontal: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - if(actual->is_vertical) - remote_build->set_direction(NORTH); - else - remote_build->set_direction(EAST); - } - } - break; - case df::enums::building_type::AxleVertical: - break; - case df::enums::building_type::WaterWheel: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - if (actual->is_vertical) - remote_build->set_direction(NORTH); - else - remote_build->set_direction(EAST); - } - } - break; - case df::enums::building_type::Windmill: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - if (actual->orient_x < 0) - remote_build->set_direction(WEST); - else if (actual->orient_x > 0) - remote_build->set_direction(EAST); - else if (actual->orient_y < 0) - remote_build->set_direction(NORTH); - else if (actual->orient_y > 0) - remote_build->set_direction(SOUTH); - else - remote_build->set_direction(WEST); - } - } - break; - case df::enums::building_type::TractionBench: - break; - case df::enums::building_type::Slab: - break; - case df::enums::building_type::Nest: - break; - case df::enums::building_type::NestBox: - break; - case df::enums::building_type::Hive: - break; - case df::enums::building_type::Rollers: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto direction = actual->direction; - switch (direction) - { - case df::enums::screw_pump_direction::FromNorth: - remote_build->set_direction(NORTH); - break; - case df::enums::screw_pump_direction::FromEast: - remote_build->set_direction(EAST); - break; - case df::enums::screw_pump_direction::FromSouth: - remote_build->set_direction(SOUTH); - break; - case df::enums::screw_pump_direction::FromWest: - remote_build->set_direction(WEST); - break; - default: - break; - } - } - } - break; - default: - break; - } -} - RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { switch (material) @@ -1469,10 +1171,9 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net { for (int i = 0; i < actualBuilding->contained_items.size(); i++) { - if (isItemChanged(actualBuilding->contained_items[i]->item->id)) - { - CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); - } + auto buildingItem = out_bld->add_items(); + buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); + CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); } } } @@ -1869,220 +1570,6 @@ static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, return CR_OK; } -static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out) -{ - FOR_ENUM_ITEMS(building_type, bt) - { - BuildingDefinition * bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(-1); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt)); - - switch (bt) - { - case df::enums::building_type::NONE: - break; - case df::enums::building_type::Chair: - break; - case df::enums::building_type::Bed: - break; - case df::enums::building_type::Table: - break; - case df::enums::building_type::Coffin: - break; - case df::enums::building_type::FarmPlot: - break; - case df::enums::building_type::Furnace: - FOR_ENUM_ITEMS(furnace_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(furnace_type, st)); - - if (st == furnace_type::Custom) - { - for (int i = 0; i < world->raws.buildings.furnaces.size(); i++) - { - auto cust = world->raws.buildings.furnaces[i]; - - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(cust->id); - bld->set_id(cust->code); - bld->set_name(cust->name); - } - } - } - break; - case df::enums::building_type::TradeDepot: - break; - case df::enums::building_type::Shop: - FOR_ENUM_ITEMS(shop_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(shop_type, st)); - - } - break; - case df::enums::building_type::Door: - break; - case df::enums::building_type::Floodgate: - break; - case df::enums::building_type::Box: - break; - case df::enums::building_type::Weaponrack: - break; - case df::enums::building_type::Armorstand: - break; - case df::enums::building_type::Workshop: - FOR_ENUM_ITEMS(workshop_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(workshop_type, st)); - - if (st == workshop_type::Custom) - { - for (int i = 0; i < world->raws.buildings.workshops.size(); i++) - { - auto cust = world->raws.buildings.workshops[i]; - - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(cust->id); - bld->set_id(cust->code); - bld->set_name(cust->name); - } - } - } - break; - case df::enums::building_type::Cabinet: - break; - case df::enums::building_type::Statue: - break; - case df::enums::building_type::WindowGlass: - break; - case df::enums::building_type::WindowGem: - break; - case df::enums::building_type::Well: - break; - case df::enums::building_type::Bridge: - break; - case df::enums::building_type::RoadDirt: - break; - case df::enums::building_type::RoadPaved: - break; - case df::enums::building_type::SiegeEngine: - FOR_ENUM_ITEMS(siegeengine_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(siegeengine_type, st)); - - } - break; - case df::enums::building_type::Trap: - FOR_ENUM_ITEMS(trap_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(trap_type, st)); - - } - break; - case df::enums::building_type::AnimalTrap: - break; - case df::enums::building_type::Support: - break; - case df::enums::building_type::ArcheryTarget: - break; - case df::enums::building_type::Chain: - break; - case df::enums::building_type::Cage: - break; - case df::enums::building_type::Stockpile: - break; - case df::enums::building_type::Civzone: - FOR_ENUM_ITEMS(civzone_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(civzone_type, st)); - - } - break; - case df::enums::building_type::Weapon: - break; - case df::enums::building_type::Wagon: - break; - case df::enums::building_type::ScrewPump: - break; - case df::enums::building_type::Construction: - FOR_ENUM_ITEMS(construction_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(construction_type, st)); - - } - break; - case df::enums::building_type::Hatch: - break; - case df::enums::building_type::GrateWall: - break; - case df::enums::building_type::GrateFloor: - break; - case df::enums::building_type::BarsVertical: - break; - case df::enums::building_type::BarsFloor: - break; - case df::enums::building_type::GearAssembly: - break; - case df::enums::building_type::AxleHorizontal: - break; - case df::enums::building_type::AxleVertical: - break; - case df::enums::building_type::WaterWheel: - break; - case df::enums::building_type::Windmill: - break; - case df::enums::building_type::TractionBench: - break; - case df::enums::building_type::Slab: - break; - case df::enums::building_type::Nest: - break; - case df::enums::building_type::NestBox: - break; - case df::enums::building_type::Hive: - break; - case df::enums::building_type::Rollers: - break; - default: - break; - } - } - return CR_OK; -} - DFCoord GetMapCenter() { DFCoord output; From 78cb4a31b906102cece7a2f55fccab0a35bc0d7d Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Mar 2017 09:50:38 -0400 Subject: [PATCH 0446/1012] New tweak: condition-material (fixes bug 9905) http://www.bay12games.com/dwarves/mantisbt/view.php?id=9905 --- dfhack.init-example | 3 + docs/Plugins.rst | 1 + library/xml | 2 +- plugins/tweak/tweak.cpp | 5 + plugins/tweak/tweaks/condition-material.h | 134 ++++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 plugins/tweak/tweaks/condition-material.h diff --git a/dfhack.init-example b/dfhack.init-example index 169d40046..f8878ea0a 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -181,6 +181,9 @@ tweak farm-plot-select # Add Shift-Left/Right controls to import agreement screen tweak import-priority-category +# Fixes a crash in the work order contition material list (bug 9905). +tweak condition-material + # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled tweak civ-view-agreement diff --git a/docs/Plugins.rst b/docs/Plugins.rst index e2b829020..c81b284c3 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -272,6 +272,7 @@ Subcommands that persist until disabled or DF quits: reagents. :block-labors: Prevents labors that can't be used from being toggled :civ-view-agreement: Fixes overlapping text on the "view agreement" screen +:condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). :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 wear every 20 years. diff --git a/library/xml b/library/xml index 8727ebd74..9b834c089 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8727ebd74a3f5d90e34a08266a3719e0cd5817d9 +Subproject commit 9b834c089efb4657d43a8fa4f8f0822e8224e576 diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 88c57fb62..1bab3ebcf 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -81,6 +81,7 @@ #include "tweaks/advmode-contained.h" #include "tweaks/block-labors.h" #include "tweaks/civ-agreement-ui.h" +#include "tweaks/condition-material.h" #include "tweaks/craft-age-wear.h" #include "tweaks/eggs-fertile.h" #include "tweaks/embark-profile-name.h" @@ -183,6 +184,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector list_entries; + std::vector list_unk1; + std::vector list_unk2; + std::vector list_unk3; + std::vector list_visible; + }; + + static std::map order_mat_data; + + static void register_screen(T_screen *scr) + { + if (order_mat_data.find(scr) != order_mat_data.end()) + { + unregister_screen(scr); + } + auto data = new T_order_mat_data; + data->list_entries = scr->list_entries; + data->list_unk1 = scr->list_unk1; + data->list_unk2 = scr->list_unk2; + data->list_unk3 = scr->list_unk3; + data->list_visible = scr->list_visible; + order_mat_data[scr] = data; + } + + static void unregister_screen(T_screen *scr) + { + if (order_mat_data.find(scr) != order_mat_data.end() && order_mat_data[scr]) + { + T_order_mat_data *data = order_mat_data[scr]; + scr->list_entries = data->list_entries; + scr->list_unk1 = data->list_unk1; + scr->list_unk2 = data->list_unk2; + scr->list_unk3 = data->list_unk3; + scr->list_visible = data->list_visible; + delete data; + order_mat_data.erase(scr); + } + } + + void apply_filter() + { + if (order_mat_data.find(this) != order_mat_data.end() && order_mat_data[this]) + { + list_idx = 0; + T_order_mat_data *data = order_mat_data[this]; + // keep the first item ("no material") around, because attempts to delete it + // result in it still being displayed first, regardless of list_entries[0] + list_entries.resize(1); + list_unk1.resize(1); + list_unk2.resize(1); + list_unk3.resize(1); + list_visible.resize(1); + // skip "no material" here + for (size_t i = 1; i < data->list_entries.size(); i++) + { + // cap it at 32767 elements to be safe + if (list_entries.size() >= INT16_MAX) + { + break; + } + std::string *s = data->list_entries[i]; + if (s->find(filter) != std::string::npos) + { + list_entries.push_back(data->list_entries[i]); + list_unk1.push_back(data->list_unk1[i]); + list_unk2.push_back(data->list_unk2[i]); + list_unk3.push_back(data->list_unk3[i]); + // this should be small enough to fit in an int16_t + list_visible.push_back(int16_t(list_entries.size() - 1)); + } + } + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + if (mode == T_mode::Material) + { + for (auto key : *input) + { + if (key == LEAVESCREEN || key == SELECT) + { + INTERPOSE_NEXT(feed)(input); + unregister_screen(this); + return; + } + else if (key == STANDARDSCROLL_UP || key == STANDARDSCROLL_DOWN || + key == STANDARDSCROLL_PAGEUP || key == STANDARDSCROLL_PAGEDOWN) + { + INTERPOSE_NEXT(feed)(input); + } + int ch = Screen::keyToChar(key); + if (ch != -1) + { + if (ch == 0) + { + if (!filter.empty()) + { + filter.erase(filter.size() - 1); + } + } + else + { + filter += tolower(char(ch)); + } + apply_filter(); + } + } + } + else + { + INTERPOSE_NEXT(feed)(input); + if (mode == T_mode::Material) + { + register_screen(this); + apply_filter(); + } + } + } +}; + +std::map condition_material_hook::order_mat_data; + +IMPLEMENT_VMETHOD_INTERPOSE(condition_material_hook, feed); From c244901c165b038abbe6d650aa5ca86076a90ad5 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 29 Mar 2017 20:22:09 +0530 Subject: [PATCH 0447/1012] Removed end of line space. (Sorry, travis) --- plugins/remotefortressreader/building_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 1a49a7c02..5d1ce6ffa 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -22,7 +22,7 @@ using namespace df::enums; using namespace RemoteFortressReader; using namespace std; -DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::BuildingList *out) +DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::BuildingList *out) { FOR_ENUM_ITEMS(building_type, bt) { From bbaf3210f159f213b59e9880f5659a714f4a9bd3 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 29 Mar 2017 21:58:12 +0530 Subject: [PATCH 0448/1012] Send DF version info over remoteFortressReader --- plugins/proto/RemoteFortressReader.proto | 7 +++++ .../remotefortressreader.cpp | 31 +++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index d8a0b0a61..d9bb581b3 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -764,3 +764,10 @@ message SingleBool { optional bool Value = 1; } + +message VersionInfo +{ + optional string dwarf_fortress_version = 1; + optional string dfhack_version = 2; + optional string remote_fortress_reader_version = 3; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 8274b7db5..b976ef644 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,4 +1,5 @@ -#define DF_VERSION 42004 +#define DF_VERSION_INT 42004 +#define RFR_VERSION "0.16.1" #include #include @@ -16,6 +17,7 @@ #include "SDL_events.h" #include "SDL_keyboard.h" #include "TileTypes.h" +#include "DFHackVersion.h" #include "modules/Gui.h" #include "modules/Items.h" @@ -89,7 +91,7 @@ #include "df/world_site_realization.h" #include "df/entity_position.h" -#if DF_VERSION > 40001 +#if DF_VERSION_INT > 40001 #include "df/plant_growth.h" #include "df/plant_growth_print.h" #include "df/plant_tree_info.h" @@ -140,6 +142,7 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve static command_result SendDigCommand(color_ostream &stream, const DigCommand *in); static command_result SetPauseState(color_ostream & stream, const SingleBool * in); static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); +static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); @@ -377,7 +380,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m case df::enums::tiletype_material::RIVER: return RemoteFortressReader::RIVER; break; -#if DF_VERSION > 40001 +#if DF_VERSION_INT > 40001 case df::enums::tiletype_material::ROOT: return RemoteFortressReader::ROOT; break; @@ -438,7 +441,7 @@ RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special spec case df::enums::tiletype_special::TRACK: return RemoteFortressReader::TRACK; break; -#if DF_VERSION > 40001 +#if DF_VERSION_INT > 40001 case df::enums::tiletype_special::SMOOTH_DEAD: return RemoteFortressReader::SMOOTH_DEAD; break; @@ -496,17 +499,17 @@ RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) case df::enums::tiletype_shape::BROOK_TOP: return RemoteFortressReader::BROOK_TOP; break; -#if DF_VERSION > 40001 +#if DF_VERSION_INT > 40001 case df::enums::tiletype_shape::BRANCH: return RemoteFortressReader::BRANCH; break; #endif -#if DF_VERSION < 40001 +#if DF_VERSION_INT < 40001 case df::enums::tiletype_shape::TREE: return RemoteFortressReader::TREE_SHAPE; break; #endif -#if DF_VERSION > 40001 +#if DF_VERSION_INT > 40001 case df::enums::tiletype_shape::TRUNK_BRANCH: return RemoteFortressReader::TRUNK_BRANCH; @@ -898,7 +901,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i basePlant->set_name(pp->name); basePlant->mutable_mat_pair()->set_mat_type(-1); basePlant->mutable_mat_pair()->set_mat_index(i); -#if DF_VERSION > 40001 +#if DF_VERSION_INT > 40001 for (int g = 0; g < pp->growths.size(); g++) { df::plant_growth* growth = pp->growths[g]; @@ -1423,7 +1426,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in int max_y = in->max_y() / 3; int max_z = in->max_z(); -#if DF_VERSION < 40001 +#if DF_VERSION_INT < 40001 //plants are gotten differently here #else for (int xx = min_x; xx < max_x; xx++) @@ -2564,4 +2567,12 @@ static command_result GetPauseState(color_ostream &stream, const EmptyMessage *i { out->set_value(World::ReadPauseState()); return CR_OK; -} \ No newline at end of file +} + +command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) +{ + out->set_dfhack_version(DFHACK_VERSION); + out->set_dwarf_fortress_version(DF_VERSION); + out->set_remote_fortress_reader_version(RFR_VERSION); + return command_result(); +} From 7c626ba3c25494d08c10f0c4af183cb32c9acae3 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 29 Mar 2017 22:05:49 +0530 Subject: [PATCH 0449/1012] Actually add the versionInfo function to the RPC list. --- plugins/remotefortressreader/remotefortressreader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index b976ef644..866dd8d42 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -250,6 +250,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("SendDigCommand", SendDigCommand); svc->addFunction("SetPauseState", SetPauseState); svc->addFunction("GetPauseState", GetPauseState); + svc->addFunction("GetVersionInfo", GetVersionInfo); return svc; } From 06225dd896562901ecc275493aae81bda2e3c751 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Thu, 30 Mar 2017 16:23:14 +0530 Subject: [PATCH 0450/1012] Send building items and items kept in buildings separately. --- .../remotefortressreader.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 866dd8d42..507bd5349 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1170,14 +1170,22 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net continue; auto out_bld = NetBlock->add_buildings(); CopyBuilding(i, out_bld); - df::building_actual* actualBuilding = strict_virtual_cast(bld); + df::building_actual* actualBuilding = virtual_cast(bld); if (actualBuilding) { for (int i = 0; i < actualBuilding->contained_items.size(); i++) { - auto buildingItem = out_bld->add_items(); - buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); - CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); + if (actualBuilding->contained_items[i]->use_mode == 0) + { + if (isItemChanged(actualBuilding->contained_items[i]->item->id)) + CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); + } + else + { + auto buildingItem = out_bld->add_items(); + buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); + CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); + } } } } From 9edfeb5b8c719782727fc13663411b420c93c246 Mon Sep 17 00:00:00 2001 From: Paul Fenwick Date: Mon, 27 Mar 2017 10:59:09 +1100 Subject: [PATCH 0451/1012] README.md: Fix broken markdown links and header. Github's markdown parser does not permit newlines between link titles and link text. This change removes the newlines, resulting in a less pleasing source, but one which renders when viewed on github. Github also requires a space between the '#' and text of headings, so we fix that, too. --- README.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 50769e0c5..c3d51c4d1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ -#DFHack Readme +# DFHack Readme -[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)] -(https://travis-ci.org/DFHack/dfhack) -[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)] -(https://dfhack.readthedocs.org) -[![License](https://img.shields.io/badge/license-ZLib-blue.svg)] -(https://en.wikipedia.org/wiki/Zlib_License) -[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)] -(https://github.com/DFHack/dfhack/issues) -[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)] -(https://github.com/DFHack/dfhack/pulls) +[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack) +[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org) +[![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License) +[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)](https://github.com/DFHack/dfhack/issues) +[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)](https://github.com/DFHack/dfhack/pulls) DFHack is a Dwarf Fortress memory access library, distributed with scripts and plugins implementing a wide variety of useful functions and tools. @@ -19,6 +14,6 @@ from the README.html page in the DFHack distribution, or as raw text in the `./d If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. -If that's unclear or you need more help, try [the Bay12 forums thread] -(http://www.bay12forums.com/smf/index.php?topic=139553) or the #dfhack IRC -channel on freenode. +If that's unclear or you need more help, try +[the Bay12 forums thread](http://www.bay12forums.com/smf/index.php?topic=139553) +or the #dfhack IRC channel on freenode. From 93a977bf5c5cdda5f2a1efeb2cb45fba9dc3e494 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 30 Mar 2017 23:57:55 -0400 Subject: [PATCH 0452/1012] Add @pjf to Authors.rst Ref #1074 --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index c606fbe38..39f2a0301 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -75,6 +75,7 @@ Nick Rart nickrart comestible Nikolay Amiantov abbradar nocico nocico Omniclasm +Paul Fenwick pjf PeridexisErrant PeridexisErrant Petr Mrázek peterix potato From e36e4fa1c16cdf9f0398a43e2be09ba8efb49daa Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Wed, 5 Apr 2017 15:35:48 +0530 Subject: [PATCH 0453/1012] Support archery target rotation in RemoteFortressReader --- .../remotefortressreader/building_reader.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 5d1ce6ffa..4e31e0360 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -1,6 +1,7 @@ #include "building_reader.h" #include "DataDefs.h" +#include "df/building_archerytargetst.h" #include "df/building_axle_horizontalst.h" #include "df/building_bridgest.h" #include "df/building_def_furnacest.h" @@ -389,6 +390,30 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re case df::enums::building_type::Support: break; case df::enums::building_type::ArcheryTarget: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto facing = actual->archery_direction; + switch (facing) + { + case df::building_archerytargetst::TopToBottom: + remote_build->set_direction(NORTH); + break; + case df::building_archerytargetst::BottomToTop: + remote_build->set_direction(SOUTH); + break; + case df::building_archerytargetst::LeftToRight: + remote_build->set_direction(WEST); + break; + case df::building_archerytargetst::RightToLeft: + remote_build->set_direction(EAST); + break; + default: + break; + } + } + } break; case df::enums::building_type::Chain: break; From 06737644cdfb23445f0b98bc11783c0a1f36555b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 19 Apr 2017 13:31:25 -0400 Subject: [PATCH 0454/1012] new tweak: hotkey-clear --- dfhack.init-example | 3 +++ docs/Plugins.rst | 1 + plugins/tweak/tweak.cpp | 6 +++++ plugins/tweak/tweaks/hotkey-clear.h | 41 +++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 plugins/tweak/tweaks/hotkey-clear.h diff --git a/dfhack.init-example b/dfhack.init-example index f8878ea0a..a6fd6ffba 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -184,6 +184,9 @@ tweak import-priority-category # Fixes a crash in the work order contition material list (bug 9905). tweak condition-material +# Adds an option to clear currently-bound hotkeys +tweak hotkey-clear + # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled tweak civ-view-agreement diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c81b284c3..568688a53 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -287,6 +287,7 @@ Subcommands that persist until disabled or DF quits: 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 +:hotkey-clear: Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu) :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 1bab3ebcf..a9ef091c7 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -90,6 +90,7 @@ #include "tweaks/fast-trade.h" #include "tweaks/fps-min.h" #include "tweaks/hide-priority.h" +#include "tweaks/hotkey-clear.h" #include "tweaks/import-priority-category.h" #include "tweaks/kitchen-keys.h" #include "tweaks/kitchen-prefs-color.h" @@ -205,6 +206,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode == df::ui_sidebar_mode::Hotkeys) + { + int x = 26, y = 19; + OutputHotkeyString(x, y, "Clear", df::interface_key::CUSTOM_C, false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (ui->main.mode == df::ui_sidebar_mode::Hotkeys && + input->count(df::interface_key::CUSTOM_C) && + !ui->main.in_rename_hotkey) + { + auto &hotkey = ui->main.hotkeys[ui->main.selected_hotkey]; + hotkey.name = ""; + hotkey.cmd = df::ui_hotkey::T_cmd::None; + hotkey.x = 0; + hotkey.y = 0; + hotkey.z = 0; + hotkey.unit_id = 0; + hotkey.item_id = 0; + } + else + { + INTERPOSE_NEXT(feed)(input); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(hotkey_clear_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(hotkey_clear_hook, render); From 6c616e349ca9b86fcbfe42fc624374162b628bba Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 23 Apr 2017 17:13:59 -0400 Subject: [PATCH 0455/1012] Update news --- NEWS.rst | 25 +++++++++++++++++++---- docs/NEWS-dev.rst | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 4229da17f..4c9b01f00 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -14,7 +14,7 @@ Internals Lua - New [Internal Commands | Plugins | Scripts | Features] + New [Internal Commands | Plugins | Scripts | Tweaks | Features] Fixes Misc Improvements Removed @@ -46,6 +46,7 @@ Internals - Several structure fixes to match 64-bit DF's memory layout - Added ``DFHack::Job::removeJob()`` function - Updated TinyXML from 2.5.3 to 2.6.2 +- Added the ability to download files manually before building Lua --- @@ -56,6 +57,7 @@ Lua - ``df.new()`` supports more types: ``char``, ``intptr_t``, ``uintptr_t``, ``long``, ``unsigned long`` - String representations of vectors and a few other containers now include their lengths - Added a ``tile-material`` module +- Added a ``Painter:key_string()`` method Ruby ---- @@ -71,33 +73,46 @@ New Plugins New Scripts ----------- +- `adv-rumors`: improves the "Bring up specific incident or rumor" menu in adventure mode - `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. +- `install-info`: Logs basic troubleshooting information about the current DFHack installation - `load-save`: loads a save non-interactively - `modtools/change-build-menu`: Edit the build mode sidebar menus - `modtools/if-entity`: Run a command if the current entity matches a given ID - `season-palette`: Swap color palettes with the changes of the seasons +New Tweaks +---------- +- `tweak condition-material `: fixes a crash in the work order condition material list +- `tweak hotkey-clear `: adds an option to clear bindings from DF hotkeys + Fixes ----- - The DF path on OS X can now contain spaces and ``:`` characters - Buildings::setOwner() changes now persist properly when saved - ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable +- Fixed ``plug`` output alignment for plugins with long names - `add-thought`: fixed support for emotion names -- `autofarm`: Made surface farms detect local biome - `devel/find-offsets`: fixed a crash when vtables used by globals aren't available - `manipulator`: - Fixed crash when selecting a profession from an empty list - Custom professions are now sorted alphabetically more reliably -- `modtools/create-unit`: stopped permanently overwriting the creature creation - menu in arena mode +- `modtools/create-unit`: + + - stopped permanently overwriting the creature creation menu in arena mode + - now uses non-English names + +- `modtools/item-trigger`: fixed errors with plant growths +- `remotefortressreader`: fixed a crash when serializing the local map - `title-version`: now hidden when loading an arena Misc Improvements ----------------- - Documented all default keybindings (from :file:`dfhack.init-example`) in the docs for the relevant commands; updates enforced by build system. +- `gui/extended-status`: added a feature to queue beds - `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.) - `remotefortressreader`: Added support for @@ -106,6 +121,8 @@ Misc Improvements - wall info - site towers, world buildings - surface material + - building items + - DF version info - `title-version`: Added a prerelease indicator diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index df74735c0..2be774672 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,57 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.43.05-beta2 +==================== + +Fixes +----- +- Fixed Buildings::updateBuildings(), along with building creation/deletion events +- Fixed ``plug`` output alignment for plugins with long names +- Fixed a crash that happened when a ``LUA_PATH`` environment variable was set +- `add-thought`: fixed number conversion +- `gui/workflow`: fixed range editing producing the wrong results for certain numbers +- `modtools/create-unit`: now uses non-English names +- `modtools/item-trigger`: fixed errors with plant growths +- `remotefortressreader`: fixed a crash when serializing the local map +- `stockflow`: fixed an issue with non-integer manager order limits +- `title-folder`: fixed compatibility issues with certain SDL libraries on macOS + +Structures +---------- +- Added some missing renderer VTable addresses on macOS +- ``entity.resources.organic``: identified ``parchment`` +- ``entity_sell_category``: added ``Parchment`` and ``CupsMugsGoblets`` +- ``ui_advmode_menu``: added ``Build`` +- ``ui_unit_view_mode``: added ``PrefOccupation`` +- ``unit_skill``: identified ``natural_skill_lvl`` (was ``unk_1c``) +- ``viewscreen_jobmanagementst``: identified ``max_workshops`` +- ``viewscreen_overallstatusst``: made ``visible_pages`` an enum +- ``viewscreen_pricest``: identified fields +- ``viewscreen_workquota_conditionst``: gave some fields ``unk`` names + +API Changes +----------- +- Allowed the Lua API to accept integer-like floats and strings when expecting an integer +- Lua: New ``Painter:key_string()`` method +- Lua: Added ``dfhack.getArchitecture()`` and ``dfhack.getArchitectureName()`` + +Additions/Removals: +------------------- +- Added `adv-rumors` script: improves the "Bring up specific incident or rumor" menu in adventure mode +- Added `install-info` script for basic troubleshooting +- Added `tweak condition-material `: fixes a crash in the work order condition material list +- Added `tweak hotkey-clear `: adds an option to clear bindings from DF hotkeys +- `autofarm`: reverted local biome detection (from 0.43.05-alpha3) + +Other Changes +------------- +- Added a DOWNLOAD_RUBY CMake option, to allow use of a system/external ruby library +- Added the ability to download files manually before building +- `gui/extended-status`: added a feature to queue beds +- `remotefortressreader`: added building items, DF version info +- `stonesense`: Added support for 64-bit macOS and Linux + DFHack 0.43.05-beta1 ==================== From 3cfbd735dcf5db19bed18ee155c113eef161b86e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 23 Apr 2017 17:14:22 -0400 Subject: [PATCH 0456/1012] Update xml, stonesense, scripts --- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index 9b834c089..f550d736d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9b834c089efb4657d43a8fa4f8f0822e8224e576 +Subproject commit f550d736d5dc12d9218a152dc25029287567c485 diff --git a/plugins/stonesense b/plugins/stonesense index 1925760b2..00f0782bb 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 1925760b2f611d246d1715a2e3cfb591a02ef00b +Subproject commit 00f0782bbbdc1a739da163a2ac57ef350fa84ec6 diff --git a/scripts b/scripts index cf367974b..0e22223f2 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit cf367974b5e1513d454b2988d45122e98cd28f52 +Subproject commit 0e22223f2dd48314722f9d68ac95e92f059c05a9 From 22db0e4a1edb1b67deb45ef8fa5fbb220e320b4e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 23 Apr 2017 17:23:38 -0400 Subject: [PATCH 0457/1012] Bump to 0.43.05-beta2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7c0835a4..615ce4e95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "beta1") +SET(DFHACK_RELEASE "beta2") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 9d5b69cb20c08a88306232380370a11439cc304c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 23 Apr 2017 18:16:00 -0400 Subject: [PATCH 0458/1012] git-describe.cmake: use 8-digit git commit abbreviations The length here was previously determined automatically, and jumped from 7 to 8 recently, breaking devel/check-release. Since Git seems to think 8 characters is better now, specify it explicitly to avoid future breakages. --- library/git-describe.cmake | 4 ++-- scripts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 9a22617ad..3fb3095ff 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,10 +1,10 @@ -execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long +execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --long WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_COMMIT) -execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --exact-match +execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --exact-match WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" RESULT_VARIABLE DFHACK_GIT_TAGGED_RESULT OUTPUT_QUIET ERROR_QUIET) diff --git a/scripts b/scripts index 0e22223f2..95dbacdab 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 0e22223f2dd48314722f9d68ac95e92f059c05a9 +Subproject commit 95dbacdab21ce24dbc25aacc8173d370b099bce9 From bc478039483e3bbadf9e74a222fc0656c87c606e Mon Sep 17 00:00:00 2001 From: Amostubal Date: Mon, 24 Apr 2017 07:07:08 -0500 Subject: [PATCH 0459/1012] fix of relations errors. There is a lot more errors in this file, mostly called unk variables... I bet they have names, I've tried to figure out what they were renamed too, but I'm not fully understanding the xml. --- library/lua/makeown.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/lua/makeown.lua b/library/lua/makeown.lua index bf3d8077c..2b16db1df 100644 --- a/library/lua/makeown.lua +++ b/library/lua/makeown.lua @@ -144,14 +144,14 @@ function make_citizen(unit) hf.caste = unit.caste hf.sex = unit.sex hf.appeared_year = dfg.cur_year - hf.born_year = unit.relations.birth_year - hf.born_seconds = unit.relations.birth_time - hf.curse_year = unit.relations.curse_year - hf.curse_seconds = unit.relations.curse_time - hf.anon_1 = unit.relations.anon_2 - hf.anon_2 = unit.relations.anon_3 - hf.old_year = unit.relations.old_year - hf.old_seconds = unit.relations.old_time + hf.born_year = unit.birth_year + hf.born_seconds = unit.birth_time + hf.curse_year = unit.curse_year + hf.curse_seconds = unit.curse_time + hf.birth_year_bias=unit.bias_birth_bias + hf.birth_time_bias=unit.birth_time_bias + hf.old_year = unit.old_year + hf.old_seconds = unit.old_time hf.died_year = -1 hf.died_seconds = -1 hf.name:assign(unit.name) @@ -299,4 +299,4 @@ end -return _ENV \ No newline at end of file +return _ENV From 71b553b3052a14e36fe0c24f8bb06e20f3b06414 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 28 Apr 2017 17:25:58 -0400 Subject: [PATCH 0460/1012] Add a few functions to retrieve unit social activities --- library/LuaApi.cpp | 4 ++++ library/include/modules/Units.h | 6 ++++++ library/modules/Units.cpp | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index a2446c78a..c2621699f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -62,6 +62,8 @@ distribution. #include "MiscUtils.h" +#include "df/activity_entry.h" +#include "df/activity_event.h" #include "df/job.h" #include "df/job_item.h" #include "df/building.h" @@ -1580,6 +1582,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isUndead), WRAPM(Units, isGelded), WRAPM(Units, isDomesticated), + WRAPM(Units, getMainSocialActivity), + WRAPM(Units, getMainSocialEvent), { NULL, NULL } }; diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 79b8ff18c..08baf98d6 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -41,6 +41,8 @@ distribution. namespace df { + struct activity_entry; + struct activity_event; struct nemesis_record; struct burrow; struct identity; @@ -301,6 +303,10 @@ DFHACK_EXPORT int8_t getProfessionColor(df::unit *unit, bool ignore_noble = fals DFHACK_EXPORT int8_t getCasteProfessionColor(int race, int caste, df::profession pid); DFHACK_EXPORT std::string getSquadName(df::unit *unit); + +DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit); +DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit); + } } #endif diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 0596d529f..94b11b87e 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -48,6 +48,7 @@ using namespace std; #include "Core.h" #include "MiscUtils.h" +#include "df/activity_entry.h" #include "df/burrow.h" #include "df/caste_raw.h" #include "df/creature_raw.h" @@ -1831,6 +1832,24 @@ std::string Units::getSquadName(df::unit *unit) return Translation::TranslateName(&squad->name, true); } +df::activity_entry *Units::getMainSocialActivity(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->social_activities.empty()) + return nullptr; + + return df::activity_entry::find(unit->social_activities[unit->social_activities.size() - 1]); +} + +df::activity_event *Units::getMainSocialEvent(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + df::activity_entry *entry = getMainSocialActivity(unit); + if (!entry || entry->events.empty()) + return nullptr; + return entry->events[entry->events.size() - 1]; +} + bool Units::isMerchant(df::unit* unit) { CHECK_NULL_POINTER(unit); From 690fec9d81fe5330fc6cecee9c3d45e776773f38 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 28 Apr 2017 17:26:20 -0400 Subject: [PATCH 0461/1012] manipulator: show social activities in job column --- plugins/manipulator.cpp | 49 +++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 76b8251b5..2940b493b 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -15,8 +15,9 @@ #include #include #include - #include + +#include "df/activity_event.h" #include "df/world.h" #include "df/ui.h" #include "df/graphic.h" @@ -289,7 +290,8 @@ struct UnitInfo int active_index; string squad_effective_name; string squad_info; - string job_info; + string job_desc; + enum { IDLE, SOCIAL, JOB } job_mode; bool selected; struct { // Used for custom professions, 1-indexed @@ -363,16 +365,18 @@ bool sortBySquad (const UnitInfo *d1, const UnitInfo *d2) bool sortByJob (const UnitInfo *d1, const UnitInfo *d2) { - bool gt = false; + if (d1->job_mode != d2->job_mode) + { + if (descending) + return int(d1->job_mode) < int(d2->job_mode); + else + return int(d1->job_mode) > int(d2->job_mode); + } - if (d1->job_info == "Idle") - gt = false; - else if (d2->job_info == "Idle") - gt = true; + if (descending) + return d1->job_desc > d2->job_desc; else - gt = (d1->job_info > d2->job_info); - - return descending ? gt : !gt; + return d1->job_desc < d2->job_desc; } bool sortByStress (const UnitInfo *d1, const UnitInfo *d2) @@ -1234,9 +1238,18 @@ void viewscreen_unitlaborsst::refreshNames() cur->profession = Units::getProfessionName(unit); if (unit->job.current_job == NULL) { - cur->job_info = "Idle"; + df::activity_event *event = Units::getMainSocialEvent(unit); + if (event) { + event->getName(unit->id, &cur->job_desc); + cur->job_mode = UnitInfo::SOCIAL; + } + else { + cur->job_desc = "Idle"; + cur->job_mode = UnitInfo::IDLE; + } } else { - cur->job_info = DFHack::Job::getName(unit->job.current_job); + cur->job_desc = DFHack::Job::getName(unit->job.current_job); + cur->job_mode = UnitInfo::JOB; } if (unit->military.squad_id > -1) { cur->squad_effective_name = Units::getSquadName(unit); @@ -1283,7 +1296,7 @@ void viewscreen_unitlaborsst::calcSize() if (detail_mode == DETAIL_MODE_SQUAD) { detail_cmp = units[i]->squad_info.size(); } else if (detail_mode == DETAIL_MODE_JOB) { - detail_cmp = units[i]->job_info.size(); + detail_cmp = units[i]->job_desc.size(); } else { detail_cmp = units[i]->profession.size(); } @@ -1954,11 +1967,13 @@ void viewscreen_unitlaborsst::render() fg = 11; detail_str = cur->squad_info; } else if (detail_mode == DETAIL_MODE_JOB) { - detail_str = cur->job_info; - if (detail_str == "Idle") { - fg = 14; + detail_str = cur->job_desc; + if (cur->job_mode == UnitInfo::IDLE) { + fg = COLOR_YELLOW; + } else if (cur->job_mode == UnitInfo::SOCIAL) { + fg = COLOR_LIGHTGREEN; } else { - fg = 10; + fg = COLOR_LIGHTCYAN; } } else { fg = cur->color; From 932a49e89e9d05d084ff139fe2675620fa3fd64d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 29 Apr 2017 11:06:29 -0400 Subject: [PATCH 0462/1012] Re-add workflow keybindings Several users have asked to have these back, and they don't interfere with the game unless workflow is enabled. This reverts part of commit b68cff0384faa936984206c34563be9a9daeb0f7. --- dfhack.init-example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index a6fd6ffba..a93585d14 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -135,6 +135,10 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job +# workflow front-end +keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +keybinding add Alt-W@overallstatus "gui/workflow status" + # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" From 3d86025016e78b13d1bff582b1103b317bbd045a Mon Sep 17 00:00:00 2001 From: Paul Fenwick Date: Mon, 27 Mar 2017 10:59:09 +1100 Subject: [PATCH 0463/1012] README.md: Fix broken markdown links and header. Github's markdown parser does not permit newlines between link titles and link text. This change removes the newlines, resulting in a less pleasing source, but one which renders when viewed on github. Github also requires a space between the '#' and text of headings, so we fix that, too. --- README.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 50769e0c5..c3d51c4d1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ -#DFHack Readme +# DFHack Readme -[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)] -(https://travis-ci.org/DFHack/dfhack) -[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)] -(https://dfhack.readthedocs.org) -[![License](https://img.shields.io/badge/license-ZLib-blue.svg)] -(https://en.wikipedia.org/wiki/Zlib_License) -[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)] -(https://github.com/DFHack/dfhack/issues) -[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)] -(https://github.com/DFHack/dfhack/pulls) +[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack) +[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org) +[![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License) +[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)](https://github.com/DFHack/dfhack/issues) +[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)](https://github.com/DFHack/dfhack/pulls) DFHack is a Dwarf Fortress memory access library, distributed with scripts and plugins implementing a wide variety of useful functions and tools. @@ -19,6 +14,6 @@ from the README.html page in the DFHack distribution, or as raw text in the `./d If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. -If that's unclear or you need more help, try [the Bay12 forums thread] -(http://www.bay12forums.com/smf/index.php?topic=139553) or the #dfhack IRC -channel on freenode. +If that's unclear or you need more help, try +[the Bay12 forums thread](http://www.bay12forums.com/smf/index.php?topic=139553) +or the #dfhack IRC channel on freenode. From 03f56f73154955b363cfb7affe50102517a40e8e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 30 Apr 2017 17:06:57 -0400 Subject: [PATCH 0464/1012] gunzip.pl: use binary output mode Fixes an issue with LF being turned into CRLF on Windows --- depends/gunzip.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/gunzip.pl b/depends/gunzip.pl index 4a21daafd..caabad5a8 100755 --- a/depends/gunzip.pl +++ b/depends/gunzip.pl @@ -18,4 +18,4 @@ if (-f $out_file and !exists($args{'--force'})) { die "output file exists, not overwriting: \"$out_file\""; } -gunzip $in_file => $out_file or die "gunzip failed: $GunzipError\n"; +gunzip $in_file => $out_file, BinModeOut => 1 or die "gunzip failed: $GunzipError\n"; From cd5944689c433398def5141ff5ef255e02b3b519 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 30 Apr 2017 17:36:42 -0400 Subject: [PATCH 0465/1012] Use find_package() to find Perl, and run it earlier Previously, PERL_EXECUTABLE was set manually in library/CMakeLists.txt, which caused gunzip.pl to fail if was run before that happened. --- CMakeLists.txt | 2 ++ library/CMakeLists.txt | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 615ce4e95..186fabf4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,8 @@ IF(CMAKE_CROSSCOMPILING) INCLUDE("${DFHACK_NATIVE_BUILD_DIR}/ImportExecutables.cmake") ENDIF() +find_package(Perl REQUIRED) + # 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/library/CMakeLists.txt b/library/CMakeLists.txt index 9f8477993..7a296dbc7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -16,8 +16,6 @@ ENDIF() include_directories (proto) include_directories (include) -SET(PERL_EXECUTABLE "perl" CACHE FILEPATH "This is the perl executable to run in the codegen step. Tweak it if you need to run a specific one.") - execute_process(COMMAND ${PERL_EXECUTABLE} xml/list.pl xml ${dfapi_SOURCE_DIR}/include/df ";" WORKING_DIRECTORY ${dfapi_SOURCE_DIR} OUTPUT_VARIABLE GENERATED_HDRS) From e3258defd7a7e5910c01a831cb3024fc85f1a1f1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 4 May 2017 14:37:14 -0400 Subject: [PATCH 0466/1012] Expose Gui::revealInDwarfmodeMap() to Lua Closes #1085 --- library/LuaApi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c2621699f..0c153f91e 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1459,6 +1459,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showAutoAnnouncement), + WRAPM(Gui, revealInDwarfmodeMap), { NULL, NULL } }; From 1e469453fcf947744c35c1280890482354cb39dc Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 4 May 2017 21:47:12 -0400 Subject: [PATCH 0467/1012] Add Designations module, currently supporting plant/tree designations This makes it easier for tools to properly handle designating and undesignating trees for chopping and plants for gathering, which changed significantly in 0.40.20. Ref #531 (?), #656, #1014, #1018, #1030, #1076 --- library/CMakeLists.txt | 22 ++--- library/LuaApi.cpp | 14 +++ library/include/modules/Designations.h | 18 ++++ library/modules/Designations.cpp | 119 +++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 library/include/modules/Designations.h create mode 100644 library/modules/Designations.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 7a296dbc7..033c82053 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -113,36 +113,39 @@ SET(MODULE_HEADERS include/modules/Buildings.h include/modules/Burrows.h include/modules/Constructions.h -include/modules/Units.h +include/modules/Designations.h include/modules/Engravings.h include/modules/EventManager.h +include/modules/Filesystem.h +include/modules/Graphic.h include/modules/Gui.h include/modules/GuiHooks.h include/modules/Items.h include/modules/Job.h include/modules/kitchen.h -include/modules/Maps.h include/modules/MapCache.h +include/modules/Maps.h include/modules/Materials.h include/modules/Notes.h +include/modules/Once.h include/modules/Random.h include/modules/Renderer.h include/modules/Screen.h include/modules/Translation.h +include/modules/Units.h include/modules/Vermin.h include/modules/World.h -include/modules/Graphic.h -include/modules/Once.h -include/modules/Filesystem.h ) SET( MODULE_SOURCES modules/Buildings.cpp modules/Burrows.cpp modules/Constructions.cpp -modules/Units.cpp +modules/Designations.cpp modules/Engravings.cpp modules/EventManager.cpp +modules/Filesystem.cpp +modules/Graphic.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp @@ -151,16 +154,15 @@ modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp modules/Notes.cpp +modules/Once.cpp modules/Random.cpp modules/Renderer.cpp modules/Screen.cpp modules/Translation.cpp +modules/Units.cpp modules/Vermin.cpp -modules/World.cpp -modules/Graphic.cpp modules/Windows.cpp -modules/Once.cpp -modules/Filesystem.cpp +modules/World.cpp ) SET(STATIC_FIELDS_FILES) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0c153f91e..fd4ce1c02 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -56,6 +56,7 @@ distribution. #include "modules/Constructions.h" #include "modules/Random.h" #include "modules/Filesystem.h" +#include "modules/Designations.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -92,6 +93,7 @@ distribution. #include "df/itemdef.h" #include "df/enabler.h" #include "df/feature_init.h" +#include "df/plant.h" #include #include @@ -2314,6 +2316,17 @@ static const luaL_Reg dfhack_filesystem_funcs[] = { {NULL, NULL} }; +/***** Designations module *****/ + +static const LuaWrapper::FunctionReg dfhack_designations_module[] = { + WRAPM(Designations, markPlant), + WRAPM(Designations, unmarkPlant), + WRAPM(Designations, canMarkPlant), + WRAPM(Designations, canUnmarkPlant), + WRAPM(Designations, isPlantMarked), + {NULL, NULL} +}; + /***** Internal module *****/ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) @@ -2792,5 +2805,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "constructions", dfhack_constructions_module); OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); + OpenModule(state, "designations", dfhack_designations_module); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } diff --git a/library/include/modules/Designations.h b/library/include/modules/Designations.h new file mode 100644 index 000000000..6dcf85f59 --- /dev/null +++ b/library/include/modules/Designations.h @@ -0,0 +1,18 @@ +#pragma once + +namespace df { + struct plant; +} + +namespace DFHack { + namespace Designations { + // Mark or un-mark a plant (e.g. fell trees, gather plants) + // Return value indicates whether the plant's designation was changed or not + // (This can be false if markPlant() is called on an already-designated plant, for example) + DFHACK_EXPORT bool markPlant(const df::plant *plant); + DFHACK_EXPORT bool unmarkPlant(const df::plant *plant); + DFHACK_EXPORT bool canMarkPlant(const df::plant *plant); + DFHACK_EXPORT bool canUnmarkPlant(const df::plant *plant); + DFHACK_EXPORT bool isPlantMarked(const df::plant *plant); + } +} diff --git a/library/modules/Designations.cpp b/library/modules/Designations.cpp new file mode 100644 index 000000000..8cae35d78 --- /dev/null +++ b/library/modules/Designations.cpp @@ -0,0 +1,119 @@ +#include "DataDefs.h" +#include "Error.h" + +#include "modules/Designations.h" +#include "modules/Job.h" +#include "modules/Maps.h" + +#include "df/job.h" +#include "df/map_block.h" +#include "df/plant.h" +#include "df/tile_dig_designation.h" +#include "df/world.h" + +using namespace DFHack; +using namespace df::enums; + +using df::global::world; + +static df::map_block *getPlantBlock(const df::plant *plant) +{ + if (!world) + return nullptr; + return Maps::getTileBlock(plant->pos); +} + +bool Designations::isPlantMarked(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + df::map_block *block = getPlantBlock(plant); + if (!block) + return false; + + if (block->designation[plant->pos.x % 16][plant->pos.y % 16].bits.dig == tile_dig_designation::Default) + return true; + + for (auto *link = world->job_list.next; link; link = link->next) + { + df::job *job = link->item; + if (!job) + continue; + if (job->job_type != job_type::FellTree && job->job_type != job_type::GatherPlants) + continue; + if (job->pos == plant->pos) + return true; + } + return false; +} + +bool Designations::canMarkPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (!getPlantBlock(plant)) + return false; + + return !isPlantMarked(plant); +} + +bool Designations::markPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (canMarkPlant(plant)) + { + df::map_block *block = getPlantBlock(plant); + block->designation[plant->pos.x % 16][plant->pos.y % 16].bits.dig = tile_dig_designation::Default; + block->flags.bits.designated = true; + return true; + } + else + { + return false; + } +} + +bool Designations::canUnmarkPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (!getPlantBlock(plant)) + return false; + + return isPlantMarked(plant); +} + +bool Designations::unmarkPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (canUnmarkPlant(plant)) + { + df::map_block *block = getPlantBlock(plant); + block->designation[plant->pos.x % 16][plant->pos.y % 16].bits.dig = tile_dig_designation::No; + block->flags.bits.designated = true; + + auto *link = world->job_list.next; + while (link) + { + auto *next = link->next; + df::job *job = link->item; + + if (!job) + continue; + if (job->job_type != job_type::FellTree && job->job_type != job_type::GatherPlants) + continue; + if (job->pos == plant->pos) + Job::removeJob(job); + + link = next; + } + + return true; + } + else + { + return false; + } +} From a527091172b978a3181e56a1eeba0a0fe858e115 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 4 May 2017 21:51:16 -0400 Subject: [PATCH 0468/1012] autochop: Fix marking/unmarking trees (use Designations module) Fixes #656 Fixes #1076 Closes #1030 --- plugins/autochop.cpp | 49 +++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 73a3da24e..13ffa70ea 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -10,24 +10,26 @@ #include "DataDefs.h" #include "TileTypes.h" -#include "df/world.h" -#include "df/map_block.h" -#include "df/tile_dig_designation.h" -#include "df/plant_raw.h" -#include "df/plant.h" -#include "df/ui.h" #include "df/burrow.h" -#include "df/item_flags.h" #include "df/item.h" +#include "df/item_flags.h" #include "df/items_other_id.h" +#include "df/job.h" +#include "df/map_block.h" +#include "df/plant.h" +#include "df/plant_raw.h" +#include "df/tile_dig_designation.h" +#include "df/ui.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" -#include "modules/Screen.h" -#include "modules/Maps.h" #include "modules/Burrows.h" -#include "modules/World.h" -#include "modules/MapCache.h" +#include "modules/Designations.h" #include "modules/Gui.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "modules/World.h" #include @@ -228,38 +230,33 @@ static int do_chop_designation(bool chop, bool count_only) if (!count_only && !watchedBurrows.isValidPos(plant->pos)) continue; - bool dirty = false; - if (chop && cur->designation[x][y].bits.dig == tile_dig_designation::No) + if (chop && !Designations::isPlantMarked(plant)) { if (count_only) { - ++count; + if (Designations::canMarkPlant(plant)) + count++; } else { - cur->designation[x][y].bits.dig = tile_dig_designation::Default; - dirty = true; + if (Designations::markPlant(plant)) + count++; } } - if (!chop && cur->designation[x][y].bits.dig == tile_dig_designation::Default) + if (!chop && Designations::isPlantMarked(plant)) { if (count_only) { - ++count; + if (Designations::canUnmarkPlant(plant)) + count++; } else { - cur->designation[x][y].bits.dig = tile_dig_designation::No; - dirty = true; + if (Designations::unmarkPlant(plant)) + count++; } } - - if (dirty) - { - cur->flags.bits.designated = true; - ++count; - } } return count; From d3c496cc2bf7d96475cb46871eae7233c153918c Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 14:45:46 -0400 Subject: [PATCH 0469/1012] Add getSelectedPlant() and related functions Currently only works with the center tile of multi-tile trees --- docs/Lua API.rst | 4 +++ library/LuaApi.cpp | 1 + library/include/modules/Gui.h | 6 +++++ library/include/modules/Screen.h | 11 +++++--- library/lua/utils.lua | 2 ++ library/modules/Gui.cpp | 45 ++++++++++++++++++++++++++++++++ library/modules/Screen.cpp | 9 +++++++ 7 files changed, 74 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2db98abad..1c08407ae 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -921,6 +921,10 @@ Gui module Returns the building selected via :kbd:`q`, :kbd:`t`, :kbd:`k` or :kbd:`i`. +* ``dfhack.gui.getSelectedPlant([silent])`` + + Returns the plant selected via :kbd:`k`. + * ``dfhack.gui.writeToGamelog(text)`` Writes a string to :file:`gamelog.txt` without doing an announcement. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index fd4ce1c02..2a4fb64d3 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1453,6 +1453,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedBuilding), + WRAPM(Gui, getSelectedPlant), WRAPM(Gui, writeToGamelog), WRAPM(Gui, makeAnnouncement), WRAPM(Gui, addCombatReport), diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index ae53e7603..7ea4d0e48 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -45,6 +45,7 @@ namespace df { struct job; struct unit; struct item; + struct plant; }; /** @@ -104,6 +105,11 @@ namespace DFHack DFHACK_EXPORT df::building *getAnyBuilding(df::viewscreen *top); DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false); + // A plant is selected, e.g. via 'k' + DFHACK_EXPORT bool any_plant_hotkey(df::viewscreen *top); + DFHACK_EXPORT df::plant *getAnyPlant(df::viewscreen *top); + DFHACK_EXPORT df::plant *getSelectedPlant(color_ostream &out, bool quiet = false); + // Low-level API that gives full control over announcements and reports DFHACK_EXPORT void writeToGamelog(std::string message); diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 2c4a29062..4e53b0b54 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -44,6 +44,7 @@ namespace df struct item; struct unit; struct building; + struct plant; } /** @@ -326,10 +327,11 @@ namespace DFHack virtual std::string getFocusString() = 0; virtual void onShow() {}; virtual void onDismiss() {}; - virtual df::unit *getSelectedUnit() { return NULL; } - virtual df::item *getSelectedItem() { return NULL; } - virtual df::job *getSelectedJob() { return NULL; } - virtual df::building *getSelectedBuilding() { return NULL; } + virtual df::unit *getSelectedUnit() { return nullptr; } + virtual df::item *getSelectedItem() { return nullptr; } + virtual df::job *getSelectedJob() { return nullptr; } + virtual df::building *getSelectedBuilding() { return nullptr; } + virtual df::plant *getSelectedPlant() { return nullptr; } }; class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { @@ -369,5 +371,6 @@ namespace DFHack virtual df::item *getSelectedItem(); virtual df::job *getSelectedJob(); virtual df::building *getSelectedBuilding(); + virtual df::plant *getSelectedPlant(); }; } diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 4800042f0..07db41808 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -635,6 +635,8 @@ function df_shortcut_var(k) return dfhack.gui.getSelectedWorkshopJob() elseif k == 'unit' then return dfhack.gui.getSelectedUnit() + elseif k == 'plant' then + return dfhack.gui.getSelectedPlant() else for g in pairs(df.global) do if g == k then diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 9a86e69ec..5ad569c5a 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -92,6 +92,7 @@ using namespace DFHack; #include "df/game_mode.h" #include "df/unit.h" #include "df/occupation.h" +#include "df/plant.h" using namespace df::enums; using df::global::gview; @@ -1120,6 +1121,50 @@ df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet) return building; } +df::plant *Gui::getAnyPlant(df::viewscreen *top) +{ + using df::global::cursor; + using df::global::ui; + using df::global::world; + + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedPlant(); + + if (Gui::dwarfmode_hotkey(top)) + { + if (!cursor || !ui || !world) + return nullptr; + + if (ui->main.mode == ui_sidebar_mode::LookAround) + { + for (df::plant *plant : world->plants.all) + { + if (plant->pos.x == cursor->x && plant->pos.y == cursor->y && plant->pos.z == cursor->z) + { + return plant; + } + } + } + } + + return nullptr; +} + +bool Gui::any_plant_hotkey(df::viewscreen *top) +{ + return getAnyPlant(top) != nullptr; +} + +df::plant *Gui::getSelectedPlant(color_ostream &out, bool quiet) +{ + df::plant *plant = getAnyPlant(Core::getTopViewscreen()); + + if (!plant && !quiet) + out.printerr("No plant is selected in the UI.\n"); + + return plant; +} + // DFHACK_EXPORT void Gui::writeToGamelog(std::string message) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 71103ffda..0b9a500fa 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -57,6 +57,7 @@ using namespace DFHack; #include "df/job.h" #include "df/building.h" #include "df/renderer.h" +#include "df/plant.h" using namespace df::enums; using df::global::init; @@ -934,3 +935,11 @@ df::building *dfhack_lua_viewscreen::getSelectedBuilding() safe_call_lua(do_notify, 1, 1); return Lua::GetDFObject(Lua::Core::State, -1); } + +df::plant *dfhack_lua_viewscreen::getSelectedPlant() +{ + Lua::StackUnwinder frame(Lua::Core::State); + lua_pushstring(Lua::Core::State, "onGetSelectedPlant"); + safe_call_lua(do_notify, 1, 1); + return Lua::GetDFObject(Lua::Core::State, -1); +} From b7b83e5ce1f1ce04b1b46f64f31a647db6219c1d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 14:46:57 -0400 Subject: [PATCH 0470/1012] Fix major errors in autochop docs --- docs/Plugins.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 568688a53..4b5985ae7 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1657,13 +1657,14 @@ quotas. Open the dashboard by running:: - getplants autochop + enable autochop -The plugin must be activated (with ``c``) before it can be used. You can then set logging quotas -and restrict designations to specific burrows (with 'Enter') if desired. The plugin's activity -cycle runs once every in game day. +The plugin must be activated (with :kbd:`d`-:kbd:`t`-:kbd:`c`-:kbd:`a`) before +it can be used. You can then set logging quotas and restrict designations to +specific burrows (with 'Enter') if desired. The plugin's activity cycle runs +once every in game day. -If you add ``enable getplants`` to your dfhack.init there will be a hotkey to +If you add ``enable autochop`` to your dfhack.init there will be a hotkey to open the dashboard from the chop designation menu. From 81610d0259d656526aca2d83f6a3081b9af6f22f Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 14:49:32 -0400 Subject: [PATCH 0471/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index f550d736d..b48397cf5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f550d736d5dc12d9218a152dc25029287567c485 +Subproject commit b48397cf5b4c954a098151943f7314a1a6eacf90 From b92cb6dcdbe8085b11454286b2fe8afb9544bbc3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 14:50:02 -0400 Subject: [PATCH 0472/1012] Update scripts/autounsuspend --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 95dbacdab..1d768b69f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 95dbacdab21ce24dbc25aacc8173d370b099bce9 +Subproject commit 1d768b69f640ce7a00713c0ce9a9e6ca1d557218 From adb14491d014e6a5d5d608bd19090f556e6429ef Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 17:24:51 -0400 Subject: [PATCH 0473/1012] Fix loop in Designations::unmarkPlant() --- library/modules/Designations.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/modules/Designations.cpp b/library/modules/Designations.cpp index 8cae35d78..87b935cc3 100644 --- a/library/modules/Designations.cpp +++ b/library/modules/Designations.cpp @@ -100,12 +100,12 @@ bool Designations::unmarkPlant(const df::plant *plant) auto *next = link->next; df::job *job = link->item; - if (!job) - continue; - if (job->job_type != job_type::FellTree && job->job_type != job_type::GatherPlants) - continue; - if (job->pos == plant->pos) + if (job && + (job->job_type == job_type::FellTree || job->job_type == job_type::GatherPlants) && + job->pos == plant->pos) + { Job::removeJob(job); + } link = next; } From 5c784f4ba3ceed96764d7e00beb8118296ae343e Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 17:31:54 -0400 Subject: [PATCH 0474/1012] autochop: Avoid moving menu options around when toggling burrows --- plugins/autochop.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 13ffa70ea..286910e71 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -587,6 +587,7 @@ public: if (burrows_column.getSelectedElems().size() > 0) { OutputString(COLOR_GREEN, x, y, "Will chop in selected burrows", true, left_margin); + ++y; } else { From adaccceba7f0815c0340d088cd97ce3d6119edcd Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 17:36:02 -0400 Subject: [PATCH 0475/1012] autochop: fix display of unnamed burrows --- plugins/autochop.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 286910e71..dbc79af42 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -387,10 +387,12 @@ public: auto last_selected_index = burrows_column.highlighted_index; burrows_column.clear(); - for (auto iter = ui->burrows.list.begin(); iter != ui->burrows.list.end(); iter++) + for (df::burrow *burrow : ui->burrows.list) { - df::burrow* burrow = *iter; - auto elem = ListEntry(burrow->name, burrow); + string name = burrow->name; + if (name.empty()) + name = "Burrow " + int_to_string(burrow->id + 1); + auto elem = ListEntry(name, burrow); elem.selected = watchedBurrows.isBurrowWatched(burrow); burrows_column.add(elem); } From fbae598086967f2639bb61a2da74138b66e8555d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 17:36:34 -0400 Subject: [PATCH 0476/1012] Add extra gui/workflow binding to work with gui/extended-status --- dfhack.init-example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index a93585d14..19bef37e6 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -138,6 +138,8 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow keybinding add Alt-W@overallstatus "gui/workflow status" +# equivalent to the one above when gui/extended-status is enabled +keybinding add Alt-W@dfhack/lua/status_overlay "gui/workflow status" # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" From 55931703ac23e29089b00d7ff1a379f019f4c062 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 May 2017 21:25:25 -0400 Subject: [PATCH 0477/1012] Designate the correct tile for trees with multi-tile trunks DF always designates the southeast trunk tile, while plant.pos is the center, which is sometimes inaccessible (see #1014) --- library/LuaApi.cpp | 12 +++++- library/include/modules/Designations.h | 3 ++ library/modules/Designations.cpp | 52 +++++++++++++++++++++----- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 2a4fb64d3..266677045 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2328,6 +2328,16 @@ static const LuaWrapper::FunctionReg dfhack_designations_module[] = { {NULL, NULL} }; +static int designations_getPlantDesignationTile(lua_State *state) +{ + return Lua::PushPosXYZ(state, Designations::getPlantDesignationTile(Lua::CheckDFObject(state, 1))); +} + +static const luaL_Reg dfhack_designations_funcs[] = { + {"getPlantDesignationTile", designations_getPlantDesignationTile}, + {NULL, NULL} +}; + /***** Internal module *****/ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) @@ -2806,6 +2816,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "constructions", dfhack_constructions_module); OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); - OpenModule(state, "designations", dfhack_designations_module); + OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } diff --git a/library/include/modules/Designations.h b/library/include/modules/Designations.h index 6dcf85f59..914605e58 100644 --- a/library/include/modules/Designations.h +++ b/library/include/modules/Designations.h @@ -14,5 +14,8 @@ namespace DFHack { DFHACK_EXPORT bool canMarkPlant(const df::plant *plant); DFHACK_EXPORT bool canUnmarkPlant(const df::plant *plant); DFHACK_EXPORT bool isPlantMarked(const df::plant *plant); + + // Return the tile that should be designated for this plant + DFHACK_EXPORT df::coord getPlantDesignationTile(const df::plant *plant); } } diff --git a/library/modules/Designations.cpp b/library/modules/Designations.cpp index 87b935cc3..3fb325096 100644 --- a/library/modules/Designations.cpp +++ b/library/modules/Designations.cpp @@ -8,6 +8,8 @@ #include "df/job.h" #include "df/map_block.h" #include "df/plant.h" +#include "df/plant_tree_info.h" +#include "df/plant_tree_tile.h" #include "df/tile_dig_designation.h" #include "df/world.h" @@ -20,18 +22,48 @@ static df::map_block *getPlantBlock(const df::plant *plant) { if (!world) return nullptr; - return Maps::getTileBlock(plant->pos); + return Maps::getTileBlock(Designations::getPlantDesignationTile(plant)); +} + +df::coord Designations::getPlantDesignationTile(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (!plant->tree_info) + return plant->pos; + + int dimx = plant->tree_info->dim_x; + int dimy = plant->tree_info->dim_y; + int cx = dimx / 2; + int cy = dimy / 2; + + // Find the southeast trunk tile + int x = cx; + int y = cy; + + while (x + 1 < dimx && y + 1 < dimy) + { + if (plant->tree_info->body[0][(y * dimx) + (x + 1)].bits.trunk) + ++x; + else if (plant->tree_info->body[0][((y + 1) * dimx) + x].bits.trunk) + ++y; + else + break; + } + + return df::coord(plant->pos.x - cx + x, plant->pos.y - cy + y, plant->pos.z); } bool Designations::isPlantMarked(const df::plant *plant) { CHECK_NULL_POINTER(plant); - df::map_block *block = getPlantBlock(plant); + df::coord des_pos = getPlantDesignationTile(plant); + df::map_block *block = Maps::getTileBlock(des_pos); if (!block) return false; - if (block->designation[plant->pos.x % 16][plant->pos.y % 16].bits.dig == tile_dig_designation::Default) + if (block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig == tile_dig_designation::Default) return true; for (auto *link = world->job_list.next; link; link = link->next) @@ -41,7 +73,7 @@ bool Designations::isPlantMarked(const df::plant *plant) continue; if (job->job_type != job_type::FellTree && job->job_type != job_type::GatherPlants) continue; - if (job->pos == plant->pos) + if (job->pos == des_pos) return true; } return false; @@ -63,8 +95,9 @@ bool Designations::markPlant(const df::plant *plant) if (canMarkPlant(plant)) { - df::map_block *block = getPlantBlock(plant); - block->designation[plant->pos.x % 16][plant->pos.y % 16].bits.dig = tile_dig_designation::Default; + df::coord des_pos = getPlantDesignationTile(plant); + df::map_block *block = Maps::getTileBlock(des_pos); + block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig = tile_dig_designation::Default; block->flags.bits.designated = true; return true; } @@ -90,8 +123,9 @@ bool Designations::unmarkPlant(const df::plant *plant) if (canUnmarkPlant(plant)) { - df::map_block *block = getPlantBlock(plant); - block->designation[plant->pos.x % 16][plant->pos.y % 16].bits.dig = tile_dig_designation::No; + df::coord des_pos = getPlantDesignationTile(plant); + df::map_block *block = Maps::getTileBlock(des_pos); + block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig = tile_dig_designation::No; block->flags.bits.designated = true; auto *link = world->job_list.next; @@ -102,7 +136,7 @@ bool Designations::unmarkPlant(const df::plant *plant) if (job && (job->job_type == job_type::FellTree || job->job_type == job_type::GatherPlants) && - job->pos == plant->pos) + job->pos == des_pos) { Job::removeJob(job); } From 3dc2c2259b96b1a6bb1e13babf17121c61f0a8ab Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 May 2017 00:11:07 -0400 Subject: [PATCH 0478/1012] Update getplants to use Designations module Fixes #531 Fixes #1014 Fixes #1018 --- plugins/getplants.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 87fc35960..2cf382d01 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -1,5 +1,7 @@ // (un)designate matching plants for gathering/cutting +#include + #include "Core.h" #include "Console.h" #include "Export.h" @@ -7,18 +9,19 @@ #include "DataDefs.h" #include "TileTypes.h" -#include "df/world.h" #include "df/map_block.h" -#include "df/tile_dig_designation.h" -#include "df/plant_raw.h" #include "df/plant.h" +#include "df/plant_raw.h" +#include "df/tile_dig_designation.h" +#include "df/world.h" +#include "modules/Designations.h" #include "modules/Maps.h" -#include using std::string; using std::vector; using std::set; + using namespace DFHack; using namespace df::enums; @@ -129,20 +132,14 @@ command_result df_getplants (color_ostream &out, vector & parameters) continue; if (cur->designation[x][y].bits.hidden) continue; - if (deselect && cur->designation[x][y].bits.dig == tile_dig_designation::Default) + if (deselect && Designations::unmarkPlant(plant)) { - cur->designation[x][y].bits.dig = tile_dig_designation::No; - dirty = true; ++count; } - if (!deselect && cur->designation[x][y].bits.dig == tile_dig_designation::No) + if (!deselect && Designations::markPlant(plant)) { - cur->designation[x][y].bits.dig = tile_dig_designation::Default; - dirty = true; ++count; } - if (dirty) - cur->flags.bits.designated = true; } if (count) out.print("Updated %d plant designations.\n", count); @@ -171,4 +168,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Sat, 6 May 2017 15:15:32 -0400 Subject: [PATCH 0479/1012] Update stonesense - fix #1083 --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 00f0782bb..1ffdc984d 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 00f0782bbbdc1a739da163a2ac57ef350fa84ec6 +Subproject commit 1ffdc984d0c3d50e790b9ff5991c02fb21dec463 From a62a87fe25c7d0729c3c9a973567d65b05f2b2c9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 May 2017 16:43:39 -0400 Subject: [PATCH 0480/1012] Update NEWS.rst --- NEWS.rst | 29 +++++++++++++++++++++++++---- scripts | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 4c9b01f00..7f7695254 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -35,16 +35,19 @@ Changelog .. contents:: :depth: 2 -DFHack future -============= +DFHack 0.43.05-r1 +================= Internals --------- - 64-bit support on all platforms -- Visual Studio 2015 now required on Windows instead of 2010 -- GCC 4.8 or newer required on Linux and OS X (and now supported on OS X) - Several structure fixes to match 64-bit DF's memory layout - Added ``DFHack::Job::removeJob()`` function +- New module: ``Designations`` - handles designation creation (currently for plants only) +- Added ``Gui::getSelectedPlant()`` +- Added ``Units::getMainSocialActivity()``, ``Units::getMainSocialEvent()`` +- Visual Studio 2015 now required to build on Windows instead of 2010 +- GCC 4.8 or newer required to build on Linux and OS X (and now supported on OS X) - Updated TinyXML from 2.5.3 to 2.6.2 - Added the ability to download files manually before building @@ -58,6 +61,7 @@ Lua - String representations of vectors and a few other containers now include their lengths - Added a ``tile-material`` module - Added a ``Painter:key_string()`` method +- Made ``dfhack.gui.revealInDwarfmodeMap()`` available Ruby ---- @@ -80,6 +84,7 @@ New Scripts - `modtools/change-build-menu`: Edit the build mode sidebar menus - `modtools/if-entity`: Run a command if the current entity matches a given ID - `season-palette`: Swap color palettes with the changes of the seasons +- `unforbid`: Unforbids all items New Tweaks ---------- @@ -93,7 +98,20 @@ Fixes - ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable - Fixed ``plug`` output alignment for plugins with long names - `add-thought`: fixed support for emotion names +- `autochop`: + + - fixed several issues with job creation and removal + - stopped designating the center tile (unreachable) for large trees + - stopped options from moving when enabling and disabling burrows + - fixed display of unnamed burrows + - `devel/find-offsets`: fixed a crash when vtables used by globals aren't available +- `getplants`: + + - fixed several issues with job creation and removal + - stopped designating the center tile (unreachable) for large trees + +- `gui/workflow`: added extra keybinding to work with `gui/extended-status` - `manipulator`: - Fixed crash when selecting a profession from an empty list @@ -112,8 +130,10 @@ Misc Improvements ----------------- - Documented all default keybindings (from :file:`dfhack.init-example`) in the docs for the relevant commands; updates enforced by build system. +- `autounsuspend`: reduced update frequency to address potential performance issues - `gui/extended-status`: added a feature to queue beds - `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.) +- `manipulator`: added social activities to job column - `remotefortressreader`: Added support for - world map snow coverage @@ -125,6 +145,7 @@ Misc Improvements - DF version info - `title-version`: Added a prerelease indicator +- `workflow`: Re-added ``Alt-W`` keybindings DFHack 0.43.03-r1 ================= diff --git a/scripts b/scripts index 1d768b69f..e9c3119e7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1d768b69f640ce7a00713c0ce9a9e6ca1d557218 +Subproject commit e9c3119e751c3e2073eb02bbcda01d140cc6ae4a From 60b5786d93e241f463d9b2186340f4227beacd6b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 May 2017 16:51:49 -0400 Subject: [PATCH 0481/1012] Add some missing people to Authors.rst --- docs/Authors.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 39f2a0301..034ff8635 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -12,6 +12,7 @@ Name Github Other 8Z 8Z acwatkins acwatkins Alexander Gavrilov angavrilov ag +Amostubal Amostubal AndreasPK AndreasPK Angus Mezick amezick Antalia tamarakorr @@ -88,7 +89,9 @@ rampaging-poet Raoul van Putten Raoul XQ raoulxq reverb +Rich Rauenzahn rrauenza Rinin Rinin +rndmvar rndmvar Robert Heinrich rh73 Robert Janetzko robertjanetzko rofl0r rofl0r @@ -108,6 +111,7 @@ Simon Jackson sizeak stolencatkarma sv-esk sv-esk Tacomagic +TheHologram TheHologram Tim Walberg twalberg Timothy Collett danaris Tom Jobbins TheBloke From a1f692686ae1d6c55cbacbc26ce968b6e26d569c Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 7 May 2017 19:24:30 +0530 Subject: [PATCH 0482/1012] Use forward slashes instead of underscored to separate building subtypes --- .../remotefortressreader/building_reader.cpp | 18 +++++++++--------- .../remotefortressreader.cpp | 4 ---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 4e31e0360..2b9f95390 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -54,7 +54,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(furnace_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(furnace_type, st)); if (st == furnace_type::Custom) { @@ -66,7 +66,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(cust->id); - bld->set_id(cust->code); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + cust->code); bld->set_name(cust->name); } } @@ -81,7 +81,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(shop_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(shop_type, st)); } break; @@ -102,7 +102,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(workshop_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(workshop_type, st)); if (st == workshop_type::Custom) { @@ -114,7 +114,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(cust->id); - bld->set_id(cust->code); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + cust->code); bld->set_name(cust->name); } } @@ -143,7 +143,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(siegeengine_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(siegeengine_type, st)); } break; @@ -154,7 +154,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(trap_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(trap_type, st)); } break; @@ -177,7 +177,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(civzone_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(civzone_type, st)); } break; @@ -194,7 +194,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D bld->mutable_building_type()->set_building_type(bt); bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(construction_type, st)); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(construction_type, st)); } break; diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 507bd5349..ac06fd160 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -593,7 +593,6 @@ void CopyMat(RemoteFortressReader::MatPair * mat, int type, int index) map hashes; -//check if the tiletypes have changed bool IsTiletypeChanged(DFCoord pos) { uint16_t hash; @@ -612,7 +611,6 @@ bool IsTiletypeChanged(DFCoord pos) map waterHashes; -//check if the designations have changed bool IsDesignationChanged(DFCoord pos) { uint16_t hash; @@ -631,7 +629,6 @@ bool IsDesignationChanged(DFCoord pos) map buildingHashes; -//check if the designations have changed bool IsBuildingChanged(DFCoord pos) { df::map_block * block = Maps::getBlock(pos); @@ -652,7 +649,6 @@ bool IsBuildingChanged(DFCoord pos) map spatterHashes; -//check if map spatters have changed bool IsspatterChanged(DFCoord pos) { df::map_block * block = Maps::getBlock(pos); From 67e60fdaa1844fccda9dd39b6a2b348bc3e499cd Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 7 May 2017 13:23:33 -0600 Subject: [PATCH 0483/1012] Fix issue #874 (revflood doesn't always see past constructed downstairs) --- plugins/reveal.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 20b36467e..314259fd4 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -401,12 +401,25 @@ command_result revflood(color_ostream &out, vector & params) if(!MCache->testCoord(current)) continue; - df::tiletype tt = MCache->baseTiletypeAt(current); df::tile_designation des = MCache->designationAt(current); if(!des.bits.hidden) { continue; } + + // use base tile (beneath constructions/ice), to avoid bug #1871 + df::tiletype tt = MCache->baseTiletypeAt(current); + + // unless the actual tile is a downward stairway + df::tiletype ctt = MCache->tiletypeAt(current); + switch (tileShape(ctt)) + { + case tiletype_shape::STAIR_UPDOWN: + case tiletype_shape::STAIR_DOWN: + tt = ctt; + break; + } + bool below = 0; bool above = 0; bool sides = 0; From 8bda7384660c06a83e95e106be56ff4fcbe090e2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 May 2017 16:31:30 -0400 Subject: [PATCH 0484/1012] df_expr_to_ref: support integer array indices --- library/lua/utils.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 07db41808..a6007a08f 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -659,15 +659,17 @@ df_env = df_shortcut_env() function df_expr_to_ref(expr) expr = expr:gsub('%["(.-)"%]', function(field) return '.' .. field end) :gsub('%[\'(.-)\'%]', function(field) return '.' .. field end) + :gsub('%[(%d+)]', function(field) return '.' .. field end) local parts = split_string(expr, '%.') local obj = df_env[parts[1]] for i = 2, #parts do - local cur = obj[parts[i]] + local key = tonumber(parts[i]) or parts[i] + local cur = obj[key] if i == #parts and ((type(cur) ~= 'userdata') or type(cur) == 'userdata' and getmetatable(cur) == nil) then - obj = obj:_field(parts[i]) + obj = obj:_field(key) else - obj = obj[parts[i]] + obj = obj[key] end end return obj From 4dff21897650e97aadd217bc1510a27d2a4780ba Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 May 2017 13:49:07 -0400 Subject: [PATCH 0485/1012] ruby: fix crash when unloading plugin on Windows --- NEWS.rst | 1 + plugins/ruby/ruby.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 7f7695254..000606863 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -124,6 +124,7 @@ Fixes - `modtools/item-trigger`: fixed errors with plant growths - `remotefortressreader`: fixed a crash when serializing the local map +- `ruby`: fixed a crash when unloading the plugin on Windows - `title-version`: now hidden when loading an arena Misc Improvements diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index ed38868bd..db96e3ed9 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -296,7 +296,7 @@ void (*ruby_sysinit)(int *, const char ***); void (*ruby_init)(void); void (*ruby_init_loadpath)(void); void (*ruby_script)(const char*); -void (*ruby_finalize)(void); +int (*ruby_cleanup)(int); ID (*rb_intern)(const char*); VALUE (*rb_funcall)(VALUE, ID, int, ...); VALUE (*rb_define_module)(const char*); @@ -351,7 +351,7 @@ static int df_loadruby(void) rbloadsym(ruby_init); rbloadsym(ruby_init_loadpath); rbloadsym(ruby_script); - rbloadsym(ruby_finalize); + rbloadsym(ruby_cleanup); rbloadsym(rb_intern); rbloadsym(rb_funcall); rbloadsym(rb_define_module); @@ -476,7 +476,7 @@ static void df_rubythread(void *p) case RB_DIE: running = 0; - ruby_finalize(); + ruby_cleanup(0); break; case RB_EVAL: From 88f9eaeebb2205b70884a19e9a2be25c517f75dc Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 May 2017 13:50:08 -0400 Subject: [PATCH 0486/1012] stonesense: Disable overlay in STANDARD-based modes Closes dfhack/dfhack#1078 --- NEWS.rst | 1 + plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 000606863..5360d77c7 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -125,6 +125,7 @@ Fixes - `modtools/item-trigger`: fixed errors with plant growths - `remotefortressreader`: fixed a crash when serializing the local map - `ruby`: fixed a crash when unloading the plugin on Windows +- `stonesense`: disabled overlay in STANDARD-based print modes to prevent crashes - `title-version`: now hidden when loading an arena Misc Improvements diff --git a/plugins/stonesense b/plugins/stonesense index 1ffdc984d..efe406b77 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 1ffdc984d0c3d50e790b9ff5991c02fb21dec463 +Subproject commit efe406b77ba864eb2a7ca66d1f16816c6e0fab7a From 9e32269e90f5b86e2e2fe5a2a2fec0674a36ac93 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 May 2017 13:52:26 -0400 Subject: [PATCH 0487/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index b48397cf5..665e6d552 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b48397cf5b4c954a098151943f7314a1a6eacf90 +Subproject commit 665e6d552d3390868e6d76acd5f49ebf938adcd1 From 948e9b2e43c5fbf6b5374d03fffb0f886471cedf Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 May 2017 16:39:10 -0400 Subject: [PATCH 0488/1012] Update scripts/prefchange (#1080) --- NEWS.rst | 1 + scripts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 5360d77c7..1e3fac0cb 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -190,6 +190,7 @@ Misc Improvements - `catsplosion`: now a lua script instead of a plugin - `fix/diplomats`: replaces ``fixdiplomats`` - `fix/merchants`: replaces ``fixmerchants`` +- `prefchange`: added a ``help`` option - Unified script documentation and in-terminal help options Removed diff --git a/scripts b/scripts index e9c3119e7..162f3c3bc 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e9c3119e751c3e2073eb02bbcda01d140cc6ae4a +Subproject commit 162f3c3bcc35987c53c6f934b77f007ad175c9b7 From 629b4526c69059149563d19015f8adf7e89e67ec Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 May 2017 10:40:53 -0400 Subject: [PATCH 0489/1012] Update NEWS (#874) --- NEWS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.rst b/NEWS.rst index 1e3fac0cb..9fcfd5fa3 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -179,6 +179,7 @@ Fixes - `gui/create-item`: Fixed quality when creating multiple items - `gui/mod-manager`: Fixed error when mods folder doesn't exist - `modtools/item-trigger`: Fixed handling of items with subtypes +- `reveal`: ``revflood`` now handles constructed stairs with floors in generated fortresses - `stockflow`: - Can order metal mechanisms From d083b92e9c866d922c7f33fac1e78e97f8568d57 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 May 2017 12:40:57 -0400 Subject: [PATCH 0490/1012] probe: display tiletype enum names as well --- NEWS.rst | 1 + plugins/probe.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 9fcfd5fa3..9260ad1fc 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -192,6 +192,7 @@ Misc Improvements - `fix/diplomats`: replaces ``fixdiplomats`` - `fix/merchants`: replaces ``fixmerchants`` - `prefchange`: added a ``help`` option +- `probe`: now displays raw tiletype names - Unified script documentation and in-terminal help options Removed diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 94ebc1884..63acfe3da 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -119,6 +119,7 @@ void describeTile(color_ostream &out, df::tiletype tiletype) out.print("%d", tiletype); if(tileName(tiletype)) out.print(" = %s",tileName(tiletype)); + out.print(" (%s)", ENUM_KEY_STR(tiletype, tiletype).c_str()); out.print("\n"); df::tiletype_shape shape = tileShape(tiletype); From b51e1ad2ef1849a974bec4dff805457a3b59a0e9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 May 2017 19:51:11 -0400 Subject: [PATCH 0491/1012] Add missing anchors to Plugins.rst --- docs/Plugins.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 4b5985ae7..5c7a78c0b 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -101,12 +101,16 @@ Examples: Please report any living/active creatures with cursetype "unknown" - this is most likely with mods which introduce new types of curses. +.. _flows: + flows ===== A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map. +.. _probe: + probe ===== Can be used to determine tile properties like temperature. @@ -587,6 +591,8 @@ Usage: :confirm enable option1 [option2...]: Enable (or disable) specific confirmation dialogues. +.. _follow: + follow ====== Makes the game view follow the currently highlighted unit after you exit from the From a285175dfc0545b14fdd272c2605996e06ba47a2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 May 2017 21:59:53 -0400 Subject: [PATCH 0492/1012] Add more missing anchors to Plugins.rst --- docs/Plugins.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 5c7a78c0b..75e3a8f8e 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -996,6 +996,8 @@ Options: **item-type ** Replace the exact item type id in the job item. +.. _job-material: + job-material ============ Alter the material of the selected job. Similar to ``job item-material ...`` @@ -1012,6 +1014,8 @@ Invoked as:: * In :kbd:`b` mode, during selection of building components positions the cursor over the first available choice with the matching material. +.. _job-duplicate: + job-duplicate ============= In :kbd:`q` mode, when a job is highlighted within a workshop or furnace @@ -1203,6 +1207,8 @@ Extra options for ``map``: :mud: Remove mud in addition to the normal stuff. :snow: Also remove snow coverings. +.. _spotclean: + spotclean ========= Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal @@ -1242,6 +1248,7 @@ in the :kbd:`k` list, or inside a container. If called again before the game is resumed, cancels destruction of the item. :dfhack-keybind:`autodump-destroy-item` +.. _cleanowned: cleanowned ========== @@ -1540,6 +1547,8 @@ Examples Stuff up to 50 owned tame male animals who are not grazers into cages built on the current default zone. +.. _autonestbox: + autonestbox =========== Assigns unpastured female egg-layers to nestbox zones. Requires that you create From 787c54d8a709cc45b7d2f93acabe74ffd244890e Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 May 2017 22:01:57 -0400 Subject: [PATCH 0493/1012] Remove several unused CMake and Doxygen files --- CMake/Modules/CMakeVS10FindMake.cmake | 28 - CMake/Modules/FindCurses.cmake | 23 - CMake/Modules/FindDocutils.cmake | 3 - CMake/Modules/FindDoxygen.cmake | 185 --- library/CMakeLists.txt | 5 - library/doc/CMakeLists.txt | 55 - library/doc/Doxyfile.in | 1554 ------------------------- library/doc/Doxygen.html | 9 - library/doc/img/.dot | 0 library/doc/index.dxgen | 42 - 10 files changed, 1904 deletions(-) delete mode 100644 CMake/Modules/CMakeVS10FindMake.cmake delete mode 100644 CMake/Modules/FindCurses.cmake delete mode 100644 CMake/Modules/FindDocutils.cmake delete mode 100644 CMake/Modules/FindDoxygen.cmake delete mode 100644 library/doc/CMakeLists.txt delete mode 100644 library/doc/Doxyfile.in delete mode 100644 library/doc/Doxygen.html delete mode 100644 library/doc/img/.dot delete mode 100644 library/doc/index.dxgen diff --git a/CMake/Modules/CMakeVS10FindMake.cmake b/CMake/Modules/CMakeVS10FindMake.cmake deleted file mode 100644 index 460de25e5..000000000 --- a/CMake/Modules/CMakeVS10FindMake.cmake +++ /dev/null @@ -1,28 +0,0 @@ - -#============================================================================= -# Copyright 2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distributed this file outside of CMake, substitute the full -# License text for the above reference.) - -# We use MSBuild as the build tool for VS 10 -FIND_PROGRAM(CMAKE_MAKE_PROGRAM - NAMES MSBuild - HINTS - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0\\Setup\\VS;ProductDir] - "$ENV{SYSTEMROOT}/Microsoft.NET/Framework/[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0;CLR Version]/" - "c:/WINDOWS/Microsoft.NET/Framework/[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0;CLR Version]/" - "$ENV{SYSTEMROOT}/Microsoft.NET/Framework/[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress\\10.0;CLR Version]/" - ) - -MARK_AS_ADVANCED(CMAKE_MAKE_PROGRAM) -SET(MSVC10 1) -SET(MSVC_VERSION 1600) - diff --git a/CMake/Modules/FindCurses.cmake b/CMake/Modules/FindCurses.cmake deleted file mode 100644 index 98adf9815..000000000 --- a/CMake/Modules/FindCurses.cmake +++ /dev/null @@ -1,23 +0,0 @@ -IF(Curses_FOUND) - SET(Curses_FIND_QUIETLY TRUE) -ENDIF() - -FIND_PATH(Curses_INCLUDE_PATH - NAMES ncurses.h curses.h - PATH_SUFFIXES ncurses - PATHS /usr/include/ncursesw /usr/include /usr/local/include /usr/pkg/include -) - -FIND_LIBRARY(Curses_LIBRARY - NAMES ncursesw - PATHS /lib /usr/lib /usr/local/lib /usr/pkg/lib -) - -IF (Curses_INCLUDE_PATH AND Curses_LIBRARY) - SET(Curses_FOUND TRUE) -ENDIF() - -MARK_AS_ADVANCED( - Curses_INCLUDE_PATH - Curses_LIBRARY -) diff --git a/CMake/Modules/FindDocutils.cmake b/CMake/Modules/FindDocutils.cmake deleted file mode 100644 index 8103628df..000000000 --- a/CMake/Modules/FindDocutils.cmake +++ /dev/null @@ -1,3 +0,0 @@ -FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py) -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE) \ No newline at end of file diff --git a/CMake/Modules/FindDoxygen.cmake b/CMake/Modules/FindDoxygen.cmake deleted file mode 100644 index 94d20a9ff..000000000 --- a/CMake/Modules/FindDoxygen.cmake +++ /dev/null @@ -1,185 +0,0 @@ -# - This module looks for Doxygen and the path to Graphviz's dot -# Doxygen is a documentation generation tool. Please see -# http://www.doxygen.org -# -# This module accepts the following optional variables: -# -# DOXYGEN_SKIP_DOT = If true this module will skip trying to find Dot -# (an optional component often used by Doxygen) -# -# This modules defines the following variables: -# -# DOXYGEN_EXECUTABLE = The path to the doxygen command. -# DOXYGEN_FOUND = Was Doxygen found or not? -# -# DOXYGEN_DOT_EXECUTABLE = The path to the dot program used by doxygen. -# DOXYGEN_DOT_FOUND = Was Dot found or not? -# DOXYGEN_DOT_PATH = The path to dot not including the executable -# -# - -#Copyright 2000-2009 Kitware, Inc., Insight Software Consortium -#All rights reserved. -# -#Redistribution and use in source and binary forms, with or without -#modification, are permitted provided that the following conditions -#are met: -# -#* Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -#* Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -#* Neither the names of Kitware, Inc., the Insight Software Consortium, -# nor the names of their contributors may be used to endorse or promote -# products derived from this software without specific prior written -# permission. -# -#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -#A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -#HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -#SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -#LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -#----------------------------------------------------------------------------- -# -#The above copyright and license notice applies to distributions of -#CMake in source and binary form. Some source files contain additional -#notices of original copyright by their contributors; see each source -#for details. Third-party software packages supplied with CMake under -#compatible licenses provide their own copyright notices documented in -#corresponding subdirectories. -# -#----------------------------------------------------------------------------- -# -#CMake was initially developed by Kitware with the following sponsorship: -# -# * National Library of Medicine at the National Institutes of Health -# as part of the Insight Segmentation and Registration Toolkit (ITK). -# -# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel -# Visualization Initiative. -# -# * National Alliance for Medical Image Computing (NAMIC) is funded by the -# National Institutes of Health through the NIH Roadmap for Medical Research, -# Grant U54 EB005149. -# -# * Kitware, Inc. - - -# For backwards compatibility support -IF(Doxygen_FIND_QUIETLY) - SET(DOXYGEN_FIND_QUIETLY TRUE) -ENDIF(Doxygen_FIND_QUIETLY) - -# ===== Rationale for OS X AppBundle mods below ===== -# With the OS X GUI version, Doxygen likes to be installed to /Applications and -# it contains the doxygen executable in the bundle. In the versions I've -# seen, it is located in Resources, but in general, more often binaries are -# located in MacOS. -# -# NOTE: The official Doxygen.app that is distributed for OS X uses non-standard -# conventions. Instead of the command-line "doxygen" tool being placed in -# Doxygen.app/Contents/MacOS, "Doxywizard" is placed there instead and -# "doxygen" is placed in Contents/Resources. This is most likely done -# so that something happens when people double-click on the Doxygen.app -# package. Unfortunately, CMake gets confused by this as when it sees the -# bundle it uses "Doxywizard" as the executable to use instead of -# "doxygen". Therefore to work-around this issue we temporarily disable -# the app-bundle feature, just for this CMake module: -if(APPLE) - # Save the old setting - SET(TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE}) - # Disable the App-bundle detection feature - SET(CMAKE_FIND_APPBUNDLE "NEVER") -endif() -# FYI: -# In the older versions of OS X Doxygen, dot was included with the -# Doxygen bundle. But the new versions require you to download -# Graphviz.app which contains "dot" in it's bundle. -# ============== End OSX stuff ================ - -# -# Find Doxygen... -# - -FIND_PROGRAM(DOXYGEN_EXECUTABLE - NAMES doxygen - PATHS - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\doxygen_is1;Inno Setup: App Path]/bin" - /Applications/Doxygen.app/Contents/Resources - /Applications/Doxygen.app/Contents/MacOS - DOC "Doxygen documentation generation tool (http://www.doxygen.org)" -) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Doxygen DEFAULT_MSG DOXYGEN_EXECUTABLE) - -# -# Find Dot... -# - -IF(NOT DOXYGEN_SKIP_DOT) - FIND_PROGRAM(DOXYGEN_DOT_EXECUTABLE - NAMES dot - PATHS - "$ENV{ProgramFiles}/Graphviz2.26.3/bin" - "C:/Program Files/Graphviz2.26.3/bin" - "$ENV{ProgramFiles}/Graphviz 2.21/bin" - "C:/Program Files/Graphviz 2.21/bin" - "$ENV{ProgramFiles}/ATT/Graphviz/bin" - "C:/Program Files/ATT/Graphviz/bin" - [HKEY_LOCAL_MACHINE\\SOFTWARE\\ATT\\Graphviz;InstallPath]/bin - /Applications/Graphviz.app/Contents/MacOS - /Applications/Doxygen.app/Contents/Resources - /Applications/Doxygen.app/Contents/MacOS - DOC "Graphviz Dot tool for using Doxygen" - ) - - if(DOXYGEN_DOT_EXECUTABLE) - set(DOXYGEN_DOT_FOUND TRUE) - # The Doxyfile wants the path to Dot, not the entire path and executable - get_filename_component(DOXYGEN_DOT_PATH "${DOXYGEN_DOT_EXECUTABLE}" PATH CACHE) - endif() - -endif(NOT DOXYGEN_SKIP_DOT) - -# -# Backwards compatibility... -# - -if(APPLE) - # Restore the old app-bundle setting setting - SET(CMAKE_FIND_APPBUNDLE ${TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE}) -endif() - -# Maintain the _FOUND variables as "YES" or "NO" for backwards compatibility -# (allows people to stuff them directly into Doxyfile with configure_file()) -if(DOXYGEN_FOUND) - set(DOXYGEN_FOUND "YES") -else() - set(DOXYGEN_FOUND "NO") -endif() -if(DOXYGEN_DOT_FOUND) - set(DOXYGEN_DOT_FOUND "YES") -else() - set(DOXYGEN_DOT_FOUND "NO") -endif() - -# For backwards compatibility support -SET (DOXYGEN ${DOXYGEN_EXECUTABLE} ) -SET (DOT ${DOXYGEN_DOT_EXECUTABLE} ) - -MARK_AS_ADVANCED( - DOXYGEN_EXECUTABLE - DOXYGEN_DOT_EXECUTABLE - DOXYGEN_DOT_PATH - ) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 033c82053..ce73741a2 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -8,7 +8,6 @@ ENDIF() ## build options OPTION(BUILD_DEVEL "Install/package files required for development (For SDK)." OFF) -OPTION(BUILD_DOXYGEN "Create/install/package doxygen documentation for DFHack (For SDK)." OFF) IF(UNIX) OPTION(CONSOLE_NO_CATCH "Make the console not catch 'CTRL+C' events for easier debugging." OFF) ENDIF() @@ -437,8 +436,4 @@ if(BUILD_DEVEL) install(DIRECTORY include/ DESTINATION ${DFHACK_INCLUDES_DESTINATION} FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" ) #linux: include - # Building the docs - IF(BUILD_DOXYGEN) - add_subdirectory (doc) - ENDIF() endif() diff --git a/library/doc/CMakeLists.txt b/library/doc/CMakeLists.txt deleted file mode 100644 index 085f21119..000000000 --- a/library/doc/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# repurposed from libnoise: http://github.com/qknight/libnoise/tree/master/doc/ -# following code and comments is by the original author, with some changes by -# me (peterix) -# ------------------------------------------------------------------------------ -# -# many thanks go to Philippe Poilbarbe for writing the code this file is based on -# http://www.cmake.org/pipermail/cmake/2006-August/010794.html -# -# much later i also found this: -# http://tobias.rautenkranz.ch/cmake/doxygen/ -# but it is hard to understand... - -FIND_PACKAGE(Doxygen QUIET) - -IF(DOXYGEN_FOUND) - SET(DOXYGEN_LANGUAGE "English" CACHE STRING "Language used by doxygen") - MARK_AS_ADVANCED(DOXYGEN_LANGUAGE) - - # you could also set the version with this, see Doxygen.in - # there you will find a line like this: - # PROJECT_NUMBER = @DFHACK_VERSION@ - # @DFHACK_VERSION@ is then replaced by our global DFHACK_VERSION - # - # for instance you could uncomment the next 3 lines and change the version for testing - # SET(DFHACK_VERSION - # "1.2.3-foo500" - # ) - - # doxygen can reference external images with IMAGE_PATH, this is how we set it dynamically - SET( CMAKE_DOXYGEN_IMAGE_PATH - "${CMAKE_CURRENT_SOURCE_DIR}/img" - ) - - # doxygen searches for source code (defined in FILE_PATTERNS, for example: *.cpp *.h) - # with DOXYGEN_SOURCE_DIR we fill a list of directories and later we write it into - # the Doxyfile with a REGEX REPLACE (see below) - SET( DOXYGEN_SOURCE_DIR - "${dfhack_SOURCE_DIR}/library" - ) - - STRING(REGEX REPLACE ";" " " CMAKE_DOXYGEN_INPUT_LIST "${DOXYGEN_SOURCE_DIR}") - set(DOXYFILE_DOT "NO") - if(DOXYGEN_DOT_EXECUTABLE) - set(DOXYFILE_DOT "YES") - endif() - CONFIGURE_FILE(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - ADD_CUSTOM_TARGET(doxygen ALL - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - - INSTALL( DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html/" DESTINATION ${DFHACK_DEVDOC_DESTINATION}/doxygen ) - INSTALL( FILES "Doxygen.html" DESTINATION ${DFHACK_DEVDOC_DESTINATION}) -ELSE(DOXYGEN_FOUND) - MESSAGE (WARNING "Doxygen binary couldn't be found. Can't build development documentation.'") -ENDIF(DOXYGEN_FOUND) \ No newline at end of file diff --git a/library/doc/Doxyfile.in b/library/doc/Doxyfile.in deleted file mode 100644 index 5ee8f500e..000000000 --- a/library/doc/Doxyfile.in +++ /dev/null @@ -1,1554 +0,0 @@ -# Doxyfile 1.6.3 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = dfhack - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = "@DFHACK_VERSION@" - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = html -# @DOXYGEN_OUTPUT_DIR@ - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. - -OUTPUT_LANGUAGE = @DOXYGEN_LANGUAGE@ - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = NO - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) - -JAVADOC_AUTOBRIEF = YES - -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it parses. -# With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this tag. -# The format is ext=language, where ext is a file extension, and language is one of -# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, -# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = YES - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. - -TYPEDEF_HIDES_STRUCT = NO - -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penality. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols - -SYMBOL_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = NO - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by -# doxygen. The layout file controls the global structure of the generated output files -# in an output format independent way. The create the layout file that represents -# doxygen's defaults, run doxygen with the -l option. You can optionally specify a -# file name after the option, if omitted DoxygenLayout.xml will be used as the name -# of the layout file. - -LAYOUT_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = YES - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = @CMAKE_DOXYGEN_INPUT_LIST@ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 - -FILE_PATTERNS = *.cpp \ - *.h \ - *.dxgen - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = Private - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = @CMAKE_DOXYGEN_IMAGE_PATH@ - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = NO - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = . - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. - -HTML_TIMESTAMP = YES - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). - -HTML_DYNAMIC_SECTIONS = NO - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. - -GENERATE_DOCSET = NO - -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. - -CHM_INDEX_ENCODING = - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER -# are set, an additional index file will be generated that can be used as input for -# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated -# HTML documentation. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders - -QHP_VIRTUAL_FOLDER = doc - -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. -# For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see -# Qt Help Project / Custom Filters. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's -# filter section matches. -# Qt Help Project / Filter Attributes. - -QHP_SECT_FILTER_ATTRS = - -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before the help appears. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 8 - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. - -GENERATE_TREEVIEW = NO - -# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list. - -USE_INLINE_TREES = NO - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. - -FORMULA_FONTSIZE = 10 - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. - -SEARCHENGINE = NO - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be implemented using a PHP enabled web server instead of at the web client using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server based approach is that it scales better to large projects and allows full text search. The disadvances is that it is more difficult to setup -# and does not have live searching capabilities. - -SERVER_BASED_SEARCH = NO - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4wide - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = NO - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = NO - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. - -LATEX_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = NO - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = @DOXYFILE_DOT@ - -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. - -DOT_FONTNAME = FreeSans - -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = YES - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = "@DOXYGEN_DOT_PATH@" - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES diff --git a/library/doc/Doxygen.html b/library/doc/Doxygen.html deleted file mode 100644 index da9a6fb65..000000000 --- a/library/doc/Doxygen.html +++ /dev/null @@ -1,9 +0,0 @@ - - - -REDIRECT! - - -This is a redirect to the doxygen stuff. - - diff --git a/library/doc/img/.dot b/library/doc/img/.dot deleted file mode 100644 index e69de29bb..000000000 diff --git a/library/doc/index.dxgen b/library/doc/index.dxgen deleted file mode 100644 index bfb6b2312..000000000 --- a/library/doc/index.dxgen +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -/*! \page index -

      -\htmlonly -

      DFHack

      - -\endhtmlonly -
      - -

      Introduction

      -DFHack is a Dwarf Fortress memory access library and a set of basic tools using -this library. The library is a work in progress, so things might change as more -tools are written for it. - -It is an attempt to unite the various ways tools access DF memory and allow for -easier development of new tools. In general, you can use it to move memory -objects in and out of Dwarf Fortress really fast, regardless of DF version or OS. -*/ - From 98980b82bae9539204e5fb344fc50cc015fe023b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 May 2017 21:54:04 -0400 Subject: [PATCH 0494/1012] Update Authors.rst dfhack/scripts#18 dfhack/df-structures#186 --- docs/Authors.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 034ff8635..40f2f7748 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -21,6 +21,7 @@ AtomicChicken AtomicChicken belal jimhester Ben Lubar BenLubar Ben Rosser TC01 +brndd brndd Caldfir caldfir Carter Bray Qartar Chris Dombroski cdombroski @@ -63,6 +64,7 @@ melkor217 melkor217 Meneth32 Meph Michael Casadevall NCommander +Michael Crouch creidieki Michon van Dooren MaienM miffedmap miffedmap Mike Stewart thewonderidiot From b9b52963dea1351b0778a0a796edd75f3bb3ea9a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 May 2017 21:55:18 -0400 Subject: [PATCH 0495/1012] Update NEWS.rst dfhack/scripts#18 dfhack/scripts#14 --- NEWS.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 9260ad1fc..ee12e1a32 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -117,10 +117,17 @@ Fixes - Fixed crash when selecting a profession from an empty list - Custom professions are now sorted alphabetically more reliably +- `modtools/create-item`: + + - made gloves usable by specifying handedness + - now creates pairs of boots and gloves + - `modtools/create-unit`: - stopped permanently overwriting the creature creation menu in arena mode - now uses non-English names + - added ``-setUnitToFort`` option to make a unit a civ/group member more easily + - fixed some issues where units would appear in unrevealed areas of the map - `modtools/item-trigger`: fixed errors with plant growths - `remotefortressreader`: fixed a crash when serializing the local map From 01c6416824a7f32e77f77a21a30e53476e3a9c05 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 May 2017 21:56:08 -0400 Subject: [PATCH 0496/1012] Update submodules --- depends/clsocket | 2 +- library/xml | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/clsocket b/depends/clsocket index b08d765a7..d1565a3b7 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit b08d765a7ddacf66693cb2721833b3b56b092aa3 +Subproject commit d1565a3b711514048477e11bc04f60f6218af37f diff --git a/library/xml b/library/xml index 665e6d552..a8e80088b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 665e6d552d3390868e6d76acd5f49ebf938adcd1 +Subproject commit a8e80088b9cc934da993dc244ece2d0ae14143da diff --git a/scripts b/scripts index 162f3c3bc..0e41c190d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 162f3c3bcc35987c53c6f934b77f007ad175c9b7 +Subproject commit 0e41c190d3f22c71aa7f8e3090d1b7d53c944495 From 2396392b79429028aca2338ed76e4bd1575aff1e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 May 2017 21:58:07 -0400 Subject: [PATCH 0497/1012] Bump to r1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 186fabf4f..e261443b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,8 +139,8 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "beta2") -SET(DFHACK_PRERELEASE TRUE) +SET(DFHACK_RELEASE "r1") +SET(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 236561ff43117c91023bd15145eff200875cd9a6 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 14 May 2017 11:12:41 +0530 Subject: [PATCH 0498/1012] get the full building list in the requested area in one go, rather than several overlapping chunks. --- .../remotefortressreader.cpp | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index ac06fd160..2c921607b 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1137,19 +1137,13 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N } -void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) { - int minX = DfBlock->map_pos.x; - int minY = DfBlock->map_pos.y; - int Z = DfBlock->map_pos.z; - - int maxX = minX + 15; - int maxY = minY + 15; for (int i = 0; i < df::global::world->buildings.all.size(); i++) { df::building * bld = df::global::world->buildings.all[i]; - if (bld->x1 > maxX || bld->y1 > maxY || bld->x2 < minX || bld->y2 < minY) + if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) continue; int z2 = bld->z; @@ -1162,7 +1156,7 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net z2 = well_building->bucket_z; } } - if (bld->z < Z || z2 > Z) + if (bld->z < min.z || z2 >= max.z) continue; auto out_bld = NetBlock->add_buildings(); CopyBuilding(i, out_bld); @@ -1171,17 +1165,9 @@ void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * Net { for (int i = 0; i < actualBuilding->contained_items.size(); i++) { - if (actualBuilding->contained_items[i]->use_mode == 0) - { - if (isItemChanged(actualBuilding->contained_items[i]->item->id)) - CopyItem(NetBlock->add_items(), actualBuilding->contained_items[i]->item); - } - else - { - auto buildingItem = out_bld->add_items(); - buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); - CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); - } + auto buildingItem = out_bld->add_items(); + buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); + CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); } } } @@ -1314,8 +1300,11 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in int min_y = in->min_y(); int max_x = in->max_x(); int max_y = in->max_y(); + int min_z = in->min_z(); + int max_z = in->max_z(); + bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest. //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); - for (int zz = in->max_z() - 1; zz >= in->min_z(); zz--) + for (int zz = max_z - 1; zz >= min_z; zz--) { // (di, dj) is a vector - direction in which we move right now int di = 1; @@ -1346,12 +1335,12 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in || block->occupancy[xxx][yyy].bits.building > 0) nonAir++; } - if (nonAir > 0) + if (nonAir > 0 || !sentBuildings) { bool tileChanged = IsTiletypeChanged(pos); bool desChanged = IsDesignationChanged(pos); bool spatterChanged = IsspatterChanged(pos); - bool buildingChanged = IsBuildingChanged(pos); + bool buildingChanged = !sentBuildings; bool itemsChanged = areItemsChanged(&block->items); //bool bldChanged = IsBuildingChanged(pos); RemoteFortressReader::MapBlock *net_block; @@ -1365,7 +1354,10 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in if (desChanged) CopyDesignation(block, net_block, &MC, pos); if (buildingChanged) - CopyBuildings(block, net_block, &MC, pos); + { + CopyBuildings(DFCoord(min_x, min_y, min_z), DFCoord(max_x, max_y, max_z), net_block, &MC); + sentBuildings = true; + } if (spatterChanged) Copyspatters(block, net_block, &MC, pos); if (itemsChanged) From 583166523144fb53a32407158ce80720f6c3b3c4 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 14 May 2017 16:07:05 +0530 Subject: [PATCH 0499/1012] Fix wrong units used between two bounding boxes. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 2c921607b..0a0c684d3 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1355,7 +1355,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in CopyDesignation(block, net_block, &MC, pos); if (buildingChanged) { - CopyBuildings(DFCoord(min_x, min_y, min_z), DFCoord(max_x, max_y, max_z), net_block, &MC); + CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); sentBuildings = true; } if (spatterChanged) From c097ab192a9efabe983829c66baec71efc15e867 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 16 May 2017 21:27:13 -0400 Subject: [PATCH 0500/1012] Update scripts (gui/extended-status) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 0e41c190d..bbf833bd0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 0e41c190d3f22c71aa7f8e3090d1b7d53c944495 +Subproject commit bbf833bd086586a700e87d90519a60c04dc7147a From b92095190f18bcdf29873d33576143bdd128da64 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 May 2017 20:51:43 -0400 Subject: [PATCH 0501/1012] Update stonesense, scripts, xml --- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index a8e80088b..f88b520a0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a8e80088b9cc934da993dc244ece2d0ae14143da +Subproject commit f88b520a0d06753dd0ddb059c74f5b785ad98a44 diff --git a/plugins/stonesense b/plugins/stonesense index efe406b77..13c4c0b4b 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit efe406b77ba864eb2a7ca66d1f16816c6e0fab7a +Subproject commit 13c4c0b4b802cc50aaa5a48cb9f93678348cc165 diff --git a/scripts b/scripts index bbf833bd0..f6862e8c4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit bbf833bd086586a700e87d90519a60c04dc7147a +Subproject commit f6862e8c4351bc2e319df3e2b925c9b65eb7f1bb From 22de28c3ed8bd68e60b9fc3b21c4751bcb74eebc Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 19 May 2017 11:49:25 -0400 Subject: [PATCH 0502/1012] embark-tools sand: hide indicator when it overlaps with confirmation window --- plugins/embark-tools.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 274f8cad7..81f7dd6cd 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -178,7 +178,14 @@ public: auto dim = Screen::getWindowSize(); int x = dim.x - 28, y = 13; - if (screen->page == 0) + if (screen->page == start_sitest::T_page::Biome && ( + int(screen->in_embark_aquifer) + + int(screen->in_embark_salt) + + int(screen->in_embark_large) + + int(screen->in_embark_narrow) + + int(screen->in_embark_only_warning) + + int(screen->in_embark_civ_dying) + ) < 2) { OutputString(COLOR_YELLOW, x, y, indicator); } From 8600efd06b376f5571c9491f35c9dc86f52ecbdd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 20 May 2017 16:27:14 -0400 Subject: [PATCH 0503/1012] Make git-describe faster if no git index files have changed Running "git commit" in core and xml touches .git/index and .git/modules/library/xml/index, respectively, so if neither of those have changed, neither has the git information we care about, so avoid running git entirely (which can be slow sometimes). --- library/git-describe.cmake | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 3fb3095ff..bdde32ef9 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,3 +1,16 @@ +if(NOT EXISTS ${dfhack_SOURCE_DIR}/.git/index OR NOT EXISTS ${dfhack_SOURCE_DIR}/.git/modules/library/xml/index) + MESSAGE(FATAL_ERROR "Could not find git index file(s)") +endif() + +set(git_describe_tmp_h ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h) +set(git_describe_h ${dfhack_SOURCE_DIR}/library/include/git-describe.h) + +if(EXISTS ${git_describe_tmp_h} AND + NOT(${dfhack_SOURCE_DIR}/.git/index IS_NEWER_THAN ${git_describe_tmp_h}) AND + NOT(${dfhack_SOURCE_DIR}/.git/modules/library/xml/index IS_NEWER_THAN ${git_describe_tmp_h})) + return() +endif() + execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --long WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) @@ -17,9 +30,6 @@ execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY "${dfhack_SOURCE_DIR}/library/xml" OUTPUT_VARIABLE DFHACK_GIT_XML_COMMIT) -set(git_describe_tmp_h ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h) -set(git_describe_h ${dfhack_SOURCE_DIR}/library/include/git-describe.h) - file(WRITE ${git_describe_tmp_h} "") macro(git_describe_definition var) From 3f98cd3da0a835135fe5500c9f03c9fa60f4c5a0 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 22 May 2017 22:03:12 +0530 Subject: [PATCH 0504/1012] Add active state to some building types. --- plugins/proto/RemoteFortressReader.proto | 1 + .../remotefortressreader/building_reader.cpp | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index d9bb581b3..6312b5250 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -172,6 +172,7 @@ message BuildingInstance optional BuildingExtents room = 12; optional BuildingDirection direction = 13; //Doesn't mean anything for most buildings repeated BuildingItem items = 14; + optional int32 active = 15; } message RiverEdge diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 2b9f95390..a2a38370a 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -6,13 +6,17 @@ #include "df/building_bridgest.h" #include "df/building_def_furnacest.h" #include "df/building_def_workshopst.h" +#include "df/building_doorst.h" +#include "df/building_floodgatest.h" #include "df/building_rollersst.h" #include "df/building_screw_pumpst.h" #include "df/building_siegeenginest.h" #include "df/building_water_wheelst.h" #include "df/building_wellst.h" #include "df/building_windmillst.h" +#include "df/building_workshopst.h" #include "df/world.h" +#include "df/machine.h" #include "modules/Buildings.h" @@ -304,9 +308,29 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re case df::enums::building_type::Shop: break; case df::enums::building_type::Door: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->door_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::Floodgate: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::Box: break; case df::enums::building_type::Weaponrack: @@ -314,7 +338,15 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re case df::enums::building_type::Armorstand: break; case df::enums::building_type::Workshop: + { + auto actual = strict_virtual_cast(local_build); + if (actual && actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } break; + } case df::enums::building_type::Cabinet: break; case df::enums::building_type::Statue: @@ -350,6 +382,10 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re default: break; } + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); } } break; From 2d6dab0f245af8ebbb3dce60828789cd94181196 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 23 May 2017 21:41:34 +0530 Subject: [PATCH 0505/1012] Add a special case for retraction bridge direction --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/building_reader.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 6312b5250..593c174df 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -98,6 +98,7 @@ enum BuildingDirection EAST = 1; SOUTH = 2; WEST = 3; + NONE = 4; } enum TileDigDesignation diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index a2a38370a..307600008 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -366,6 +366,7 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re switch (direction) { case df::building_bridgest::Retracting: + remote_build->set_direction(NONE); break; case df::building_bridgest::Left: remote_build->set_direction(WEST); From addbb1a94f707f145d533c969dfebf4eb4e56e28 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Thu, 25 May 2017 15:38:31 +0530 Subject: [PATCH 0506/1012] (Hopefully) send active-state wil all applicable buildings. --- .../remotefortressreader/building_reader.cpp | 348 +++++++++++++++++- 1 file changed, 335 insertions(+), 13 deletions(-) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 307600008..09cfb6b76 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -1,20 +1,62 @@ #include "building_reader.h" #include "DataDefs.h" -#include "df/building_archerytargetst.h" -#include "df/building_axle_horizontalst.h" -#include "df/building_bridgest.h" -#include "df/building_def_furnacest.h" -#include "df/building_def_workshopst.h" +//building types +#include "df/building_actual.h" +#include "df/building_chairst.h" +#include "df/building_bedst.h" +#include "df/building_tablest.h" +#include "df/building_coffinst.h" +#include "df/building_farmplotst.h" +#include "df/building_furnacest.h" +#include "df/building_tradedepotst.h" +#include "df/building_shopst.h" #include "df/building_doorst.h" #include "df/building_floodgatest.h" -#include "df/building_rollersst.h" -#include "df/building_screw_pumpst.h" +#include "df/building_boxst.h" +#include "df/building_weaponrackst.h" +#include "df/building_armorstandst.h" +#include "df/building_workshopst.h" +#include "df/building_cabinetst.h" +#include "df/building_statuest.h" +#include "df/building_window_glassst.h" +#include "df/building_window_gemst.h" +#include "df/building_wellst.h" +#include "df/building_bridgest.h" +#include "df/building_road_dirtst.h" +#include "df/building_road_pavedst.h" #include "df/building_siegeenginest.h" +#include "df/building_trapst.h" +#include "df/building_animaltrapst.h" +#include "df/building_supportst.h" +#include "df/building_archerytargetst.h" +#include "df/building_chainst.h" +#include "df/building_cagest.h" +#include "df/building_stockpilest.h" +#include "df/building_civzonest.h" +#include "df/building_weaponst.h" +#include "df/building_wagonst.h" +#include "df/building_screw_pumpst.h" +#include "df/building_constructionst.h" +#include "df/building_hatchst.h" +#include "df/building_grate_wallst.h" +#include "df/building_grate_floorst.h" +#include "df/building_bars_verticalst.h" +#include "df/building_bars_floorst.h" +#include "df/building_gear_assemblyst.h" +#include "df/building_axle_horizontalst.h" +#include "df/building_axle_verticalst.h" #include "df/building_water_wheelst.h" -#include "df/building_wellst.h" #include "df/building_windmillst.h" -#include "df/building_workshopst.h" +#include "df/building_traction_benchst.h" +#include "df/building_slabst.h" +#include "df/building_nestst.h" +#include "df/building_nest_boxst.h" +#include "df/building_hivest.h" +#include "df/building_rollersst.h" + +#include "df/building_def_furnacest.h" +#include "df/building_def_workshopst.h" #include "df/world.h" #include "df/machine.h" @@ -286,27 +328,81 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re } } - + //Add building-specific info switch (type) { case df::enums::building_type::NONE: + { + auto actual = virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Chair: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Bed: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Table: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Coffin: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::FarmPlot: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Furnace: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::TradeDepot: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Shop: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Door: { auto actual = strict_virtual_cast(local_build); @@ -332,11 +428,29 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re break; } case df::enums::building_type::Box: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Weaponrack: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Armorstand: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Workshop: { auto actual = strict_virtual_cast(local_build); @@ -348,15 +462,46 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re break; } case df::enums::building_type::Cabinet: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Statue: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::WindowGlass: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::WindowGem: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Well: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->bucket_z); + } break; + } case df::enums::building_type::Bridge: { auto actual = strict_virtual_cast(local_build); @@ -388,12 +533,24 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re else remote_build->set_active(0); } + break; } - break; case df::enums::building_type::RoadDirt: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::RoadPaved: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::SiegeEngine: { auto actual = strict_virtual_cast(local_build); @@ -418,14 +575,34 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re break; } } + break; } - break; case df::enums::building_type::Trap: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->state); + } break; + } case df::enums::building_type::AnimalTrap: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Support: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->support_flags.bits.triggered); + } break; + } case df::enums::building_type::ArcheryTarget: { auto actual = strict_virtual_cast(local_build); @@ -450,20 +627,62 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re break; } } - } break; + } case df::enums::building_type::Chain: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->chain_flags.bits.triggered); + } break; + } case df::enums::building_type::Cage: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->cage_flags.bits.triggered); + } break; + } case df::enums::building_type::Stockpile: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Civzone: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Weapon: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::Wagon: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::ScrewPump: { auto actual = strict_virtual_cast(local_build); @@ -487,23 +706,95 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re default: break; } + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } } } break; case df::enums::building_type::Construction: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } break; + } case df::enums::building_type::Hatch: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->door_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::GrateWall: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::GrateFloor: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::BarsVertical: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::BarsFloor: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } break; + } case df::enums::building_type::GearAssembly: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } break; + } case df::enums::building_type::AxleHorizontal: { auto actual = strict_virtual_cast(local_build); @@ -513,11 +804,27 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re remote_build->set_direction(NORTH); else remote_build->set_direction(EAST); + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } } } break; case df::enums::building_type::AxleVertical: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } break; + } case df::enums::building_type::WaterWheel: { auto actual = strict_virtual_cast(local_build); @@ -527,6 +834,11 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re remote_build->set_direction(NORTH); else remote_build->set_direction(EAST); + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } } } break; @@ -545,6 +857,11 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re remote_build->set_direction(SOUTH); else remote_build->set_direction(WEST); + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } } } break; @@ -581,6 +898,11 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re default: break; } + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } } } break; From 49ea61562f0ee8ccd762720dbea51e6232256536 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 25 May 2017 18:06:51 -0400 Subject: [PATCH 0507/1012] uicommon: remove nullptr definition This is available in GCC 4.8+ and MSVC 2015 --- plugins/uicommon.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 347ef5dbf..1261ed933 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -37,10 +37,6 @@ using std::set; using namespace DFHack; using namespace df::enums; -#ifndef HAVE_NULLPTR -#define nullptr 0L -#endif - #define COLOR_TITLE COLOR_BROWN #define COLOR_UNSELECTED COLOR_GREY #define COLOR_SELECTED COLOR_WHITE From 561bb1ce0f87a34541e3c13c0c18938d27d7ddcb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 27 May 2017 00:10:07 -0400 Subject: [PATCH 0508/1012] Travis: temporarily disable Lua check --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 932375656..3e5677ff7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ script: - python travis/lint.py - python travis/authors-rst.py - python travis/script-docs.py -- python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" +- python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" || true - python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis From 290537604277fc5a90dc136804c5ffc223b6c804 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 27 May 2017 00:26:14 -0400 Subject: [PATCH 0509/1012] Fix multiple issues with the confirm plugin - Only allow one confirmation to be open at once. This avoids situations where pressing "s" would open another confirmation instead of the settings screen (e.g. in the trade screen), and allows all confirmations to be implemented without priorities specified. - Fix #821: close any active confirmations when they are disabled. - Some misc. cleanup, C++11 features - Fixed a memory leak --- plugins/confirm.cpp | 171 ++++++++++++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 63 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index b9927f4ea..c616ac493 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -24,6 +24,8 @@ using namespace DFHack; using namespace df::enums; +using std::map; +using std::queue; using std::string; using std::vector; @@ -36,9 +38,9 @@ typedef std::set ikey_set; command_result df_confirm (color_ostream &out, vector & parameters); struct conf_wrapper; -static std::map confirmations; +static map confirmations; string active_id; -std::queue cmds; +queue cmds; template inline bool in_vector (std::vector &vec, FT item) @@ -58,6 +60,26 @@ string char_replace (string s, char a, char b) bool set_conf_state (string name, bool state); +class confirmation_base { +public: + enum cstate { INACTIVE, ACTIVE, SELECTED }; + virtual string get_id() = 0; + virtual bool set_state(cstate) = 0; + + static bool set_state(string id, cstate state) + { + if (active && active->get_id() == id) + { + active->set_state(state); + return true; + } + return false; + } +protected: + static confirmation_base *active; +}; +confirmation_base *confirmation_base::active = nullptr; + struct conf_wrapper { private: bool enabled; @@ -74,9 +96,9 @@ public: bool apply (bool state) { if (state == enabled) return true; - for (auto h = hooks.begin(); h != hooks.end(); ++h) + for (auto hook : hooks) { - if (!(**h).apply(state)) + if (!hook->apply(state)) return false; } enabled = state; @@ -88,8 +110,8 @@ public: namespace trade { static bool goods_selected (const std::vector &selected) { - for (auto it = selected.begin(); it != selected.end(); ++it) - if (*it) + for (char c : selected) + if (c) return true; return false; } @@ -112,11 +134,10 @@ namespace trade { { // check to see if item is in a container // (if the container is not selected, it will be detected separately) - std::vector &refs = items[i]->general_refs; bool in_container = false; - for (auto it = refs.begin(); it != refs.end(); ++it) + for (auto ref : items[i]->general_refs) { - if (virtual_cast(*it)) + if (virtual_cast(ref)) { in_container = true; break; @@ -154,7 +175,7 @@ namespace conf_lua { if (out) { delete out; - out = NULL; + out = nullptr; } lua_close(l_state); } @@ -180,20 +201,20 @@ namespace conf_lua { int get_ids (lua_State *L) { lua_newtable(L); - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - Lua::TableInsert(L, it->first, true); + for (auto item : confirmations) + Lua::TableInsert(L, item.first, true); return 1; } int get_conf_data (lua_State *L) { lua_newtable(L); int i = 1; - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + for (auto item : confirmations) { Lua::Push(L, i++); lua_newtable(L); - Lua::TableInsert(L, "id", it->first); - Lua::TableInsert(L, "enabled", it->second->is_enabled()); + Lua::TableInsert(L, "id", item.first); + Lua::TableInsert(L, "enabled", item.second->is_enabled()); lua_settable(L, -3); } return 1; @@ -233,29 +254,42 @@ void show_options() } template -class confirmation { +class confirmation : public confirmation_base { public: - enum cstate { INACTIVE, ACTIVE, SELECTED }; typedef T screen_type; screen_type *screen; - void set_state (cstate s) + + bool set_state (cstate s) override { + if (confirmation_base::active && confirmation_base::active != this) + { + // Stop this confirmation from appearing over another one + return false; + } + state = s; - if (s == INACTIVE) + if (s == INACTIVE) { active_id = ""; - else + confirmation_base::active = nullptr; + } + else { active_id = get_id(); + confirmation_base::active = this; + } + return true; } bool feed (ikey_set *input) { if (state == INACTIVE) { - for (auto it = input->begin(); it != input->end(); ++it) + for (df::interface_key key : *input) { - if (intercept_key(*it)) + if (intercept_key(key)) { - last_key = *it; - set_state(ACTIVE); - return true; + if (set_state(ACTIVE)) + { + last_key = key; + return true; + } } } return false; @@ -290,8 +324,8 @@ public: { split_string(&lines, get_message(), "\n"); size_t max_length = 40; - for (auto it = lines.begin(); it != lines.end(); ++it) - max_length = std::max(max_length, it->size()); + for (string line : lines) + max_length = std::max(max_length, line.size()); int width = max_length + 4; int height = lines.size() + 4; int x1 = (gps->dimx / 2) - (width / 2); @@ -341,7 +375,7 @@ public: set_state(INACTIVE); } } - virtual string get_id() = 0; + virtual string get_id() override = 0; #define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id()); bool intercept_key (df::interface_key key) { @@ -383,15 +417,12 @@ protected: }; template -int conf_register(confirmation *c, ...) +int conf_register(confirmation *c, const vector &hooks) { conf_wrapper *w = new conf_wrapper(); confirmations[c->get_id()] = w; - va_list args; - va_start(args, c); - while (VMethodInterposeLinkBase *hook = va_arg(args, VMethodInterposeLinkBase*)) + for (auto hook : hooks) w->add_hook(hook); - va_end(args); return 0; } @@ -419,35 +450,35 @@ struct cls##_hooks : cls::screen_type { \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); \ -static int conf_register_##cls = conf_register(&cls##_instance, \ +static int conf_register_##cls = conf_register(&cls##_instance, {\ &INTERPOSE_HOOK(cls##_hooks, feed), \ &INTERPOSE_HOOK(cls##_hooks, render), \ &INTERPOSE_HOOK(cls##_hooks, key_conflict), \ - NULL); +}); -#define DEFINE_CONFIRMATION(cls, screen, prio) \ +#define DEFINE_CONFIRMATION(cls, screen) \ class confirmation_##cls : public confirmation { \ virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ }; \ - IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, prio); + IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0); /* This section defines stubs for all confirmation dialogs, with methods implemented in plugins/lua/confirm.lua. IDs (used in the "confirm enable/disable" command, by Lua, and in the docs) are obtained by replacing '_' with '-' in the first argument to DEFINE_CONFIRMATION */ -DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst, -1); -DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(haul_delete, viewscreen_dwarfmodest, 0); -DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, 0); -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); +DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(haul_delete, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst); +DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst); +DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { @@ -469,9 +500,9 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) { if (is_enabled != enable) { - for (auto c = confirmations.begin(); c != confirmations.end(); ++c) + for (auto c : confirmations) { - if (!c->second->apply(enable)) + if (!c.second->apply(enable)) return CR_FAILURE; } is_enabled = enable; @@ -488,6 +519,13 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) if (plugin_enable(out, false) != CR_OK) return CR_FAILURE; conf_lua::cleanup(); + + for (auto item : confirmations) + { + delete item.second; + } + confirmations.clear(); + return CR_OK; } @@ -504,14 +542,21 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) bool set_conf_state (string name, bool state) { bool found = false; - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + for (auto it : confirmations) { - if (it->first == name) + if (it.first == name) { found = true; - it->second->apply(state); + it.second->apply(state); } } + + if (state == false) + { + // dismiss the confirmation too + confirmation_base::set_state(name, confirmation_base::INACTIVE); + } + return found; } @@ -528,23 +573,23 @@ command_result df_confirm (color_ostream &out, vector & parameters) if (parameters.empty() || in_vector(parameters, "help") || in_vector(parameters, "status")) { out << "Available options: \n"; - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - out.print(" %20s: %s\n", it->first.c_str(), it->second->is_enabled() ? "enabled" : "disabled"); + for (auto it : confirmations) + out.print(" %20s: %s\n", it.first.c_str(), it.second->is_enabled() ? "enabled" : "disabled"); return CR_OK; } - for (auto it = parameters.begin(); it != parameters.end(); ++it) + for (string param : parameters) { - if (*it == "enable") + if (param == "enable") state = true; - else if (*it == "disable") + else if (param == "disable") state = false; - else if (*it == "all") + else if (param == "all") { - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - it->second->apply(state); + for (auto it : confirmations) + it.second->apply(state); } else - enable_conf(out, *it, state); + enable_conf(out, param, state); } return CR_OK; } From f59be2a9a07cc38d922f8a4bf701d44fd705c667 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 27 May 2017 00:56:40 -0400 Subject: [PATCH 0510/1012] Expand focus strings for jobmanagement and workquota_condition viewscreens --- dfhack.init-example | 2 +- library/modules/Gui.cpp | 14 ++++++++++++++ scripts | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 19bef37e6..bc6c5a915 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -53,7 +53,7 @@ keybinding add Alt-S@title gui/settings-manager keybinding add Alt-S@dwarfmode/Default gui/settings-manager # change quantity of manager orders -keybinding add Alt-Q@jobmanagement gui/manager-quantity +keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity ############################## # Generic adv mode bindings # diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 5ad569c5a..a8e54d27e 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -53,6 +53,7 @@ using namespace DFHack; #include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_jobst.h" #include "df/viewscreen_joblistst.h" +#include "df/viewscreen_jobmanagementst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_itemst.h" @@ -66,6 +67,7 @@ using namespace DFHack; #include "df/viewscreen_petst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" #include "df/ui_unit_view_mode.h" #include "df/ui_sidebar_menus.h" @@ -557,6 +559,18 @@ DEFINE_GET_FOCUS_STRING_HANDLER(locations) focus += "/" + enum_item_key(screen->menu); } +DEFINE_GET_FOCUS_STRING_HANDLER(jobmanagement) +{ + focus += (screen->in_max_workshops ? "/MaxWorkshops" : "/Main"); +} + +DEFINE_GET_FOCUS_STRING_HANDLER(workquota_condition) +{ + focus += "/" + enum_item_key(screen->mode); + if (screen->item_count_edit) + focus += "/EditCount"; +} + std::string Gui::getFocusString(df::viewscreen *top) { if (!top) diff --git a/scripts b/scripts index f6862e8c4..c4f6877a6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f6862e8c4351bc2e319df3e2b925c9b65eb7f1bb +Subproject commit c4f6877a69ac1c69a405d122dd6e75c5605fe782 From e51c5fc2cc0c32d0260c391539d70f70a0e26635 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 28 May 2017 23:11:37 -0400 Subject: [PATCH 0511/1012] Allow key_pen option in some widget text --- library/lua/gui/widgets.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 4bc48c1b5..c51d9ea73 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -248,16 +248,17 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) if token.text or token.key then local text = ''..(getval(token.text) or '') - local keypen + local keypen = dfhack.pen.parse(token.key_pen or COLOR_LIGHTGREEN) if dc then local tpen = getval(token.pen) if disabled or is_disabled(token) then dc:pen(getval(token.dpen) or tpen or dpen) - keypen = COLOR_GREEN + if keypen.fg ~= COLOR_BLACK then + keypen.bold = false + end else dc:pen(tpen or pen) - keypen = COLOR_LIGHTGREEN end end From f4e18b2d8747c3e8fd90233b5670379d7a6882fa Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 30 May 2017 09:17:09 -0400 Subject: [PATCH 0512/1012] Lua: implement to_first argument in dfhack.screen.dismiss() This was mentioned in Lua API.rst, but never actually implemented. --- library/LuaApi.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 266677045..775e0a348 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2144,7 +2144,8 @@ int screen_show(lua_State *L) static int screen_dismiss(lua_State *L) { df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); - Screen::dismiss(screen); + bool to_first = lua_toboolean(L, 2); + Screen::dismiss(screen, to_first); return 0; } From 939aff3231b6defc8d652406f9558d437dce64f6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 31 May 2017 12:12:15 -0400 Subject: [PATCH 0513/1012] readTile(): add hook support and map parameter --- library/LuaApi.cpp | 3 ++- library/include/modules/Screen.h | 3 ++- library/modules/Screen.cpp | 34 ++++++++++++++++++++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 775e0a348..09e96780c 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2074,7 +2074,8 @@ static int screen_readTile(lua_State *L) { int x = luaL_checkint(L, 1); int y = luaL_checkint(L, 2); - Pen pen = Screen::readTile(x, y); + bool map = lua_toboolean(L, 3); + Pen pen = Screen::readTile(x, y, map); Lua::Push(L, pen); return 1; } diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 4e53b0b54..b97c9fc77 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -189,7 +189,7 @@ namespace DFHack DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y, bool map = false); /// Retrieves one screen tile from the buffer - DFHACK_EXPORT Pen readTile(int x, int y); + DFHACK_EXPORT Pen readTile(int x, int y, bool map = false); /// Paint a string onto the screen. Ignores ch and tile of pen. DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text, bool map = false); @@ -297,6 +297,7 @@ namespace DFHack }; namespace Hooks { + GUI_HOOK_DECLARE(get_tile, Pen, (int x, int y, bool map)); GUI_HOOK_DECLARE(set_tile, void, (const Pen &pen, int x, int y, bool map)); } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 0b9a500fa..f299797d2 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -127,19 +127,11 @@ bool Screen::paintTile(const Pen &pen, int x, int y, bool map) return true; } -Pen Screen::readTile(int x, int y) +static Pen doGetTile_default(int x, int y, bool map) { - if (!gps) return Pen(0,0,0,-1); - - auto dim = getWindowSize(); - if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) - return Pen(0,0,0,-1); - + auto dim = Screen::getWindowSize(); int index = x*dim.y + y; auto screen = gps->screen + index*4; - if (screen[3] & 0x80) - return Pen(0,0,0,-1); - Pen pen( screen[0], screen[1], screen[2], screen[3]?true:false, gps->screentexpos[index] @@ -162,6 +154,28 @@ Pen Screen::readTile(int x, int y) return pen; } +GUI_HOOK_DEFINE(Screen::Hooks::get_tile, doGetTile_default); +static Pen doGetTile(int x, int y, bool map) +{ + return GUI_HOOK_TOP(Screen::Hooks::get_tile)(x, y, map); +} + +Pen Screen::readTile(int x, int y, bool map) +{ + if (!gps) return Pen(0,0,0,-1); + + auto dim = getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) + return Pen(0,0,0,-1); + + int index = x*dim.y + y; + auto screen = gps->screen + index*4; + if (screen[3] & 0x80) + return Pen(0,0,0,-1); + + return doGetTile(x, y, map); +} + bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text, bool map) { auto dim = getWindowSize(); From 2681392e272f065cd178689f6375ccc50078c2c7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 31 May 2017 12:12:52 -0400 Subject: [PATCH 0514/1012] siege-engine: use map tiles --- plugins/siege-engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 323e4292e..0d29e8c48 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -1121,7 +1121,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: if (is_in_range(engine->building_rect, tile_pos)) continue; - Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); + Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y, true); if (!cur_tile.valid()) continue; @@ -1159,7 +1159,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; - Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y); + Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y, true); } } } From d41ff4e8368d66a6cb2fb0373cd01e97131aa909 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jun 2017 16:17:59 -0400 Subject: [PATCH 0515/1012] Implement map parameters for Lua paintString() and fillRect(), update docs --- docs/Lua API.rst | 6 +++--- library/LuaApi.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 1c08407ae..cd2379c48 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1640,19 +1640,19 @@ Basic painting functions: Returns *false* if coordinates out of bounds, or other error. -* ``dfhack.screen.readTile(x,y)`` +* ``dfhack.screen.readTile(x,y[,map])`` Retrieves the contents of the specified tile from the screen buffers. Returns a pen object, or *nil* if invalid or TrueType. -* ``dfhack.screen.paintString(pen,x,y,text)`` +* ``dfhack.screen.paintString(pen,x,y,text[,map])`` Paints the string starting at *x,y*. Uses the string characters in sequence to override the ``ch`` field of pen. Returns *true* if painting at least one character succeeded. -* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2)`` +* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2[,map])`` Fills the rectangle specified by the coordinates with the given pen. Returns *true* if painting at least one character succeeded. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 09e96780c..f5c2d8363 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2087,7 +2087,8 @@ static int screen_paintString(lua_State *L) int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); const char *text = luaL_checkstring(L, 4); - lua_pushboolean(L, Screen::paintString(pen, x, y, text)); + bool map = lua_toboolean(L, 5); + lua_pushboolean(L, Screen::paintString(pen, x, y, text, map)); return 1; } @@ -2099,7 +2100,8 @@ static int screen_fillRect(lua_State *L) int y1 = luaL_checkint(L, 3); int x2 = luaL_checkint(L, 4); int y2 = luaL_checkint(L, 5); - lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2)); + bool map = lua_toboolean(L, 6); + lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2, map)); return 1; } From 044b3a0789f21769aec1c1fc5456bd6222004bd2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jun 2017 16:19:11 -0400 Subject: [PATCH 0516/1012] resume: use uicommon and draw to map --- plugins/resume.cpp | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 8553fd4f0..bd0b5f4ee 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -27,6 +27,8 @@ #include "modules/World.h" +#include "uicommon.h" + using std::map; using std::string; using std::vector; @@ -50,30 +52,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -template -static void for_each_(vector &v, Fn func) -{ - for_each(v.begin(), v.end(), func); -} - -template -static void transform_(vector &src, vector &dst, Fn func) -{ - transform(src.begin(), src.end(), back_inserter(dst), func); -} - -void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - if (newline) - { - ++y; - x = left_margin; - } - else - x += text.length(); -} - df::job *get_suspended_job(df::building *bld) { if (bld->getBuildStage() != 0) @@ -168,7 +146,7 @@ void show_suspended_buildings() else if (sb->was_resumed) color = COLOR_RED; - OutputString(color, x, y, "X"); + OutputString(color, x, y, "X", false, 0, 0, true /* map */); } sb++; From b28742cbd0b0b6cbbbdba6c1ba1c7ba5a10e73ab Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jun 2017 16:20:26 -0400 Subject: [PATCH 0517/1012] Respect script path when running ruby scripts Fixes #1099 --- library/Core.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 198e49df4..a6b91f588 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -373,7 +373,7 @@ static command_result enableLuaScript(color_ostream &out, std::string name, bool return ok ? CR_OK : CR_FAILURE; } -static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string name, vector &args) +static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string filename, vector &args) { if (!plug_mgr->ruby || !plug_mgr->ruby->is_enabled()) return CR_FAILURE; @@ -383,7 +383,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, rbcmd += "'" + args[i] + "', "; rbcmd += "]\n"; - rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }"; + rbcmd += "catch(:script_finished) { load '" + filename + "' }"; return plug_mgr->ruby->eval_ruby(out, rbcmd.c_str()); } @@ -1236,7 +1236,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if ( lua ) res = runLuaScript(con, first, parts); else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) - res = runRubyScript(con, plug_mgr, first, parts); + res = runRubyScript(con, plug_mgr, filename, parts); else if ( try_autocomplete(con, first, completed) ) res = CR_NOT_IMPLEMENTED; else From 346e8b91a9f6a8e505022bf741fcf2310afb6600 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jun 2017 23:36:02 -0400 Subject: [PATCH 0518/1012] Use getDwarfmodeViewDims() in getPanelLayout() Allows C++ hooks to affect Lua scripts as well --- library/lua/gui/dwarfmode.lua | 43 ++++++++++++++++------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 5f23053d8..27649bc6e 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -15,33 +15,30 @@ AREA_MAP_WIDTH = 23 MENU_WIDTH = 30 function getPanelLayout() - local sw, sh = dscreen.getWindowSize() - local view_height = sh-2 - local view_rb = sw-1 - local area_x2 = sw-AREA_MAP_WIDTH-2 - local menu_x2 = sw-MENU_WIDTH-2 - local menu_x1 = area_x2-MENU_WIDTH-1 + local dims = dfhack.gui.getDwarfmodeViewDims() local area_pos = df.global.ui_area_map_width local menu_pos = df.global.ui_menu_width - local rv = {} - if area_pos < 3 then - rv.area_map = gui.mkdims_xy(area_x2+1,1,view_rb-1,view_height) - view_rb = area_x2 + if dims.menu_forced then + menu_pos = area_pos - 1 end - if menu_pos < area_pos or df.global.ui.main.mode ~= 0 then - if menu_pos >= area_pos then - rv.menu_forced = true - menu_pos = area_pos-1 - end - local menu_x = menu_x2 - if menu_pos < 2 then menu_x = menu_x1 end - rv.menu = gui.mkdims_xy(menu_x+1,1,view_rb-1,view_height) - view_rb = menu_x + + local rv = { + menu_pos = menu_pos, + area_pos = area_pos, + map = gui.mkdims_xy(dims.map_x1, dims.map_y1, dims.map_x2, dims.map_y2), + } + + if dims.menu_forced then + rv.menu_forced = true + end + if dims.menu_on then + rv.menu = gui.mkdims_xy(dims.menu_x1, dims.y1, dims.menu_x2, dims.y2) end - rv.area_pos = area_pos - rv.menu_pos = menu_pos - rv.map = gui.mkdims_xy(1,1,view_rb-1,view_height) + if dims.area_on then + rv.area_map = gui.mkdims_xy(dims.area_x1, dims.y1, dims.area_x2, dims.y2) + end + return rv end @@ -476,7 +473,7 @@ function WorkshopOverlay:render(dc) if is_slated_for_remove(self.workshop) then return end - + WorkshopOverlay.super.render(self, dc) end return _ENV From 43c4a88068c720dc688b063746f0d83a08146a60 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 00:40:14 -0400 Subject: [PATCH 0519/1012] Move some checks in paintTile/readTile after hooks are called --- library/include/modules/Screen.h | 2 +- library/modules/Screen.cpp | 28 ++++++++++++++-------------- plugins/devel/color-dfhack-text.cpp | 16 ++++++++-------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index b97c9fc77..673533f3e 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -298,7 +298,7 @@ namespace DFHack namespace Hooks { GUI_HOOK_DECLARE(get_tile, Pen, (int x, int y, bool map)); - GUI_HOOK_DECLARE(set_tile, void, (const Pen &pen, int x, int y, bool map)); + GUI_HOOK_DECLARE(set_tile, bool, (const Pen &pen, int x, int y, bool map)); } } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index f299797d2..0469859ea 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -95,8 +95,12 @@ bool Screen::inGraphicsMode() return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS); } -static void doSetTile_default(const Pen &pen, int x, int y, bool map) +static bool doSetTile_default(const Pen &pen, int x, int y, bool map) { + auto dim = Screen::getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) + return false; + int index = ((x * gps->dimy) + y); auto screen = gps->screen + index*4; screen[0] = uint8_t(pen.ch); @@ -108,10 +112,12 @@ static void doSetTile_default(const Pen &pen, int x, int y, bool map) gps->screentexpos_grayscale[index] = (pen.tile_mode == Screen::Pen::TileColor); gps->screentexpos_cf[index] = pen.tile_fg; gps->screentexpos_cbr[index] = pen.tile_bg; + + return true; } GUI_HOOK_DEFINE(Screen::Hooks::set_tile, doSetTile_default); -static void doSetTile(const Pen &pen, int x, int y, bool map) +static bool doSetTile(const Pen &pen, int x, int y, bool map) { GUI_HOOK_TOP(Screen::Hooks::set_tile)(pen, x, y, map); } @@ -120,9 +126,6 @@ bool Screen::paintTile(const Pen &pen, int x, int y, bool map) { if (!gps || !pen.valid()) return false; - auto dim = getWindowSize(); - if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false; - doSetTile(pen, x, y, map); return true; } @@ -130,8 +133,14 @@ bool Screen::paintTile(const Pen &pen, int x, int y, bool map) static Pen doGetTile_default(int x, int y, bool map) { auto dim = Screen::getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) + return Pen(0,0,0,-1); + int index = x*dim.y + y; auto screen = gps->screen + index*4; + if (screen[3] & 0x80) + return Pen(0,0,0,-1); + Pen pen( screen[0], screen[1], screen[2], screen[3]?true:false, gps->screentexpos[index] @@ -164,15 +173,6 @@ Pen Screen::readTile(int x, int y, bool map) { if (!gps) return Pen(0,0,0,-1); - auto dim = getWindowSize(); - if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) - return Pen(0,0,0,-1); - - int index = x*dim.y + y; - auto screen = gps->screen + index*4; - if (screen[3] & 0x80) - return Pen(0,0,0,-1); - return doGetTile(x, y, map); } diff --git a/plugins/devel/color-dfhack-text.cpp b/plugins/devel/color-dfhack-text.cpp index d0db8e710..c80c3cd4d 100644 --- a/plugins/devel/color-dfhack-text.cpp +++ b/plugins/devel/color-dfhack-text.cpp @@ -19,9 +19,9 @@ struct { uint8_t tick; } config; -void color_text_tile(const Screen::Pen &pen, int x, int y, bool map); +bool color_text_tile(const Screen::Pen &pen, int x, int y, bool map); GUI_HOOK_CALLBACK(Screen::Hooks::set_tile, color_text_hook, color_text_tile); -void color_text_tile(const Screen::Pen &pen, int x, int y, bool map) +bool color_text_tile(const Screen::Pen &pen, int x, int y, bool map) { Screen::Pen pen2 = pen; uint8_t color = config.flicker ? config.tick % 8 : config.color; @@ -40,24 +40,24 @@ void color_text_tile(const Screen::Pen &pen, int x, int y, bool map) return color_text_hook.next()(pen2, x, y, map); } -void aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map); +bool aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map); GUI_HOOK_CALLBACK(Screen::Hooks::set_tile, aaaaa_set_tile_hook, aaaaa_set_tile); -void aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map) +bool aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map) { Screen::Pen pen2 = pen; if ((pen.ch >= 'A' && pen.ch <= 'Z') || (pen.ch >= '0' && pen.ch <= '9')) pen2.ch = 'A'; else if (pen.ch >= 'a' && pen.ch <= 'z') pen2.ch = 'a'; - aaaaa_set_tile_hook.next()(pen2, x, y, map); + return aaaaa_set_tile_hook.next()(pen2, x, y, map); } -void shift_set_tile(const Screen::Pen &pen, int x, int y, bool map); +bool shift_set_tile(const Screen::Pen &pen, int x, int y, bool map); GUI_HOOK_CALLBACK(Screen::Hooks::set_tile, shift_set_tile_hook, shift_set_tile); -void shift_set_tile(const Screen::Pen &pen, int x, int y, bool map) +bool shift_set_tile(const Screen::Pen &pen, int x, int y, bool map) { x = (x + 1) % gps->dimx; - shift_set_tile_hook.next()(pen, x, y, map); + return shift_set_tile_hook.next()(pen, x, y, map); } DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) From 9df35dd844d6fe04e32a17ff9f858f31c2bae2a8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 00:40:59 -0400 Subject: [PATCH 0520/1012] revealInDwarfmodeMap: use map y dimensions --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 660bc4f69..941f652de 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1548,7 +1548,7 @@ bool Gui::revealInDwarfmodeMap(df::coord pos, bool center) auto dims = getDwarfmodeViewDims(); int w = dims.map_x2 - dims.map_x1 + 1; - int h = dims.y2 - dims.y1 + 1; + int h = dims.map_y2 - dims.map_y1 + 1; *window_z = pos.z; From ae1aa49089ccf8fb8d564d247371de70e333eb36 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 12:42:51 -0400 Subject: [PATCH 0521/1012] Add map parameter to Lua paintTile() and use in Painter Also updated gui/siege-engine.lua --- docs/Lua API.rst | 2 +- library/LuaApi.cpp | 3 ++- library/lua/gui.lua | 15 +++++++++++---- scripts | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index cd2379c48..96d459a88 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1634,7 +1634,7 @@ Basic painting functions: Checks if [GRAPHICS:YES] was specified in init. -* ``dfhack.screen.paintTile(pen,x,y[,char,tile])`` +* ``dfhack.screen.paintTile(pen,x,y[,char,tile,map])`` Paints a tile using given parameters. See below for a description of pen. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index a0b09714d..ea834992a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2091,7 +2091,8 @@ static int screen_paintTile(lua_State *L) } if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) pen.tile = luaL_checkint(L, 5); - lua_pushboolean(L, Screen::paintTile(pen, x, y)); + bool map = lua_toboolean(L, 6); + lua_pushboolean(L, Screen::paintTile(pen, x, y, map)); return 1; } diff --git a/library/lua/gui.lua b/library/lua/gui.lua index b83691acf..3a829c529 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -221,6 +221,7 @@ function Painter:init(args) self.y = self.y1 self.cur_pen = to_pen(args.pen or COLOR_GREY) self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN) + self.to_map = false end function Painter.new(rect, pen) @@ -295,6 +296,11 @@ function Painter:key_pen(pen,...) return self end +function Painter:map(to_map) + self.to_map = to_map + return self +end + function Painter:clear() dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2) return self @@ -308,20 +314,20 @@ function Painter:fill(x1,y1,x2,y2,pen,bg,bold) y1 = math.max(y1+self.y1,self.clip_y1) x2 = math.min(x2+self.x1,self.clip_x2) y2 = math.min(y2+self.y1,self.clip_y2) - dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) + dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2,self.to_map) return self end function Painter:char(char,pen,...) if self:isValidPos() then - dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char) + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, nil, self.to_map) end return self:advance(1, nil) end function Painter:tile(char,tile,pen,...) if self:isValidPos() then - dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile) + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile, self.to_map) end return self:advance(1, nil) end @@ -340,7 +346,8 @@ function Painter:string(text,pen,...) dscreen.paintString( to_pen(self.cur_pen, pen, ...), self.x+dx, self.y, - string.sub(text,dx+1,len) + string.sub(text,dx+1,len), + self.to_map ) end end diff --git a/scripts b/scripts index c4f6877a6..22cf766a9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit c4f6877a69ac1c69a405d122dd6e75c5605fe782 +Subproject commit 22cf766a9b81cdf892cfb4181305ca7eaf1d56a7 From 62c9f2695a4a80f40d8e201096ee685c898fa2ce Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 15:00:51 -0400 Subject: [PATCH 0522/1012] automaterial: use uicommon.h, draw to map --- plugins/automaterial.cpp | 56 ++++++++++------------------------------ 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 60ccdd52a..2465c6325 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -14,6 +14,7 @@ // DF data structure definition headers #include "DataDefs.h" #include "MiscUtils.h" +#include "TileTypes.h" #include "df/build_req_choice_genst.h" #include "df/build_req_choice_specst.h" #include "df/construction_type.h" @@ -25,6 +26,7 @@ #include "df/job.h" #include "df/world.h" #include "df/building_constructionst.h" +#include "df/job_item.h" #include "modules/Gui.h" #include "modules/Screen.h" @@ -34,8 +36,7 @@ #include "modules/Maps.h" #include "modules/MapCache.h" -#include "TileTypes.h" -#include "df/job_item.h" +#include "uicommon.h" using namespace std; using std::map; @@ -79,27 +80,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - if (newline) - { - ++y; - x = left_margin; - } - else - x += text.length(); -} - -void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE) -{ - OutputString(10, x, y, hotkey); - string display(": "); - display.append(text); - OutputString(color, x, y, display, newline, left_margin); -} - -void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) +void AMOutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) { OutputHotkeyString(x, y, text, hotkey); OutputString(COLOR_WHITE, x, y, ": "); @@ -109,16 +90,7 @@ void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bo OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin); } -static string int_to_string(int i) -{ - return static_cast( &(ostringstream() << i))->str(); -} - //START UI Functions -struct coord32_t -{ - int32_t x, y, z; -}; static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST; static coord32_t box_first, box_second; @@ -668,7 +640,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest x = x - vport.x + 1; y = y - vport.y + 1; - OutputString(COLOR_GREEN, x, y, "X"); + OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */); } else if (show_box_selection && box_select_mode == SELECT_SECOND) { @@ -688,7 +660,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest int32_t x = xB - vport.x + 1; int32_t y = yB - vport.y + 1; - OutputString(color, x, y, "X"); + OutputString(color, x, y, "X", false, 0, 0, true /* map */); } } } @@ -698,7 +670,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { int32_t x = it->pos.x - vport.x + 1; int32_t y = it->pos.y - vport.y + 1; - OutputString(COLOR_GREEN, x, y, "X"); + OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */); } } } @@ -1114,7 +1086,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { - OutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin); + AMOutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin); if (box_select_mode == SELECT_MATERIALS) { @@ -1127,16 +1099,16 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - OutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); - OutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); + AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); + AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); ++y; - OutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); + AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); if (box_select_enabled) { - OutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); + AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - OutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); + AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); } ++y; if (box_select_enabled) @@ -1165,7 +1137,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest int cx = box_first.x; int cy = box_first.y; - OutputString(COLOR_BROWN, cx, cy, "X"); + OutputString(COLOR_BROWN, cx, cy, "X", false, 0, 0, true /* map */); } OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); From 5278856b3740f2b58a2d24c8a88a0d6e1e71d7da Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 15:39:23 -0400 Subject: [PATCH 0523/1012] mousequery: Fix parameters to getDepthAt() --- plugins/mousequery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 2da47d8cd..7f0095c58 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -65,7 +65,7 @@ static df::coord get_mouse_pos(int32_t &mx, int32_t &my) pos.x = vx + mx - 1; pos.y = vy + my - 1; - pos.z = vz - Gui::getDepthAt(pos.x, pos.y); + pos.z = vz - Gui::getDepthAt(mx, my); return pos; } From 4eb767c3f00ab8de73a17ba3d7e0027f16afed1f Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 15:39:49 -0400 Subject: [PATCH 0524/1012] Expose getDepthAt() to Lua --- library/LuaApi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ea834992a..a746118b8 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1483,6 +1483,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showAutoAnnouncement), WRAPM(Gui, revealInDwarfmodeMap), + WRAPM(Gui, getDepthAt), { NULL, NULL } }; From 92c2e52e45b1defde5f73a935a3e0d72e639d341 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jun 2017 21:22:19 -0400 Subject: [PATCH 0525/1012] Fix MSVC error --- library/modules/Screen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 0469859ea..06faf610f 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -119,7 +119,7 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) GUI_HOOK_DEFINE(Screen::Hooks::set_tile, doSetTile_default); static bool doSetTile(const Pen &pen, int x, int y, bool map) { - GUI_HOOK_TOP(Screen::Hooks::set_tile)(pen, x, y, map); + return GUI_HOOK_TOP(Screen::Hooks::set_tile)(pen, x, y, map); } bool Screen::paintTile(const Pen &pen, int x, int y, bool map) From 9f18cb5bdcab50b35301d167f65f7e0dd9ef1016 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Jun 2017 11:24:23 -0400 Subject: [PATCH 0526/1012] resume: use map y dimension and fix off-by-one error --- plugins/resume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/resume.cpp b/plugins/resume.cpp index bd0b5f4ee..5909c0956 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -125,7 +125,7 @@ void show_suspended_buildings() auto dims = Gui::getDwarfmodeViewDims(); int left_margin = vx + dims.map_x2; - int bottom_margin = vy + dims.y2; + int bottom_margin = vy + dims.map_y2 - 1; for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();) { From b2f05834d9ce6fa43bf78d45da9a306f3d4d9b9d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Jun 2017 18:25:49 -0400 Subject: [PATCH 0527/1012] buildingplan: fix an issue preventing some numbers from being used in names --- plugins/buildingplan.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 6f833ba6d..f72888d00 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,9 +1,11 @@ #include "buildingplan-lib.h" +#include "df/ui_sidebar_menus.h" DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.14 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_shutdown ( color_ostream &out ) @@ -188,6 +190,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (isInNobleRoomQueryMode()) { + if (ui_sidebar_menus->barracks.in_rename) + return false; auto np = getNoblePositionOfSelectedBuildingOwner(); df::interface_key last_token = get_string_key(input); if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) From da98a5054db98465fde4c6e60ef071d8403f9545 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Jun 2017 19:24:26 -0400 Subject: [PATCH 0528/1012] Make sc-script help more helpful --- library/Core.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index a6b91f588..83b77f7d0 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1126,13 +1126,9 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else if (builtin == "sc-script") { - if (parts.size() < 1) + if (parts.empty() || parts[0] == "help" || parts[0] == "?") { con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; - return CR_WRONG_USAGE; - } - if (parts[0] == "help" || parts[0] == "?") - { con << "Valid event names (SC_ prefix is optional):" << endl; for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) { From 6be805f509fbb3fd3fa1a9c4e155f592e72ffd5c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Jun 2017 23:51:07 -0400 Subject: [PATCH 0529/1012] Enable tweak embark-profile-name by default --- dfhack.init-example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index bc6c5a915..13fd6191a 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -193,6 +193,9 @@ tweak condition-material # Adds an option to clear currently-bound hotkeys tweak hotkey-clear +# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving +tweak embark-profile-name + # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled tweak civ-view-agreement From c3d5663320ca7a1a414dc4a49ed1d310c8046f40 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 6 Jun 2017 23:34:34 -0400 Subject: [PATCH 0530/1012] Ruby: use Gui module to get selected unit This allows "exterminate it" to work from command-prompt, among other things. Fixes #1100 --- plugins/ruby/ruby.cpp | 11 ++++++++++- plugins/ruby/unit.rb | 38 +------------------------------------- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index db96e3ed9..15f61ac35 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -5,9 +5,11 @@ #include "PluginManager.h" #include "VersionInfo.h" #include "MemAccess.h" - #include "DataDefs.h" + +#include "modules/Gui.h" #include "df/global_objects.h" +#include "df/unit.h" #include "tinythread.h" @@ -627,6 +629,12 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) return rb_uint2inum(*(uintptr_t*)rb_num2ulong(objptr)); } +static VALUE rb_dfget_selected_unit_id(VALUE self) +{ + df::unit *u = Gui::getAnyUnit(Core::getTopViewscreen()); + return rb_int2inum(u ? u->id : -1); +} + // run a dfhack command, as if typed from the dfhack console static VALUE rb_dfhack_run(VALUE self, VALUE cmd) { @@ -1138,6 +1146,7 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1); rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1); + rb_define_singleton_method(rb_cDFHack, "get_selected_unit_id", RUBY_METHOD_FUNC(rb_dfget_selected_unit_id), 0); rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); rb_define_singleton_method(rb_cDFHack, "print_color", RUBY_METHOD_FUNC(rb_dfprint_color), 2); diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 0ce02ae71..3aaaf7653 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -6,43 +6,7 @@ module DFHack # with an argument that respond to x/y/z (eg cursor), find first unit at this position def unit_find(what=:selected, y=nil, z=nil) if what == :selected - case curview._rtti_classname - when :viewscreen_itemst - ref = curview.entry_ref[curview.cursor_pos] - ref.unit_tg if ref.kind_of?(GeneralRefUnit) - when :viewscreen_unitlistst - v = curview - v.units[v.page][v.cursor_pos[v.page]] - when :viewscreen_petst - v = curview - case v.mode - when :List - v.animal[v.cursor].unit if !v.is_vermin[v.cursor] - when :SelectTrainer - v.trainer_unit[v.trainer_cursor] - end - when :viewscreen_dwarfmodest - case ui.main.mode - when :ViewUnits - # nobody selected => idx == 0 - v = world.units.active[ui_selected_unit] - v if v and v.pos.z == cursor.z - when :LookAround - k = ui_look_list.items[ui_look_cursor] - k.unit if k.type == :Unit - else - ui.follow_unit_tg if ui.follow_unit != -1 - end - when :viewscreen_dungeonmodest - case ui_advmode.menu - when :Default - world.units.active[0] - else - unit_find(cursor) # XXX - end - when :viewscreen_dungeon_monsterstatusst - curview.unit - end + return world.units.all.binsearch(df.get_selected_unit_id) elsif what.kind_of?(Integer) # search by id return world.units.all.binsearch(what) if not z From a19e268923cf00046a560c701eefb25add6579ea Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 7 Jun 2017 15:00:30 -0400 Subject: [PATCH 0531/1012] Make re-invoking command-prompt hide the current prompt --- plugins/command-prompt.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index 042d5f642..21bf69b01 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -314,6 +314,7 @@ command_result show_prompt(color_ostream &out, std::vector & param if (Gui::getCurFocus() == "dfhack/commandprompt") { Screen::dismiss(Gui::getCurViewscreen(true)); + return CR_OK; } std::string params; for(size_t i=0;i Date: Wed, 7 Jun 2017 20:51:40 -0400 Subject: [PATCH 0532/1012] getAnyUnit: add support for viewscreen_unitst, viewscreen_textviewerst --- library/modules/Gui.cpp | 87 ++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 941f652de..ccbad8f00 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -46,55 +46,58 @@ using namespace DFHack; #include "modules/Maps.h" #include "DataDefs.h" -#include "df/world.h" + +#include "df/announcement_flags.h" +#include "df/announcements.h" +#include "df/assign_trade_status.h" +#include "df/building_civzonest.h" +#include "df/building_furnacest.h" +#include "df/building_trapst.h" +#include "df/building_workshopst.h" +#include "df/game_mode.h" +#include "df/general_ref.h" #include "df/global_objects.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/viewscreen_dungeonmodest.h" +#include "df/graphic.h" +#include "df/interfacest.h" +#include "df/job.h" +#include "df/layer_object_listst.h" +#include "df/occupation.h" +#include "df/plant.h" +#include "df/popup_message.h" +#include "df/report.h" +#include "df/route_stockpile_link.h" +#include "df/stop_depart_condition.h" +#include "df/ui_advmode.h" +#include "df/ui_build_selector.h" +#include "df/ui_look_list.h" +#include "df/ui_sidebar_menus.h" +#include "df/ui_unit_view_mode.h" +#include "df/unit.h" +#include "df/unit_inventory_item.h" +#include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_dungeon_monsterstatusst.h" -#include "df/viewscreen_jobst.h" +#include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_itemst.h" #include "df/viewscreen_joblistst.h" #include "df/viewscreen_jobmanagementst.h" -#include "df/viewscreen_unitlistst.h" -#include "df/viewscreen_buildinglistst.h" -#include "df/viewscreen_itemst.h" +#include "df/viewscreen_jobst.h" #include "df/viewscreen_layer.h" -#include "df/viewscreen_layer_noblelistst.h" -#include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_overall_healthst.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" +#include "df/viewscreen_textviewerst.h" +#include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_unitst.h" #include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" -#include "df/ui_unit_view_mode.h" -#include "df/ui_sidebar_menus.h" -#include "df/ui_look_list.h" -#include "df/ui_advmode.h" -#include "df/job.h" -#include "df/ui_build_selector.h" -#include "df/building_workshopst.h" -#include "df/building_furnacest.h" -#include "df/building_trapst.h" -#include "df/building_civzonest.h" -#include "df/general_ref.h" -#include "df/unit_inventory_item.h" -#include "df/report.h" -#include "df/popup_message.h" -#include "df/interfacest.h" -#include "df/graphic.h" -#include "df/layer_object_listst.h" -#include "df/assign_trade_status.h" -#include "df/announcement_flags.h" -#include "df/announcements.h" -#include "df/stop_depart_condition.h" -#include "df/route_stockpile_link.h" -#include "df/game_mode.h" -#include "df/unit.h" -#include "df/occupation.h" -#include "df/plant.h" +#include "df/world.h" using namespace df::enums; using df::global::gview; @@ -815,6 +818,11 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) using df::global::ui_look_list; using df::global::ui_selected_unit; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top)) + { + return screen->unit; + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_joblistst, top)) { if (auto unit = vector_get(screen->units, screen->cursor_pos)) @@ -910,6 +918,13 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top)) + { + if (screen->parent) + return getAnyUnit(screen->parent); + return NULL; + } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedUnit(); From acf50b755f9251c43d665b9e656e2476e8201f9f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jun 2017 11:20:56 -0400 Subject: [PATCH 0533/1012] tweak hotkey-clear: fix label position --- plugins/tweak/tweaks/hotkey-clear.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/tweak/tweaks/hotkey-clear.h b/plugins/tweak/tweaks/hotkey-clear.h index 014c39f1d..bc85c112e 100644 --- a/plugins/tweak/tweaks/hotkey-clear.h +++ b/plugins/tweak/tweaks/hotkey-clear.h @@ -10,7 +10,8 @@ struct hotkey_clear_hook : df::viewscreen_dwarfmodest { INTERPOSE_NEXT(render)(); if (ui->main.mode == df::ui_sidebar_mode::Hotkeys) { - int x = 26, y = 19; + auto dims = Gui::getDwarfmodeViewDims(); + int x = dims.menu_x1 + 1, y = 19; OutputHotkeyString(x, y, "Clear", df::interface_key::CUSTOM_C, false, 0, COLOR_WHITE, COLOR_LIGHTRED); } } From 8d23660d552a2eb613023291ff181df5b808ce98 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jun 2017 11:21:30 -0400 Subject: [PATCH 0534/1012] Expose Gui::getAny* to Lua --- library/LuaApi.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index a746118b8..cb57f8456 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1474,6 +1474,10 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedBuilding), WRAPM(Gui, getSelectedPlant), + WRAPM(Gui, getAnyUnit), + WRAPM(Gui, getAnyItem), + WRAPM(Gui, getAnyBuilding), + WRAPM(Gui, getAnyPlant), WRAPM(Gui, writeToGamelog), WRAPM(Gui, makeAnnouncement), WRAPM(Gui, addCombatReport), From ba686839648e04ccb95fbe960ae9ae8fd680918a Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jun 2017 13:05:12 -0400 Subject: [PATCH 0535/1012] Fix ncurses wgetch() on OS X Ref #731 --- library/CMakeLists.txt | 1 + library/Hooks-darwin.cpp | 10 +++------- library/include/Core.h | 3 ++- library/include/Hooks.h | 1 + 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index ce73741a2..1513e1dca 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -358,6 +358,7 @@ IF(APPLE) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) + TARGET_LINK_LIBRARIES(dfhack ncurses) SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) ENDIF() diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index 81ed828bc..8bed6bb75 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -68,6 +68,7 @@ DYLD_INTERPOSE(DFH_SDL_Init,SDL_Init); DYLD_INTERPOSE(DFH_SDL_PollEvent,SDL_PollEvent); DYLD_INTERPOSE(DFH_SDL_Quit,SDL_Quit); DYLD_INTERPOSE(DFH_SDL_NumJoysticks,SDL_NumJoysticks); +DYLD_INTERPOSE(DFH_wgetch,wgetch); /******************************************************************************* * SDL part starts here * @@ -127,16 +128,11 @@ DFhackCExport int SDL_PushEvent(SDL::Event* event) } struct WINDOW; -DFhackCExport int wgetch(WINDOW *win) +DFhackCExport int DFH_wgetch(WINDOW *win) { - static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); - if(!_wgetch) - { - exit(EXIT_FAILURE); - } DFHack::Core & c = DFHack::Core::getInstance(); wgetch_again: - int in = _wgetch(win); + int in = wgetch(win); int out; if(c.ncurses_wgetch(in, out)) { diff --git a/library/include/Core.h b/library/include/Core.h index f76ba2b73..6bcf9ee8e 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -109,13 +109,14 @@ namespace DFHack friend void ::DFH_SDL_Quit(void); friend int ::DFH_SDL_PollEvent(SDL::Event *); friend int ::DFH_SDL_Init(uint32_t flags); + friend int ::DFH_wgetch(WINDOW * w); #else friend int ::SDL_NumJoysticks(void); friend void ::SDL_Quit(void); friend int ::SDL_PollEvent(SDL::Event *); friend int ::SDL_Init(uint32_t flags); -#endif friend int ::wgetch(WINDOW * w); +#endif friend int ::egg_init(void); friend int ::egg_shutdown(void); friend int ::egg_tick(void); diff --git a/library/include/Hooks.h b/library/include/Hooks.h index f29290cc3..97caecfa5 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -52,6 +52,7 @@ DFhackCExport int DFH_SDL_NumJoysticks(void); DFhackCExport void DFH_SDL_Quit(void); DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event); DFhackCExport int DFH_SDL_Init(uint32_t flags); +DFhackCExport int DFH_wgetch(WINDOW * win); #endif DFhackCExport int SDL_NumJoysticks(void); DFhackCExport void SDL_Quit(void); From 1321a64613cf8c7e1503999fdf3f7a9f3d03597b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 17:21:34 -0400 Subject: [PATCH 0536/1012] Remove lots of old/unused functions from the Units module --- library/include/modules/Units.h | 135 +---------- library/modules/Units.cpp | 383 +------------------------------- plugins/zone.cpp | 2 +- 3 files changed, 10 insertions(+), 510 deletions(-) diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 08baf98d6..80c7fcabf 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -60,123 +60,12 @@ namespace DFHack { namespace Units { -/** - * \ingroup grp_units - */ -struct t_skill -{ - uint32_t id; - uint32_t rating; - uint32_t experience; -}; -/** - * \ingroup grp_units - */ -struct t_job -{ - bool active; - uint32_t jobId; - uint8_t jobType; - uint32_t occupationPtr; -}; -/** - * \ingroup grp_units - */ -struct t_like -{ - int16_t type; - int16_t itemClass; - int16_t itemIndex; - t_matglossPair material; - bool active; - uint32_t mystery; -}; -// FIXME: THIS IS VERY, VERY BAD. -#define NUM_CREATURE_LABORS 96 -#define NUM_CREATURE_TRAITS 30 -#define NUM_CREATURE_MENTAL_ATTRIBUTES 13 -#define NUM_CREATURE_PHYSICAL_ATTRIBUTES 6 -/** - * Structure for holding a copy of a DF unit's soul - * \ingroup grp_units - */ -struct t_soul -{ - uint8_t numSkills; - t_skill skills[256]; - //uint8_t numLikes; - //t_like likes[32]; - uint16_t traits[NUM_CREATURE_TRAITS]; - t_attrib analytical_ability; - t_attrib focus; - t_attrib willpower; - t_attrib creativity; - t_attrib intuition; - t_attrib patience; - t_attrib memory; - t_attrib linguistic_ability; - t_attrib spatial_sense; - t_attrib musicality; - t_attrib kinesthetic_sense; - t_attrib empathy; - t_attrib social_awareness; -}; -#define MAX_COLORS 15 -struct df_unit; -/** - * Structure for holding a limited copy of a DF unit - * \ingroup grp_units - */ -struct t_unit -{ - df::unit * origin; - uint16_t x; - uint16_t y; - uint16_t z; - uint32_t race; - int32_t civ; - - df::unit_flags1 flags1; - df::unit_flags2 flags2; - df::unit_flags3 flags3; - - t_name name; - - int16_t mood; - int16_t mood_skill; - t_name artifact_name; - - uint8_t profession; - std::string custom_profession; - - // enabled labors - uint8_t labors[NUM_CREATURE_LABORS]; - t_job current_job; - - uint32_t happiness; - uint32_t id; - t_attrib strength; - t_attrib agility; - t_attrib toughness; - t_attrib endurance; - t_attrib recuperation; - t_attrib disease_resistance; - int32_t squad_leader_id; - uint8_t sex; - uint16_t caste; - uint32_t pregnancy_timer; //Countdown timer to giving birth - //bool has_default_soul; - //t_soul defaultSoul; - uint32_t nbcolors; - uint32_t color[MAX_COLORS]; - - int32_t birth_year; - uint32_t birth_time; -}; +static const int MAX_COLORS = 15; + /** - * The Creatures module - allows reading all non-vermin creatures and their properties + * The Units module - allows reading all non-vermin creatures and their properties * \ingroup grp_modules * \ingroup grp_units */ @@ -187,24 +76,12 @@ DFHACK_EXPORT bool isValid(); // Read creatures in a box, starting with index. Returns -1 if no more creatures // found. Call repeatedly do get all creatures in a specified box (uses tile coords) DFHACK_EXPORT int32_t getNumCreatures(); -DFHACK_EXPORT int32_t GetCreatureInBox(const int32_t index, df::unit ** furball, +DFHACK_EXPORT df::unit * getCreature(const int32_t index); +DFHACK_EXPORT int32_t getCreatureInBox(const int32_t index, df::unit ** furball, const uint16_t x1, const uint16_t y1,const uint16_t z1, const uint16_t x2, const uint16_t y2,const uint16_t z2); -DFHACK_EXPORT df::unit * GetCreature(const int32_t index); -DFHACK_EXPORT void CopyCreature(df::unit * source, t_unit & target); - -DFHACK_EXPORT bool ReadJob(const df::unit * unit, std::vector & mat); - -DFHACK_EXPORT bool ReadInventoryByIdx(const uint32_t index, std::vector & item); -DFHACK_EXPORT bool ReadInventoryByPtr(const df::unit * unit, std::vector & item); - -DFHACK_EXPORT int32_t FindIndexById(int32_t id); - -/* Getters */ -DFHACK_EXPORT uint32_t GetDwarfRaceIndex ( void ); -DFHACK_EXPORT int32_t GetDwarfCivId ( void ); -DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); +DFHACK_EXPORT int32_t findIndexById(int32_t id); /// Returns the true position of the unit (non-trivial in case of caged). DFHACK_EXPORT df::coord getPosition(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 94b11b87e..b8170148c 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -93,7 +93,7 @@ int32_t Units::getNumCreatures() return world->units.all.size(); } -df::unit * Units::GetCreature (const int32_t index) +df::unit * Units::getCreature (const int32_t index) { if (!isValid()) return NULL; @@ -104,7 +104,7 @@ df::unit * Units::GetCreature (const int32_t index) } // returns index of creature actually read or -1 if no creature can be found -int32_t Units::GetCreatureInBox (int32_t index, df::unit ** furball, +int32_t Units::getCreatureInBox (int32_t index, df::unit ** furball, const uint16_t x1, const uint16_t y1, const uint16_t z1, const uint16_t x2, const uint16_t y2, const uint16_t z2) { @@ -133,387 +133,10 @@ int32_t Units::GetCreatureInBox (int32_t index, df::unit ** furball, return -1; } -void Units::CopyCreature(df::unit * source, t_unit & furball) -{ - if(!isValid()) return; - // read pointer from vector at position - furball.origin = source; - - //read creature from memory - // name - Translation::readName(furball.name, &source->name); - - // basic stuff - furball.id = source->id; - furball.x = source->pos.x; - furball.y = source->pos.y; - furball.z = source->pos.z; - furball.race = source->race; - furball.civ = source->civ_id; - furball.sex = source->sex; - furball.caste = source->caste; - furball.flags1.whole = source->flags1.whole; - furball.flags2.whole = source->flags2.whole; - furball.flags3.whole = source->flags3.whole; - // custom profession - furball.custom_profession = source->custom_profession; - // profession - furball.profession = source->profession; - // happiness - furball.happiness = 100;//source->status.happiness; - // physical attributes - memcpy(&furball.strength, source->body.physical_attrs, sizeof(source->body.physical_attrs)); - - // mood stuff - furball.mood = source->mood; - furball.mood_skill = source->job.mood_skill; // FIXME: really? More like currently used skill anyway. - Translation::readName(furball.artifact_name, &source->status.artifact_name); - - // labors - memcpy(&furball.labors, &source->status.labors, sizeof(furball.labors)); - - furball.birth_year = source->birth_year; - furball.birth_time = source->birth_time; - furball.pregnancy_timer = source->pregnancy_timer; - // appearance - furball.nbcolors = source->appearance.colors.size(); - if(furball.nbcolors>MAX_COLORS) - furball.nbcolors = MAX_COLORS; - for(uint32_t i = 0; i < furball.nbcolors; i++) - { - furball.color[i] = source->appearance.colors[i]; - } - - //likes. FIXME: where do they fit in now? The soul? - /* - DfVector likes(d->p, temp + offs.creature_likes_offset); - furball.numLikes = likes.getSize(); - for(uint32_t i = 0;iread(temp2,sizeof(t_like),(uint8_t *) &furball.likes[i]); - } - */ - /* - if(d->Ft_soul) - { - uint32_t soul = p->readDWord(addr_cr + offs.default_soul_offset); - furball.has_default_soul = false; - - if(soul) - { - furball.has_default_soul = true; - // get first soul's skills - DfVector skills(soul + offs.soul_skills_vector_offset); - furball.defaultSoul.numSkills = skills.size(); - - for (uint32_t i = 0; i < furball.defaultSoul.numSkills;i++) - { - uint32_t temp2 = skills[i]; - // a byte: this gives us 256 skills maximum. - furball.defaultSoul.skills[i].id = p->readByte (temp2); - furball.defaultSoul.skills[i].rating = - p->readByte (temp2 + offsetof(t_skill, rating)); - furball.defaultSoul.skills[i].experience = - p->readWord (temp2 + offsetof(t_skill, experience)); - } - - // mental attributes are part of the soul - p->read(soul + offs.soul_mental_offset, - sizeof(t_attrib) * NUM_CREATURE_MENTAL_ATTRIBUTES, - (uint8_t *)&furball.defaultSoul.analytical_ability); - - // traits as well - p->read(soul + offs.soul_traits_offset, - sizeof (uint16_t) * NUM_CREATURE_TRAITS, - (uint8_t *) &furball.defaultSoul.traits); - } - } - */ - if(source->job.current_job == NULL) - { - furball.current_job.active = false; - } - else - { - furball.current_job.active = true; - furball.current_job.jobType = source->job.current_job->job_type; - furball.current_job.jobId = source->job.current_job->id; - } -} - -int32_t Units::FindIndexById(int32_t creature_id) +int32_t Units::findIndexById(int32_t creature_id) { return df::unit::binsearch_index(world->units.all, creature_id); } -/* -bool Creatures::WriteLabors(const uint32_t index, uint8_t labors[NUM_CREATURE_LABORS]) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - - p->write(temp + d->creatures.labors_offset, NUM_CREATURE_LABORS, labors); - uint32_t pickup_equip; - p->readDWord(temp + d->creatures.pickup_equipment_bit, pickup_equip); - pickup_equip |= 1u; - p->writeDWord(temp + d->creatures.pickup_equipment_bit, pickup_equip); - return true; -} - -bool Creatures::WriteHappiness(const uint32_t index, const uint32_t happinessValue) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord (temp + d->creatures.happiness_offset, happinessValue); - return true; -} - -bool Creatures::WriteFlags(const uint32_t index, - const uint32_t flags1, - const uint32_t flags2) -{ - if(!d->Started || !d->Ft_basic) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord (temp + d->creatures.flags1_offset, flags1); - p->writeDWord (temp + d->creatures.flags2_offset, flags2); - return true; -} - -bool Creatures::WriteFlags(const uint32_t index, - const uint32_t flags1, - const uint32_t flags2, - const uint32_t flags3) -{ - if(!d->Started || !d->Ft_basic) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord (temp + d->creatures.flags1_offset, flags1); - p->writeDWord (temp + d->creatures.flags2_offset, flags2); - p->writeDWord (temp + d->creatures.flags3_offset, flags3); - return true; -} - -bool Creatures::WriteSkills(const uint32_t index, const t_soul &soul) -{ - if(!d->Started || !d->Ft_soul) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - uint32_t souloff = p->readDWord(temp + d->creatures.default_soul_offset); - - if(!souloff) - { - return false; - } - - DfVector skills(souloff + d->creatures.soul_skills_vector_offset); - - for (uint32_t i=0; iwriteByte(temp2 + offsetof(t_skill, rating), soul.skills[i].rating); - p->writeWord(temp2 + offsetof(t_skill, experience), soul.skills[i].experience); - } - - return true; -} - -bool Creatures::WriteAttributes(const uint32_t index, const t_creature &creature) -{ - if(!d->Started || !d->Ft_advanced || !d->Ft_soul) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - uint32_t souloff = p->readDWord(temp + d->creatures.default_soul_offset); - - if(!souloff) - { - return false; - } - - // physical attributes - p->write(temp + d->creatures.physical_offset, - sizeof(t_attrib) * NUM_CREATURE_PHYSICAL_ATTRIBUTES, - (uint8_t *)&creature.strength); - - // mental attributes are part of the soul - p->write(souloff + d->creatures.soul_mental_offset, - sizeof(t_attrib) * NUM_CREATURE_MENTAL_ATTRIBUTES, - (uint8_t *)&creature.defaultSoul.analytical_ability); - - return true; -} - -bool Creatures::WriteSex(const uint32_t index, const uint8_t sex) -{ - if(!d->Started || !d->Ft_basic ) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeByte (temp + d->creatures.sex_offset, sex); - - return true; -} - -bool Creatures::WriteTraits(const uint32_t index, const t_soul &soul) -{ - if(!d->Started || !d->Ft_soul) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - uint32_t souloff = p->readDWord(temp + d->creatures.default_soul_offset); - - if(!souloff) - { - return false; - } - - p->write(souloff + d->creatures.soul_traits_offset, - sizeof (uint16_t) * NUM_CREATURE_TRAITS, - (uint8_t *) &soul.traits); - - return true; -} - -bool Creatures::WriteMood(const uint32_t index, const uint16_t mood) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeWord(temp + d->creatures.mood_offset, mood); - return true; -} - -bool Creatures::WriteMoodSkill(const uint32_t index, const uint16_t moodSkill) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeWord(temp + d->creatures.mood_skill_offset, moodSkill); - return true; -} - -bool Creatures::WriteJob(const t_creature * furball, std::vector const& mat) -{ - if(!d->Inited || !d->Ft_job_materials) return false; - if(!furball->current_job.active) return false; - - unsigned int i; - Process * p = d->owner; - Private::t_offsets & off = d->creatures; - DfVector cmats(furball->current_job.occupationPtr + off.job_materials_vector); - - for(i=0;iwriteWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType); - p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].itemSubtype); - p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex); - p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index); - p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags); - } - return true; -} - -bool Creatures::WritePos(const uint32_t index, const t_creature &creature) -{ - if(!d->Started) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->write (temp + d->creatures.pos_offset, 3 * sizeof (uint16_t), (uint8_t *) & (creature.x)); - return true; -} - -bool Creatures::WriteCiv(const uint32_t index, const int32_t civ) -{ - if(!d->Started) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord(temp + d->creatures.civ_offset, civ); - return true; -} - -bool Creatures::WritePregnancy(const uint32_t index, const uint32_t pregTimer) -{ - if(!d->Started) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord(temp + d->creatures.pregnancy_offset, pregTimer); - return true; -} -*/ -uint32_t Units::GetDwarfRaceIndex() -{ - return ui->race_id; -} - -int32_t Units::GetDwarfCivId() -{ - return ui->civ_id; -} -/* -bool Creatures::getCurrentCursorCreature(uint32_t & creature_index) -{ - if(!d->cursorWindowInited) return false; - Process * p = d->owner; - creature_index = p->readDWord(d->current_cursor_creature_offset); - return true; -} -*/ -/* -bool Creatures::ReadJob(const t_creature * furball, vector & mat) -{ - unsigned int i; - if(!d->Inited || !d->Ft_job_materials) return false; - if(!furball->current_job.active) return false; - - Process * p = d->owner; - Private::t_offsets & off = d->creatures; - DfVector cmats(furball->current_job.occupationPtr + off.job_materials_vector); - mat.resize(cmats.size()); - for(i=0;ireadWord(cmats[i] + off.job_material_itemtype_o); - mat[i].itemSubtype = p->readWord(cmats[i] + off.job_material_subtype_o); - mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o); - mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o); - mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o); - } - return true; -} -*/ -bool Units::ReadInventoryByIdx(const uint32_t index, std::vector & item) -{ - if(index >= world->units.all.size()) return false; - df::unit * temp = world->units.all[index]; - return ReadInventoryByPtr(temp, item); -} - -bool Units::ReadInventoryByPtr(const df::unit * unit, std::vector & items) -{ - if(!isValid()) return false; - if(!unit) return false; - items.clear(); - for (size_t i = 0; i < unit->inventory.size(); i++) - items.push_back(unit->inventory[i]->item); - return true; -} - -void Units::CopyNameTo(df::unit * creature, df::language_name * target) -{ - Translation::copyName(&creature->name, target); -} df::coord Units::getPosition(df::unit *unit) { diff --git a/plugins/zone.cpp b/plugins/zone.cpp index fe98e8811..e41704044 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -450,7 +450,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; - out << "index in units vector: " << FindIndexById(unit->id) << endl; + out << "index in units vector: " << findIndexById(unit->id) << endl; } out << endl; From 65382473f7a1cf9bf3ea6ce74ecc5078735324b6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 17:22:52 -0400 Subject: [PATCH 0537/1012] Ruby: stop cpp_new from truncating vtable pointers This fixes a crash in levers.rb, for instance. --- plugins/ruby/ruby-autogen-defs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index c1fb07ac1..c635efc2e 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -138,7 +138,7 @@ module DFHack def cpp_new(init=nil) ptr = DFHack.malloc(_sizeof) if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname) - DFHack.memory_write_int32(ptr, vt) + DFHack.memory_write_ptr(ptr, vt) # TODO call constructor end o = new._at(ptr) From 0ed7e605225eb8b8763eebe11ed24baf0c549c15 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 19:06:43 -0400 Subject: [PATCH 0538/1012] getAnyUnit: support viewscreen_layer_unit_relationshipst --- library/modules/Gui.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index ccbad8f00..611e00205 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -88,6 +88,7 @@ using namespace DFHack; #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_unit_relationshipst.h" #include "df/viewscreen_locationsst.h" #include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" @@ -838,6 +839,14 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_dungeon_monsterstatusst, top)) return screen->unit; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_unit_relationshipst, top)) + { + if (VIRTUAL_CAST_VAR(list, df::layer_object_listst, vector_get(screen->layer_objects, 0))) + return vector_get(screen->relation_unit, list->cursor); + + return NULL; + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); From dff94fc97e05d66bdbb7ab92a4ced31e63adf8c2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 19:41:59 -0400 Subject: [PATCH 0539/1012] replacment -> replacement --- library/Hooks-darwin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index 8bed6bb75..22e7cc2e9 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -59,10 +59,10 @@ typedef struct interpose_s };*/ -#define DYLD_INTERPOSE(_replacment,_replacee) \ +#define DYLD_INTERPOSE(_replacement,_replacee) \ __attribute__((used)) static struct{ const void* replacment; const void* replacee; } \ _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = \ - { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee }; + { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; DYLD_INTERPOSE(DFH_SDL_Init,SDL_Init); DYLD_INTERPOSE(DFH_SDL_PollEvent,SDL_PollEvent); From 778ffb0971a9f8cc9c82eaaffaaa425ba3fdcc1c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 20:32:36 -0400 Subject: [PATCH 0540/1012] More Units module cleanup, modernize getUnitsInBox --- library/include/modules/Units.h | 26 +++++++---------- library/modules/Units.cpp | 49 +++++++++++++-------------------- 2 files changed, 29 insertions(+), 46 deletions(-) diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 80c7fcabf..41859e983 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -23,10 +23,9 @@ distribution. */ #pragma once -#ifndef CL_MOD_CREATURES -#define CL_MOD_CREATURES + /* - * Creatures + * Units */ #include "Export.h" #include "modules/Items.h" @@ -65,21 +64,17 @@ static const int MAX_COLORS = 15; /** - * The Units module - allows reading all non-vermin creatures and their properties - * \ingroup grp_modules - * \ingroup grp_units + * The Units module - allows reading all non-vermin units and their properties */ -DFHACK_EXPORT bool isValid(); - /* Read Functions */ -// Read creatures in a box, starting with index. Returns -1 if no more creatures -// found. Call repeatedly do get all creatures in a specified box (uses tile coords) -DFHACK_EXPORT int32_t getNumCreatures(); -DFHACK_EXPORT df::unit * getCreature(const int32_t index); -DFHACK_EXPORT int32_t getCreatureInBox(const int32_t index, df::unit ** furball, - const uint16_t x1, const uint16_t y1,const uint16_t z1, - const uint16_t x2, const uint16_t y2,const uint16_t z2); +// Read units in a box, starting with index. Returns -1 if no more units +// found. Call repeatedly do get all units in a specified box (uses tile coords) +DFHACK_EXPORT int32_t getNumUnits(); +DFHACK_EXPORT df::unit *getUnit(const int32_t index); +DFHACK_EXPORT bool getUnitsInBox(std::vector &units, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2); DFHACK_EXPORT int32_t findIndexById(int32_t id); @@ -186,4 +181,3 @@ DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit); } } -#endif diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index b8170148c..ffec94910 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -83,54 +83,43 @@ using df::global::world; using df::global::ui; using df::global::gamemode; -bool Units::isValid() -{ - return (world->units.all.size() > 0); -} - -int32_t Units::getNumCreatures() +int32_t Units::getNumUnits() { return world->units.all.size(); } -df::unit * Units::getCreature (const int32_t index) +df::unit *Units::getUnit (const int32_t index) { - if (!isValid()) return NULL; - - // read pointer from vector at position - if(size_t(index) > world->units.all.size()) - return 0; - return world->units.all[index]; + return vector_get(world->units.all, index); } // returns index of creature actually read or -1 if no creature can be found -int32_t Units::getCreatureInBox (int32_t index, df::unit ** furball, - const uint16_t x1, const uint16_t y1, const uint16_t z1, - const uint16_t x2, const uint16_t y2, const uint16_t z2) +bool Units::getUnitsInBox (std::vector &units, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2) { - if (!isValid()) - return -1; + if (!world) + return false; - size_t size = world->units.all.size(); - while (size_t(index) < size) + if (x1 > x2) swap(x1, x2); + if (y1 > y2) swap(y1, y2); + if (z1 > z2) swap(z1, z2); + + units.clear(); + for (df::unit *u : world->units.all) { - // read pointer from vector at position - df::unit * temp = world->units.all[index]; - if (temp->pos.x >= x1 && temp->pos.x < x2) + if (u->pos.x >= x1 && u->pos.x <= x2) { - if (temp->pos.y >= y1 && temp->pos.y < y2) + if (u->pos.y >= y1 && u->pos.y <= y2) { - if (temp->pos.z >= z1 && temp->pos.z < z2) + if (u->pos.z >= z1 && u->pos.z <= z2) { - *furball = temp; - return index; + units.push_back(u); } } } - index++; } - *furball = NULL; - return -1; + return true; } int32_t Units::findIndexById(int32_t creature_id) From 9b63c451b1ce45c8d6423154635b84ca0c7ead85 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 21:03:42 -0400 Subject: [PATCH 0541/1012] Expose getUnitsInBox to Lua --- docs/Lua API.rst | 7 +++++++ library/LuaApi.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 96d459a88..0e242a815 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1059,6 +1059,13 @@ Units module Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. +* ``dfhack.getUnitsInBox(x1,y1,z1,x2,y2,z2[,filter])`` + + Returns a table of all units within the specified coordinates. If the ``filter`` + argument is given, only units where ``filter(unit)`` returns true will be included. + Note that ``pos2xyz()`` cannot currently be used to convert coordinate objects to + the arguments required by this function. + * ``dfhack.units.getGeneralRef(unit, type)`` Searches for a general_ref with the given type. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index cb57f8456..af6e03676 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1638,9 +1638,40 @@ static int units_getNoblePositions(lua_State *state) return 1; } +static int units_getUnitsInBox(lua_State *state) +{ + std::vector units; + int x1 = luaL_checkint(state, 1); + int y1 = luaL_checkint(state, 2); + int z1 = luaL_checkint(state, 3); + int x2 = luaL_checkint(state, 4); + int y2 = luaL_checkint(state, 5); + int z2 = luaL_checkint(state, 6); + + bool ok = Units::getUnitsInBox(units, x1, y1, z1, x2, y2, z2); + + if (ok && !lua_isnone(state, 7)) + { + luaL_checktype(state, 7, LUA_TFUNCTION); + units.erase(std::remove_if(units.begin(), units.end(), [&state](df::unit *unit) -> bool { + lua_dup(state); // copy function + Lua::PushDFObject(state, unit); + lua_call(state, 1, 1); + bool ret = lua_toboolean(state, -1); + lua_pop(state, 1); // remove return value + return !ret; + }), units.end()); + } + + Lua::PushVector(state, units); + lua_pushboolean(state, ok); + return 2; +} + static const luaL_Reg dfhack_units_funcs[] = { { "getPosition", units_getPosition }, { "getNoblePositions", units_getNoblePositions }, + { "getUnitsInBox", units_getUnitsInBox }, { NULL, NULL } }; From 27343e3253b2693339f6a4356b6f2a118238d173 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Jun 2017 21:54:08 -0400 Subject: [PATCH 0542/1012] Add unit and tile visibility functions --- docs/Lua API.rst | 8 ++++++++ library/LuaApi.cpp | 9 +++++++++ library/include/modules/Maps.h | 3 +++ library/include/modules/Units.h | 1 + library/modules/Maps.cpp | 11 +++++++++++ library/modules/Units.cpp | 7 +++++++ 6 files changed, 39 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 0e242a815..fb09f96fd 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1135,6 +1135,10 @@ Units module The unit is an alive sane citizen of the fortress; wraps the same checks the game uses to decide game-over by extinction. +* ``dfhack.units.isVisible(unit)`` + + The unit is visible on the map. + * ``dfhack.units.getAge(unit[,true_age])`` Returns the age of the unit in years as a floating-point value. @@ -1300,6 +1304,10 @@ Maps module Checks if the given df::coord or x,y,z in local tile coordinates are valid. +* ``dfhack.maps.isTileVisible(coords)``, or ``isTileVisible(x,y,z)`` + + Checks if the given df::coord or x,y,z in local tile coordinates is visible. + * ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)`` Returns a map block object for given df::coord or x,y,z in local tile coordinates. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index af6e03676..38319dcfc 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1572,6 +1572,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isSane), WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), + WRAPM(Units, isVisible), WRAPM(Units, getAge), WRAPM(Units, getKillCount), WRAPM(Units, getNominalSkill), @@ -1818,6 +1819,13 @@ static int maps_isValidTilePos(lua_State *L) return 1; } +static int maps_isTileVisible(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + lua_pushboolean(L, Maps::isTileVisible(pos)); + return 1; +} + static int maps_getTileBlock(lua_State *L) { auto pos = CheckCoordXYZ(L, 1, true); @@ -1866,6 +1874,7 @@ static int maps_getTileBiomeRgn(lua_State *L) static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, + { "isTileVisible", maps_isTileVisible }, { "getTileBlock", maps_getTileBlock }, { "ensureTileBlock", maps_ensureTileBlock }, { "getTileType", maps_getTileType }, diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index af3a9f5d5..fb5fc9860 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -263,6 +263,9 @@ extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); extern DFHACK_EXPORT bool isValidTilePos(int32_t x, int32_t y, int32_t z); inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, pos.z); } +extern DFHACK_EXPORT bool isTileVisible(int32_t x, int32_t y, int32_t z); +inline bool isTileVisible(df::coord pos) { return isTileVisible(pos.x, pos.y, pos.z); } + /** * Get the map block or NULL if block is not valid */ diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 41859e983..7394c5300 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -116,6 +116,7 @@ DFHACK_EXPORT bool isAvailableForAdoption(df::unit* unit); DFHACK_EXPORT bool isOwnCiv(df::unit* unit); DFHACK_EXPORT bool isOwnGroup(df::unit* unit); DFHACK_EXPORT bool isOwnRace(df::unit* unit); +DFHACK_EXPORT bool isVisible(df::unit* unit); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index e9674437a..af2cd5183 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -166,6 +166,17 @@ bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z) return true; } +bool Maps::isTileVisible(int32_t x, int32_t y, int32_t z) +{ + df::map_block *block = getTileBlock(x, y, z); + if (!block) + return false; + if (block->designation[x % 16][y % 16].bits.hidden) + return false; + + return true; +} + df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) { if (!isValidTilePos(x,y,z)) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index ffec94910..41e9f2eba 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -42,6 +42,7 @@ using namespace std; // we connect to those #include "modules/Units.h" #include "modules/Items.h" +#include "modules/Maps.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "ModuleFactory.h" @@ -526,6 +527,12 @@ bool Units::isOwnRace(df::unit* unit) return unit->race == ui->race_id; } +bool Units::isVisible(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return Maps::isTileVisible(unit->pos); +} + // get race name by id or unit pointer string Units::getRaceNameById(int32_t id) { From be606498d4965fd496b2c5a046bdf9f3be8cc835 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 17 Jun 2017 19:57:32 -0400 Subject: [PATCH 0543/1012] Disable automatic hyphenation in docs This was producing undesirable results, such as hyphenating every line in some paragraphs, in the middle of short words like "repeat", etc. --- docs/styles/dfhack.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 67f75b3b1..d12348c31 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -1,10 +1,10 @@ /* make sure to sync this with the base theme's css filename */ @import url("alabaster.css"); -.kbd { /* Keybinding CSS from the DF wiki; applies to :kbd:`` directives. * Use this directive for all keypresses, to make them look like keys. */ +.kbd { border: 1px solid #aaa; border-radius: 0.2em; -webkit-border-radius: 0.2em; @@ -28,13 +28,21 @@ src: url("cp437.ttf"); } -.guilabel { /* In-game text CSS from the DF wiki; applies to :guilabel:`` directives. * Use this for any text from an in-game announcement or menu. */ +.guilabel { color: #CBC7C0; font-family: cp437, 'fixedsys', monospace; background: #000000; font-size: 0.95em; padding: 0.05em 0.4em; } + +/* Override hyphenation from Sphinx's basic.css (excessive) */ +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} From f1241e6084d9d329abd82a5f402525d9d244d239 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 17 Jun 2017 20:05:26 -0400 Subject: [PATCH 0544/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 22cf766a9..d461a8417 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 22cf766a9b81cdf892cfb4181305ca7eaf1d56a7 +Subproject commit d461a8417260cba96db7028ef0602a787e48274b From 0796fafb2abeff7c6811fe5413fa205829a3570d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 18 Jun 2017 17:39:54 -0400 Subject: [PATCH 0545/1012] Add new built-in "alias" command Closes #701 --- docs/Core.rst | 23 +++++++++ library/Core.cpp | 112 +++++++++++++++++++++++++++++++++++++++-- library/include/Core.h | 11 ++++ 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index 1c24fd825..2c5d1168f 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -106,6 +106,29 @@ of DFhack, rather than plugins or scripts. .. _cls: +alias +----- +The ``alias`` command allows configuring aliases to other DFHack commands. +Aliases are resolved immediately after built-in commands, which means that an +alias cannot override a built-in command, but can override a command implemented +by a plugin or script. + +Usage: + +:``alias list``: lists all configured aliases +:``alias add [arguments...]``: adds an alias +:``alias replace [arguments...]``: replaces an existing + alias with a new command, or adds the alias if it does not already exist +:``alias delete ``: removes the specified alias + +Aliases can be given additional arguments when created and invoked, which will +be passed to the underlying command in order. An example with `devel/print-args`:: + + [DFHack]# alias add pargs devel/print-args example + [DFHack]# pargs text + example + text + cls --- Clear the terminal. Does not delete command history. diff --git a/library/Core.cpp b/library/Core.cpp index 83b77f7d0..484301c3d 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -603,6 +603,7 @@ string getBuiltinCommand(std::string cmd) cmd == "disable" || cmd == "plug" || cmd == "keybinding" || + cmd == "alias" || cmd == "fpause" || cmd == "cls" || cmd == "die" || @@ -650,6 +651,7 @@ void ls_helper(color_ostream &con, const PluginCommand &pcmd) command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) { std::string first = first_; + command_result res; if (!first.empty()) { if(first.find('\\') != std::string::npos) @@ -787,8 +789,6 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(parts.size()) { - command_result res = CR_OK; - for (size_t i = 0; i < parts.size(); i++) { std::string part = parts[i]; @@ -994,6 +994,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v con << " (aliased to " << builtin_cmd << ")"; con << std::endl; } + else if (IsAlias(parts[0])) + { + con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; + } else if (plug) { con << " is a command implemented by the plugin " << plug->getName() << std::endl; @@ -1063,6 +1067,42 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v << Gui::getFocusString(Core::getTopViewscreen()) << endl; } } + else if (builtin == "alias") + { + if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) + { + const string &name = parts[1]; + vector cmd(parts.begin() + 2, parts.end()); + if (!AddAlias(name, cmd, parts[0] == "replace")) + { + con.printerr("Could not add alias %s - already exists\n", name.c_str()); + return CR_FAILURE; + } + } + else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) + { + if (!RemoveAlias(parts[1])) + { + con.printerr("Could not remove alias %s\n", parts[1].c_str()); + return CR_FAILURE; + } + } + else if (parts.size() >= 1 && (parts[0] == "list")) + { + auto aliases = ListAliases(); + for (auto p : aliases) + { + con << p.first << ": " << join_strings(" ", p.second) << endl; + } + } + else + { + con << "Usage: " << endl + << " alias add|replace " << endl + << " alias delete|clear " << endl + << " alias list" << endl; + } + } else if (builtin == "fpause") { World::SetPauseState(true); @@ -1218,9 +1258,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_WRONG_USAGE; } } + else if (RunAlias(con, first, parts, res)) + { + return res; + } else { - command_result res = plug_mgr->InvokeCommand(con, first, parts); + res = plug_mgr->InvokeCommand(con, first, parts); if(res == CR_NOT_IMPLEMENTED) { string completed; @@ -1405,7 +1449,8 @@ Core::Core() hotkey_set = false; HotkeyMutex = 0; HotkeyCond = 0; - misc_data_mutex=0; + alias_mutex = 0; + misc_data_mutex = 0; last_world_data_ptr = NULL; last_local_map_ptr = NULL; last_pause_state = false; @@ -1526,6 +1571,7 @@ bool Core::Init() // Init global object pointers df::global::InitGlobals(); + alias_mutex = new recursive_mutex(); cerr << "Initializing Console.\n"; // init the console. @@ -2576,6 +2622,64 @@ std::vector Core::ListKeyBindings(std::string keyspec) return rv; } +bool Core::AddAlias(const std::string &name, const std::vector &command, bool replace) +{ + tthread::lock_guard lock(*alias_mutex); + if (!IsAlias(name) || replace) + { + aliases[name] = command; + return true; + } + return false; +} + +bool Core::RemoveAlias(const std::string &name) +{ + tthread::lock_guard lock(*alias_mutex); + if (IsAlias(name)) + { + aliases.erase(name); + return true; + } + return false; +} + +bool Core::IsAlias(const std::string &name) +{ + tthread::lock_guard lock(*alias_mutex); + return aliases.find(name) != aliases.end(); +} + +bool Core::RunAlias(color_ostream &out, const std::string &name, + const std::vector ¶meters, command_result &result) +{ + tthread::lock_guard lock(*alias_mutex); + if (!IsAlias(name)) + { + return false; + } + + const string &first = aliases[name][0]; + vector parts(aliases[name].begin() + 1, aliases[name].end()); + parts.insert(parts.end(), parameters.begin(), parameters.end()); + result = runCommand(out, first, parts); + return true; +} + +std::map> Core::ListAliases() +{ + tthread::lock_guard lock(*alias_mutex); + return aliases; +} + +std::string Core::GetAliasCommand(const std::string &name, const std::string &default_) +{ + tthread::lock_guard lock(*alias_mutex); + if (IsAlias(name)) + return join_strings(" ", aliases[name]); + else + return default_; +} ///////////////// // ClassNameCheck diff --git a/library/include/Core.h b/library/include/Core.h index 6bcf9ee8e..fa65645a1 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -170,6 +170,14 @@ namespace DFHack std::vector ListKeyBindings(std::string keyspec); int8_t getModstate() { return modstate; } + bool AddAlias(const std::string &name, const std::vector &command, bool replace = false); + bool RemoveAlias(const std::string &name); + bool IsAlias(const std::string &name); + bool RunAlias(color_ostream &out, const std::string &name, + const std::vector ¶meters, command_result &result); + std::map> ListAliases(); + std::string GetAliasCommand(const std::string &name, const std::string &default_ = ""); + std::string getHackPath(); bool isWorldLoaded() { return (last_world_data_ptr != NULL); } @@ -256,6 +264,9 @@ namespace DFHack tthread::mutex * HotkeyMutex; tthread::condition_variable * HotkeyCond; + std::map> aliases; + tthread::recursive_mutex * alias_mutex; + bool SelectHotkey(int key, int modifiers); // for state change tracking From 3da9176c7620f055d141e1e19438e1de9b7c3088 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 20 Jun 2017 10:51:41 +0530 Subject: [PATCH 0546/1012] Update submodules --- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index f88b520a0..29ba19bf7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f88b520a0d06753dd0ddb059c74f5b785ad98a44 +Subproject commit 29ba19bf7a4bab69854c99135f3a66fcdb2227ed diff --git a/plugins/stonesense b/plugins/stonesense index 13c4c0b4b..be793a080 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 13c4c0b4b802cc50aaa5a48cb9f93678348cc165 +Subproject commit be793a080e66db1ff79ac284070632ec1a896708 diff --git a/scripts b/scripts index c4f6877a6..d461a8417 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit c4f6877a69ac1c69a405d122dd6e75c5605fe782 +Subproject commit d461a8417260cba96db7028ef0602a787e48274b From 48f5fd7a398dcda8a13dde40606e2c3725462428 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 20 Jun 2017 11:06:37 +0530 Subject: [PATCH 0547/1012] Fix missing room info on sent buildings. --- plugins/remotefortressreader/building_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 09cfb6b76..4a4fd71ab 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -315,7 +315,7 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re remote_build->set_building_flags(local_build->flags.whole); remote_build->set_is_room(local_build->is_room); - if (local_build->is_room || local_build->getType() == df::enums::building_type::Civzone || local_build->getType() == df::enums::building_type::Stockpile) + if (local_build->room.width > 0 && local_build->room.height > 0) { auto room = remote_build->mutable_room(); room->set_pos_x(local_build->room.x); From 9ae50279a193352d1371466d49868d17fa7cb85f Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Tue, 20 Jun 2017 15:46:03 +0530 Subject: [PATCH 0548/1012] Moved the version define into a header file, and fixed removing designations that are alreaddy converted to jobs. --- plugins/proto/RemoteFortressReader.proto | 2 + plugins/remotefortressreader/CMakeLists.txt | 1 + plugins/remotefortressreader/df_version_int.h | 3 ++ .../remotefortressreader.cpp | 44 ++++++++++++++++++- 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 plugins/remotefortressreader/df_version_int.h diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 593c174df..2d53744c5 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -254,6 +254,8 @@ message MapBlock repeated TileDigDesignation tile_dig_designation = 24; repeated SpatterPile spatterPile = 25; repeated Item items = 26; + repeated bool tile_dig_designation_marker = 27; + repeated bool tile_dig_designation_auto = 28; } message MatPair { diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index b9f120059..ee6383a3c 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -7,6 +7,7 @@ SET(PROJECT_SRCS # A list of headers SET(PROJECT_HDRS building_reader.h + df_version_int.h ) #proto files to include. SET(PROJECT_PROTO diff --git a/plugins/remotefortressreader/df_version_int.h b/plugins/remotefortressreader/df_version_int.h new file mode 100644 index 000000000..6a906fe43 --- /dev/null +++ b/plugins/remotefortressreader/df_version_int.h @@ -0,0 +1,3 @@ +#ifndef DF_VERSION_INT_H +#define DF_VERSION_INT 43005 +#endif // !DF_VERSION_INT_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 0a0c684d3..b0e9fd98a 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,4 +1,4 @@ -#define DF_VERSION_INT 42004 +#include "df_version_int.h" #define RFR_VERSION "0.16.1" #include @@ -21,6 +21,7 @@ #include "modules/Gui.h" #include "modules/Items.h" +#include "modules/Job.h" #include "modules/MapCache.h" #include "modules/Maps.h" #include "modules/Materials.h" @@ -1035,6 +1036,7 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N for (int xx = 0; xx < 16; xx++) { df::tile_designation designation = DfBlock->designation[xx][yy]; + df::tile_occupancy occupancy = DfBlock->occupancy[xx][yy]; int lava = 0; int water = 0; if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) @@ -1054,10 +1056,14 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N auto fog_of_war = DfBlock->fog_of_war[xx][yy]; NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + NetBlock->add_tile_dig_designation_marker(false); + NetBlock->add_tile_dig_designation_auto(false); } else { NetBlock->add_hidden(designation.bits.hidden); + NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); + NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); switch (designation.bits.dig) { case df::enums::tile_dig_designation::No: @@ -2548,6 +2554,42 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in break; } mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + + //remove and job postings related. + for (df::job_list_link * listing = &(world->job_list); listing != NULL; listing = listing->next) + { + if (listing->item == NULL) + continue; + auto type = listing->item->job_type; + switch (type) + { + case df::enums::job_type::CarveFortification: + case df::enums::job_type::DetailWall: + case df::enums::job_type::DetailFloor: + case df::enums::job_type::Dig: + case df::enums::job_type::CarveUpwardStaircase: + case df::enums::job_type::CarveDownwardStaircase: + case df::enums::job_type::CarveUpDownStaircase: + case df::enums::job_type::CarveRamp: + case df::enums::job_type::DigChannel: + case df::enums::job_type::FellTree: + case df::enums::job_type::GatherPlants: + case df::enums::job_type::RemoveConstruction: + case df::enums::job_type::CarveTrack: + { + if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z())) + { + Job::removeJob(listing->item); + goto JOB_FOUND; + } + break; + } + default: + continue; + } + } + JOB_FOUND: + continue; } mc.WriteAll(); From 573625f717451a89b6af40250c26a224776af66c Mon Sep 17 00:00:00 2001 From: Alan Brady Date: Tue, 20 Jun 2017 23:47:04 -0700 Subject: [PATCH 0549/1012] Fix forum thread link in README.md The README is pointing to the old thread, which in turn leads to an old download. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3d51c4d1..9ea1ccb04 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,5 @@ If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. If that's unclear or you need more help, try -[the Bay12 forums thread](http://www.bay12forums.com/smf/index.php?topic=139553) +[the Bay12 forums thread](http://www.bay12forums.com/smf/index.php?topic=164123) or the #dfhack IRC channel on freenode. From 0348de48809f7b3a2e1a1312aff8bd14f4f06341 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 22 Jun 2017 20:29:10 +0530 Subject: [PATCH 0550/1012] Add a command to get the current RemoteFortressReader version --- plugins/remotefortressreader/remotefortressreader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 0a0c684d3..d009c7555 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -206,6 +206,11 @@ command_result dump_bp_mods(color_ostream &out, vector & parameters) return CR_OK; } +command_result RemoteFortressReader_version(color_ostream &out, vector ¶meters) +{ + out.print(RFR_VERSION); + return CR_OK; +} // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) @@ -220,6 +225,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Thu, 22 Jun 2017 11:06:37 -0400 Subject: [PATCH 0551/1012] Travis: Add script to build Lua 5.3 --- .travis.yml | 11 +++++++--- travis/build-lua.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 travis/build-lua.sh diff --git a/.travis.yml b/.travis.yml index 3e5677ff7..84175522d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,12 @@ sudo: false language: cpp +cache: + pip: true + directories: + - $HOME/lua53 addons: apt: packages: &default_packages - - lua5.2 - libxml-libxml-perl - libxml-libxslt-perl - zlib1g-dev:i386 @@ -19,8 +22,10 @@ matrix: - gcc-4.8-multilib - g++-4.8-multilib before_install: - pip install --user "sphinx==1.4" "requests[security]" +- pip install --user "sphinx==1.4" "requests[security]" +- sh travis/build-lua.sh script: +- export PATH="$PATH:$HOME/lua53/bin" - git tag tmp-travis-build - sh travis/git-info.sh - sphinx-build -qW -j3 . docs/html @@ -28,7 +33,7 @@ script: - python travis/lint.py - python travis/authors-rst.py - python travis/script-docs.py -- python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" || true +- python travis/script-syntax.py --ext=lua --cmd="luac5.3 -p" || true - python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis diff --git a/travis/build-lua.sh b/travis/build-lua.sh new file mode 100644 index 000000000..38abfd2a9 --- /dev/null +++ b/travis/build-lua.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +LUA_URL="https://www.lua.org/ftp/lua-5.3.3.tar.gz" +LUA_TAR=$(basename "$LUA_URL") +LUA_DIR="${LUA_TAR%.tar.*}" +LUA_SHA1="a0341bc3d1415b814cc738b2ec01ae56045d64ef" + +echo $LUA_TAR +echo $LUA_DIR + +sha1() { + python -c 'import hashlib, sys; print(hashlib.sha1(open(sys.argv[1],"rb").read()).hexdigest())' "$1" +} + +download() { + echo "Downloading $LUA_URL" + wget -O "$LUA_TAR" "$LUA_URL" + tar xvf "$LUA_TAR" +} + +build() { + cd "$LUA_DIR/src" + make generic +} + +main() { + mkdir -p "$HOME/lua53" + cd "$HOME/lua53" + mkdir -p bin + + if [ "$(sha1 "$LUA_TAR" 2>/dev/null)" != "$LUA_SHA1" ]; then + download + build + else + echo "Already downloaded" + fi + + if [ -x "$LUA_DIR/src/luac" ]; then + echo "Already built" + else + build + fi + + ln -sf "$(pwd)/$LUA_DIR/src/lua" bin/lua5.3 + ln -sf "$(pwd)/$LUA_DIR/src/luac" bin/luac5.3 +} + +main From 808835f1491ed538262a9c01743f05c67e4385c5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 22 Jun 2017 11:13:22 -0400 Subject: [PATCH 0552/1012] Download Lua insecurely Travis doesn't like the HTTPS URL: https://travis-ci.org/lethosor/dfhack/builds/245829296 --- .travis.yml | 10 +++++----- travis/build-lua.sh | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84175522d..f287c4e7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,8 +41,8 @@ script: - make -j3 notifications: email: false - irc: - channels: - - "chat.freenode.net#dfhack" - on_success: change - on_failure: always + # irc: + # channels: + # - "chat.freenode.net#dfhack" + # on_success: change + # on_failure: always diff --git a/travis/build-lua.sh b/travis/build-lua.sh index 38abfd2a9..77d6e528b 100644 --- a/travis/build-lua.sh +++ b/travis/build-lua.sh @@ -2,7 +2,7 @@ set -e -LUA_URL="https://www.lua.org/ftp/lua-5.3.3.tar.gz" +LUA_URL="http://www.lua.org/ftp/lua-5.3.3.tar.gz" LUA_TAR=$(basename "$LUA_URL") LUA_DIR="${LUA_TAR%.tar.*}" LUA_SHA1="a0341bc3d1415b814cc738b2ec01ae56045d64ef" From a718aae99472a3e530ea67700ea0870c4e18430a Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 22 Jun 2017 11:24:37 -0400 Subject: [PATCH 0553/1012] Fix more Lua download script issues --- travis/build-lua.sh | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/travis/build-lua.sh b/travis/build-lua.sh index 77d6e528b..18fd6a740 100644 --- a/travis/build-lua.sh +++ b/travis/build-lua.sh @@ -2,9 +2,10 @@ set -e +LUA_ROOT="$HOME/lua53" LUA_URL="http://www.lua.org/ftp/lua-5.3.3.tar.gz" LUA_TAR=$(basename "$LUA_URL") -LUA_DIR="${LUA_TAR%.tar.*}" +LUA_DIR="$LUA_ROOT/${LUA_TAR%.tar.*}" LUA_SHA1="a0341bc3d1415b814cc738b2ec01ae56045d64ef" echo $LUA_TAR @@ -16,8 +17,8 @@ sha1() { download() { echo "Downloading $LUA_URL" - wget -O "$LUA_TAR" "$LUA_URL" - tar xvf "$LUA_TAR" + wget -O "$LUA_ROOT/$LUA_TAR" "$LUA_URL" + tar xvf "$LUA_ROOT/$LUA_TAR" } build() { @@ -26,25 +27,27 @@ build() { } main() { - mkdir -p "$HOME/lua53" - cd "$HOME/lua53" + mkdir -p "$LUA_ROOT" + cd "$LUA_ROOT" mkdir -p bin - if [ "$(sha1 "$LUA_TAR" 2>/dev/null)" != "$LUA_SHA1" ]; then + if [ "$(sha1 "$LUA_ROOT/$LUA_TAR" 2>/dev/null)" != "$LUA_SHA1" ]; then download build else echo "Already downloaded" - fi - if [ -x "$LUA_DIR/src/luac" ]; then - echo "Already built" - else - build + if [ -x "$LUA_DIR/src/luac" ]; then + echo "Already built" + else + build + fi fi - ln -sf "$(pwd)/$LUA_DIR/src/lua" bin/lua5.3 - ln -sf "$(pwd)/$LUA_DIR/src/luac" bin/luac5.3 + echo "Linking" + ln -sf "$LUA_DIR/src/lua" "$LUA_ROOT/bin/lua5.3" + ln -sf "$LUA_DIR/src/luac" "$LUA_ROOT/bin/luac5.3" + echo "Done" } main From f020ad0546cc92cca5a551e2a56091396e070d67 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 22 Jun 2017 11:37:44 -0400 Subject: [PATCH 0554/1012] Re-enable Lua test and IRC notifications --- .travis.yml | 12 ++++++------ travis/build-lua.sh | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index f287c4e7a..ad17693a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ script: - python travis/lint.py - python travis/authors-rst.py - python travis/script-docs.py -- python travis/script-syntax.py --ext=lua --cmd="luac5.3 -p" || true +- python travis/script-syntax.py --ext=lua --cmd="luac5.3 -p" - python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis @@ -41,8 +41,8 @@ script: - make -j3 notifications: email: false - # irc: - # channels: - # - "chat.freenode.net#dfhack" - # on_success: change - # on_failure: always + irc: + channels: + - "chat.freenode.net#dfhack" + on_success: change + on_failure: always diff --git a/travis/build-lua.sh b/travis/build-lua.sh index 18fd6a740..e29d7ce87 100644 --- a/travis/build-lua.sh +++ b/travis/build-lua.sh @@ -8,8 +8,9 @@ LUA_TAR=$(basename "$LUA_URL") LUA_DIR="$LUA_ROOT/${LUA_TAR%.tar.*}" LUA_SHA1="a0341bc3d1415b814cc738b2ec01ae56045d64ef" -echo $LUA_TAR -echo $LUA_DIR +echo LUA_ROOT $LUA_ROOT +echo LUA_TAR $LUA_TAR +echo LUA_DIR $LUA_DIR sha1() { python -c 'import hashlib, sys; print(hashlib.sha1(open(sys.argv[1],"rb").read()).hexdigest())' "$1" From ac0134568ebd7d996ee10d43cba571ece9f39571 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 22 Jun 2017 23:57:04 +0530 Subject: [PATCH 0555/1012] Don't read empty rooms, and update version --- plugins/remotefortressreader/building_reader.cpp | 2 +- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 4a4fd71ab..7f0c9a6ff 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -315,7 +315,7 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re remote_build->set_building_flags(local_build->flags.whole); remote_build->set_is_room(local_build->is_room); - if (local_build->room.width > 0 && local_build->room.height > 0) + if (local_build->room.width > 0 && local_build->room.height > 0 && local_build->room.extents != nullptr) { auto room = remote_build->mutable_room(); room->set_pos_x(local_build->room.x); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 017c075f0..0305a0291 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.16.1" +#define RFR_VERSION "0.17.0" #include #include From ae809afde7af4946ed54f3c0c2f487f5533aaef5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 22 Jun 2017 23:56:35 -0400 Subject: [PATCH 0556/1012] Add Gui::refreshSidebar() This handles feeding CURSOR_DOWN_Z and CURSOR_UP_Z properly, avoiding issues when on the lowest z-level. --- library/LuaApi.cpp | 2 ++ library/include/modules/Gui.h | 1 + library/lua/gui/dwarfmode.lua | 2 ++ library/modules/Gui.cpp | 20 ++++++++++++++++++++ plugins/automaterial.cpp | 3 +-- plugins/buildingplan.cpp | 6 ++---- plugins/dwarfmonitor.cpp | 3 +-- plugins/mousequery.cpp | 11 +---------- plugins/stocks.cpp | 3 +-- plugins/tweak/tweaks/stable-cursor.h | 15 +-------------- scripts | 2 +- 11 files changed, 33 insertions(+), 35 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 38319dcfc..ca958cfdc 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1486,7 +1486,9 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showAutoAnnouncement), + WRAPM(Gui, resetDwarfmodeView), WRAPM(Gui, revealInDwarfmodeMap), + WRAPM(Gui, refreshSidebar), WRAPM(Gui, getDepthAt), { NULL, NULL } }; diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index d3e8497ce..ec5d1ea94 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -148,6 +148,7 @@ namespace DFHack DFHACK_EXPORT void resetDwarfmodeView(bool pause = false); DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false); + DFHACK_EXPORT bool refreshSidebar(); DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z); DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z); diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 27649bc6e..3764c4df7 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -14,6 +14,8 @@ local world_map = df.global.world.map AREA_MAP_WIDTH = 23 MENU_WIDTH = 30 +refreshSidebar = dfhack.gui.refreshSidebar + function getPanelLayout() local dims = dfhack.gui.getDwarfmodeViewDims() local area_pos = df.global.ui_area_map_width diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 611e00205..950aef60a 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1594,6 +1594,26 @@ bool Gui::revealInDwarfmodeMap(df::coord pos, bool center) return true; } +bool Gui::refreshSidebar() +{ + auto scr = getViewscreenByType(0); + if (scr) + { + if (df::global::window_z && *df::global::window_z == 0) + { + scr->feed_key(interface_key::CURSOR_UP_Z); + scr->feed_key(interface_key::CURSOR_DOWN_Z); + } + else + { + scr->feed_key(interface_key::CURSOR_UP_Z); + scr->feed_key(interface_key::CURSOR_DOWN_Z); + } + return true; + } + return false; +} + bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) { x = *df::global::window_x; diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 2465c6325..cfbc95ae7 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -868,8 +868,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest void move_cursor(df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); } void move_cursor(coord32_t &pos) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index f72888d00..747e5cfb7 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -107,8 +107,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest planmode_enabled[type] = !planmode_enabled[type]; if (!planmode_enabled[type]) { - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); planner.in_dummmy_screen = false; } return true; @@ -140,8 +139,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest { if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) { - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); if (planner.inQuickFortMode()) { planner.in_dummmy_screen = true; diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 2c78c0854..2ea811f38 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -147,8 +147,7 @@ static void send_key(const df::interface_key &key) static void move_cursor(df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); } static void open_stats_srceen(); diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 7f0095c58..7a70f29c4 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -507,16 +507,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return; Gui::setCursorCoords(mpos.x, mpos.y, mpos.z); - if (mpos.z == 0) - { - sendKey(interface_key::CURSOR_UP_Z); - sendKey(interface_key::CURSOR_DOWN_Z); - } - else - { - sendKey(interface_key::CURSOR_DOWN_Z); - sendKey(interface_key::CURSOR_UP_Z); - } + Gui::refreshSidebar(); } bool inBuildPlacement() diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 1d3d04d6f..a5b84672d 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -981,8 +981,7 @@ public: void move_cursor(const df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); } void send_key(const df::interface_key &key) diff --git a/plugins/tweak/tweaks/stable-cursor.h b/plugins/tweak/tweaks/stable-cursor.h index 105510c8b..7b6482d7b 100644 --- a/plugins/tweak/tweaks/stable-cursor.h +++ b/plugins/tweak/tweaks/stable-cursor.h @@ -52,20 +52,7 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest last_cursor.isValid() && cur_cursor.isValid()) { Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z); - - // Force update of ui state - set tmp; - if (last_cursor.z < 2) - tmp.insert(interface_key::CURSOR_UP_Z); - else - tmp.insert(interface_key::CURSOR_DOWN_Z); - INTERPOSE_NEXT(feed)(&tmp); - tmp.clear(); - if (last_cursor.z < 2) - tmp.insert(interface_key::CURSOR_DOWN_Z); - else - tmp.insert(interface_key::CURSOR_UP_Z); - INTERPOSE_NEXT(feed)(&tmp); + Gui::refreshSidebar(); } else if (!is_default && cur_cursor.isValid()) { diff --git a/scripts b/scripts index d461a8417..3e431de4f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit d461a8417260cba96db7028ef0602a787e48274b +Subproject commit 3e431de4fe3ffa39c9854d74b6d85de5e92834bd From a2f6c80298b8a541def7d932dc702e9f24550929 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Fri, 23 Jun 2017 16:02:30 +0530 Subject: [PATCH 0557/1012] Add compatibility defines to remotefortressreader.cpp --- .../remotefortressreader.cpp | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 0305a0291..ebc643fd5 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1775,7 +1775,9 @@ static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) plantMat->set_mat_type(419); } } +#if DF_VERSION_INT >= 43005 out->set_snow(e1->snowfall); +#endif } static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) @@ -2188,6 +2190,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); +#if DF_VERSION_INT >= 43005 STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); if (tower_info) { @@ -2212,6 +2215,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w out_wall->set_end_y(wall_info->end_y - (site_y * 48)); out_wall->set_end_z(wall_info->end_z); } +#endif } } @@ -2558,16 +2562,17 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in break; default: break; - } - mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); - - //remove and job postings related. - for (df::job_list_link * listing = &(world->job_list); listing != NULL; listing = listing->next) - { - if (listing->item == NULL) - continue; - auto type = listing->item->job_type; - switch (type) + } + mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + +#if DF_VERSION_INT >= 43005 + //remove and job postings related. + for (df::job_list_link * listing = &(world->job_list); listing != NULL; listing = listing->next) + { + if (listing->item == NULL) + continue; + auto type = listing->item->job_type; + switch (type) { case df::enums::job_type::CarveFortification: case df::enums::job_type::DetailWall: @@ -2596,6 +2601,7 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in } JOB_FOUND: continue; +#endif } mc.WriteAll(); From 31dbb792caee0ecc9bbd880dced129fd5d1987b4 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 23 Jun 2017 19:54:22 +0530 Subject: [PATCH 0558/1012] Added a function to get a partial creature raw list. --- plugins/proto/RemoteFortressReader.proto | 6 ++++++ .../remotefortressreader.cpp | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 2d53744c5..a1f8e1bdb 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -774,4 +774,10 @@ message VersionInfo optional string dwarf_fortress_version = 1; optional string dfhack_version = 2; optional string remote_fortress_reader_version = 3; +} + +message ListRequest +{ + optional int32 list_start = 1; + optional int32 list_end = 2; } \ No newline at end of file diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index ebc643fd5..9e32c1c1d 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -137,6 +137,7 @@ static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessag static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out); +static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out); static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out); static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); @@ -250,6 +251,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetRegionMaps", GetRegionMaps); svc->addFunction("GetRegionMapsNew", GetRegionMapsNew); svc->addFunction("GetCreatureRaws", GetCreatureRaws); + svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws); svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); svc->addFunction("GetPlantRaws", GetPlantRaws); svc->addFunction("CopyScreen", CopyScreen); @@ -2259,13 +2261,28 @@ static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage } static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) +{ + return GetPartialCreatureRaws(stream, NULL, out); +} + +static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out) { if (!df::global::world) return CR_FAILURE; df::world * world = df::global::world; - for (int i = 0; i < world->raws.creatures.all.size(); i++) + int list_start = 0; + int list_end = world->raws.creatures.all.size(); + + if (in != nullptr) + { + list_start = in->list_start(); + if(in->list_end() < list_end) + list_end = in->list_end(); + } + + for (int i = list_start; i < list_end; i++) { df::creature_raw * orig_creature = world->raws.creatures.all[i]; From cda5cedacf46b173b954cdf0c04fc0ca240628e1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 23 Jun 2017 10:30:16 -0400 Subject: [PATCH 0559/1012] Fix Gui::refreshSidebar() on topmost z-level --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 950aef60a..5b56ee1ed 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1606,8 +1606,8 @@ bool Gui::refreshSidebar() } else { - scr->feed_key(interface_key::CURSOR_UP_Z); scr->feed_key(interface_key::CURSOR_DOWN_Z); + scr->feed_key(interface_key::CURSOR_UP_Z); } return true; } From bf8e65311b22bffef5b3377475d12cbffa129505 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 23 Jun 2017 12:43:38 -0400 Subject: [PATCH 0560/1012] Update submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index f88b520a0..3322beb2e 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f88b520a0d06753dd0ddb059c74f5b785ad98a44 +Subproject commit 3322beb2e7f4b28ff8e573e9bec738c77026b8e9 diff --git a/plugins/stonesense b/plugins/stonesense index 13c4c0b4b..be793a080 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 13c4c0b4b802cc50aaa5a48cb9f93678348cc165 +Subproject commit be793a080e66db1ff79ac284070632ec1a896708 From 789fc6dde06b7667b434b51015498dc8b8ac886d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 23 Jun 2017 12:44:34 -0400 Subject: [PATCH 0561/1012] Documentation tweaks - Improve link in Binpatches.rst - Fix `alias` anchor in Core.rst (ref #701) - Increase depth of Plugins.rst table of contents. Some plugins were hard to find because they fit in multiple categories. --- docs/Binpatches.rst | 7 +++---- docs/Core.rst | 6 +++++- docs/Plugins.rst | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/Binpatches.rst b/docs/Binpatches.rst index 53e1455b4..a0fcc6630 100644 --- a/docs/Binpatches.rst +++ b/docs/Binpatches.rst @@ -13,15 +13,14 @@ the `binpatch` command. We recommend using a script or plugin instead of a raw patch if at all possible - that way your work will work for many versions - across multiple operating systems. There's a reason nobody has - written patches since ``0.34.11``! + across multiple operating systems. .. contents:: Getting a patch =============== -There are no binary patches available for Dwarf Fortress versions after 0.34.11 +There are no binary patches available for Dwarf Fortress versions after 0.34.11. This system is kept for the chance that someone will find it useful, so some hints on how to write your own follow. This will require disassembly and @@ -29,7 +28,7 @@ decent skill in `memory research `. * The patches are expected to be encoded in text format used by IDA. -* See :commit:`8a9e3d1a728` for examples. +* See `the patches folder in commit b0e1b51 `_ for examples. * :issue:`546` is about the future of the binpatches, and may be useful reading. diff --git a/docs/Core.rst b/docs/Core.rst index 2c5d1168f..15adcc533 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -104,7 +104,8 @@ of DFhack, rather than plugins or scripts. .. contents:: :local: -.. _cls: + +.. _alias: alias ----- @@ -129,6 +130,9 @@ be passed to the underlying command in order. An example with `devel/print-args` example text + +.. _cls: + cls --- Clear the terminal. Does not delete command history. diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 75e3a8f8e..95d827f71 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -10,7 +10,7 @@ Most commands offered by plugins are listed here, hopefully organised in a way you will find useful. .. contents:: - :depth: 2 + :depth: 3 =============================== Data inspection and visualizers From db375ae83b32d1d87fc40de61138ff20079fb59b Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 23 Jun 2017 12:46:46 -0400 Subject: [PATCH 0562/1012] Add a Pages:getSelectedPage() helper Equivalent to select(2, pages:getSelected()), but more readable. --- library/lua/gui/widgets.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index c51d9ea73..a1124323d 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -109,6 +109,10 @@ function Pages:getSelected() return self.selected, self.subviews[self.selected] end +function Pages:getSelectedPage() + return self.subviews[self.selected] +end + ---------------- -- Edit field -- ---------------- From 52b9a8f4ad57e6116b8c8550a4d30c73737cc35b Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Sat, 24 Jun 2017 10:52:09 +0530 Subject: [PATCH 0563/1012] Added a function to get a subset of the plant raws. --- .../remotefortressreader.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 9e32c1c1d..abdc267fe 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -139,6 +139,7 @@ static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out); static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out); static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out); +static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out); static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); static command_result SendDigCommand(color_ostream &stream, const DigCommand *in); @@ -254,6 +255,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws); svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); svc->addFunction("GetPlantRaws", GetPlantRaws); + svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws); svc->addFunction("CopyScreen", CopyScreen); svc->addFunction("PassKeyboardEvent", PassKeyboardEvent); svc->addFunction("SendDigCommand", SendDigCommand); @@ -2462,12 +2464,28 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe } static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out) +{ + GetPartialPlantRaws(stream, nullptr, out); + return CR_OK; +} + +static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out) { if (!df::global::world) return CR_FAILURE; df::world * world = df::global::world; + int list_start = 0; + int list_end = world->raws.plants.all.size(); + + if (in != nullptr) + { + list_start = in->list_start(); + if (in->list_end() < list_end) + list_end = in->list_end(); + } + for (int i = 0; i < world->raws.plants.all.size(); i++) { df::plant_raw* plant_local = world->raws.plants.all[i]; From 10e13c532a6f96125be67e56a576809e29940613 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 25 Jun 2017 15:21:45 -0400 Subject: [PATCH 0564/1012] Add "tweak cage-butcher" and some extra Building module functions * Buildings::markedForRemoval() * Buildings::getCageOccupants() Closes #906 --- dfhack.init-example | 1 + docs/Lua API.rst | 11 ++++ docs/Plugins.rst | 1 + library/LuaApi.cpp | 15 ++++- library/include/modules/Buildings.h | 18 +++++- library/modules/Buildings.cpp | 42 +++++++++++++ plugins/tweak/tweak.cpp | 8 +++ plugins/tweak/tweaks/cage-butcher.h | 98 +++++++++++++++++++++++++++++ 8 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 plugins/tweak/tweaks/cage-butcher.h diff --git a/dfhack.init-example b/dfhack.init-example index 13fd6191a..87462975c 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -198,6 +198,7 @@ tweak embark-profile-name # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled +tweak cage-butcher tweak civ-view-agreement tweak eggs-fertile tweak fps-min diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fb09f96fd..52470225b 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1486,6 +1486,12 @@ General Returns a list of items stored on the given stockpile. Ignores empty bins, barrels, and wheelbarrows assigned as storage and transport for that stockpile. +* ``dfhack.buildings.getCageOccupants(cage)`` + + Returns a list of units in the given built cage. Note that this is different + from the list of units assigned to the cage, which can be accessed with + ``cage.assigned_units``. + Low-level ~~~~~~~~~ Low-level building creation functions: @@ -1531,6 +1537,11 @@ Low-level building creation functions: Destroys the building, or queues a deconstruction job. Returns *true* if the building was destroyed and deallocated immediately. +* ``dfhack.buildings.markedForRemoval(building)`` + + Returns *true* if the building is marked for removal (with :kbd:`x`), *false* + otherwise. + High-level ~~~~~~~~~~ More high-level functions are implemented in lua and can be loaded by diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 95d827f71..a1fd57e85 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -275,6 +275,7 @@ Subcommands that persist until disabled or DF quits: the contents separately from the container. This forcefully skips child reagents. :block-labors: Prevents labors that can't be used from being toggled +:cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). :craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ca958cfdc..4acba0809 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -85,6 +85,7 @@ distribution. #include "df/dfhack_material_category.h" #include "df/job_material_category.h" #include "df/burrow.h" +#include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/region_map_entry.h" #include "df/flow_info.h" @@ -1985,6 +1986,7 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { WRAPM(Buildings, isPenPasture), WRAPM(Buildings, isPitPond), WRAPM(Buildings, isActive), + WRAPM(Buildings, markedForRemoval), { NULL, NULL } }; @@ -2066,13 +2068,22 @@ static int buildings_getStockpileContents(lua_State *state) return 1; } +static int buildings_getCageOccupants(lua_State *state) +{ + std::vector units; + Buildings::getCageOccupants(Lua::CheckDFObject(state, 1), units); + Lua::PushVector(state, units); + return 1; +} + static const luaL_Reg dfhack_buildings_funcs[] = { { "findAtTile", buildings_findAtTile }, { "findCivzonesAt", buildings_findCivzonesAt }, { "getCorrectSize", buildings_getCorrectSize }, { "setSize", &Lua::CallWithCatchWrapper }, - { "getStockpileContents", buildings_getStockpileContents}, - { "findPenPitAt", buildings_findPenPitAt}, + { "getStockpileContents", buildings_getStockpileContents }, + { "findPenPitAt", buildings_findPenPitAt }, + { "getCageOccupants", &Lua::CallWithCatchWrapper }, { NULL, NULL } }; diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 16df7b212..4519b8164 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -43,10 +43,12 @@ distribution. namespace df { - struct job_item; - struct item; - struct building_extents; + struct building_cagest; struct building_civzonest; + struct building_extents; + struct item; + struct job_item; + struct unit; } namespace DFHack @@ -188,6 +190,11 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector &units); } } diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 55f0869a4..759e3acd4 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -54,6 +54,7 @@ using namespace DFHack; #include "df/building_bars_floorst.h" #include "df/building_bars_verticalst.h" #include "df/building_bridgest.h" +#include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/building_coffinst.h" #include "df/building_def.h" @@ -72,7 +73,9 @@ using namespace DFHack; #include "df/buildings_other_id.h" #include "df/d_init.h" #include "df/general_ref_building_holderst.h" +#include "df/general_ref_contains_unitst.h" #include "df/item.h" +#include "df/item_cagest.h" #include "df/job.h" #include "df/job_item.h" #include "df/map_block.h" @@ -1171,6 +1174,19 @@ bool Buildings::deconstruct(df::building *bld) return true; } +bool Buildings::markedForRemoval(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + for (df::job *job : bld->jobs) + { + if (job && job->job_type == df::job_type::DestroyBuilding) + { + return true; + } + } + return false; +} + static unordered_map corner1; static unordered_map corner2; @@ -1350,3 +1366,29 @@ StockpileIterator& StockpileIterator::operator++() { return *this; } +bool Buildings::getCageOccupants(df::building_cagest *cage, vector &units) +{ + CHECK_NULL_POINTER(cage); + if (!world) + return false; + if (cage->contained_items.empty()) + return false; + + auto *cage_item = virtual_cast(cage->contained_items[0]->item); + if (!cage_item) + return false; + + units.clear(); + for (df::general_ref *gref : cage_item->general_refs) + { + auto ref = virtual_cast(gref); + if (ref) + { + df::unit *unit = df::unit::find(ref->unit_id); + if (unit) + units.push_back(unit); + } + } + + return true; +} diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index a9ef091c7..1336e3b81 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -80,6 +80,7 @@ #include "tweaks/adamantine-cloth-wear.h" #include "tweaks/advmode-contained.h" #include "tweaks/block-labors.h" +#include "tweaks/cage-butcher.h" #include "tweaks/civ-agreement-ui.h" #include "tweaks/condition-material.h" #include "tweaks/craft-age-wear.h" @@ -117,6 +118,8 @@ REQUIRE_GLOBAL(enabler); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_area_map_width); REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(ui_building_in_assign); +REQUIRE_GLOBAL(ui_building_in_resize); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(ui_look_cursor); @@ -183,6 +186,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode != df::ui_sidebar_mode::QueryBuilding) + return nullptr; + + auto cage = virtual_cast(Gui::getAnyBuilding(this)); + if (!cage) + return nullptr; + if (cage->getBuildStage() < cage->getMaxBuildStage()) + return nullptr; + if (cage->flags.bits.justice) + return nullptr; + if (Buildings::markedForRemoval(cage)) + return nullptr; + + return cage; + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + using namespace df::enums::interface_key; + INTERPOSE_NEXT(render)(); + + auto cage = get_cage(); + if (!cage) + return; + + std::vector units; + if (!Buildings::getCageOccupants(cage, units)) + return; + + auto dims = Gui::getDwarfmodeViewDims(); + for (int y = 4, i = (*ui_building_item_cursor/11)*11; + y <= 14 && i < units.size(); + ++y, ++i) + { + df::unit *unit = vector_get(units, i); + if (unit && unit->flags2.bits.slaughter) + { + int x = dims.menu_x2 - 2; + OutputString(COLOR_LIGHTMAGENTA, x, y, "Bu"); + } + } + + int x = dims.menu_x1 + 1, y = dims.y2; + OutputHotkeyString(x, y, "Butcher ", CUSTOM_B, false, 0, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, "all", CUSTOM_SHIFT_B, false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + auto cage = get_cage(); + if (cage) + { + std::vector units; + if (Buildings::getCageOccupants(cage, units)) + { + df::unit *unit = vector_get(units, *ui_building_item_cursor); + if (unit) + { + if (input->count(CUSTOM_B)) + { + unit->flags2.bits.slaughter = !unit->flags2.bits.slaughter; + } + } + if (input->count(CUSTOM_SHIFT_B)) + { + bool state = unit ? !unit->flags2.bits.slaughter : true; + for (auto u : units) + u->flags2.bits.slaughter = state; + } + } + } + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(cage_butcher_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(cage_butcher_hook, render); From fe8be909924e9f876a8058961859ab94ed679148 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 25 Jun 2017 16:42:30 -0400 Subject: [PATCH 0565/1012] Add "tweak burrow-name-cancel" to fix bug 1518 http://www.bay12games.com/dwarves/mantisbt/view.php?id=1518 Closes #526 --- dfhack.init-example | 1 + docs/Plugins.rst | 2 ++ plugins/tweak/tweak.cpp | 5 +++ plugins/tweak/tweaks/burrow-name-cancel.h | 44 +++++++++++++++++++++++ plugins/tweak/tweaks/cursor-cross.h | 0 5 files changed, 52 insertions(+) create mode 100644 plugins/tweak/tweaks/burrow-name-cancel.h create mode 100644 plugins/tweak/tweaks/cursor-cross.h diff --git a/dfhack.init-example b/dfhack.init-example index 87462975c..525e823a3 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -198,6 +198,7 @@ tweak embark-profile-name # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled +tweak burrow-name-cancel tweak cage-butcher tweak civ-view-agreement tweak eggs-fertile diff --git a/docs/Plugins.rst b/docs/Plugins.rst index a1fd57e85..eec37960a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -275,6 +275,8 @@ Subcommands that persist until disabled or DF quits: the contents separately from the container. This forcefully skips child reagents. :block-labors: Prevents labors that can't be used from being toggled +:burrow-name-cancel: Implements the "back" option when renaming a burrow, + which currently does nothing (:bug:`1518`) :cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 1336e3b81..bf044dd58 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -80,6 +80,7 @@ #include "tweaks/adamantine-cloth-wear.h" #include "tweaks/advmode-contained.h" #include "tweaks/block-labors.h" +#include "tweaks/burrow-name-cancel.h" #include "tweaks/cage-butcher.h" #include "tweaks/civ-agreement-ui.h" #include "tweaks/condition-material.h" @@ -184,6 +185,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + if (ui->main.mode == df::ui_sidebar_mode::Burrows) + { + bool was_naming = ui->burrows.in_edit_name_mode; + INTERPOSE_NEXT(feed)(input); + df::burrow *burrow = vector_get(ui->burrows.list, ui->burrows.sel_index); + if (!burrow) + return; + + if (ui->burrows.in_edit_name_mode) + { + if (!was_naming) + { + // Just started renaming - make a copy of the old name + old_name = burrow->name; + } + if (input->count(df::interface_key::LEAVESCREEN)) + { + // Cancel and restore the old name + ui->burrows.in_edit_name_mode = false; + burrow->name = old_name; + } + } + } + else + { + INTERPOSE_NEXT(feed)(input); + } + } +}; + +std::string burrow_name_cancel_hook::old_name; + +IMPLEMENT_VMETHOD_INTERPOSE(burrow_name_cancel_hook, feed); diff --git a/plugins/tweak/tweaks/cursor-cross.h b/plugins/tweak/tweaks/cursor-cross.h new file mode 100644 index 000000000..e69de29bb From 6af5f3b299c0f1f6dca4b23641995d25591d3c74 Mon Sep 17 00:00:00 2001 From: Vitaly Pronkin Date: Mon, 26 Jun 2017 09:17:23 +1200 Subject: [PATCH 0566/1012] fixing getItemBaseValue() for cheese, sheets and instruments --- library/modules/Items.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 79bed0f89..654345e34 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1062,7 +1062,6 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::CHAIN: case item_type::FLASK: case item_type::GOBLET: - case item_type::INSTRUMENT: case item_type::TOY: case item_type::CAGE: case item_type::BARREL: @@ -1162,10 +1161,13 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::MEAT: case item_type::PLANT: case item_type::PLANT_GROWTH: - case item_type::CHEESE: value = 2; break; + case item_type::CHEESE: + value = 10; + break; + case item_type::FISH: case item_type::FISH_RAW: case item_type::EGG: @@ -1222,6 +1224,10 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat value = 7; break; + case item_type::SHEET: + value = 5; + break; + case item_type::PANTS: if (size_t(item_subtype) < world->raws.itemdefs.pants.size()) value = world->raws.itemdefs.pants[item_subtype]->value; @@ -1250,16 +1256,23 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::FOOD: return 10; -// case item_type::ROCK: - default: - return 0; - case item_type::TOOL: if (size_t(item_subtype) < world->raws.itemdefs.tools.size()) value = world->raws.itemdefs.tools[item_subtype]->value; else value = 10; break; + + case item_type::INSTRUMENT: + if (size_t(item_subtype) < world->raws.itemdefs.instruments.size()) + value = world->raws.itemdefs.instruments[item_subtype]->value; + else + value = 10; + break; + +// case item_type::ROCK: + default: + return 0; } MaterialInfo mat; From 10078841fb5319ad7d870fe949172e3053b4e5f1 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 27 Jun 2017 12:58:29 -0500 Subject: [PATCH 0567/1012] Remove NDEBUG definition from RelWithDebInfo to enable asserts. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e261443b0..8b8b43c86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,10 +195,12 @@ IF(UNIX) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32 -march=i686") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") ENDIF() + STRING(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od") + STRING(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") ENDIF() # use shared libraries for protobuf From 967ab4ab1ea0bd93b6ce8486e1ef24883070a602 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 27 Jun 2017 13:47:33 -0500 Subject: [PATCH 0568/1012] autolabor: Fix an array bounds overrun when assigning haulers while traders are active. --- plugins/autolabor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index b8c89215e..95863ffb5 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1311,6 +1311,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) continue; dwarfs[dwarf]->status.labors[labor] = false; } + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) + { + num_haulers--; + } continue; } From cc0220f030a94715a2d65042993611952e196bdf Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 27 Jun 2017 21:10:14 -0400 Subject: [PATCH 0569/1012] Add a "key" option to EditField and FilteredList --- docs/Lua API.rst | 7 +++++++ library/lua/gui/widgets.lua | 28 ++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 52470225b..dce48081a 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3071,6 +3071,7 @@ Attributes: If it returns false, the character is ignored. :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. +:key: If specified, the field is disabled until this key is pressed. Must be given as a string. Label class ----------- @@ -3258,11 +3259,13 @@ supports: :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_below: If true, the edit field is placed below the list instead of above. +:edit_key: If specified, the edit field is disabled until this key is pressed. :not_found_label: Specifies the text of the label shown when no items match the filter. The list choices may include the following attributes: :search_key: If specified, used instead of **text** to match against the filter. + This is required for any entries where **text** is not a string. The widget implements: @@ -3274,6 +3277,10 @@ The widget implements: Returns the list of *all* choices. +* ``list:getVisibleChoices()`` + + Returns the *filtered* list of choices. + * ``list:getFilter()`` Returns the current filter string, and the *filtered* list of choices. diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index a1124323d..741d722dd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -125,6 +125,7 @@ EditField.ATTRS{ on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, + key = DEFAULT_NIL, } function EditField:onRenderBody(dc) @@ -135,8 +136,14 @@ function EditField:onRenderBody(dc) cursor = ' ' end local txt = self.text .. cursor - if #txt > dc.width then - txt = string.char(27)..string.sub(txt, #txt-dc.width+2) + local dx = dc.x + if self.key then + dc:key_string(self.key, '') + end + dx = dc.x - dx + local max_width = dc.width - dx + if #txt > max_width then + txt = string.char(27)..string.sub(txt, #txt-max_width+2) end dc:string(txt) end @@ -649,6 +656,7 @@ FilteredList = defclass(FilteredList, Widget) FilteredList.ATTRS { edit_below = false, + edit_key = DEFAULT_NIL, } function FilteredList:init(info) @@ -657,6 +665,8 @@ function FilteredList:init(info) frame = { l = info.icon_width, t = 0, h = 1 }, on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), + key = self.edit_key, + active = (self.edit_key == nil), } self.list = List{ frame = { t = 2 }, @@ -701,10 +711,24 @@ function FilteredList:init(info) end end +function FilteredList:onInput(keys) + if self.edit_key and keys[self.edit_key] and not self.edit.active then + self.edit.active = true + elseif keys.LEAVESCREEN and self.edit.active then + self.edit.active = false + else + self:inputToSubviews(keys) + end +end + function FilteredList:getChoices() return self.choices end +function FilteredList:getVisibleChoices() + return self.list.choices +end + function FilteredList:setChoices(choices, pos) choices = choices or {} self.choices = choices From 96dd5799f7df43b28485e488e8f87ec86e1d5b95 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 30 Jun 2017 01:00:31 +0530 Subject: [PATCH 0570/1012] Add compatibility defines for 0.34.11 --- .../remotefortressreader.cpp | 100 +++++++++++++++--- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index abdc267fe..15b48677f 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -17,8 +17,9 @@ #include "SDL_events.h" #include "SDL_keyboard.h" #include "TileTypes.h" +#if DF_VERSION_INT > 34011 #include "DFHackVersion.h" - +#endif #include "modules/Gui.h" #include "modules/Items.h" #include "modules/Job.h" @@ -29,9 +30,11 @@ #include "modules/Units.h" #include "modules/World.h" +#if DF_VERSION_INT > 34011 #include "df/army.h" #include "df/army_flags.h" #include "df/block_square_event_item_spatterst.h" +#endif #include "df/block_square_event_material_spatterst.h" #include "df/body_appearance_modifier.h" #include "df/body_part_layer_raw.h" @@ -75,7 +78,9 @@ #include "df/site_realization_building.h" #include "df/site_realization_building_info_castle_towerst.h" #include "df/site_realization_building_info_castle_wallst.h" +#if DF_VERSION_INT > 34011 #include "df/site_realization_building_info_trenchesst.h" +#endif #include "df/tissue.h" #include "df/ui.h" #include "df/unit.h" @@ -191,11 +196,13 @@ command_result dump_bp_mods(color_ostream &out, vector & parameters) output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; - if (appMod->growth_rate > 0) +#if DF_VERSION_INT > 34011 + if (appMod->growth_rate > 0) { output << appMod->growth_min << " - " << appMod->growth_max << "\n"; } else +#endif { output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; @@ -665,9 +672,14 @@ bool IsspatterChanged(DFCoord pos) df::map_block * block = Maps::getBlock(pos); bool changed = false; std::vector materials; +#if DF_VERSION_INT > 34011 std::vector items; if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) return false; +#else + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL)) + return false; +#endif uint16_t hash = 0; @@ -676,12 +688,14 @@ bool IsspatterChanged(DFCoord pos) auto mat = materials[i]; hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); } +#if DF_VERSION_INT > 34011 for (int i = 0; i < items.size(); i++) { auto item = items[i]; hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); } - if (spatterHashes[pos] != hash) +#endif + if (spatterHashes[pos] != hash) { spatterHashes[pos] = hash; return true; @@ -950,6 +964,7 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc tree_z[xx][yy] = -3000; } +#if DF_VERSION_INT > 34011 df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; for (int i = 0; i < column->plants.size(); i++) { @@ -998,7 +1013,8 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc tree_z[xxx][yyy] = localPos.z; } } - for (int yy = 0; yy < 16; yy++) +#endif + for (int yy = 0; yy < 16; yy++) for (int xx = 0; xx < 16; xx++) { df::tiletype tile = DfBlock->tiletype[xx][yy]; @@ -1072,9 +1088,11 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N else { NetBlock->add_hidden(designation.bits.hidden); +#if DF_VERSION_INT > 34011 NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); - switch (designation.bits.dig) +#endif + switch (designation.bits.dig) { case df::enums::tile_dig_designation::No: NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); @@ -1103,6 +1121,7 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N } } } +#if DF_VERSION_INT > 34011 for (int i = 0; i < world->job_postings.size(); i++) { auto job = world->job_postings[i]->job; @@ -1150,7 +1169,7 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N break; } } - +#endif } void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) @@ -1195,10 +1214,14 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB NetBlock->set_map_y(DfBlock->map_pos.y); NetBlock->set_map_z(DfBlock->map_pos.z); std::vector materials; +#if DF_VERSION_INT > 34011 std::vector items; if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL, NULL, &items)) return; - +#else + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) + return; +#endif for (int yy = 0; yy < 16; yy++) for (int xx = 0; xx < 16; xx++) { @@ -1213,6 +1236,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); send_spat->set_amount(mat->amount[xx][yy]); } +#if DF_VERSION_INT > 34011 for (int i = 0; i < items.size(); i++) { auto item = items[i]; @@ -1225,6 +1249,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB send_item->set_mat_type(item->item_type); send_item->set_mat_index(item->item_subtype); } +#endif } } @@ -1542,6 +1567,7 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, Gui::getViewCoords(x, y, z); Gui::getCursorCoords(cx, cy, cz); +#if DF_VERSION_INT > 34011 auto embark = Gui::getViewscreenByType(0); if (embark) { @@ -1552,6 +1578,7 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; } } +#endif out->set_view_pos_x(x); out->set_view_pos_y(y); @@ -1589,6 +1616,7 @@ static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, DFCoord GetMapCenter() { DFCoord output; +#if DF_VERSION_INT > 34011 auto embark = Gui::getViewscreenByType(0); if (embark) { @@ -1602,12 +1630,15 @@ DFCoord GetMapCenter() output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; } } - else if (Maps::IsValid()) + else +#endif + if (Maps::IsValid()) { int x, y, z; Maps::getPosition(x,y,z); output = DFCoord(x, y, z); } +#if DF_VERSION_INT > 34011 else for (int i = 0; i < df::global::world->armies.all.size(); i++) { @@ -1619,6 +1650,7 @@ DFCoord GetMapCenter() output.z = thisArmy->pos.z; } } +#endif return output; } @@ -1668,6 +1700,7 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, out->set_name(Translation::TranslateName(&(data->name), false)); out->set_name_english(Translation::TranslateName(&(data->name), true)); auto poles = data->flip_latitude; +#if DF_VERSION_INT > 34011 switch (poles) { case df::world_data::None: @@ -1679,12 +1712,15 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, case df::world_data::South: out->set_world_poles(WorldPoles::SOUTH_POLE); break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } +#else + out->set_world_poles(WorldPoles::NO_POLES); +#endif for (int yy = 0; yy < height; yy++) for (int xx = 0; xx < width; xx++) { @@ -1700,11 +1736,18 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, out->add_savagery(map_entry->savagery); out->add_salinity(map_entry->salinity); auto clouds = out->add_clouds(); +#if DF_VERSION_INT > 34011 clouds->set_cirrus(map_entry->clouds.bits.cirrus); clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); +#else + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); +#endif if (region->type == world_region_type::Lake) { out->add_water_elevation(region->lake_surface); @@ -1805,6 +1848,7 @@ static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage * out->set_world_height(height); out->set_name(Translation::TranslateName(&(data->name), false)); out->set_name_english(Translation::TranslateName(&(data->name), true)); +#if DF_VERSION_INT > 34011 auto poles = data->flip_latitude; switch (poles) { @@ -1823,6 +1867,9 @@ static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage * default: break; } +#else + out->set_world_poles(WorldPoles::NO_POLES); +#endif for (int yy = 0; yy < height; yy++) for (int xx = 0; xx < width; xx++) { @@ -1833,11 +1880,18 @@ static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage * regionTile->set_elevation(map_entry->elevation); SetRegionTile(regionTile, map_entry); auto clouds = out->add_clouds(); +#if DF_VERSION_INT > 34011 clouds->set_cirrus(map_entry->clouds.bits.cirrus); clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); +#else + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); +#endif } DFCoord pos = GetMapCenter(); out->set_center_x(pos.x); @@ -1933,6 +1987,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w sprintf(name, "Region %d, %d", pos_x, pos_y); out->set_name_english(name); out->set_name(name); +#if DF_VERSION_INT > 34011 auto poles = worldData->flip_latitude; switch (poles) { @@ -1951,6 +2006,9 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w default: break; } +#else + out->set_world_poles(WorldPoles::NO_POLES); +#endif df::world_region_details * south = NULL; df::world_region_details * east = NULL; @@ -2186,7 +2244,9 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w auto out_building = outputTiles[region_x][region_y]->add_buildings(); out_building->set_id(in_building->id); +#if DF_VERSION_INT > 34011 out_building->set_type((SiteRealizationBuildingType)in_building->type); +#endif out_building->set_min_x(in_building->min_x - (site_x * 48)); out_building->set_min_y(in_building->min_y - (site_y * 48)); out_building->set_max_x(in_building->max_x - (site_x * 48)); @@ -2374,12 +2434,14 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe auto orig_mod = orig_caste->bp_appearance.modifiers[k]; send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); +#if DF_VERSION_INT > 34011 if (orig_mod->growth_rate > 0) { send_mod->set_mod_min(orig_mod->growth_min); send_mod->set_mod_max(orig_mod->growth_max); } else +#endif { send_mod->set_mod_min(orig_mod->ranges[0]); send_mod->set_mod_max(orig_mod->ranges[6]); @@ -2399,12 +2461,14 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); +#if DF_VERSION_INT > 34011 if (orig_mod->growth_rate > 0) { send_mod->set_mod_min(orig_mod->growth_min); send_mod->set_mod_max(orig_mod->growth_max); } else +#endif { send_mod->set_mod_min(orig_mod->ranges[0]); send_mod->set_mod_max(orig_mod->ranges[6]); @@ -2498,6 +2562,7 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque plant_remote->set_tile(plant_local->tiles.shrub_tile); else plant_remote->set_tile(plant_local->tiles.tree_tile); +#if DF_VERSION_INT > 34011 for (int j = 0; j < plant_local->growths.size(); j++) { df::plant_growth* growth_local = plant_local->growths[j]; @@ -2530,6 +2595,7 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); } +#endif } return CR_OK; } @@ -2553,6 +2619,7 @@ static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in) { +#if DF_VERSION_INT > 34011 SDL::Event e; e.key.type = in->type(); e.key.state = in->state(); @@ -2561,6 +2628,7 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve e.key.ksym.sym = (SDL::Key)in->sym(); e.key.ksym.unicode = in->unicode(); SDL_PushEvent(&e); +#endif return CR_OK; } @@ -2658,7 +2726,11 @@ static command_result GetPauseState(color_ostream &stream, const EmptyMessage *i command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) { out->set_dfhack_version(DFHACK_VERSION); +#if DF_VERSION_INT == 34011 + out->set_dwarf_fortress_version("0.34.11"); +#else out->set_dwarf_fortress_version(DF_VERSION); +#endif out->set_remote_fortress_reader_version(RFR_VERSION); return command_result(); } From 7a7f1df908e8cc5fd761c6c15bdd2259919b2b5c Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 30 Jun 2017 13:29:05 -0400 Subject: [PATCH 0571/1012] Redirect stdout and stderr again on Windows DF appears to redirect them itself at an inconvenient point. Fixes #1124 (hopefully) --- library/Core.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 484301c3d..0f6db80f9 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1504,6 +1504,17 @@ bool Core::Init() if(errorstate) return false; + // Re-route stdout and stderr again - DF seems to set up stdout and + // stderr.txt on Windows as of 0.43.05. Also, log before switching files to + // make it obvious what's going on if someone checks the *.txt files. + #ifndef LINUX_BUILD + // Don't do this on Linux because it will break PRINT_MODE:TEXT + fprintf(stdout, "dfhack: redirecting stdout to stdout.log (again)\n"); + fprintf(stderr, "dfhack: redirecting stderr to stderr.log (again)\n"); + freopen("stdout.log", "w", stdout); + freopen("stderr.log", "w", stderr); + #endif + fprintf(stderr, "DFHack build: %s\n", Version::git_description()); // find out what we are... From 6100074ba6e3864853e9123a299fd366d7258701 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 1 Jul 2017 17:39:17 -0400 Subject: [PATCH 0572/1012] Add a few MD5 and thread functions to the Lua API --- docs/Lua API.rst | 18 ++++++++++++++ library/LuaApi.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index dce48081a..d9bbfed55 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -2090,6 +2090,24 @@ and are only documented here for completeness: This requires an extension to be specified (``.lua`` or ``.rb``) - use ``dfhack.findScript()`` to include the ``.lua`` extension automatically. +* ``dfhack.internal.md5(string)`` + + Returns the MD5 hash of the given string. + +* ``dfhack.internal.md5File(filename[,first_kb])`` + + Computes the MD5 hash of the given file. Returns ``hash, length`` on success + (where ``length`` is the number of bytes read from the file), or ``nil, + error`` on failure. + + If the parameter ``first_kb`` is specified and evaluates to ``true``, and the + hash was computed successfully, a table containing the first 1024 bytes of the + file is returned as the third return value. + +* ``dfhack.internal.threadid()`` + + Returns a numeric identifier of the current thread. + Core interpreter context ======================== diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4acba0809..8d0fa22b8 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -40,6 +40,8 @@ distribution. #include "DataFuncs.h" #include "DFHackVersion.h" #include "PluginManager.h" +#include "tinythread.h" +#include "md5wrapper.h" #include "modules/World.h" #include "modules/Gui.h" @@ -2443,16 +2445,20 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) return rv; } +static md5wrapper md5_wrap; + static uintptr_t getImageBase() { return Core::getInstance().p->getBase(); } static intptr_t getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static int8_t getModstate() { return Core::getInstance().getModstate(); } static std::string internal_strerror(int n) { return strerror(n); } +static std::string internal_md5(std::string s) { return md5_wrap.getHashFromString(s); } static const LuaWrapper::FunctionReg dfhack_internal_module[] = { WRAP(getImageBase), WRAP(getRebaseDelta), WRAP(getModstate), WRAPN(strerror, internal_strerror), + WRAPN(md5, internal_md5), { NULL, NULL } }; @@ -2855,6 +2861,56 @@ static int internal_findScript(lua_State *L) return 1; } +static int internal_threadid(lua_State *L) +{ + std::stringstream ss; + ss << tthread::this_thread::get_id(); + int i; + ss >> i; + lua_pushinteger(L, i); + return 1; +} + +static int internal_md5file(lua_State *L) +{ + const char *s = luaL_checkstring(L, 1); + uint32_t len; + char *first_kb_raw = nullptr; + std::vector first_kb; + if (lua_toboolean(L, 2)) + first_kb_raw = new char[1024]; + + std::string hash = md5_wrap.getHashFromFile(s, len, first_kb_raw); + bool err = (hash.find("file") != std::string::npos); + + if (first_kb_raw) + { + first_kb.assign(first_kb_raw, first_kb_raw + 1024); + delete[] first_kb_raw; + } + + if (err) + { + lua_pushnil(L); + lua_pushstring(L, hash.c_str()); + return 2; + } + else + { + lua_pushstring(L, hash.c_str()); + lua_pushinteger(L, len); + if (!first_kb.empty()) + { + Lua::PushVector(L, first_kb); + return 3; + } + else + { + return 2; + } + } +} + static const luaL_Reg dfhack_internal_funcs[] = { { "getPE", internal_getPE }, { "getMD5", internal_getmd5 }, @@ -2876,6 +2932,8 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "removeScriptPath", internal_removeScriptPath }, { "getScriptPaths", internal_getScriptPaths }, { "findScript", internal_findScript }, + { "threadid", internal_threadid }, + { "md5File", internal_md5file }, { NULL, NULL } }; From d628212254edde5395a5a4889164ddd870234d0f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 1 Jul 2017 17:39:39 -0400 Subject: [PATCH 0573/1012] Lua API: Use CallWithCatchWrapper for a few more functions This produces more readable backtraces when native functions that aren't wrapped using wrap_function() throw exceptions. --- library/LuaApi.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 8d0fa22b8..39b4b41cb 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1372,6 +1372,7 @@ static void OpenModule(lua_State *state, const char *mname, #define WRAPM(module, function) { #function, df::wrap_function(module::function,true) } #define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) } +#define CWRAP(name, function) { #name, &Lua::CallWithCatchWrapper } /***** DFHack module *****/ @@ -2082,10 +2083,10 @@ static const luaL_Reg dfhack_buildings_funcs[] = { { "findAtTile", buildings_findAtTile }, { "findCivzonesAt", buildings_findCivzonesAt }, { "getCorrectSize", buildings_getCorrectSize }, - { "setSize", &Lua::CallWithCatchWrapper }, - { "getStockpileContents", buildings_getStockpileContents }, + CWRAP(setSize, buildings_setSize), + CWRAP(getStockpileContents, buildings_getStockpileContents), { "findPenPitAt", buildings_findPenPitAt }, - { "getCageOccupants", &Lua::CallWithCatchWrapper }, + CWRAP(getCageOccupants, buildings_getCageOccupants), { NULL, NULL } }; @@ -2316,9 +2317,9 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, - { "show", &Lua::CallWithCatchWrapper }, - { "dismiss", screen_dismiss }, - { "isDismissed", screen_isDismissed }, + CWRAP(show, screen_show), + CWRAP(dismiss, screen_dismiss), + CWRAP(isDismissed, screen_isDismissed), { "_doSimulateInput", screen_doSimulateInput }, { "keyToChar", screen_keyToChar }, { "charToKey", screen_charToKey }, From c174c46d672377df0672d0e25638390964e33718 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 1 Jul 2017 18:04:53 -0400 Subject: [PATCH 0574/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3e431de4f..5bc372d6a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3e431de4fe3ffa39c9854d74b6d85de5e92834bd +Subproject commit 5bc372d6ad727bf8ca012ba2e8322a913d9ca448 From d6c4aa823ad3c123759f3e193e17261e54e5c8aa Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 1 Jul 2017 19:30:44 -0400 Subject: [PATCH 0575/1012] Update scripts, Authors.rst --- docs/Authors.rst | 2 ++ scripts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index 40f2f7748..128d327ba 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -78,6 +78,7 @@ Nick Rart nickrart comestible Nikolay Amiantov abbradar nocico nocico Omniclasm +Patrik Lundell PatrikLundell Paul Fenwick pjf PeridexisErrant PeridexisErrant Petr Mrázek peterix @@ -96,6 +97,7 @@ Rinin Rinin rndmvar rndmvar Robert Heinrich rh73 Robert Janetzko robertjanetzko +RocheLimit rofl0r rofl0r root Roses Pheosics diff --git a/scripts b/scripts index 5bc372d6a..2840996c5 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 5bc372d6ad727bf8ca012ba2e8322a913d9ca448 +Subproject commit 2840996c5f39917713cfb0e7ce1c6dca779177e1 From fd68971ac54da51723d3449c045b948e824e0225 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 2 Jul 2017 07:31:31 -0600 Subject: [PATCH 0576/1012] Fix silly error in strangemood plugin - missing "break;" statements in deep material item searches --- plugins/strangemood.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 2a0ed8509..7d967b006 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -911,6 +911,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) filter = NULL; continue; } + break; } if (filter) { @@ -994,6 +995,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) filter = NULL; continue; } + break; } if (filter) { From 7b3ce201679aa77a7be4e8d335c69267adb4b7ad Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 27 May 2017 16:47:18 -0500 Subject: [PATCH 0577/1012] Add orders plugin for managing manager orders. --- plugins/CMakeLists.txt | 1 + plugins/orders.cpp | 788 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 789 insertions(+) create mode 100644 plugins/orders.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f7586f03d..2ad1dfe81 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -136,6 +136,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(plants plants.cpp) DFHACK_PLUGIN(probe probe.cpp) diff --git a/plugins/orders.cpp b/plugins/orders.cpp new file mode 100644 index 000000000..344f00e29 --- /dev/null +++ b/plugins/orders.cpp @@ -0,0 +1,788 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Filesystem.h" +#include "modules/Materials.h" + +#include "jsoncpp.h" + +#include "df/building.h" +#include "df/historical_figure.h" +#include "df/itemdef_ammost.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_foodst.h" +#include "df/itemdef_glovesst.h" +#include "df/itemdef_helmst.h" +#include "df/itemdef_instrumentst.h" +#include "df/itemdef_pantsst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_siegeammost.h" +#include "df/itemdef_toolst.h" +#include "df/itemdef_toyst.h" +#include "df/itemdef_trapcompst.h" +#include "df/itemdef_weaponst.h" +#include "df/job_item.h" +#include "df/manager_order.h" +#include "df/manager_order_condition_item.h" +#include "df/manager_order_condition_order.h" +#include "df/world.h" + +using namespace DFHack; +using namespace df::enums; + +DFHACK_PLUGIN("orders"); + +REQUIRE_GLOBAL(world); + +static command_result orders_command(color_ostream & out, std::vector & parameters); + +DFhackCExport command_result plugin_init(color_ostream & out, std::vector & commands) +{ + commands.push_back(PluginCommand( + "orders", + "Manipulate manager orders.", + orders_command, + false, + "orders - Manipulate manager orders\n" + " orders export [name]\n" + " Exports the current list of manager orders to a file named dfhack-config/orders/[name].json.\n" + " orders import [name]\n" + " Imports manager orders from a file named dfhack-config/orders/[name].json.\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream & out) +{ + return CR_OK; +} + +static command_result orders_export_command(color_ostream & out, const std::string & name); +static command_result orders_import_command(color_ostream & out, const std::string & name); + +static command_result orders_command(color_ostream & out, std::vector & parameters) +{ + class color_ostream_resetter + { + color_ostream & out; + + public: + color_ostream_resetter(color_ostream & out) : out(out) {} + ~color_ostream_resetter() { out.reset_color(); } + } resetter(out); + + if (parameters.empty()) + { + return CR_WRONG_USAGE; + } + + if (parameters[0] == "export" && parameters.size() == 2) + { + return orders_export_command(out, parameters[1]); + } + + if (parameters[0] == "import" && parameters.size() == 2) + { + return orders_import_command(out, parameters[1]); + } + + return CR_WRONG_USAGE; +} + +static bool is_safe_filename(color_ostream & out, const std::string & name) +{ + if (name.empty()) + { + out << COLOR_LIGHTRED << "Missing filename!" << std::endl; + return false; + } + + for (auto it = name.begin(); it != name.end(); it++) + { + if (isalnum(*it)) + { + continue; + } + + if (*it != ' ' && *it != '.' && *it != '-' && *it != '_') + { + out << COLOR_LIGHTRED << "Invalid symbol in name: " << *it << std::endl; + return false; + } + } + + return true; +} + +template +static void bitfield_to_json_array(Json::Value & out, B bits) +{ + std::vector names; + bitfield_to_string(&names, bits); + + for (auto it = names.begin(); it != names.end(); it++) + { + out.append(*it); + } +} + +template +static void json_array_to_bitfield(B & bits, Json::Value & arr) +{ + if (arr.size() == 0) + { + return; + } + + for (Json::ArrayIndex i = arr.size() - 1; i != 0; i--) + { + int current; + if (get_bitfield_field(¤t, bits, arr[i].asString())) + { + if (!current && set_bitfield_field(&bits, arr[i].asString(), 1)) + { + Json::Value removed; + arr.removeIndex(i, &removed); + } + } + } +} + +template +static df::itemdef *get_itemdef(int16_t subtype) +{ + return D::find(subtype); +} + +template +static df::itemdef *get_itemdef(const std::string & subtype) +{ + for (auto it = D::get_vector().begin(); it != D::get_vector().end(); it++) + { + if ((*it)->id == subtype) + { + return *it; + } + } + return nullptr; +} + +template +static df::itemdef *get_itemdef(color_ostream & out, df::item_type type, ST subtype) +{ + switch (type) + { + case item_type::AMMO: + return get_itemdef(subtype); + case item_type::ARMOR: + return get_itemdef(subtype); + case item_type::FOOD: + return get_itemdef(subtype); + case item_type::GLOVES: + return get_itemdef(subtype); + case item_type::HELM: + return get_itemdef(subtype); + case item_type::INSTRUMENT: + return get_itemdef(subtype); + case item_type::PANTS: + return get_itemdef(subtype); + case item_type::SHIELD: + return get_itemdef(subtype); + case item_type::SHOES: + return get_itemdef(subtype); + case item_type::SIEGEAMMO: + return get_itemdef(subtype); + case item_type::TOOL: + return get_itemdef(subtype); + case item_type::TOY: + return get_itemdef(subtype); + case item_type::TRAPCOMP: + return get_itemdef(subtype); + case item_type::WEAPON: + return get_itemdef(subtype); + default: + out << COLOR_YELLOW << "Unhandled raw item type in manager order: " << enum_item_key(type) << "! Please report this bug to DFHack." << std::endl; + return nullptr; + } +} + +static command_result orders_export_command(color_ostream & out, const std::string & name) +{ + if (!is_safe_filename(out, name)) + { + return CR_WRONG_USAGE; + } + + Json::Value orders(Json::arrayValue); + + { + CoreSuspender suspend; + + for (auto it = world->manager_orders.begin(); it != world->manager_orders.end(); it++) + { + Json::Value order(Json::objectValue); + + order["id"] = (*it)->id; + order["job"] = enum_item_key((*it)->job_type); + if (!(*it)->reaction_name.empty()) + { + order["reaction"] = (*it)->reaction_name; + } + + if ((*it)->item_type != item_type::NONE) + { + order["item_type"] = enum_item_key((*it)->item_type); + } + if ((*it)->item_subtype != -1) + { + df::itemdef *def = get_itemdef(out, (*it)->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, (*it)->job_type) : (*it)->item_type, (*it)->item_subtype); + + if (def) + { + order["item_subtype"] = def->id; + } + } + + if ((*it)->job_type == job_type::PrepareMeal) + { + order["meal_ingredients"] = (*it)->mat_type; + } + else if ((*it)->mat_type != -1 || (*it)->mat_index != -1) + { + order["material"] = MaterialInfo(*it).getToken(); + } + + if ((*it)->item_category.whole != 0) + { + bitfield_to_json_array(order["item_category"], (*it)->item_category); + } + + if ((*it)->hist_figure_id != -1) + { + order["hist_figure"] = (*it)->hist_figure_id; + } + + if ((*it)->material_category.whole != 0) + { + bitfield_to_json_array(order["material_category"], (*it)->material_category); + } + + if ((*it)->art_spec.type != df::job_art_specification::None) + { + Json::Value art(Json::objectValue); + + art["type"] = enum_item_key((*it)->art_spec.type); + art["id"] = (*it)->art_spec.id; + if ((*it)->art_spec.subid != -1) + { + art["subid"] = (*it)->art_spec.subid; + } + + order["art"] = art; + } + + order["amount_left"] = (*it)->amount_left; + order["amount_total"] = (*it)->amount_total; + order["is_validated"] = bool((*it)->status.bits.validated); + order["is_active"] = bool((*it)->status.bits.active); + + order["frequency"] = enum_item_key((*it)->frequency); + + // TODO: finished_year, finished_year_tick + + if ((*it)->workshop_id != -1) + { + order["workshop_id"] = (*it)->workshop_id; + } + + if ((*it)->max_workshops != 0) + { + order["max_workshops"] = (*it)->max_workshops; + } + + if (!(*it)->item_conditions.empty()) + { + Json::Value conditions(Json::arrayValue); + + for (auto it2 = (*it)->item_conditions.begin(); it2 != (*it)->item_conditions.end(); it2++) + { + Json::Value condition(Json::objectValue); + + condition["condition"] = enum_item_key((*it2)->compare_type); + condition["value"] = (*it2)->compare_val; + + if ((*it2)->flags1.whole != 0 || (*it2)->flags2.whole != 0 || (*it2)->flags3.whole != 0) + { + bitfield_to_json_array(condition["flags"], (*it2)->flags1); + bitfield_to_json_array(condition["flags"], (*it2)->flags2); + bitfield_to_json_array(condition["flags"], (*it2)->flags3); + // TODO: flags4, flags5 + } + + if ((*it2)->item_type != item_type::NONE) + { + condition["item_type"] = enum_item_key((*it2)->item_type); + } + if ((*it2)->item_subtype != -1) + { + df::itemdef *def = get_itemdef(out, (*it2)->item_type, (*it2)->item_subtype); + + if (def) + { + condition["item_subtype"] = def->id; + } + } + + if ((*it2)->mat_type != -1 || (*it2)->mat_index != -1) + { + condition["material"] = MaterialInfo(*it2).getToken(); + } + + if ((*it2)->inorganic_bearing != -1) + { + condition["bearing"] = df::inorganic_raw::find((*it2)->inorganic_bearing)->id; + } + + if (!(*it2)->reaction_class.empty()) + { + condition["reaction_class"] = (*it2)->reaction_class; + } + + if (!(*it2)->has_material_reaction_product.empty()) + { + condition["reaction_product"] = (*it2)->has_material_reaction_product; + } + + if ((*it2)->has_tool_use != tool_uses::NONE) + { + condition["tool"] = enum_item_key((*it2)->has_tool_use); + } + + // TODO: anon_1, anon_2, anon_3 + + conditions.append(condition); + } + + order["item_conditions"] = conditions; + } + + if (!(*it)->order_conditions.empty()) + { + Json::Value conditions(Json::arrayValue); + + for (auto it2 = (*it)->order_conditions.begin(); it2 != (*it)->order_conditions.end(); it2++) + { + Json::Value condition(Json::objectValue); + + condition["order"] = (*it2)->order_id; + condition["condition"] = enum_item_key((*it2)->condition); + + // TODO: anon_1 + + conditions.append(condition); + } + + order["order_conditions"] = conditions; + } + + // TODO: anon_1 + + orders.append(order); + } + } + + Filesystem::mkdir("dfhack-config/orders"); + + std::ofstream file("dfhack-config/orders/" + name + ".json"); + + file << orders << std::endl; + + return file.good() ? CR_OK : CR_FAILURE; +} + +static command_result orders_import_command(color_ostream & out, const std::string & name) +{ + if (!is_safe_filename(out, name)) + { + return CR_WRONG_USAGE; + } + + Json::Value orders; + + { + std::ifstream file("dfhack-config/orders/" + name + ".json"); + + if (!file.good()) + { + out << COLOR_LIGHTRED << "Cannot find orders file." << std::endl; + return CR_FAILURE; + } + + file >> orders; + + if (!file.good()) + { + out << COLOR_LIGHTRED << "Error reading orders file." << std::endl; + return CR_FAILURE; + } + } + + if (orders.type() != Json::arrayValue) + { + out << COLOR_LIGHTRED << "Invalid orders file: expected array" << std::endl; + return CR_FAILURE; + } + + CoreSuspender suspend; + + for (auto it = world->manager_orders.begin(); it != world->manager_orders.end(); it++) + { + for (auto it2 = (*it)->item_conditions.begin(); it2 != (*it)->item_conditions.end(); it2++) + { + delete *it2; + } + for (auto it2 = (*it)->order_conditions.begin(); it2 != (*it)->order_conditions.end(); it2++) + { + delete *it2; + } + if ((*it)->anon_1) + { + for (auto it2 = (*it)->anon_1->begin(); it2 != (*it)->anon_1->end(); it2++) + { + delete *it2; + } + delete (*it)->anon_1; + } + + delete *it; + } + world->manager_orders.clear(); + world->manager_order_next_id = 0; + + for (auto it = orders.begin(); it != orders.end(); it++) + { + df::manager_order *order = new df::manager_order(); + + order->id = (*it)["id"].asInt(); + world->manager_order_next_id = order->id + 1; + + if (!find_enum_item(&order->job_type, (*it)["job"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid job type for imported manager order: " << (*it)["job"].asString() << std::endl; + + return CR_FAILURE; + } + + if (it->isMember("reaction")) + { + order->reaction_name = (*it)["reaction"].asString(); + } + + if (it->isMember("item_type")) + { + if (!find_enum_item(&order->item_type, (*it)["item_type"].asString()) || order->item_type == item_type::NONE) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid item type for imported manager order: " << (*it)["item_type"].asString() << std::endl; + + return CR_FAILURE; + } + } + if (it->isMember("item_subtype")) + { + df::itemdef *def = get_itemdef(out, order->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, order->job_type) : order->item_type, (*it)["item_subtype"].asString()); + + if (def) + { + order->item_subtype = def->subtype; + } + else + { + delete order; + + out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << (*it)["item_subtype"].asString() << std::endl; + + return CR_FAILURE; + } + } + + if (it->isMember("meal_ingredients")) + { + order->mat_type = (*it)["meal_ingredients"].asInt(); + order->mat_index = -1; + } + else if (it->isMember("material")) + { + MaterialInfo mat; + if (!mat.find((*it)["material"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid material for imported manager order: " << (*it)["material"].asString() << std::endl; + + return CR_FAILURE; + } + order->mat_type = mat.type; + order->mat_index = mat.index; + } + + if (it->isMember("item_category")) + { + json_array_to_bitfield(order->item_category, (*it)["item_category"]); + if (!(*it)["item_category"].empty()) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid item_category value for imported manager order: " << (*it)["item_category"] << std::endl; + + return CR_FAILURE; + } + } + + if (it->isMember("hist_figure")) + { + if (!df::historical_figure::find((*it)["hist_figure"].asInt())) + { + delete order; + + out << COLOR_YELLOW << "Missing historical figure for imported manager order: " << (*it)["hist_figure"].asInt() << std::endl; + + continue; + } + + order->hist_figure_id = (*it)["hist_figure"].asInt(); + } + + if (it->isMember("material_category")) + { + json_array_to_bitfield(order->material_category, (*it)["material_category"]); + if (!(*it)["material_category"].empty()) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid material_category value for imported manager order: " << (*it)["material_category"] << std::endl; + + return CR_FAILURE; + } + } + + if (it->isMember("art")) + { + if (!find_enum_item(&order->art_spec.type, (*it)["art"]["type"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid art type value for imported manager order: " << (*it)["art"]["type"].asString() << std::endl; + + return CR_FAILURE; + } + + order->art_spec.id = (*it)["art"]["id"].asInt(); + if ((*it)["art"].isMember("subid")) + { + order->art_spec.subid = (*it)["art"]["subid"].asInt(); + } + } + + order->amount_left = (*it)["amount_left"].asInt(); + order->amount_total = (*it)["amount_total"].asInt(); + order->status.bits.validated = (*it)["is_validated"].asBool(); + order->status.bits.active = (*it)["is_active"].asBool(); + + if (!find_enum_item(&order->frequency, (*it)["frequency"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid frequency value for imported manager order: " << (*it)["frequency"].asString() << std::endl; + + return CR_FAILURE; + } + + // TODO: finished_year, finished_year_tick + + if (it->isMember("workshop_id")) + { + if (!df::building::find((*it)["workshop_id"].asInt())) + { + delete order; + + out << COLOR_YELLOW << "Missing workshop for imported manager order: " << (*it)["workshop_id"].asInt() << std::endl; + + continue; + } + + order->workshop_id = (*it)["workshop_id"].asInt(); + } + + if (it->isMember("max_workshops")) + { + order->max_workshops = (*it)["max_workshops"].asInt(); + } + + if (it->isMember("item_conditions")) + { + for (auto it2 = (*it)["item_conditions"].begin(); it2 != (*it)["item_conditions"].end(); it2++) + { + df::manager_order_condition_item *condition = new df::manager_order_condition_item(); + + if (!find_enum_item(&condition->compare_type, (*it2)["condition"].asString())) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition condition for imported manager order: " << (*it2)["condition"].asString() << std::endl; + + continue; + } + + condition->compare_val = (*it2)["value"].asInt(); + + if (it2->isMember("flags")) + { + json_array_to_bitfield(condition->flags1, (*it2)["flags"]); + json_array_to_bitfield(condition->flags2, (*it2)["flags"]); + json_array_to_bitfield(condition->flags3, (*it2)["flags"]); + // TODO: flags4, flags5 + + if (!(*it2)["flags"].empty()) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition flags for imported manager order: " << (*it2)["flags"] << std::endl; + + continue; + } + } + + if (it2->isMember("item_type")) + { + if (!find_enum_item(&condition->item_type, (*it2)["item_type"].asString()) || condition->item_type == item_type::NONE) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition item type for imported manager order: " << (*it2)["item_type"].asString() << std::endl; + + continue; + } + } + if (it2->isMember("item_subtype")) + { + df::itemdef *def = get_itemdef(out, condition->item_type, (*it2)["item_subtype"].asString()); + + if (def) + { + condition->item_subtype = def->subtype; + } + else + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << (*it2)["item_subtype"].asString() << std::endl; + + continue; + } + } + + if (it2->isMember("material")) + { + MaterialInfo mat; + if (!mat.find((*it2)["material"].asString())) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition material for imported manager order: " << (*it2)["material"].asString() << std::endl; + + continue; + } + condition->mat_type = mat.type; + condition->mat_index = mat.index; + } + + if (it2->isMember("bearing")) + { + std::string bearing((*it2)["bearing"].asString()); + auto found = std::find_if(world->raws.inorganics.begin(), world->raws.inorganics.end(), [bearing](df::inorganic_raw *raw) -> bool { return raw->id == bearing; }); + if (found == world->raws.inorganics.end()) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition inorganic bearing type for imported manager order: " << (*it2)["bearing"].asString() << std::endl; + + continue; + } + condition->inorganic_bearing = found - world->raws.inorganics.begin(); + } + + if (it2->isMember("reaction_class")) + { + condition->reaction_class = (*it2)["reaction_class"].asString(); + } + + if (it2->isMember("reaction_product")) + { + condition->has_material_reaction_product = (*it2)["reaction_product"].asString(); + } + + if (it2->isMember("tool")) + { + if (!find_enum_item(&condition->has_tool_use, (*it2)["tool"].asString()) || condition->has_tool_use == tool_uses::NONE) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition tool use for imported manager order: " << (*it2)["tool"].asString() << std::endl; + + continue; + } + } + + // TODO: anon_1, anon_2, anon_3 + + order->item_conditions.push_back(condition); + } + } + + if (it->isMember("order_conditions")) + { + for (auto it2 = (*it)["order_conditions"].begin(); it2 != (*it)["order_conditions"].end(); it2++) + { + df::manager_order_condition_order *condition = new df::manager_order_condition_order(); + + int32_t id = (*it2)["order"].asInt(); + if (id == order->id || std::find_if(orders.begin(), orders.end(), [id](const Json::Value & o) -> bool { return o["id"].asInt() == id; }) == orders.end()) + { + delete condition; + + out << COLOR_YELLOW << "Missing order condition target for imported manager order: " << (*it2)["order"].asInt() << std::endl; + + continue; + } + condition->order_id = id; + + if (!find_enum_item(&condition->condition, (*it2)["condition"].asString())) + { + delete condition; + + out << COLOR_YELLOW << "Invalid order condition type for imported manager order: " << (*it2)["condition"].asString() << std::endl; + + continue; + } + + // TODO: anon_1 + + order->order_conditions.push_back(condition); + } + } + + // TODO: anon_1 + + world->manager_orders.push_back(order); + } + + return CR_OK; +} From 069cea675cb400fadaf08f9c2b1428529c8aecdb Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sun, 2 Jul 2017 16:00:45 -0500 Subject: [PATCH 0578/1012] Split import and clear into two commands. Use for-each loops for clarity. Closes #965. --- plugins/orders.cpp | 373 ++++++++++++++++++++++++--------------------- 1 file changed, 198 insertions(+), 175 deletions(-) diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 344f00e29..53c1844ac 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -52,6 +52,8 @@ DFhackCExport command_result plugin_init(color_ostream & out, std::vector & parameters) { @@ -90,6 +93,11 @@ static command_result orders_command(color_ostream & out, std::vector names; bitfield_to_string(&names, bits); - for (auto it = names.begin(); it != names.end(); it++) + for (auto & it : names) { - out.append(*it); + out.append(it); } } @@ -161,11 +169,11 @@ static df::itemdef *get_itemdef(int16_t subtype) template static df::itemdef *get_itemdef(const std::string & subtype) { - for (auto it = D::get_vector().begin(); it != D::get_vector().end(); it++) + for (auto it : D::get_vector()) { - if ((*it)->id == subtype) + if (it->id == subtype) { - return *it; + return it; } } return nullptr; @@ -222,24 +230,24 @@ static command_result orders_export_command(color_ostream & out, const std::stri { CoreSuspender suspend; - for (auto it = world->manager_orders.begin(); it != world->manager_orders.end(); it++) + for (auto it : world->manager_orders) { Json::Value order(Json::objectValue); - order["id"] = (*it)->id; - order["job"] = enum_item_key((*it)->job_type); - if (!(*it)->reaction_name.empty()) + order["id"] = it->id; + order["job"] = enum_item_key(it->job_type); + if (!it->reaction_name.empty()) { - order["reaction"] = (*it)->reaction_name; + order["reaction"] = it->reaction_name; } - if ((*it)->item_type != item_type::NONE) + if (it->item_type != item_type::NONE) { - order["item_type"] = enum_item_key((*it)->item_type); + order["item_type"] = enum_item_key(it->item_type); } - if ((*it)->item_subtype != -1) + if (it->item_subtype != -1) { - df::itemdef *def = get_itemdef(out, (*it)->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, (*it)->job_type) : (*it)->item_type, (*it)->item_subtype); + df::itemdef *def = get_itemdef(out, it->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, it->job_type) : it->item_type, it->item_subtype); if (def) { @@ -247,89 +255,89 @@ static command_result orders_export_command(color_ostream & out, const std::stri } } - if ((*it)->job_type == job_type::PrepareMeal) + if (it->job_type == job_type::PrepareMeal) { - order["meal_ingredients"] = (*it)->mat_type; + order["meal_ingredients"] = it->mat_type; } - else if ((*it)->mat_type != -1 || (*it)->mat_index != -1) + else if (it->mat_type != -1 || it->mat_index != -1) { - order["material"] = MaterialInfo(*it).getToken(); + order["material"] = MaterialInfo(it).getToken(); } - if ((*it)->item_category.whole != 0) + if (it->item_category.whole != 0) { - bitfield_to_json_array(order["item_category"], (*it)->item_category); + bitfield_to_json_array(order["item_category"], it->item_category); } - if ((*it)->hist_figure_id != -1) + if (it->hist_figure_id != -1) { - order["hist_figure"] = (*it)->hist_figure_id; + order["hist_figure"] = it->hist_figure_id; } - if ((*it)->material_category.whole != 0) + if (it->material_category.whole != 0) { - bitfield_to_json_array(order["material_category"], (*it)->material_category); + bitfield_to_json_array(order["material_category"], it->material_category); } - if ((*it)->art_spec.type != df::job_art_specification::None) + if (it->art_spec.type != df::job_art_specification::None) { Json::Value art(Json::objectValue); - art["type"] = enum_item_key((*it)->art_spec.type); - art["id"] = (*it)->art_spec.id; - if ((*it)->art_spec.subid != -1) + art["type"] = enum_item_key(it->art_spec.type); + art["id"] = it->art_spec.id; + if (it->art_spec.subid != -1) { - art["subid"] = (*it)->art_spec.subid; + art["subid"] = it->art_spec.subid; } order["art"] = art; } - order["amount_left"] = (*it)->amount_left; - order["amount_total"] = (*it)->amount_total; - order["is_validated"] = bool((*it)->status.bits.validated); - order["is_active"] = bool((*it)->status.bits.active); + order["amount_left"] = it->amount_left; + order["amount_total"] = it->amount_total; + order["is_validated"] = bool(it->status.bits.validated); + order["is_active"] = bool(it->status.bits.active); - order["frequency"] = enum_item_key((*it)->frequency); + order["frequency"] = enum_item_key(it->frequency); // TODO: finished_year, finished_year_tick - if ((*it)->workshop_id != -1) + if (it->workshop_id != -1) { - order["workshop_id"] = (*it)->workshop_id; + order["workshop_id"] = it->workshop_id; } - if ((*it)->max_workshops != 0) + if (it->max_workshops != 0) { - order["max_workshops"] = (*it)->max_workshops; + order["max_workshops"] = it->max_workshops; } - if (!(*it)->item_conditions.empty()) + if (!it->item_conditions.empty()) { Json::Value conditions(Json::arrayValue); - for (auto it2 = (*it)->item_conditions.begin(); it2 != (*it)->item_conditions.end(); it2++) + for (auto it2 : it->item_conditions) { Json::Value condition(Json::objectValue); - condition["condition"] = enum_item_key((*it2)->compare_type); - condition["value"] = (*it2)->compare_val; + condition["condition"] = enum_item_key(it2->compare_type); + condition["value"] = it2->compare_val; - if ((*it2)->flags1.whole != 0 || (*it2)->flags2.whole != 0 || (*it2)->flags3.whole != 0) + if (it2->flags1.whole != 0 || it2->flags2.whole != 0 || it2->flags3.whole != 0) { - bitfield_to_json_array(condition["flags"], (*it2)->flags1); - bitfield_to_json_array(condition["flags"], (*it2)->flags2); - bitfield_to_json_array(condition["flags"], (*it2)->flags3); + bitfield_to_json_array(condition["flags"], it2->flags1); + bitfield_to_json_array(condition["flags"], it2->flags2); + bitfield_to_json_array(condition["flags"], it2->flags3); // TODO: flags4, flags5 } - if ((*it2)->item_type != item_type::NONE) + if (it2->item_type != item_type::NONE) { - condition["item_type"] = enum_item_key((*it2)->item_type); + condition["item_type"] = enum_item_key(it2->item_type); } - if ((*it2)->item_subtype != -1) + if (it2->item_subtype != -1) { - df::itemdef *def = get_itemdef(out, (*it2)->item_type, (*it2)->item_subtype); + df::itemdef *def = get_itemdef(out, it2->item_type, it2->item_subtype); if (def) { @@ -337,29 +345,29 @@ static command_result orders_export_command(color_ostream & out, const std::stri } } - if ((*it2)->mat_type != -1 || (*it2)->mat_index != -1) + if (it2->mat_type != -1 || it2->mat_index != -1) { - condition["material"] = MaterialInfo(*it2).getToken(); + condition["material"] = MaterialInfo(it2).getToken(); } - if ((*it2)->inorganic_bearing != -1) + if (it2->inorganic_bearing != -1) { - condition["bearing"] = df::inorganic_raw::find((*it2)->inorganic_bearing)->id; + condition["bearing"] = df::inorganic_raw::find(it2->inorganic_bearing)->id; } - if (!(*it2)->reaction_class.empty()) + if (!it2->reaction_class.empty()) { - condition["reaction_class"] = (*it2)->reaction_class; + condition["reaction_class"] = it2->reaction_class; } - if (!(*it2)->has_material_reaction_product.empty()) + if (!it2->has_material_reaction_product.empty()) { - condition["reaction_product"] = (*it2)->has_material_reaction_product; + condition["reaction_product"] = it2->has_material_reaction_product; } - if ((*it2)->has_tool_use != tool_uses::NONE) + if (it2->has_tool_use != tool_uses::NONE) { - condition["tool"] = enum_item_key((*it2)->has_tool_use); + condition["tool"] = enum_item_key(it2->has_tool_use); } // TODO: anon_1, anon_2, anon_3 @@ -370,16 +378,16 @@ static command_result orders_export_command(color_ostream & out, const std::stri order["item_conditions"] = conditions; } - if (!(*it)->order_conditions.empty()) + if (!it->order_conditions.empty()) { Json::Value conditions(Json::arrayValue); - for (auto it2 = (*it)->order_conditions.begin(); it2 != (*it)->order_conditions.end(); it2++) + for (auto it2 : it->order_conditions) { Json::Value condition(Json::objectValue); - condition["order"] = (*it2)->order_id; - condition["condition"] = enum_item_key((*it2)->condition); + condition["order"] = it2->order_id; + condition["condition"] = enum_item_key(it2->condition); // TODO: anon_1 @@ -439,65 +447,47 @@ static command_result orders_import_command(color_ostream & out, const std::stri CoreSuspender suspend; - for (auto it = world->manager_orders.begin(); it != world->manager_orders.end(); it++) + std::map id_mapping; + for (auto it : orders) { - for (auto it2 = (*it)->item_conditions.begin(); it2 != (*it)->item_conditions.end(); it2++) - { - delete *it2; - } - for (auto it2 = (*it)->order_conditions.begin(); it2 != (*it)->order_conditions.end(); it2++) - { - delete *it2; - } - if ((*it)->anon_1) - { - for (auto it2 = (*it)->anon_1->begin(); it2 != (*it)->anon_1->end(); it2++) - { - delete *it2; - } - delete (*it)->anon_1; - } - - delete *it; + id_mapping[it["id"].asInt()] = world->manager_order_next_id; + world->manager_order_next_id++; } - world->manager_orders.clear(); - world->manager_order_next_id = 0; - for (auto it = orders.begin(); it != orders.end(); it++) + for (auto & it : orders) { df::manager_order *order = new df::manager_order(); - order->id = (*it)["id"].asInt(); - world->manager_order_next_id = order->id + 1; + order->id = id_mapping.at(it["id"].asInt()); - if (!find_enum_item(&order->job_type, (*it)["job"].asString())) + if (!find_enum_item(&order->job_type, it["job"].asString())) { delete order; - out << COLOR_LIGHTRED << "Invalid job type for imported manager order: " << (*it)["job"].asString() << std::endl; + out << COLOR_LIGHTRED << "Invalid job type for imported manager order: " << it["job"].asString() << std::endl; return CR_FAILURE; } - if (it->isMember("reaction")) + if (it.isMember("reaction")) { - order->reaction_name = (*it)["reaction"].asString(); + order->reaction_name = it["reaction"].asString(); } - if (it->isMember("item_type")) + if (it.isMember("item_type")) { - if (!find_enum_item(&order->item_type, (*it)["item_type"].asString()) || order->item_type == item_type::NONE) + if (!find_enum_item(&order->item_type, it["item_type"].asString()) || order->item_type == item_type::NONE) { delete order; - out << COLOR_LIGHTRED << "Invalid item type for imported manager order: " << (*it)["item_type"].asString() << std::endl; + out << COLOR_LIGHTRED << "Invalid item type for imported manager order: " << it["item_type"].asString() << std::endl; return CR_FAILURE; } } - if (it->isMember("item_subtype")) + if (it.isMember("item_subtype")) { - df::itemdef *def = get_itemdef(out, order->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, order->job_type) : order->item_type, (*it)["item_subtype"].asString()); + df::itemdef *def = get_itemdef(out, order->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, order->job_type) : order->item_type, it["item_subtype"].asString()); if (def) { @@ -507,25 +497,25 @@ static command_result orders_import_command(color_ostream & out, const std::stri { delete order; - out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << (*it)["item_subtype"].asString() << std::endl; + out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << it["item_subtype"].asString() << std::endl; return CR_FAILURE; } } - if (it->isMember("meal_ingredients")) + if (it.isMember("meal_ingredients")) { - order->mat_type = (*it)["meal_ingredients"].asInt(); + order->mat_type = it["meal_ingredients"].asInt(); order->mat_index = -1; } - else if (it->isMember("material")) + else if (it.isMember("material")) { MaterialInfo mat; - if (!mat.find((*it)["material"].asString())) + if (!mat.find(it["material"].asString())) { delete order; - out << COLOR_LIGHTRED << "Invalid material for imported manager order: " << (*it)["material"].asString() << std::endl; + out << COLOR_LIGHTRED << "Invalid material for imported manager order: " << it["material"].asString() << std::endl; return CR_FAILURE; } @@ -533,147 +523,147 @@ static command_result orders_import_command(color_ostream & out, const std::stri order->mat_index = mat.index; } - if (it->isMember("item_category")) + if (it.isMember("item_category")) { - json_array_to_bitfield(order->item_category, (*it)["item_category"]); - if (!(*it)["item_category"].empty()) + json_array_to_bitfield(order->item_category, it["item_category"]); + if (!it["item_category"].empty()) { delete order; - out << COLOR_LIGHTRED << "Invalid item_category value for imported manager order: " << (*it)["item_category"] << std::endl; + out << COLOR_LIGHTRED << "Invalid item_category value for imported manager order: " << it["item_category"] << std::endl; return CR_FAILURE; } } - if (it->isMember("hist_figure")) + if (it.isMember("hist_figure")) { - if (!df::historical_figure::find((*it)["hist_figure"].asInt())) + if (!df::historical_figure::find(it["hist_figure"].asInt())) { delete order; - out << COLOR_YELLOW << "Missing historical figure for imported manager order: " << (*it)["hist_figure"].asInt() << std::endl; + out << COLOR_YELLOW << "Missing historical figure for imported manager order: " << it["hist_figure"].asInt() << std::endl; continue; } - order->hist_figure_id = (*it)["hist_figure"].asInt(); + order->hist_figure_id = it["hist_figure"].asInt(); } - if (it->isMember("material_category")) + if (it.isMember("material_category")) { - json_array_to_bitfield(order->material_category, (*it)["material_category"]); - if (!(*it)["material_category"].empty()) + json_array_to_bitfield(order->material_category, it["material_category"]); + if (!it["material_category"].empty()) { delete order; - out << COLOR_LIGHTRED << "Invalid material_category value for imported manager order: " << (*it)["material_category"] << std::endl; + out << COLOR_LIGHTRED << "Invalid material_category value for imported manager order: " << it["material_category"] << std::endl; return CR_FAILURE; } } - if (it->isMember("art")) + if (it.isMember("art")) { - if (!find_enum_item(&order->art_spec.type, (*it)["art"]["type"].asString())) + if (!find_enum_item(&order->art_spec.type, it["art"]["type"].asString())) { delete order; - out << COLOR_LIGHTRED << "Invalid art type value for imported manager order: " << (*it)["art"]["type"].asString() << std::endl; + out << COLOR_LIGHTRED << "Invalid art type value for imported manager order: " << it["art"]["type"].asString() << std::endl; return CR_FAILURE; } - order->art_spec.id = (*it)["art"]["id"].asInt(); - if ((*it)["art"].isMember("subid")) + order->art_spec.id = it["art"]["id"].asInt(); + if (it["art"].isMember("subid")) { - order->art_spec.subid = (*it)["art"]["subid"].asInt(); + order->art_spec.subid = it["art"]["subid"].asInt(); } } - order->amount_left = (*it)["amount_left"].asInt(); - order->amount_total = (*it)["amount_total"].asInt(); - order->status.bits.validated = (*it)["is_validated"].asBool(); - order->status.bits.active = (*it)["is_active"].asBool(); + order->amount_left = it["amount_left"].asInt(); + order->amount_total = it["amount_total"].asInt(); + order->status.bits.validated = it["is_validated"].asBool(); + order->status.bits.active = it["is_active"].asBool(); - if (!find_enum_item(&order->frequency, (*it)["frequency"].asString())) + if (!find_enum_item(&order->frequency, it["frequency"].asString())) { delete order; - out << COLOR_LIGHTRED << "Invalid frequency value for imported manager order: " << (*it)["frequency"].asString() << std::endl; + out << COLOR_LIGHTRED << "Invalid frequency value for imported manager order: " << it["frequency"].asString() << std::endl; return CR_FAILURE; } // TODO: finished_year, finished_year_tick - if (it->isMember("workshop_id")) + if (it.isMember("workshop_id")) { - if (!df::building::find((*it)["workshop_id"].asInt())) + if (!df::building::find(it["workshop_id"].asInt())) { delete order; - out << COLOR_YELLOW << "Missing workshop for imported manager order: " << (*it)["workshop_id"].asInt() << std::endl; + out << COLOR_YELLOW << "Missing workshop for imported manager order: " << it["workshop_id"].asInt() << std::endl; continue; } - order->workshop_id = (*it)["workshop_id"].asInt(); + order->workshop_id = it["workshop_id"].asInt(); } - if (it->isMember("max_workshops")) + if (it.isMember("max_workshops")) { - order->max_workshops = (*it)["max_workshops"].asInt(); + order->max_workshops = it["max_workshops"].asInt(); } - if (it->isMember("item_conditions")) + if (it.isMember("item_conditions")) { - for (auto it2 = (*it)["item_conditions"].begin(); it2 != (*it)["item_conditions"].end(); it2++) + for (auto & it2 : it["item_conditions"]) { df::manager_order_condition_item *condition = new df::manager_order_condition_item(); - if (!find_enum_item(&condition->compare_type, (*it2)["condition"].asString())) + if (!find_enum_item(&condition->compare_type, it2["condition"].asString())) { delete condition; - out << COLOR_YELLOW << "Invalid item condition condition for imported manager order: " << (*it2)["condition"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid item condition condition for imported manager order: " << it2["condition"].asString() << std::endl; continue; } - condition->compare_val = (*it2)["value"].asInt(); + condition->compare_val = it2["value"].asInt(); - if (it2->isMember("flags")) + if (it2.isMember("flags")) { - json_array_to_bitfield(condition->flags1, (*it2)["flags"]); - json_array_to_bitfield(condition->flags2, (*it2)["flags"]); - json_array_to_bitfield(condition->flags3, (*it2)["flags"]); + json_array_to_bitfield(condition->flags1, it2["flags"]); + json_array_to_bitfield(condition->flags2, it2["flags"]); + json_array_to_bitfield(condition->flags3, it2["flags"]); // TODO: flags4, flags5 - if (!(*it2)["flags"].empty()) + if (!it2["flags"].empty()) { delete condition; - out << COLOR_YELLOW << "Invalid item condition flags for imported manager order: " << (*it2)["flags"] << std::endl; + out << COLOR_YELLOW << "Invalid item condition flags for imported manager order: " << it2["flags"] << std::endl; continue; } } - if (it2->isMember("item_type")) + if (it2.isMember("item_type")) { - if (!find_enum_item(&condition->item_type, (*it2)["item_type"].asString()) || condition->item_type == item_type::NONE) + if (!find_enum_item(&condition->item_type, it2["item_type"].asString()) || condition->item_type == item_type::NONE) { delete condition; - out << COLOR_YELLOW << "Invalid item condition item type for imported manager order: " << (*it2)["item_type"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid item condition item type for imported manager order: " << it2["item_type"].asString() << std::endl; continue; } } - if (it2->isMember("item_subtype")) + if (it2.isMember("item_subtype")) { - df::itemdef *def = get_itemdef(out, condition->item_type, (*it2)["item_subtype"].asString()); + df::itemdef *def = get_itemdef(out, condition->item_type, it2["item_subtype"].asString()); if (def) { @@ -683,20 +673,20 @@ static command_result orders_import_command(color_ostream & out, const std::stri { delete condition; - out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << (*it2)["item_subtype"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << it2["item_subtype"].asString() << std::endl; continue; } } - if (it2->isMember("material")) + if (it2.isMember("material")) { MaterialInfo mat; - if (!mat.find((*it2)["material"].asString())) + if (!mat.find(it2["material"].asString())) { delete condition; - out << COLOR_YELLOW << "Invalid item condition material for imported manager order: " << (*it2)["material"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid item condition material for imported manager order: " << it2["material"].asString() << std::endl; continue; } @@ -704,38 +694,38 @@ static command_result orders_import_command(color_ostream & out, const std::stri condition->mat_index = mat.index; } - if (it2->isMember("bearing")) + if (it2.isMember("bearing")) { - std::string bearing((*it2)["bearing"].asString()); + std::string bearing(it2["bearing"].asString()); auto found = std::find_if(world->raws.inorganics.begin(), world->raws.inorganics.end(), [bearing](df::inorganic_raw *raw) -> bool { return raw->id == bearing; }); if (found == world->raws.inorganics.end()) { delete condition; - out << COLOR_YELLOW << "Invalid item condition inorganic bearing type for imported manager order: " << (*it2)["bearing"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid item condition inorganic bearing type for imported manager order: " << it2["bearing"].asString() << std::endl; continue; } condition->inorganic_bearing = found - world->raws.inorganics.begin(); } - if (it2->isMember("reaction_class")) + if (it2.isMember("reaction_class")) { - condition->reaction_class = (*it2)["reaction_class"].asString(); + condition->reaction_class = it2["reaction_class"].asString(); } - if (it2->isMember("reaction_product")) + if (it2.isMember("reaction_product")) { - condition->has_material_reaction_product = (*it2)["reaction_product"].asString(); + condition->has_material_reaction_product = it2["reaction_product"].asString(); } - if (it2->isMember("tool")) + if (it2.isMember("tool")) { - if (!find_enum_item(&condition->has_tool_use, (*it2)["tool"].asString()) || condition->has_tool_use == tool_uses::NONE) + if (!find_enum_item(&condition->has_tool_use, it2["tool"].asString()) || condition->has_tool_use == tool_uses::NONE) { delete condition; - out << COLOR_YELLOW << "Invalid item condition tool use for imported manager order: " << (*it2)["tool"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid item condition tool use for imported manager order: " << it2["tool"].asString() << std::endl; continue; } @@ -747,28 +737,28 @@ static command_result orders_import_command(color_ostream & out, const std::stri } } - if (it->isMember("order_conditions")) + if (it.isMember("order_conditions")) { - for (auto it2 = (*it)["order_conditions"].begin(); it2 != (*it)["order_conditions"].end(); it2++) + for (auto & it2 : it["order_conditions"]) { df::manager_order_condition_order *condition = new df::manager_order_condition_order(); - int32_t id = (*it2)["order"].asInt(); - if (id == order->id || std::find_if(orders.begin(), orders.end(), [id](const Json::Value & o) -> bool { return o["id"].asInt() == id; }) == orders.end()) + int32_t id = it2["order"].asInt(); + if (id == it["id"].asInt() || !id_mapping.count(id)) { delete condition; - out << COLOR_YELLOW << "Missing order condition target for imported manager order: " << (*it2)["order"].asInt() << std::endl; + out << COLOR_YELLOW << "Missing order condition target for imported manager order: " << it2["order"].asInt() << std::endl; continue; } - condition->order_id = id; + condition->order_id = id_mapping.at(id); - if (!find_enum_item(&condition->condition, (*it2)["condition"].asString())) + if (!find_enum_item(&condition->condition, it2["condition"].asString())) { delete condition; - out << COLOR_YELLOW << "Invalid order condition type for imported manager order: " << (*it2)["condition"].asString() << std::endl; + out << COLOR_YELLOW << "Invalid order condition type for imported manager order: " << it2["condition"].asString() << std::endl; continue; } @@ -786,3 +776,36 @@ static command_result orders_import_command(color_ostream & out, const std::stri return CR_OK; } + +static command_result orders_clear_command(color_ostream & out) +{ + CoreSuspender suspend; + + for (auto order : world->manager_orders) + { + for (auto condition : order->item_conditions) + { + delete condition; + } + for (auto condition : order->order_conditions) + { + delete condition; + } + if (order->anon_1) + { + for (auto anon_1 : *order->anon_1) + { + delete anon_1; + } + delete order->anon_1; + } + + delete order; + } + + out << "Deleted " << world->manager_orders.size() << " manager orders." << std::endl; + + world->manager_orders.clear(); + + return CR_OK; +} From 8a0777bb1ef72ccca313b618df2971e2ffbd7ba1 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 4 Jul 2017 20:51:25 +0530 Subject: [PATCH 0579/1012] Add stdint.h to the buildingreader header. --- plugins/remotefortressreader/building_reader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/remotefortressreader/building_reader.h b/plugins/remotefortressreader/building_reader.h index 6b789359b..8b3743a1f 100644 --- a/plugins/remotefortressreader/building_reader.h +++ b/plugins/remotefortressreader/building_reader.h @@ -1,5 +1,6 @@ #ifndef BUILDING_READER_H #define BUILDING_READER_H +#include #include "RemoteClient.h" #include "RemoteFortressReader.pb.h" From 03772a6899ceb33e38b00e88eae76c8bcf973829 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 4 Jul 2017 21:10:59 +0530 Subject: [PATCH 0580/1012] Don't support windmill direction on old DF --- plugins/remotefortressreader/building_reader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 7f0c9a6ff..a8d15597a 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -847,6 +847,7 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re auto actual = strict_virtual_cast(local_build); if (actual) { +#if DF_VERSION_INT > 34011 if (actual->orient_x < 0) remote_build->set_direction(WEST); else if (actual->orient_x > 0) @@ -856,6 +857,7 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re else if (actual->orient_y > 0) remote_build->set_direction(SOUTH); else +#endif remote_build->set_direction(WEST); if (actual->machine.machine_id >= 0) { From 2d07e5edea6cdec288f8cb4706109944adeefdc3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 5 Jul 2017 23:54:55 -0400 Subject: [PATCH 0581/1012] Add new "pathable" plugin --- dfhack.init-example | 3 ++ docs/Plugins.rst | 13 ++++++ plugins/CMakeLists.txt | 1 + plugins/lua/pathable.lua | 11 +++++ plugins/pathable.cpp | 90 ++++++++++++++++++++++++++++++++++++++++ scripts | 2 +- 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 plugins/lua/pathable.lua create mode 100644 plugins/pathable.cpp diff --git a/dfhack.init-example b/dfhack.init-example index 525e823a3..8c8289595 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -147,6 +147,9 @@ keybinding add Shift-B@pet/List/Unit "gui/autobutcher" # assign weapon racks to squads so that they can be used keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack +# view pathable tiles from active cursor +keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable + ############################ # UI and game logic tweaks # ############################ diff --git a/docs/Plugins.rst b/docs/Plugins.rst index eec37960a..6137d080b 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -109,6 +109,19 @@ A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map. +.. _pathable: + +pathable +======== + +This plugin implements the back end of the `gui/pathable` script. It exports a +single Lua function, in ``hack/lua/plugins/pathable.lua``: + +* ``paintScreen(cursor[,skip_unrevealed])``: Paint each visible of the screen + green or red, depending on whether it can be pathed to from the tile at + ``cursor``. If ``skip_unrevealed`` is specified and true, do not draw + unrevealed tiles. + .. _probe: probe diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f7586f03d..a197364ff 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -136,6 +136,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(plants plants.cpp) DFHACK_PLUGIN(probe probe.cpp) diff --git a/plugins/lua/pathable.lua b/plugins/lua/pathable.lua new file mode 100644 index 000000000..b5f5addfc --- /dev/null +++ b/plugins/lua/pathable.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.pathable') + +--[[ + +Native functions: (see Plugins.rst for details) + +- paintScreen(cursor[,skip_unrevealed]) + +]] + +return _ENV diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp new file mode 100644 index 000000000..6007d8401 --- /dev/null +++ b/plugins/pathable.cpp @@ -0,0 +1,90 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "DataFuncs.h" +#include "DataIdentity.h" +#include "Export.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "df/world.h" + +using namespace DFHack; + +DFHACK_PLUGIN("pathable"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(window_x); +REQUIRE_GLOBAL(window_y); +REQUIRE_GLOBAL(window_z); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + return CR_OK; +} + +static void paintScreen(df::coord cursor, bool skip_unrevealed = false) +{ + auto dims = Gui::getDwarfmodeViewDims(); + for (int y = dims.map_y1; y <= dims.map_y2; y++) + { + for (int x = dims.map_x1; x <= dims.map_x2; x++) + { + Screen::Pen cur_tile = Screen::readTile(x, y, true); + if (!cur_tile.valid()) + continue; + + df::coord map_pos( + *window_x + x - dims.map_x1, + *window_y + y - dims.map_y1, + *window_z + ); + + // Keep yellow cursor + if (map_pos == cursor) + continue; + + if (map_pos.x < 0 || map_pos.x >= world->map.x_count || + map_pos.y < 0 || map_pos.y >= world->map.y_count || + map_pos.z < 0 || map_pos.z >= world->map.z_count) + { + continue; + } + + if (skip_unrevealed && !Maps::isTileVisible(map_pos)) + continue; + + int color = Maps::canWalkBetween(cursor, map_pos) ? COLOR_GREEN : COLOR_RED; + + if (cur_tile.fg && cur_tile.ch != ' ') + { + cur_tile.fg = color; + cur_tile.bg = 0; + } + else + { + cur_tile.fg = 0; + cur_tile.bg = color; + } + + cur_tile.bold = false; + + if (cur_tile.tile) + cur_tile.tile_mode = Screen::Pen::CharColor; + + Screen::paintTile(cur_tile, x, y, true); + } + } +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(paintScreen), + DFHACK_LUA_END +}; + diff --git a/scripts b/scripts index 2840996c5..3baa24fec 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 2840996c5f39917713cfb0e7ce1c6dca779177e1 +Subproject commit 3baa24fec93461218b5b658de94884ebff0a0b23 From 7fed961fcd09c4d8cf05f34660ab988140d62bd1 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 11 Jul 2017 15:10:42 -0500 Subject: [PATCH 0582/1012] Make repeatUtil.cancel work even when called from the callback. Closes #1122. --- library/lua/repeat-util.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/library/lua/repeat-util.lua b/library/lua/repeat-util.lua index a7ccbefaa..c229ea70d 100644 --- a/library/lua/repeat-util.lua +++ b/library/lua/repeat-util.lua @@ -17,7 +17,9 @@ function cancel(name) if not repeating[name] then return false end - dfhack.timeout_active(repeating[name],nil) + if repeating[name] ~= -1 then + dfhack.timeout_active(repeating[name],nil) + end repeating[name] = nil return true end @@ -26,8 +28,11 @@ function scheduleEvery(name,time,timeUnits,func) cancel(name) local function helper() func() - repeating[name] = dfhack.timeout(time,timeUnits,helper) + if repeating[name] then + repeating[name] = dfhack.timeout(time,timeUnits,helper) + end end + repeating[name] = -1 helper() end From e3cd820fb13544862e7ed423d12362a5f19505c5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 16:28:21 -0400 Subject: [PATCH 0583/1012] Limit recursive runCommand() calls to 20 Addresses an issue with recursive aliases crashing (#701) --- library/Core.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 0f6db80f9..fccf7f464 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -135,6 +135,16 @@ struct Core::Private } }; +struct CommandDepthCounter +{ + static const int MAX_DEPTH = 20; + static thread_local int depth; + CommandDepthCounter() { depth++; } + ~CommandDepthCounter() { depth--; } + bool ok() { return depth < MAX_DEPTH; } +}; +thread_local int CommandDepthCounter::depth = 0; + void Core::cheap_tokenise(string const& input, vector &output) { string *cur = NULL; @@ -651,6 +661,14 @@ void ls_helper(color_ostream &con, const PluginCommand &pcmd) command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) { std::string first = first_; + CommandDepthCounter counter; + if (!counter.ok()) + { + con.printerr("Cannot invoke \"%s\": maximum command depth exceeded (%i)\n", + first.c_str(), CommandDepthCounter::MAX_DEPTH); + return CR_FAILURE; + } + command_result res; if (!first.empty()) { From 3103d697d95820267bbbcd87f58fb71bb354f4b6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 16:30:49 -0400 Subject: [PATCH 0584/1012] Add an onLoad.init-example file Closes #1093 --- dfhack.init-example | 7 +++++++ library/CMakeLists.txt | 2 +- onLoad.init-example | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 onLoad.init-example diff --git a/dfhack.init-example b/dfhack.init-example index 8c8289595..3542956ce 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -268,3 +268,10 @@ view-item-info enable # a replacement for the "load game" screen gui/load-screen enable + +############################## +# Extra DFHack command files # +############################## + +# Run commands in this file when a world loads +sc-script add SC_WORLD_LOADED onLoad.init-example diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 1513e1dca..40c122de9 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -411,7 +411,7 @@ endif() install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) #linux: share/dfhack #install the example autoexec file -install(FILES ../dfhack.init-example +install(FILES ../dfhack.init-example ../onLoad.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) install(TARGETS dfhack-run dfhack-client binpatch diff --git a/onLoad.init-example b/onLoad.init-example new file mode 100644 index 000000000..08dd536a7 --- /dev/null +++ b/onLoad.init-example @@ -0,0 +1,2 @@ +repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] +repeat -name warn-stuck-trees -time 10 -timeUnits days -command [ warn-stuck-trees ] From bdc746f3f13445413bb9c80e70650a3a0dc74829 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 16:53:09 -0400 Subject: [PATCH 0585/1012] Replace broken include guard --- plugins/remotefortressreader/df_version_int.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/df_version_int.h b/plugins/remotefortressreader/df_version_int.h index 6a906fe43..d6d0bd750 100644 --- a/plugins/remotefortressreader/df_version_int.h +++ b/plugins/remotefortressreader/df_version_int.h @@ -1,3 +1,2 @@ -#ifndef DF_VERSION_INT_H +#pragma once #define DF_VERSION_INT 43005 -#endif // !DF_VERSION_INT_H From c5fad31a4c5c62a8c547e0619dd5230012a37add Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 16:55:50 -0400 Subject: [PATCH 0586/1012] Fix indentation --- .../remotefortressreader.cpp | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 15b48677f..025658aa6 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -197,7 +197,7 @@ command_result dump_bp_mods(color_ostream &out, vector & parameters) output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; #if DF_VERSION_INT > 34011 - if (appMod->growth_rate > 0) + if (appMod->growth_rate > 0) { output << appMod->growth_min << " - " << appMod->growth_max << "\n"; } @@ -1014,7 +1014,7 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc } } #endif - for (int yy = 0; yy < 16; yy++) + for (int yy = 0; yy < 16; yy++) for (int xx = 0; xx < 16; xx++) { df::tiletype tile = DfBlock->tiletype[xx][yy]; @@ -1092,7 +1092,7 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); #endif - switch (designation.bits.dig) + switch (designation.bits.dig) { case df::enums::tile_dig_designation::No: NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); @@ -1313,7 +1313,7 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc { int id = DfBlock->items[i]; - + auto item = df::item::find(id); if(item) CopyItem(NetBlock->add_items(), item); @@ -1632,7 +1632,7 @@ DFCoord GetMapCenter() } else #endif - if (Maps::IsValid()) + if (Maps::IsValid()) { int x, y, z; Maps::getPosition(x,y,z); @@ -1712,14 +1712,14 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, case df::world_data::South: out->set_world_poles(WorldPoles::SOUTH_POLE); break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif for (int yy = 0; yy < height; yy++) for (int xx = 0; xx < width; xx++) @@ -2665,17 +2665,17 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in break; default: break; - } - mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + } + mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); #if DF_VERSION_INT >= 43005 - //remove and job postings related. - for (df::job_list_link * listing = &(world->job_list); listing != NULL; listing = listing->next) - { - if (listing->item == NULL) - continue; - auto type = listing->item->job_type; - switch (type) + //remove and job postings related. + for (df::job_list_link * listing = &(world->job_list); listing != NULL; listing = listing->next) + { + if (listing->item == NULL) + continue; + auto type = listing->item->job_type; + switch (type) { case df::enums::job_type::CarveFortification: case df::enums::job_type::DetailWall: From e20ddb7f1dee1e444489539c0127e19b243094de Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 19:00:24 -0400 Subject: [PATCH 0587/1012] Update NEWS, document orders --- NEWS.rst | 154 +++++++++++++++++++++++++++++++++++++++++++++++ docs/Plugins.rst | 12 ++++ 2 files changed, 166 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index ee12e1a32..9d8b05a78 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -14,6 +14,7 @@ Internals Lua + Ruby New [Internal Commands | Plugins | Scripts | Tweaks | Features] Fixes Misc Improvements @@ -35,6 +36,159 @@ Changelog .. contents:: :depth: 2 +DFHack 0.43.05-r2 +================= + +Internals +--------- +- Rebuilding DFHack can be faster if nothing Git-related has changed +- Plugins can now hook Screen::readTile() +- Improved Lua compatibility with plugins that hook into GUI functions (like TWBT) +- Expanded focus strings for jobmanagement and workquota_condition viewscreens +- ``Gui::getAnyUnit()``: added support for viewscreen_unitst, + viewscreen_textviewerst, viewscreen_layer_unit_relationshipst +- Fixed (limited) keybinding support in PRINT_MODE:TEXT on macOS +- Added a new standardized ``Gui::refreshSidebar()`` function to fix behavior of + some plugins on the lowest z-level +- New ``Buildings`` module functions: ``markedForRemoval()``, ``getCageOccupants()`` +- Limited recursive command invocations to 20 to prevent crashes +- Added an ``onLoad.init-example`` file + +Lua +--- +- Improved C++ exception handling for some native functions that aren't direct + wrappers around C++ functions (in this case, error messages could be nil and + cause the Lua interpreter to quit) +- Added support for a ``key_pen`` option in Label widgets +- Fixed ``to_first`` argument to ``dfhack.screen.dismiss()`` +- Added optional ``map`` parameters to some screen functions +- Exposed some more functions to Lua: + + - ``dfhack.gui.refreshSidebar()`` + - ``dfhack.gui.getAnyUnit()`` + - ``dfhack.gui.getAnyBuilding()`` + - ``dfhack.gui.getAnyItem()`` + - ``dfhack.gui.getAnyPlant()`` + - ``dfhack.gui.getDepthAt()`` + - ``dfhack.units.getUnitsInBox()`` + - ``dfhack.units.isVisible()`` + - ``dfhack.maps.isTileVisible()`` + - ``dfhack.buildings.markedForRemoval()`` + - ``dfhack.buildings.getCageOccupants()`` + - ``dfhack.internal.md5()`` + - ``dfhack.internal.md5File()`` + - ``dfhack.internal.threadid()`` + +- New function: ``widgets.Pages:getSelectedPage()`` +- Added a ``key`` option to EditField and FilteredList widgets +- Fixed an issue preventing ``repeatUtil.cancel()`` from working when called + from the callback + +Ruby +---- +- Fixed a crash when creating new instances of DF virtual classes (e.g. fixes a + `lever` crash) +- Ruby scripts can now be loaded from any script paths specified (from script- + paths.txt or registered through the Lua API) +- ``unit_find()`` now uses ``Gui::getSelectedUnit()`` and works in more places + (e.g. `exterminate` now works from more screens, like `command-prompt`) + +New Internal Commands +--------------------- +- `alias`: allows configuring aliases for other commands + +New Plugins +----------- +- `orders`: Manipulate manager orders +- `pathable`: Back-end for `gui/pathable` + +New Scripts +----------- +- `clear-smoke`: Removes all smoke from the map +- `empty-bin`: Empty a bin onto the floor +- `fix/retrieve-units`: Spawns stuck invaders/guests +- `fix/stuck-merchants`: Dismisses stuck merchants that haven't entered the map yet +- `gui/pathable`: View whether tiles on the map can be pathed to +- `gui/teleport`: A front-end for the `teleport` script +- `warn-stuck-trees`: Detects citizens stuck in trees + +New Tweaks +---------- +- `tweak` burrow-name-cancel: Implements the "back" option when renaming a + burrow, which currently does nothing (:bug:`1518`) +- `tweak` cage-butcher: Adds an option to butcher units when viewing cages with "q" + +Fixes +----- +- Enforced use of ``stdout.log`` and ``stderr.log`` (instead of their ``.txt`` + counterparts) on Windows +- Fixed ``getItemBaseValue()`` for cheese, sheets and instruments +- Fixed alignment in: + + - ``viewscreen_choose_start_sitest`` + - ``viewscreen_export_graphical_mapst`` + - ``viewscreen_setupadventurest`` + - ``viewscreen_setupdwarfgamest`` + +- `adv-max-skills`: fixed error due to viewscreen changes +- `autolabor`: fixed a crash when assigning haulers while traders are active +- `buildingplan`: fixed an issue that prevented certain numbers from being used + in building names +- `confirm`: + + - dialogs are now closed permanently when disabled from the settings UI + - fixed an issue that could have prevented closing dialogs opened by pressing "s" + +- `embark-tools`: stopped the sand indicator from overlapping dialogs +- `exportlegends`: fixed some crashes and site map issues +- `devel/find-offsets`: fixed ``current_weather`` scan +- `gui/extended-status`: fixed an error when no beds are available +- `gui/family-affairs`: fixed issues with assigning lovers +- `gui/gm-editor`: + + - made keybinding display order consistent + - stopped keys from performing actions in help screen + +- `gui/manager-quantity`: + + - now allows orders with a limit of 0 + - fixed screen detection + +- `gui/mechanisms`, `gui/room-list`: fixed an issue when recentering the map when exiting +- `lever`: prevented pulling non-lever buildings, which can cause crashes +- `markdown`: fixed file encoding +- `modtools/create-unit`: + + - fixed when popup announcements are present + - added checks to ensure that the current game mode is restored + +- `resume`: stopped drawing on the map border +- `show-unit-syndromes`: fixed an error when handling some syndromes +- `strangemood`: fixed some issues with material searches +- `view-item-info`: fixed a color-related error for some materials + +Misc Improvements +----------------- +- Docs: prevented automatic hyphenation in some browsers, which was producing + excessive hyphenation sometimes +- `command-prompt`: invoking ``command-prompt`` a second time now hides the prompt +- `gui/extended-status`: added an option to assign/replace the manager +- `gui/load-screen`: + + - adjusted dialog width for long folder names + - added modification times and DF versions to dialog + +- `gui/mechanisms`, `gui/room-list`, `gui/siege-engine`: add and list "exit to map" options +- `lever`: added support for pulling levers at high priority +- `markdown`: now recognizes ``-n`` in addition to ``/n`` +- `remotefortressreader`: more data exported, used by Armok Vision v0.17.0 +- `resume`, `siege-engine`: improved compatibility with GUI-hooking plugins (like TWBT) +- `sc-script`: improved help text +- `teleport`: can now be used as a module +- `tweak` embark-profile-name: now enabled in ``dfhack.init-example`` +- `tweak` hotkey-clear: fixed display on larger screens + + DFHack 0.43.05-r1 ================= diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 6137d080b..0ed65c4a3 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1698,6 +1698,18 @@ once every in game day. If you add ``enable autochop`` to your dfhack.init there will be a hotkey to open the dashboard from the chop designation menu. +.. _orders: + +orders +====== + +A plugin for manipulating manager orders. + +Subcommands: + +:export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. +:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. +:clear: Deletes all manager orders in the current embark. ================ Map modification From 48a61420b779607e2a46e084675516c769c46d7f Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 19:06:37 -0400 Subject: [PATCH 0588/1012] Bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8b43c86..7e78340d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,7 +139,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "r1") +SET(DFHACK_RELEASE "r2") SET(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From ce1158780f524401d5bb424f24cfd7c27843d5a9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 13 Jul 2017 13:07:58 -0400 Subject: [PATCH 0589/1012] Fix crash in stl_vsprintf() from reusing va_list --- library/MiscUtils.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 5348086c1..cb3150152 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -61,7 +61,10 @@ std::string stl_vsprintf(const char *fmt, va_list args) { std::vector buf; buf.resize(4096); for (;;) { - int rsz = vsnprintf(&buf[0], buf.size(), fmt, args); + va_list args2; + va_copy(args2, args); + int rsz = vsnprintf(&buf[0], buf.size(), fmt, args2); + va_end(args2); if (rsz < 0) buf.resize(buf.size()*2); From 15ed1bfd67f5bf87694aab5370210bd9d7f86fa5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 13 Jul 2017 13:08:27 -0400 Subject: [PATCH 0590/1012] stl_vsprintf: avoid truncating 4096-byte strings --- library/MiscUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index cb3150152..a6bb4b412 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -68,7 +68,7 @@ std::string stl_vsprintf(const char *fmt, va_list args) { if (rsz < 0) buf.resize(buf.size()*2); - else if (unsigned(rsz) > buf.size()) + else if (unsigned(rsz) >= buf.size()) buf.resize(rsz+1); else return std::string(&buf[0], rsz); From 1ee5debfbabbd57f7b750fe458d09bddfc20c0d9 Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Mon, 17 Jul 2017 15:29:23 +0530 Subject: [PATCH 0591/1012] Send the existence of all buildings on the map. --- plugins/remotefortressreader/building_reader.cpp | 2 +- plugins/remotefortressreader/remotefortressreader.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index a8d15597a..12598ea81 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -287,7 +287,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build) { df::building * local_build = df::global::world->buildings.all[buildingIndex]; - remote_build->set_index(buildingIndex); + remote_build->set_index(local_build->id); int minZ = local_build->z; if (local_build->getType() == df::enums::building_type::Well) { diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 025658aa6..9b19edcc6 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.17.0" +#define RFR_VERSION "0.17.1" #include #include @@ -1179,7 +1179,11 @@ void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * Ne { df::building * bld = df::global::world->buildings.all[i]; if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); continue; + } int z2 = bld->z; @@ -1192,7 +1196,11 @@ void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * Ne } } if (bld->z < min.z || z2 >= max.z) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); continue; + } auto out_bld = NetBlock->add_buildings(); CopyBuilding(i, out_bld); df::building_actual* actualBuilding = virtual_cast(bld); From 90dd9e932e03e18cb29196a4f5a2941649662099 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 20 Jul 2017 03:53:45 -0500 Subject: [PATCH 0592/1012] exclude on-duty military personnel from health labor check --- plugins/labormanager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index 699e04605..cec70ab1e 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -2224,8 +2224,12 @@ private: out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); // determine if dwarf has medical needs - // babies cannot currently receive health care even if they need it - if (dwarf->dwarf->profession != profession::BABY && dwarf->dwarf->health) + if (dwarf->dwarf->health && !( + // on-duty military will not necessarily break to get minor injuries attended + ENUM_ATTR(profession, military, dwarf->dwarf->profession) || + // babies cannot currently receive health care even if they need it + dwarf->dwarf->profession == profession::BABY) + ) { if (dwarf->dwarf->health->flags.bits.needs_recovery) cnt_recover_wounded++; From ac1d6f947fb84de102d321e81152d5fca144d3fa Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 21 Jul 2017 10:24:00 -0500 Subject: [PATCH 0593/1012] Update clsocket submodule. See DFHack/clsocket#10. --- depends/clsocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/clsocket b/depends/clsocket index d1565a3b7..0f0ad78c4 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit d1565a3b711514048477e11bc04f60f6218af37f +Subproject commit 0f0ad78c4fd429caacd0694b5c868dbeacea16b6 From 17a7885ef2356b3bbdf286ff67d63edc4ff2a168 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 21 Jul 2017 13:30:05 -0500 Subject: [PATCH 0594/1012] Fix a bunch of 64-bit Windows warnings --- library/include/Pragma.h | 2 ++ library/modules/Filesystem.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/library/include/Pragma.h b/library/include/Pragma.h index 8f82b8d3c..00472e732 100644 --- a/library/include/Pragma.h +++ b/library/include/Pragma.h @@ -58,6 +58,8 @@ distribution. #pragma warning( disable: 4482) // nonstandard extension used: 'extern' before template explicit instantiation #pragma warning( disable: 4231) + // ignore warnings about putting a vector index into an int + #pragma warning( disable: 4267) #endif #endif diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 05d4e4fda..430e6351e 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -53,7 +53,7 @@ using namespace DFHack; bool Filesystem::chdir (std::string path) { - return !(bool)::chdir(path.c_str()); + return ::chdir(path.c_str()) == 0; } std::string Filesystem::getcwd () @@ -79,7 +79,7 @@ bool Filesystem::mkdir (std::string path) fail = ::mkdir(path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); #endif - return !(bool)fail; + return fail == 0; } bool Filesystem::rmdir (std::string path) @@ -90,7 +90,7 @@ bool Filesystem::rmdir (std::string path) #else fail = ::rmdir(path.c_str()); #endif - return !(bool)fail; + return fail == 0; } #ifdef _WIN32 @@ -116,13 +116,13 @@ _filetype mode2type (mode_t mode) { bool Filesystem::stat (std::string path, STAT_STRUCT &info) { - return !(bool)(STAT_FUNC(path.c_str(), &info)); + return (STAT_FUNC(path.c_str(), &info)) == 0; } bool Filesystem::exists (std::string path) { STAT_STRUCT info; - return (bool)Filesystem::stat(path, info); + return Filesystem::stat(path, info); } _filetype Filesystem::filetype (std::string path) From 15ae72edec0c0a84d9ea358ca8177c254d20844e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 22 Jul 2017 04:31:30 -0500 Subject: [PATCH 0595/1012] labormanager: Refactor source, separating the job labor mapper into a separate source file. --- plugins/CMakeLists.txt | 2 +- plugins/labormanager.cpp | 1041 ++--------------------- plugins/labormanager.h | 4 + plugins/labormanager_joblabormapper.cpp | 919 ++++++++++++++++++++ plugins/labormanager_joblabormapper.h | 21 + 5 files changed, 1033 insertions(+), 954 deletions(-) create mode 100644 plugins/labormanager.h create mode 100644 plugins/labormanager_joblabormapper.cpp create mode 100644 plugins/labormanager_joblabormapper.h diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d94dc52c6..b09651678 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -128,7 +128,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) - DFHACK_PLUGIN(labormanager labormanager.cpp) + DFHACK_PLUGIN(labormanager labormanager.cpp labormanager.h labormanager_joblabormapper.cpp labormanager_joblabormapper.h) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) diff --git a/plugins/labormanager.cpp b/plugins/labormanager.cpp index cec70ab1e..f791fe398 100644 --- a/plugins/labormanager.cpp +++ b/plugins/labormanager.cpp @@ -68,6 +68,9 @@ #include #include +#include "labormanager.h" +#include "labormanager_joblabormapper.h" + using namespace std; using std::string; using std::endl; @@ -96,7 +99,7 @@ enum ConfigFlags { // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom -command_result labormanager (color_ostream &out, std::vector & parameters); +command_result labormanager(color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. // The name string provided must correspond to the filename - labormanager.plug.so or labormanager.plug.dll in this case @@ -393,8 +396,8 @@ struct labor_info return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); } void mark_assigned() { - config.ival(3) = (* df::global::cur_year); - config.ival(4) = (* df::global::cur_year_tick); + config.ival(3) = (*df::global::cur_year); + config.ival(4) = (*df::global::cur_year_tick); } }; @@ -500,7 +503,7 @@ static const struct labor_default default_labor_infos[] = { /* BOOKBINDING */ {100, 0, TOOL_NONE} }; -void debug (char* fmt, ...); + struct dwarf_info_t { @@ -531,189 +534,20 @@ struct dwarf_info_t }; -/* -* Here starts all the complicated stuff to try to deduce labors from jobs. -* This is all way more complicated than it really ought to be, but I have -* not found a way to make it simpler. -*/ - -static df::unit_labor hauling_labor_map[] = -{ - df::unit_labor::HAUL_ITEM, /* BAR */ - df::unit_labor::HAUL_STONE, /* SMALLGEM */ - df::unit_labor::HAUL_ITEM, /* BLOCKS */ - df::unit_labor::HAUL_STONE, /* ROUGH */ - df::unit_labor::HAUL_STONE, /* BOULDER */ - df::unit_labor::HAUL_WOOD, /* WOOD */ - df::unit_labor::HAUL_FURNITURE, /* DOOR */ - df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ - df::unit_labor::HAUL_FURNITURE, /* BED */ - df::unit_labor::HAUL_FURNITURE, /* CHAIR */ - df::unit_labor::HAUL_ITEM, /* CHAIN */ - df::unit_labor::HAUL_ITEM, /* FLASK */ - df::unit_labor::HAUL_ITEM, /* GOBLET */ - df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ - df::unit_labor::HAUL_ITEM, /* TOY */ - df::unit_labor::HAUL_FURNITURE, /* WINDOW */ - df::unit_labor::HAUL_ANIMALS, /* CAGE */ - df::unit_labor::HAUL_ITEM, /* BARREL */ - df::unit_labor::HAUL_ITEM, /* BUCKET */ - df::unit_labor::HAUL_ANIMALS, /* ANIMALTRAP */ - df::unit_labor::HAUL_FURNITURE, /* TABLE */ - df::unit_labor::HAUL_FURNITURE, /* COFFIN */ - df::unit_labor::HAUL_FURNITURE, /* STATUE */ - df::unit_labor::HAUL_REFUSE, /* CORPSE */ - df::unit_labor::HAUL_ITEM, /* WEAPON */ - df::unit_labor::HAUL_ITEM, /* ARMOR */ - df::unit_labor::HAUL_ITEM, /* SHOES */ - df::unit_labor::HAUL_ITEM, /* SHIELD */ - df::unit_labor::HAUL_ITEM, /* HELM */ - df::unit_labor::HAUL_ITEM, /* GLOVES */ - df::unit_labor::HAUL_FURNITURE, /* BOX */ - df::unit_labor::HAUL_ITEM, /* BIN */ - df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ - df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ - df::unit_labor::HAUL_FURNITURE, /* CABINET */ - df::unit_labor::HAUL_ITEM, /* FIGURINE */ - df::unit_labor::HAUL_ITEM, /* AMULET */ - df::unit_labor::HAUL_ITEM, /* SCEPTER */ - df::unit_labor::HAUL_ITEM, /* AMMO */ - df::unit_labor::HAUL_ITEM, /* CROWN */ - df::unit_labor::HAUL_ITEM, /* RING */ - df::unit_labor::HAUL_ITEM, /* EARRING */ - df::unit_labor::HAUL_ITEM, /* BRACELET */ - df::unit_labor::HAUL_ITEM, /* GEM */ - df::unit_labor::HAUL_FURNITURE, /* ANVIL */ - df::unit_labor::HAUL_REFUSE, /* CORPSEPIECE */ - df::unit_labor::HAUL_REFUSE, /* REMAINS */ - df::unit_labor::HAUL_FOOD, /* MEAT */ - df::unit_labor::HAUL_FOOD, /* FISH */ - df::unit_labor::HAUL_FOOD, /* FISH_RAW */ - df::unit_labor::HAUL_REFUSE, /* VERMIN */ - df::unit_labor::HAUL_ITEM, /* PET */ - df::unit_labor::HAUL_ITEM, /* SEEDS */ - df::unit_labor::HAUL_FOOD, /* PLANT */ - df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ - df::unit_labor::HAUL_FOOD, /* LEAVES */ - df::unit_labor::HAUL_ITEM, /* THREAD */ - df::unit_labor::HAUL_ITEM, /* CLOTH */ - df::unit_labor::HAUL_ITEM, /* TOTEM */ - df::unit_labor::HAUL_ITEM, /* PANTS */ - df::unit_labor::HAUL_ITEM, /* BACKPACK */ - df::unit_labor::HAUL_ITEM, /* QUIVER */ - df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ - df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ - df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ - df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ - df::unit_labor::HAUL_FOOD, /* DRINK */ - df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ - df::unit_labor::HAUL_FOOD, /* CHEESE */ - df::unit_labor::HAUL_FOOD, /* FOOD */ - df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ - df::unit_labor::HAUL_ITEM, /* COIN */ - df::unit_labor::HAUL_FOOD, /* GLOB */ - df::unit_labor::HAUL_STONE, /* ROCK */ - df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ - df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ - df::unit_labor::HAUL_FURNITURE, /* GRATE */ - df::unit_labor::HAUL_FURNITURE, /* QUERN */ - df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ - df::unit_labor::HAUL_ITEM, /* SPLINT */ - df::unit_labor::HAUL_ITEM, /* CRUTCH */ - df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ - df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ - df::unit_labor::HAUL_ITEM, /* TOOL */ - df::unit_labor::HAUL_FURNITURE, /* SLAB */ - df::unit_labor::HAUL_FOOD, /* EGG */ - df::unit_labor::HAUL_ITEM, /* BOOK */ -}; - -static df::unit_labor workshop_build_labor[] = -{ - /* Carpenters */ df::unit_labor::CARPENTER, - /* Farmers */ df::unit_labor::PROCESS_PLANT, - /* Masons */ df::unit_labor::MASON, - /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, - /* Jewelers */ df::unit_labor::CUT_GEM, - /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, - /* MagmaForge */ df::unit_labor::METAL_CRAFT, - /* Bowyers */ df::unit_labor::BOWYER, - /* Mechanics */ df::unit_labor::MECHANIC, - /* Siege */ df::unit_labor::SIEGECRAFT, - /* Butchers */ df::unit_labor::BUTCHER, - /* Leatherworks */ df::unit_labor::LEATHER, - /* Tanners */ df::unit_labor::TANNER, - /* Clothiers */ df::unit_labor::CLOTHESMAKER, - /* Fishery */ df::unit_labor::CLEAN_FISH, - /* Still */ df::unit_labor::BREWER, - /* Loom */ df::unit_labor::WEAVER, - /* Quern */ df::unit_labor::MILLER, - /* Kennels */ df::unit_labor::ANIMALTRAIN, - /* Kitchen */ df::unit_labor::COOK, - /* Ashery */ df::unit_labor::LYE_MAKING, - /* Dyers */ df::unit_labor::DYER, - /* Millstone */ df::unit_labor::MILLER, - /* Custom */ df::unit_labor::NONE, - /* Tool */ df::unit_labor::NONE -}; - -static df::building* get_building_from_job(df::job* j) -{ - for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) - { - if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) - { - int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; - df::building* bld = binsearch_in_vector(world->buildings.all, id); - return bld; - } - } - return 0; -} - -static df::unit_labor construction_build_labor (df::building_actual* b) -{ - if (b->getType() == df::building_type::RoadPaved) - return df::unit_labor::BUILD_ROAD; - // Find last item in building with use mode appropriate to the building's constructions state - // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block - // For wells 0 mechanism, 1 rope, 2 bucket, 3 block - // Trade depots and bridges use the last one too - // Must check use mode b/c buildings may have items in them that are not part of the building - - df::item* i = 0; - for (auto p = b->contained_items.begin(); p != b->contained_items.end(); p++) - if (b->construction_stage > 0 && (*p)->use_mode == 2 || - b->construction_stage == 0 && (*p)->use_mode == 0) - i = (*p)->item; - - MaterialInfo matinfo; - if (i && matinfo.decode(i)) - { - if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) - return df::unit_labor::METAL_CRAFT; - if (matinfo.material->flags.is_set(df::material_flags::WOOD)) - return df::unit_labor::CARPENTER; - } - return df::unit_labor::MASON; -} - color_ostream* debug_stream; -void debug (const char* fmt, ...) +void debug(const char* fmt, ...) { if (debug_stream) { va_list args; - va_start(args,fmt); + va_start(args, fmt); debug_stream->vprint(fmt, args); va_end(args); } } -void debug_pause () +void debug_pause() { if (pause_on_error) { @@ -722,707 +556,6 @@ void debug_pause () } } -class JobLaborMapper { - -private: - class jlfunc - { - public: - virtual df::unit_labor get_labor(df::job* j) = 0; - }; - - class jlfunc_const : public jlfunc - { - private: - df::unit_labor labor; - public: - df::unit_labor get_labor(df::job* j) - { - return labor; - } - jlfunc_const(df::unit_labor l) : labor(l) {}; - }; - - class jlfunc_hauling : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - df::item* item = 0; - if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) - return (df::unit_labor) j->item_subtype; - - for (auto i = j->items.begin(); i != j->items.end(); i++) - { - if ((*i)->role == 7) - { - item = (*i)->item; - break; - } - } - - if (item && item->flags.bits.container) - { - for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) - { - if ((*a)->getType() == df::general_ref_type::CONTAINS_ITEM) - { - int item_id = ((df::general_ref_contains_itemst *) (*a))->item_id; - item = binsearch_in_vector(world->items.all, item_id); - break; - } - } - } - - df::unit_labor l = item ? hauling_labor_map[item->getType()] : df::unit_labor::HAUL_ITEM; - if (item && l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) - l = df::unit_labor::HAUL_BODY; - return l; - } - jlfunc_hauling() {}; - }; - - class jlfunc_construct_bld : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - if (j->flags.bits.item_lost) - return df::unit_labor::NONE; - - df::building* bld = get_building_from_job (j); - switch (bld->getType()) - { - case df::building_type::Hive: - return df::unit_labor::BEEKEEPING; - case df::building_type::Workshop: - { - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->design && !ws->design->flags.bits.designed) - return df::unit_labor::ARCHITECT; - if (ws->type == df::workshop_type::Custom) - { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - } - break; - case df::building_type::Construction: - return df::unit_labor::BUILD_CONSTRUCTION; - case df::building_type::Furnace: - case df::building_type::TradeDepot: - case df::building_type::Bridge: - case df::building_type::ArcheryTarget: - case df::building_type::WaterWheel: - case df::building_type::RoadPaved: - case df::building_type::Well: - case df::building_type::ScrewPump: - case df::building_type::Wagon: - case df::building_type::Shop: - case df::building_type::Support: - case df::building_type::Windmill: - { - df::building_actual* b = (df::building_actual*) bld; - if (b->design && !b->design->flags.bits.designed) - return df::unit_labor::ARCHITECT; - return construction_build_labor(b); - } - break; - case df::building_type::FarmPlot: - return df::unit_labor::PLANT; - case df::building_type::Chair: - case df::building_type::Bed: - case df::building_type::Table: - case df::building_type::Coffin: - case df::building_type::Door: - case df::building_type::Floodgate: - case df::building_type::Box: - case df::building_type::Weaponrack: - case df::building_type::Armorstand: - case df::building_type::Cabinet: - case df::building_type::Statue: - case df::building_type::WindowGlass: - case df::building_type::WindowGem: - case df::building_type::Cage: - case df::building_type::NestBox: - case df::building_type::TractionBench: - case df::building_type::Slab: - case df::building_type::Chain: - case df::building_type::GrateFloor: - case df::building_type::Hatch: - case df::building_type::BarsFloor: - case df::building_type::BarsVertical: - case df::building_type::GrateWall: - case df::building_type::Bookcase: - case df::building_type::Instrument: - return df::unit_labor::HAUL_FURNITURE; - case df::building_type::Trap: - case df::building_type::GearAssembly: - case df::building_type::AxleHorizontal: - case df::building_type::AxleVertical: - case df::building_type::Rollers: - return df::unit_labor::MECHANIC; - case df::building_type::AnimalTrap: - return df::unit_labor::TRAPPER; - case df::building_type::Civzone: - case df::building_type::Nest: - case df::building_type::Stockpile: - case df::building_type::Weapon: - return df::unit_labor::NONE; - case df::building_type::SiegeEngine: - return df::unit_labor::SIEGECRAFT; - case df::building_type::RoadDirt: - return df::unit_labor::BUILD_ROAD; - } - - debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", - ENUM_KEY_STR(building_type, bld->getType()).c_str()); - debug_pause(); - - return df::unit_labor::NONE; - } - jlfunc_construct_bld() {} - }; - - class jlfunc_destroy_bld : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job (j); - df::building_type type = bld->getType(); - - switch (bld->getType()) - { - case df::building_type::Hive: - return df::unit_labor::BEEKEEPING; - case df::building_type::Workshop: - { - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->type == df::workshop_type::Custom) - { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - } - break; - case df::building_type::Construction: - return df::unit_labor::REMOVE_CONSTRUCTION; - case df::building_type::Furnace: - case df::building_type::TradeDepot: - case df::building_type::Wagon: - case df::building_type::Bridge: - case df::building_type::ScrewPump: - case df::building_type::ArcheryTarget: - case df::building_type::RoadPaved: - case df::building_type::Shop: - case df::building_type::Support: - case df::building_type::WaterWheel: - case df::building_type::Well: - case df::building_type::Windmill: - { - auto b = (df::building_actual*) bld; - return construction_build_labor(b); - } - break; - case df::building_type::FarmPlot: - return df::unit_labor::PLANT; - case df::building_type::Trap: - case df::building_type::AxleHorizontal: - case df::building_type::AxleVertical: - case df::building_type::GearAssembly: - case df::building_type::Rollers: - return df::unit_labor::MECHANIC; - case df::building_type::Chair: - case df::building_type::Bed: - case df::building_type::Table: - case df::building_type::Coffin: - case df::building_type::Door: - case df::building_type::Floodgate: - case df::building_type::Box: - case df::building_type::Weaponrack: - case df::building_type::Armorstand: - case df::building_type::Cabinet: - case df::building_type::Statue: - case df::building_type::WindowGlass: - case df::building_type::WindowGem: - case df::building_type::Cage: - case df::building_type::NestBox: - case df::building_type::TractionBench: - case df::building_type::Slab: - case df::building_type::Chain: - case df::building_type::Hatch: - case df::building_type::BarsFloor: - case df::building_type::BarsVertical: - case df::building_type::GrateFloor: - case df::building_type::GrateWall: - case df::building_type::Bookcase: - case df::building_type::Instrument: - return df::unit_labor::HAUL_FURNITURE; - case df::building_type::AnimalTrap: - return df::unit_labor::TRAPPER; - case df::building_type::Civzone: - case df::building_type::Nest: - case df::building_type::RoadDirt: - case df::building_type::Stockpile: - case df::building_type::Weapon: - return df::unit_labor::NONE; - case df::building_type::SiegeEngine: - return df::unit_labor::SIEGECRAFT; - } - - debug ("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", - ENUM_KEY_STR(building_type, bld->getType()).c_str()); - debug_pause(); - - return df::unit_labor::NONE; - } - jlfunc_destroy_bld() {} - }; - - class jlfunc_make : public jlfunc - { - private: - df::unit_labor metaltype; - public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job(j); - if (bld->getType() == df::building_type::Workshop) - { - df::workshop_type type = ((df::building_workshopst*)(bld))->type; - switch (type) - { - case df::workshop_type::Craftsdwarfs: - { - df::item_type jobitem = j->job_items[0]->item_type; - switch (jobitem) - { - case df::item_type::BOULDER: - return df::unit_labor::STONE_CRAFT; - case df::item_type::NONE: - if (j->material_category.bits.bone || - j->material_category.bits.horn || - j->material_category.bits.tooth || - j->material_category.bits.shell) - return df::unit_labor::BONE_CARVE; - else - { - debug ("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); - debug_pause(); - return df::unit_labor::NONE; - } - case df::item_type::WOOD: - return df::unit_labor::WOOD_CRAFT; - case df::item_type::CLOTH: - return df::unit_labor::CLOTHESMAKER; - case df::item_type::SKIN_TANNED: - return df::unit_labor::LEATHER; - default: - debug ("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", - ENUM_KEY_STR(item_type, jobitem).c_str()); - debug_pause(); - return df::unit_labor::NONE; - } - } - case df::workshop_type::Masons: - return df::unit_labor::MASON; - case df::workshop_type::Carpenters: - return df::unit_labor::CARPENTER; - case df::workshop_type::Leatherworks: - return df::unit_labor::LEATHER; - case df::workshop_type::Clothiers: - return df::unit_labor::CLOTHESMAKER; - case df::workshop_type::Bowyers: - return df::unit_labor::BOWYER; - case df::workshop_type::MagmaForge: - case df::workshop_type::MetalsmithsForge: - return metaltype; - default: - debug ("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", - ENUM_KEY_STR(workshop_type, type).c_str()); - debug_pause(); - return df::unit_labor::NONE; - } - } - else if (bld->getType() == df::building_type::Furnace) - { - df::furnace_type type = ((df::building_furnacest*)(bld))->type; - switch (type) - { - case df::furnace_type::MagmaGlassFurnace: - case df::furnace_type::GlassFurnace: - return df::unit_labor::GLASSMAKER; - default: - debug ("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", - ENUM_KEY_STR(furnace_type, type).c_str()); - debug_pause(); - return df::unit_labor::NONE; - } - } - - debug ("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", - ENUM_KEY_STR(building_type, bld->getType()).c_str()); - debug_pause(); - - return df::unit_labor::NONE; - } - - jlfunc_make (df::unit_labor mt) : metaltype(mt) {} - }; - - class jlfunc_custom : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); - return labor; - } - } - return df::unit_labor::NONE; - } - jlfunc_custom() {} - }; - - map jlf_cache; - - jlfunc* jlf_const(df::unit_labor l) { - jlfunc* jlf; - if (jlf_cache.count(l) == 0) - { - jlf = new jlfunc_const(l); - jlf_cache[l] = jlf; - } - else - jlf = jlf_cache[l]; - - return jlf; - } -private: - std::map job_to_labor_table; - -public: - ~JobLaborMapper() - { - std::set log; - - for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) - { - if (!log.count(i->second)) - { - log.insert(i->second); - delete i->second; - } - i->second = 0; - } - - FOR_ENUM_ITEMS (job_type, j) - { - if (j < 0) - continue; - - jlfunc* p = job_to_labor_table[j]; - if (!log.count(p)) - { - log.insert(p); - delete p; - } - job_to_labor_table[j] = 0; - } - } - - JobLaborMapper() - { - jlfunc* jlf_hauling = new jlfunc_hauling(); - jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); - jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); - jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); - jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); - - jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); - - job_to_labor_table[df::job_type::CarveFortification] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::DetailWall] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::DetailFloor] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::Dig] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveUpwardStaircase] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveDownwardStaircase] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveUpDownStaircase] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveRamp] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); - job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); - job_to_labor_table[df::job_type::RemoveConstruction] = jlf_const(df::unit_labor::REMOVE_CONSTRUCTION); - job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); - job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); - job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; - job_to_labor_table[df::job_type::Eat] = jlf_no_labor; - job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; - job_to_labor_table[df::job_type::Drink] = jlf_no_labor; - job_to_labor_table[df::job_type::Drink2] = jlf_no_labor; - job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; - job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; - job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::HAUL_ITEM); - job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); - job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); - job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; - job_to_labor_table[df::job_type::Kidnap] = jlf_no_labor; - job_to_labor_table[df::job_type::BeatCriminal] = jlf_no_labor; - job_to_labor_table[df::job_type::StartingFistFight] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectTaxes] = jlf_no_labor; - job_to_labor_table[df::job_type::GuardTaxCollector] = jlf_no_labor; - job_to_labor_table[df::job_type::CatchLiveLandAnimal] = jlf_const(df::unit_labor::HUNT); - job_to_labor_table[df::job_type::CatchLiveFish] = jlf_const(df::unit_labor::FISH); - job_to_labor_table[df::job_type::ReturnKill] = jlf_no_labor; - job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; - job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; - job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; - job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; - job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; - job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; - job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; - job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; - job_to_labor_table[df::job_type::GoShopping] = jlf_no_labor; - job_to_labor_table[df::job_type::GoShopping2] = jlf_no_labor; - job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); - job_to_labor_table[df::job_type::Rest] = jlf_no_labor; - job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; - job_to_labor_table[df::job_type::DumpItem] = jlf_const(df::unit_labor::HAUL_REFUSE); - job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodMagmaForge] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodBrooding] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodFell] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodCarpenter] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodMason] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodBowyer] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodTanner] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodWeaver] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodGlassmaker] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodMechanics] = jlf_no_labor; - job_to_labor_table[df::job_type::ConstructBuilding] = new jlfunc_construct_bld(); - job_to_labor_table[df::job_type::ConstructDoor] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructFloodgate] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructBed] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructThrone] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCoffin] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructTable] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructChest] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructBin] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructArmorStand] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructWeaponRack] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCabinet] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); - job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; - job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); - job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); - job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); - job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); - job_to_labor_table[df::job_type::EncrustWithGlass] = jlf_const(df::unit_labor::ENCRUST_GEM); - job_to_labor_table[df::job_type::DestroyBuilding] = new jlfunc_destroy_bld(); - job_to_labor_table[df::job_type::SmeltOre] = jlf_const(df::unit_labor::SMELT); - job_to_labor_table[df::job_type::MeltMetalObject] = jlf_const(df::unit_labor::SMELT); - job_to_labor_table[df::job_type::ExtractMetalStrands] = jlf_const(df::unit_labor::EXTRACT_STRAND); - job_to_labor_table[df::job_type::PlantSeeds] = jlf_const(df::unit_labor::PLANT); - job_to_labor_table[df::job_type::HarvestPlants] = jlf_const(df::unit_labor::PLANT); - job_to_labor_table[df::job_type::TrainHuntingAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); - job_to_labor_table[df::job_type::TrainWarAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); - job_to_labor_table[df::job_type::MakeWeapon] = jlf_make_weapon; - job_to_labor_table[df::job_type::ForgeAnvil] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCatapultParts] = jlf_const(df::unit_labor::SIEGECRAFT); - job_to_labor_table[df::job_type::ConstructBallistaParts] = jlf_const(df::unit_labor::SIEGECRAFT); - job_to_labor_table[df::job_type::MakeArmor] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeHelm] = jlf_make_armor; - job_to_labor_table[df::job_type::MakePants] = jlf_make_armor; - job_to_labor_table[df::job_type::StudWith] = jlf_make_object; - job_to_labor_table[df::job_type::ButcherAnimal] = jlf_const(df::unit_labor::BUTCHER); - job_to_labor_table[df::job_type::PrepareRawFish] = jlf_const(df::unit_labor::CLEAN_FISH); - job_to_labor_table[df::job_type::MillPlants] = jlf_const(df::unit_labor::MILLER); - job_to_labor_table[df::job_type::BaitTrap] = jlf_const(df::unit_labor::TRAPPER); - job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); - job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); - job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); - job_to_labor_table[df::job_type::WeaveCloth] = jlf_const(df::unit_labor::WEAVER); - job_to_labor_table[df::job_type::MakeGloves] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeShoes] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeShield] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeCage] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeChain] = jlf_make_object; - job_to_labor_table[df::job_type::MakeFlask] = jlf_make_object; - job_to_labor_table[df::job_type::MakeGoblet] = jlf_make_object; - job_to_labor_table[df::job_type::MakeToy] = jlf_make_object; - job_to_labor_table[df::job_type::MakeAnimalTrap] = jlf_const(df::unit_labor::TRAPPER); - job_to_labor_table[df::job_type::MakeBarrel] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeBucket] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeWindow] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeTotem] = jlf_const(df::unit_labor::BONE_CARVE); - job_to_labor_table[df::job_type::MakeAmmo] = jlf_make_weapon; - job_to_labor_table[df::job_type::DecorateWith] = jlf_make_object; - job_to_labor_table[df::job_type::MakeBackpack] = jlf_make_object; - job_to_labor_table[df::job_type::MakeQuiver] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeBallistaArrowHead] = jlf_make_weapon; - job_to_labor_table[df::job_type::AssembleSiegeAmmo] = jlf_const(df::unit_labor::SIEGECRAFT); - job_to_labor_table[df::job_type::LoadCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::LoadBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); - job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_make_weapon; - job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; - job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::PullLever] = jlf_const(df::unit_labor::PULL_LEVER); - job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; - job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; - job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; - job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; - job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; - job_to_labor_table[df::job_type::ChainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); - job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; - job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING) ; - job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; - job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; - job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY) ; - job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); - job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING) ; - job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING) ; - job_to_labor_table[df::job_type::DrainAquarium] = jlf_no_labor; - job_to_labor_table[df::job_type::FillAquarium] = jlf_no_labor; - job_to_labor_table[df::job_type::FillPond] = jlf_no_labor; - job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; - job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; - job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; - job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; - job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; - job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); - job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); - job_to_labor_table[df::job_type::MakeAsh] = jlf_const(df::unit_labor::BURN_WOOD); - job_to_labor_table[df::job_type::MakeLye] = jlf_const(df::unit_labor::LYE_MAKING); - job_to_labor_table[df::job_type::MakePotashFromLye] = jlf_const(df::unit_labor::POTASH_MAKING); - job_to_labor_table[df::job_type::FertilizeField] = jlf_const(df::unit_labor::PLANT); - job_to_labor_table[df::job_type::MakePotashFromAsh] = jlf_const(df::unit_labor::POTASH_MAKING); - job_to_labor_table[df::job_type::DyeThread] = jlf_const(df::unit_labor::DYER); - job_to_labor_table[df::job_type::DyeCloth] = jlf_const(df::unit_labor::DYER); - job_to_labor_table[df::job_type::SewImage] = jlf_make_object; - job_to_labor_table[df::job_type::MakePipeSection] = jlf_make_furniture; - job_to_labor_table[df::job_type::OperatePump] = jlf_const(df::unit_labor::OPERATE_PUMP); - job_to_labor_table[df::job_type::ManageWorkOrders] = jlf_no_labor; - job_to_labor_table[df::job_type::UpdateStockpileRecords] = jlf_no_labor; - job_to_labor_table[df::job_type::TradeAtDepot] = jlf_no_labor; - job_to_labor_table[df::job_type::ConstructHatchCover] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; - job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); - job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; - job_to_labor_table[df::job_type::BringCrutch] = jlf_const(df::unit_labor::BONE_SETTING); - job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); - job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); - job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; - job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); - job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); - job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); - job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::MakeTool] = jlf_make_object; - job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); - job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); - job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); - job_to_labor_table[df::job_type::CauseTrouble] = jlf_no_labor; - job_to_labor_table[df::job_type::DrinkBlood] = jlf_no_labor; - job_to_labor_table[df::job_type::ReportCrime] = jlf_no_labor; - job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; - job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); - job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); - job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); - job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_hauling; - job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); - job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; - job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; - job_to_labor_table[df::job_type::MakeScepter] = jlf_make_object; - job_to_labor_table[df::job_type::MakeCrown] = jlf_make_object; - job_to_labor_table[df::job_type::MakeRing] = jlf_make_object; - job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; - job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; - job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; - - job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation - }; - - df::unit_labor find_job_labor(df::job* j) - { - if (j->job_type == df::job_type::CustomReaction) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - return ENUM_ATTR(job_skill, labor, skill); - } - } - return df::unit_labor::NONE; - } - - - df::unit_labor labor; - if (job_to_labor_table.count(j->job_type) == 0) - { - debug("LABORMANAGER: job has no job to labor table entry: %s (%d)\n", ENUM_KEY_STR(job_type, j->job_type).c_str(), j->job_type); - debug_pause(); - labor = df::unit_labor::NONE; - } else { - - labor = job_to_labor_table[j->job_type]->get_labor(j); - } - - return labor; - } -}; - -/* End of labor deducer */ - static JobLaborMapper* labor_mapper = 0; static bool initialized = false; @@ -1527,8 +660,8 @@ static void generate_labor_to_skill_map() } struct skill_attr_weight { - int phys_attr_weights [6]; - int mental_attr_weights [13]; + int phys_attr_weights[6]; + int mental_attr_weights[13]; }; static struct skill_attr_weight skill_attr_weights[ENUM_LAST_ITEM(job_skill) + 1] = @@ -1687,10 +820,10 @@ static void enable_plugin(color_ostream &out) init_state(); } -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { // initialize labor infos table from default table - if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) + if (ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) return CR_FAILURE; // Fill the command list with your commands. @@ -1721,7 +854,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector busy_dwarfs; private: - void set_labor (dwarf_info_t* dwarf, df::unit_labor labor, bool value) + void set_labor(dwarf_info_t* dwarf, df::unit_labor labor, bool value) { if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) { @@ -1821,7 +954,7 @@ private: } } - void process_job (df::job* j) + void process_job(df::job* j) { if (j->flags.bits.suspend || j->flags.bits.item_lost) return; @@ -1860,7 +993,7 @@ private: } } - df::unit_labor labor = labor_mapper->find_job_labor (j); + df::unit_labor labor = labor_mapper->find_job_labor(j); if (labor != df::unit_labor::NONE) { @@ -1873,7 +1006,8 @@ private: if (d->bits.outside) labor_outside[labor] = true; } - } else { + } + else { labor_infos[labor].mark_assigned(); labor_in_use[labor]++; } @@ -1937,7 +1071,7 @@ private: if (bl->designation[x][y].bits.hidden) { df::coord p = bl->map_pos; - df::coord c(p.x, p.y, p.z-1); + df::coord c(p.x, p.y, p.z - 1); if (Maps::getTileDesignation(c)->bits.hidden) continue; } @@ -2145,7 +1279,7 @@ private: if (dwarf->dwarf->social_activities.size() > 0) { if (print_debug) - out.print ("Dwarf %s is engaged in a social activity. Info only.\n", dwarf->dwarf->name.first_name.c_str()); + out.print("Dwarf %s is engaged in a social activity. Info only.\n", dwarf->dwarf->name.first_name.c_str()); } if (dwarf->dwarf->profession == profession::BABY || @@ -2173,7 +1307,7 @@ private: state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors dwarf->clear_all = true; if (print_debug) - out.print ("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); + out.print("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); } else { @@ -2260,7 +1394,7 @@ private: int high_skill = 0; - FOR_ENUM_ITEMS (unit_labor, labor) + FOR_ENUM_ITEMS(unit_labor, labor) { if (labor == df::unit_labor::NONE) continue; @@ -2287,7 +1421,8 @@ private: set_labor(dwarf, labor, false); } - } else { + } + else { if (state == IDLE) available_dwarfs.push_back(dwarf); @@ -2310,7 +1445,7 @@ private: busy_dwarfs.clear(); } - int score_labor (dwarf_info_t* d, df::unit_labor labor) + int score_labor(dwarf_info_t* d, df::unit_labor labor) { int skill_level = 0; int xp = 0; @@ -2405,51 +1540,51 @@ public: // add job entries for designation-related jobs - labor_needed[df::unit_labor::MINE] += dig_count; - labor_needed[df::unit_labor::CUTWOOD] += tree_count; - labor_needed[df::unit_labor::DETAIL] += detail_count; + labor_needed[df::unit_labor::MINE] += dig_count; + labor_needed[df::unit_labor::CUTWOOD] += tree_count; + labor_needed[df::unit_labor::DETAIL] += detail_count; labor_needed[df::unit_labor::HERBALIST] += plant_count; // add job entries for health care labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; - labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; + labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; - labor_needed[df::unit_labor::SURGERY] += cnt_surgery; - labor_needed[df::unit_labor::SUTURING] += cnt_suture; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_crutch; + labor_needed[df::unit_labor::SURGERY] += cnt_surgery; + labor_needed[df::unit_labor::SUTURING] += cnt_suture; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_crutch; labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; // add entries for hauling jobs - labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; - labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; - labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; - labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[4]; - labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; - labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; - labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; + labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; + labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[4]; + labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; + labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; + labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; - labor_needed[df::unit_labor::HAUL_ANIMALS] += world->stockpile.num_jobs[9]; - - labor_needed[df::unit_labor::HAUL_STONE] += (world->stockpile.num_jobs[1] >= world->stockpile.num_haulers[1]) ? 1 : 0; - labor_needed[df::unit_labor::HAUL_WOOD] += (world->stockpile.num_jobs[2] >= world->stockpile.num_haulers[2]) ? 1 : 0; - labor_needed[df::unit_labor::HAUL_ITEM] += (world->stockpile.num_jobs[3] >= world->stockpile.num_haulers[3]) ? 1 : 0; - labor_needed[df::unit_labor::HAUL_BODY] += (world->stockpile.num_jobs[5] >= world->stockpile.num_haulers[5]) ? 1 : 0; - labor_needed[df::unit_labor::HAUL_FOOD] += (world->stockpile.num_jobs[6] >= world->stockpile.num_haulers[6]) ? 1 : 0; - labor_needed[df::unit_labor::HAUL_REFUSE] += (world->stockpile.num_jobs[7] >= world->stockpile.num_haulers[7]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ANIMALS] += world->stockpile.num_jobs[9]; + + labor_needed[df::unit_labor::HAUL_STONE] += (world->stockpile.num_jobs[1] >= world->stockpile.num_haulers[1]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_WOOD] += (world->stockpile.num_jobs[2] >= world->stockpile.num_haulers[2]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ITEM] += (world->stockpile.num_jobs[3] >= world->stockpile.num_haulers[3]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_BODY] += (world->stockpile.num_jobs[5] >= world->stockpile.num_haulers[5]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FOOD] += (world->stockpile.num_jobs[6] >= world->stockpile.num_haulers[6]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_REFUSE] += (world->stockpile.num_jobs[7] >= world->stockpile.num_haulers[7]) ? 1 : 0; labor_needed[df::unit_labor::HAUL_FURNITURE] += (world->stockpile.num_jobs[8] >= world->stockpile.num_haulers[8]) ? 1 : 0; - labor_needed[df::unit_labor::HAUL_ANIMALS] += (world->stockpile.num_jobs[9] >= world->stockpile.num_haulers[9]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ANIMALS] += (world->stockpile.num_jobs[9] >= world->stockpile.num_haulers[9]) ? 1 : 0; int binjobs = world->stockpile.num_jobs[4] + ((world->stockpile.num_jobs[4] >= world->stockpile.num_haulers[4]) ? 1 : 0); - labor_needed[df::unit_labor::HAUL_ITEM] += binjobs; - labor_needed[df::unit_labor::HAUL_FOOD] += priority_food; + labor_needed[df::unit_labor::HAUL_ITEM] += binjobs; + labor_needed[df::unit_labor::HAUL_FOOD] += priority_food; // add entries for vehicle hauling @@ -2488,7 +1623,7 @@ public: labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); if (print_debug && before != labor_needed[l]) - out.print ("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); + out.print("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); } @@ -2498,7 +1633,7 @@ public: priority_food = 1; if (print_debug) - out.print ("priority food count = %d\n", priority_food); + out.print("priority food count = %d\n", priority_food); while (!available_dwarfs.empty() && priority_food > 0) { @@ -2529,7 +1664,7 @@ public: if (l == df::unit_labor::NONE) continue; - set_labor (*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); + set_labor(*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); } available_dwarfs.erase(bestdwarf); @@ -2544,7 +1679,7 @@ public: { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d, busy = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + out.print("labor_needed [%s] = %d, busy = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, labor_infos[i->first].busy_dwarfs, labor_outside[i->first], labor_infos[i->first].idle_dwarfs); } } @@ -2565,7 +1700,7 @@ public: int priority = labor_infos[l].priority(); - priority += labor_infos[l].time_since_last_assigned()/12; + priority += labor_infos[l].time_since_last_assigned() / 12; priority -= labor_infos[l].busy_dwarfs; base_priority[l] = priority; @@ -2583,7 +1718,7 @@ public: to_assign.clear(); - int av = available_dwarfs.size(); + size_t av = available_dwarfs.size(); while (!pq.empty() && av > 0) { @@ -2598,12 +1733,12 @@ public: if (--labor_needed[labor] > 0) { - priority-=10; + priority -= 10; pq2.push(make_pair(priority, labor)); } if (pq.empty()) - while(!pq2.empty()) + while (!pq2.empty()) { pq.push(pq2.top()); pq2.pop(); @@ -2665,7 +1800,7 @@ public: tools_enum t = default_labor_infos[l].tool; - if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) + if (l == best_labor && (t == TOOL_NONE || tool_in_use[t] < tool_count[t])) { set_labor(*bestdwarf, l, true); if (t != TOOL_NONE && !((*bestdwarf)->has_tool[t])) @@ -2707,9 +1842,9 @@ public: for (auto d = busy_dwarfs.begin(); d != busy_dwarfs.end(); d++) { - int current_score = score_labor (*d, (*d)->using_labor); + int current_score = score_labor(*d, (*d)->using_labor); - FOR_ENUM_ITEMS (unit_labor, l) + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; @@ -2718,7 +1853,7 @@ public: if (labor_needed[l] <= 0) continue; - int score = score_labor (*d, l); + int score = score_labor(*d, l); if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) score += 1000000; @@ -2751,7 +1886,7 @@ public: if (canary_dwarf) { - FOR_ENUM_ITEMS (unit_labor, l) + FOR_ENUM_ITEMS(unit_labor, l) { if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && canary & (1 << l)) @@ -2763,22 +1898,22 @@ public: set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); if (print_debug) - out.print ("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); + out.print("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); } else { if (print_debug) - out.print ("No dwarf available to set as the hauling canary!\n"); + out.print("No dwarf available to set as the hauling canary!\n"); } /* Assign any leftover dwarfs to "standard" labors */ if (print_debug) - out.print ("After assignment, %d dwarfs left over\n", available_dwarfs.size()); + out.print("After assignment, %d dwarfs left over\n", available_dwarfs.size()); for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) { - FOR_ENUM_ITEMS (unit_labor, l) + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; @@ -2789,7 +1924,7 @@ public: l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER || l == df::unit_labor::HAUL_TRADE); - } + } } /* check for dwarfs assigned no labors and assign them the bucket list if there are */ @@ -2799,7 +1934,7 @@ public: continue; bool any = false; - FOR_ENUM_ITEMS (unit_labor, l) + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; @@ -2810,11 +1945,11 @@ public: } } - set_labor (*d, df::unit_labor::PULL_LEVER, true); + set_labor(*d, df::unit_labor::PULL_LEVER, true); if (any) continue; - FOR_ENUM_ITEMS (unit_labor, l) + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; @@ -2835,7 +1970,7 @@ public: if ((*d)->dwarf->military.pickup_flags.bits.update) continue; - FOR_ENUM_ITEMS (unit_labor, l) + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) continue; @@ -2903,18 +2038,18 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan return CR_OK; } -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +DFhackCExport command_result plugin_onupdate(color_ostream &out) { static int step_count = 0; // check run conditions - if(!initialized || !world || !world->map.block_index || !enable_labormanager) + if (!initialized || !world || !world->map.block_index || !enable_labormanager) { // give up if we shouldn't be running' return CR_OK; } -// if (++step_count < 60) -// return CR_OK; + // if (++step_count < 60) + // return CR_OK; if (*df::global::process_jobs) return CR_OK; @@ -2928,7 +2063,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } -void print_labor (df::unit_labor labor, color_ostream &out) +void print_labor(df::unit_labor labor, color_ostream &out) { string labor_name = ENUM_KEY_STR(unit_labor, labor); out << labor_name << ": "; @@ -2942,7 +2077,7 @@ void print_labor (df::unit_labor labor, color_ostream &out) << endl; } -df::unit_labor lookup_labor_by_name (std::string& name) +df::unit_labor lookup_labor_by_name(std::string& name) { df::unit_labor labor = df::unit_labor::NONE; @@ -2955,7 +2090,7 @@ df::unit_labor lookup_labor_by_name (std::string& name) return labor; } -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isWorldLoaded()) { out.printerr("World is not loaded: please load a fort first.\n"); @@ -2966,7 +2101,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) { enable_plugin(out); } - else if(!enable && enable_labormanager) + else if (!enable && enable_labormanager) { enable_labormanager = false; setOptionEnabled(CF_ENABLED, false); @@ -2977,7 +2112,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) return CR_OK; } -command_result labormanager (color_ostream &out, std::vector & parameters) +command_result labormanager(color_ostream &out, std::vector & parameters) { CoreSuspender suspend; @@ -3014,7 +2149,7 @@ command_result labormanager (color_ostream &out, std::vector & par if (parameters[2] == "none") v = 0; else - v = atoi (parameters[2].c_str()); + v = atoi(parameters[2].c_str()); if (parameters[0] == "max") labor_infos[labor].set_maximum_dwarfs(v); diff --git a/plugins/labormanager.h b/plugins/labormanager.h new file mode 100644 index 000000000..c958900b1 --- /dev/null +++ b/plugins/labormanager.h @@ -0,0 +1,4 @@ +#pragma once + +void debug(const char* fmt, ...); +void debug_pause(); diff --git a/plugins/labormanager_joblabormapper.cpp b/plugins/labormanager_joblabormapper.cpp new file mode 100644 index 000000000..c3e21c962 --- /dev/null +++ b/plugins/labormanager_joblabormapper.cpp @@ -0,0 +1,919 @@ +/*a + * This file contains the logic to attempt to intuit the labor required for + * a given job. This is way more complicated than it should be, but I have + * not figured out how to make it simpler. + * + * Usage: + * Instantiate an instance of the JobLaborMapper class + * Call the find_job_labor method of that class instance, + * passing the job as the only argument, to determine the labor for that job + * When done, destroy the instance + * + * The class should allow you to create multiple instances, although there is + * little benefit to doing so. jlfuncs are not reused across instances. + * + */ + + +#include "DataDefs.h" +#include "MiscUtils.h" +#include "modules/Materials.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +using namespace std; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using df::global::ui; +using df::global::world; + +#include "labormanager.h" +#include "labormanager_joblabormapper.h" + +static df::unit_labor hauling_labor_map[] = +{ + df::unit_labor::HAUL_ITEM, /* BAR */ + df::unit_labor::HAUL_STONE, /* SMALLGEM */ + df::unit_labor::HAUL_ITEM, /* BLOCKS */ + df::unit_labor::HAUL_STONE, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* BOULDER */ + df::unit_labor::HAUL_WOOD, /* WOOD */ + df::unit_labor::HAUL_FURNITURE, /* DOOR */ + df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ + df::unit_labor::HAUL_FURNITURE, /* BED */ + df::unit_labor::HAUL_FURNITURE, /* CHAIR */ + df::unit_labor::HAUL_ITEM, /* CHAIN */ + df::unit_labor::HAUL_ITEM, /* FLASK */ + df::unit_labor::HAUL_ITEM, /* GOBLET */ + df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ + df::unit_labor::HAUL_ITEM, /* TOY */ + df::unit_labor::HAUL_FURNITURE, /* WINDOW */ + df::unit_labor::HAUL_ANIMALS, /* CAGE */ + df::unit_labor::HAUL_ITEM, /* BARREL */ + df::unit_labor::HAUL_ITEM, /* BUCKET */ + df::unit_labor::HAUL_ANIMALS, /* ANIMALTRAP */ + df::unit_labor::HAUL_FURNITURE, /* TABLE */ + df::unit_labor::HAUL_FURNITURE, /* COFFIN */ + df::unit_labor::HAUL_FURNITURE, /* STATUE */ + df::unit_labor::HAUL_REFUSE, /* CORPSE */ + df::unit_labor::HAUL_ITEM, /* WEAPON */ + df::unit_labor::HAUL_ITEM, /* ARMOR */ + df::unit_labor::HAUL_ITEM, /* SHOES */ + df::unit_labor::HAUL_ITEM, /* SHIELD */ + df::unit_labor::HAUL_ITEM, /* HELM */ + df::unit_labor::HAUL_ITEM, /* GLOVES */ + df::unit_labor::HAUL_FURNITURE, /* BOX */ + df::unit_labor::HAUL_ITEM, /* BIN */ + df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ + df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ + df::unit_labor::HAUL_FURNITURE, /* CABINET */ + df::unit_labor::HAUL_ITEM, /* FIGURINE */ + df::unit_labor::HAUL_ITEM, /* AMULET */ + df::unit_labor::HAUL_ITEM, /* SCEPTER */ + df::unit_labor::HAUL_ITEM, /* AMMO */ + df::unit_labor::HAUL_ITEM, /* CROWN */ + df::unit_labor::HAUL_ITEM, /* RING */ + df::unit_labor::HAUL_ITEM, /* EARRING */ + df::unit_labor::HAUL_ITEM, /* BRACELET */ + df::unit_labor::HAUL_ITEM, /* GEM */ + df::unit_labor::HAUL_FURNITURE, /* ANVIL */ + df::unit_labor::HAUL_REFUSE, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* REMAINS */ + df::unit_labor::HAUL_FOOD, /* MEAT */ + df::unit_labor::HAUL_FOOD, /* FISH */ + df::unit_labor::HAUL_FOOD, /* FISH_RAW */ + df::unit_labor::HAUL_REFUSE, /* VERMIN */ + df::unit_labor::HAUL_ITEM, /* PET */ + df::unit_labor::HAUL_ITEM, /* SEEDS */ + df::unit_labor::HAUL_FOOD, /* PLANT */ + df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ + df::unit_labor::HAUL_FOOD, /* LEAVES */ + df::unit_labor::HAUL_ITEM, /* THREAD */ + df::unit_labor::HAUL_ITEM, /* CLOTH */ + df::unit_labor::HAUL_ITEM, /* TOTEM */ + df::unit_labor::HAUL_ITEM, /* PANTS */ + df::unit_labor::HAUL_ITEM, /* BACKPACK */ + df::unit_labor::HAUL_ITEM, /* QUIVER */ + df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ + df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ + df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ + df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ + df::unit_labor::HAUL_FOOD, /* DRINK */ + df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ + df::unit_labor::HAUL_FOOD, /* CHEESE */ + df::unit_labor::HAUL_FOOD, /* FOOD */ + df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ + df::unit_labor::HAUL_ITEM, /* COIN */ + df::unit_labor::HAUL_FOOD, /* GLOB */ + df::unit_labor::HAUL_STONE, /* ROCK */ + df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ + df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ + df::unit_labor::HAUL_FURNITURE, /* GRATE */ + df::unit_labor::HAUL_FURNITURE, /* QUERN */ + df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ + df::unit_labor::HAUL_ITEM, /* SPLINT */ + df::unit_labor::HAUL_ITEM, /* CRUTCH */ + df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ + df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ + df::unit_labor::HAUL_ITEM, /* TOOL */ + df::unit_labor::HAUL_FURNITURE, /* SLAB */ + df::unit_labor::HAUL_FOOD, /* EGG */ + df::unit_labor::HAUL_ITEM, /* BOOK */ +}; + +static df::unit_labor workshop_build_labor[] = +{ + /* Carpenters */ df::unit_labor::CARPENTER, + /* Farmers */ df::unit_labor::PROCESS_PLANT, + /* Masons */ df::unit_labor::MASON, + /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, + /* Jewelers */ df::unit_labor::CUT_GEM, + /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, + /* MagmaForge */ df::unit_labor::METAL_CRAFT, + /* Bowyers */ df::unit_labor::BOWYER, + /* Mechanics */ df::unit_labor::MECHANIC, + /* Siege */ df::unit_labor::SIEGECRAFT, + /* Butchers */ df::unit_labor::BUTCHER, + /* Leatherworks */ df::unit_labor::LEATHER, + /* Tanners */ df::unit_labor::TANNER, + /* Clothiers */ df::unit_labor::CLOTHESMAKER, + /* Fishery */ df::unit_labor::CLEAN_FISH, + /* Still */ df::unit_labor::BREWER, + /* Loom */ df::unit_labor::WEAVER, + /* Quern */ df::unit_labor::MILLER, + /* Kennels */ df::unit_labor::ANIMALTRAIN, + /* Kitchen */ df::unit_labor::COOK, + /* Ashery */ df::unit_labor::LYE_MAKING, + /* Dyers */ df::unit_labor::DYER, + /* Millstone */ df::unit_labor::MILLER, + /* Custom */ df::unit_labor::NONE, + /* Tool */ df::unit_labor::NONE +}; + +static df::building* get_building_from_job(df::job* j) +{ + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; +} + +static df::unit_labor construction_build_labor(df::building_actual* b) +{ + if (b->getType() == df::building_type::RoadPaved) + return df::unit_labor::BUILD_ROAD; + // Find last item in building with use mode appropriate to the building's constructions state + // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block + // For wells 0 mechanism, 1 rope, 2 bucket, 3 block + // Trade depots and bridges use the last one too + // Must check use mode b/c buildings may have items in them that are not part of the building + + df::item* i = 0; + for (auto p = b->contained_items.begin(); p != b->contained_items.end(); p++) + if (b->construction_stage > 0 && (*p)->use_mode == 2 || + b->construction_stage == 0 && (*p)->use_mode == 0) + i = (*p)->item; + + MaterialInfo matinfo; + if (i && matinfo.decode(i)) + { + if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) + return df::unit_labor::METAL_CRAFT; + if (matinfo.material->flags.is_set(df::material_flags::WOOD)) + return df::unit_labor::CARPENTER; + } + return df::unit_labor::MASON; +} + + +class jlfunc +{ +public: + virtual df::unit_labor get_labor(df::job* j) = 0; +}; + +class jlfunc_const : public jlfunc +{ +private: + df::unit_labor labor; +public: + df::unit_labor get_labor(df::job* j) + { + return labor; + } + jlfunc_const(df::unit_labor l) : labor(l) {}; +}; + +class jlfunc_hauling : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::item* item = 0; + if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) + return (df::unit_labor) j->item_subtype; + + for (auto i = j->items.begin(); i != j->items.end(); i++) + { + if ((*i)->role == 7) + { + item = (*i)->item; + break; + } + } + + if (item && item->flags.bits.container) + { + for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) + { + if ((*a)->getType() == df::general_ref_type::CONTAINS_ITEM) + { + int item_id = ((df::general_ref_contains_itemst *) (*a))->item_id; + item = binsearch_in_vector(world->items.all, item_id); + break; + } + } + } + + df::unit_labor l = item ? hauling_labor_map[item->getType()] : df::unit_labor::HAUL_ITEM; + if (item && l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) + l = df::unit_labor::HAUL_BODY; + return l; + } + jlfunc_hauling() {}; +}; + +class jlfunc_construct_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + if (j->flags.bits.item_lost) + return df::unit_labor::NONE; + + df::building* bld = get_building_from_job(j); + switch (bld->getType()) + { + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; + case df::building_type::Workshop: + { + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->design && !ws->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } + break; + case df::building_type::Construction: + return df::unit_labor::BUILD_CONSTRUCTION; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Bridge: + case df::building_type::ArcheryTarget: + case df::building_type::WaterWheel: + case df::building_type::RoadPaved: + case df::building_type::Well: + case df::building_type::ScrewPump: + case df::building_type::Wagon: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::Windmill: + { + df::building_actual* b = (df::building_actual*) bld; + if (b->design && !b->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + return construction_build_labor(b); + } + break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: + case df::building_type::Cage: + case df::building_type::NestBox: + case df::building_type::TractionBench: + case df::building_type::Slab: + case df::building_type::Chain: + case df::building_type::GrateFloor: + case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateWall: + case df::building_type::Bookcase: + case df::building_type::Instrument: + return df::unit_labor::HAUL_FURNITURE; + case df::building_type::Trap: + case df::building_type::GearAssembly: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: + case df::building_type::Rollers: + return df::unit_labor::MECHANIC; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; + case df::building_type::RoadDirt: + return df::unit_labor::BUILD_ROAD; + } + + debug("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); + + return df::unit_labor::NONE; + } + jlfunc_construct_bld() {} +}; + +class jlfunc_destroy_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + df::building_type type = bld->getType(); + + switch (bld->getType()) + { + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; + case df::building_type::Workshop: + { + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } + break; + case df::building_type::Construction: + return df::unit_labor::REMOVE_CONSTRUCTION; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Wagon: + case df::building_type::Bridge: + case df::building_type::ScrewPump: + case df::building_type::ArcheryTarget: + case df::building_type::RoadPaved: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::WaterWheel: + case df::building_type::Well: + case df::building_type::Windmill: + { + auto b = (df::building_actual*) bld; + return construction_build_labor(b); + } + break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; + case df::building_type::Trap: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: + case df::building_type::GearAssembly: + case df::building_type::Rollers: + return df::unit_labor::MECHANIC; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: + case df::building_type::Cage: + case df::building_type::NestBox: + case df::building_type::TractionBench: + case df::building_type::Slab: + case df::building_type::Chain: + case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateFloor: + case df::building_type::GrateWall: + case df::building_type::Bookcase: + case df::building_type::Instrument: + return df::unit_labor::HAUL_FURNITURE; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::RoadDirt: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; + } + + debug("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); + + return df::unit_labor::NONE; + } + jlfunc_destroy_bld() {} +}; + +class jlfunc_make : public jlfunc +{ +private: + df::unit_labor metaltype; +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + if (bld->getType() == df::building_type::Workshop) + { + df::workshop_type type = ((df::building_workshopst*)(bld))->type; + switch (type) + { + case df::workshop_type::Craftsdwarfs: + { + df::item_type jobitem = j->job_items[0]->item_type; + switch (jobitem) + { + case df::item_type::BOULDER: + return df::unit_labor::STONE_CRAFT; + case df::item_type::NONE: + if (j->material_category.bits.bone || + j->material_category.bits.horn || + j->material_category.bits.tooth || + j->material_category.bits.shell) + return df::unit_labor::BONE_CARVE; + else + { + debug("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); + debug_pause(); + return df::unit_labor::NONE; + } + case df::item_type::WOOD: + return df::unit_labor::WOOD_CRAFT; + case df::item_type::CLOTH: + return df::unit_labor::CLOTHESMAKER; + case df::item_type::SKIN_TANNED: + return df::unit_labor::LEATHER; + default: + debug("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", + ENUM_KEY_STR(item_type, jobitem).c_str()); + debug_pause(); + return df::unit_labor::NONE; + } + } + case df::workshop_type::Masons: + return df::unit_labor::MASON; + case df::workshop_type::Carpenters: + return df::unit_labor::CARPENTER; + case df::workshop_type::Leatherworks: + return df::unit_labor::LEATHER; + case df::workshop_type::Clothiers: + return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::Bowyers: + return df::unit_labor::BOWYER; + case df::workshop_type::MagmaForge: + case df::workshop_type::MetalsmithsForge: + return metaltype; + default: + debug("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", + ENUM_KEY_STR(workshop_type, type).c_str()); + debug_pause(); + return df::unit_labor::NONE; + } + } + else if (bld->getType() == df::building_type::Furnace) + { + df::furnace_type type = ((df::building_furnacest*)(bld))->type; + switch (type) + { + case df::furnace_type::MagmaGlassFurnace: + case df::furnace_type::GlassFurnace: + return df::unit_labor::GLASSMAKER; + default: + debug("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", + ENUM_KEY_STR(furnace_type, type).c_str()); + debug_pause(); + return df::unit_labor::NONE; + } + } + + debug("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); + + return df::unit_labor::NONE; + } + + jlfunc_make(df::unit_labor mt) : metaltype(mt) {} +}; + +class jlfunc_custom : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); + return labor; + } + } + return df::unit_labor::NONE; + } + jlfunc_custom() {} +}; + +jlfunc* JobLaborMapper::jlf_const(df::unit_labor l) { + jlfunc* jlf; + if (jlf_cache.count(l) == 0) + { + jlf = new jlfunc_const(l); + jlf_cache[l] = jlf; + } + else + jlf = jlf_cache[l]; + + return jlf; +} + +JobLaborMapper::~JobLaborMapper() +{ + std::set log; + + for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) + { + if (!log.count(i->second)) + { + log.insert(i->second); + delete i->second; + } + i->second = 0; + } + + FOR_ENUM_ITEMS(job_type, j) + { + if (j < 0) + continue; + + jlfunc* p = job_to_labor_table[j]; + if (!log.count(p)) + { + log.insert(p); + delete p; + } + job_to_labor_table[j] = 0; + } +} + +JobLaborMapper::JobLaborMapper() +{ + jlfunc* jlf_hauling = new jlfunc_hauling(); + jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); + jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); + jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); + jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + + jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); + + job_to_labor_table[df::job_type::CarveFortification] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailWall] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailFloor] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::Dig] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveDownwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpDownStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveRamp] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); + job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); + job_to_labor_table[df::job_type::RemoveConstruction] = jlf_const(df::unit_labor::REMOVE_CONSTRUCTION); + job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); + job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; + job_to_labor_table[df::job_type::Eat] = jlf_no_labor; + job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink2] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; + job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::HAUL_ITEM); + job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; + job_to_labor_table[df::job_type::Kidnap] = jlf_no_labor; + job_to_labor_table[df::job_type::BeatCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::StartingFistFight] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectTaxes] = jlf_no_labor; + job_to_labor_table[df::job_type::GuardTaxCollector] = jlf_no_labor; + job_to_labor_table[df::job_type::CatchLiveLandAnimal] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::CatchLiveFish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::ReturnKill] = jlf_no_labor; + job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; + job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; + job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; + job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; + job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; + job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; + job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; + job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping2] = jlf_no_labor; + job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); + job_to_labor_table[df::job_type::Rest] = jlf_no_labor; + job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; + job_to_labor_table[df::job_type::DumpItem] = jlf_const(df::unit_labor::HAUL_REFUSE); + job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMagmaForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBrooding] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodFell] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodCarpenter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMason] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBowyer] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodTanner] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodWeaver] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodGlassmaker] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMechanics] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructBuilding] = new jlfunc_construct_bld(); + job_to_labor_table[df::job_type::ConstructDoor] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructFloodgate] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBed] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructThrone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCoffin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructTable] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructChest] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructArmorStand] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructWeaponRack] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCabinet] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); + job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::EncrustWithGlass] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::DestroyBuilding] = new jlfunc_destroy_bld(); + job_to_labor_table[df::job_type::SmeltOre] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::MeltMetalObject] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::ExtractMetalStrands] = jlf_const(df::unit_labor::EXTRACT_STRAND); + job_to_labor_table[df::job_type::PlantSeeds] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::HarvestPlants] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::TrainHuntingAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::TrainWarAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::MakeWeapon] = jlf_make_weapon; + job_to_labor_table[df::job_type::ForgeAnvil] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCatapultParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::ConstructBallistaParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::MakeArmor] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeHelm] = jlf_make_armor; + job_to_labor_table[df::job_type::MakePants] = jlf_make_armor; + job_to_labor_table[df::job_type::StudWith] = jlf_make_object; + job_to_labor_table[df::job_type::ButcherAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::PrepareRawFish] = jlf_const(df::unit_labor::CLEAN_FISH); + job_to_labor_table[df::job_type::MillPlants] = jlf_const(df::unit_labor::MILLER); + job_to_labor_table[df::job_type::BaitTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); + job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); + job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); + job_to_labor_table[df::job_type::WeaveCloth] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::MakeGloves] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShoes] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShield] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeCage] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeChain] = jlf_make_object; + job_to_labor_table[df::job_type::MakeFlask] = jlf_make_object; + job_to_labor_table[df::job_type::MakeGoblet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeToy] = jlf_make_object; + job_to_labor_table[df::job_type::MakeAnimalTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MakeBarrel] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeBucket] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeWindow] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeTotem] = jlf_const(df::unit_labor::BONE_CARVE); + job_to_labor_table[df::job_type::MakeAmmo] = jlf_make_weapon; + job_to_labor_table[df::job_type::DecorateWith] = jlf_make_object; + job_to_labor_table[df::job_type::MakeBackpack] = jlf_make_object; + job_to_labor_table[df::job_type::MakeQuiver] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeBallistaArrowHead] = jlf_make_weapon; + job_to_labor_table[df::job_type::AssembleSiegeAmmo] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::LoadCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::LoadBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_make_weapon; + job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; + job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::PullLever] = jlf_const(df::unit_labor::PULL_LEVER); + job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST); + job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH); + job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN); + job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::ChainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); + job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE); + job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS); + job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::DRESSING_WOUNDS); + job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY); + job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); + job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::DrainAquarium] = jlf_no_labor; + job_to_labor_table[df::job_type::FillAquarium] = jlf_no_labor; + job_to_labor_table[df::job_type::FillPond] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; + job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeAsh] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeLye] = jlf_const(df::unit_labor::LYE_MAKING); + job_to_labor_table[df::job_type::MakePotashFromLye] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::FertilizeField] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::MakePotashFromAsh] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::DyeThread] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::DyeCloth] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::SewImage] = jlf_make_object; + job_to_labor_table[df::job_type::MakePipeSection] = jlf_make_furniture; + job_to_labor_table[df::job_type::OperatePump] = jlf_const(df::unit_labor::OPERATE_PUMP); + job_to_labor_table[df::job_type::ManageWorkOrders] = jlf_no_labor; + job_to_labor_table[df::job_type::UpdateStockpileRecords] = jlf_no_labor; + job_to_labor_table[df::job_type::TradeAtDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructHatchCover] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; + job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; + job_to_labor_table[df::job_type::BringCrutch] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); + job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; + job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); + job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); + job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::MakeTool] = jlf_make_object; + job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); + job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CauseTrouble] = jlf_no_labor; + job_to_labor_table[df::job_type::DrinkBlood] = jlf_no_labor; + job_to_labor_table[df::job_type::ReportCrime] = jlf_no_labor; + job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_hauling; + job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); + job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; + job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeScepter] = jlf_make_object; + job_to_labor_table[df::job_type::MakeCrown] = jlf_make_object; + job_to_labor_table[df::job_type::MakeRing] = jlf_make_object; + job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; + job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; + + job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation +}; + +df::unit_labor JobLaborMapper::find_job_labor(df::job* j) +{ + if (j->job_type == df::job_type::CustomReaction) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + return ENUM_ATTR(job_skill, labor, skill); + } + } + return df::unit_labor::NONE; + } + + + df::unit_labor labor; + if (job_to_labor_table.count(j->job_type) == 0) + { + debug("LABORMANAGER: job has no job to labor table entry: %s (%d)\n", ENUM_KEY_STR(job_type, j->job_type).c_str(), j->job_type); + debug_pause(); + labor = df::unit_labor::NONE; + } + else { + + labor = job_to_labor_table[j->job_type]->get_labor(j); + } + + return labor; +} + +/* End of labor deducer */ \ No newline at end of file diff --git a/plugins/labormanager_joblabormapper.h b/plugins/labormanager_joblabormapper.h new file mode 100644 index 000000000..5bc14d72a --- /dev/null +++ b/plugins/labormanager_joblabormapper.h @@ -0,0 +1,21 @@ +#pragma once + + +class jlfunc; + +class JobLaborMapper { + +private: + std::map job_to_labor_table; + std::map jlf_cache; + + jlfunc* JobLaborMapper::jlf_const(df::unit_labor l); + +public: + ~JobLaborMapper(); + JobLaborMapper(); + + df::unit_labor find_job_labor(df::job* j); + + +}; From fd19935fe95d5150168d1db48292d01961291da1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 22 Jul 2017 04:54:05 -0500 Subject: [PATCH 0596/1012] labormanager: remove stupid --- plugins/labormanager_joblabormapper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labormanager_joblabormapper.h b/plugins/labormanager_joblabormapper.h index 5bc14d72a..adde5c340 100644 --- a/plugins/labormanager_joblabormapper.h +++ b/plugins/labormanager_joblabormapper.h @@ -9,7 +9,7 @@ private: std::map job_to_labor_table; std::map jlf_cache; - jlfunc* JobLaborMapper::jlf_const(df::unit_labor l); + jlfunc* jlf_const(df::unit_labor l); public: ~JobLaborMapper(); From 92a962a9c028b772090973a4e8e05b0307745339 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 25 Jul 2017 08:31:02 +0530 Subject: [PATCH 0597/1012] Send grass levels over RemoteFortressReader --- plugins/proto/RemoteFortressReader.proto | 1 + .../remotefortressreader/remotefortressreader.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index a1f8e1bdb..f2218dfb6 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -256,6 +256,7 @@ message MapBlock repeated Item items = 26; repeated bool tile_dig_designation_marker = 27; repeated bool tile_dig_designation_auto = 28; + repeated int32 grass_percent = 29; } message MatPair { diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 9b19edcc6..57f08d713 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -34,6 +34,7 @@ #include "df/army.h" #include "df/army_flags.h" #include "df/block_square_event_item_spatterst.h" +#include "df/block_square_event_grassst.h" #endif #include "df/block_square_event_material_spatterst.h" #include "df/body_appearance_modifier.h" @@ -1224,7 +1225,8 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB std::vector materials; #if DF_VERSION_INT > 34011 std::vector items; - if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + std::vector grasses; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, &grasses, NULL, NULL, &items)) return; #else if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) @@ -1257,6 +1259,14 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB send_item->set_mat_type(item->item_type); send_item->set_mat_index(item->item_subtype); } + int grassPercent = 0; + for (int i = 0; i < grasses.size(); i++) + { + auto grass = grasses[i]; + if (grass->amount[xx][yy] > grassPercent) + grassPercent = grass->amount[xx][yy]; + } + NetBlock->add_grass_percent(grassPercent); #endif } } From 8ae7a1235df2f9fd7e7a084f7266892376ad765a Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 27 Jul 2017 18:41:46 +0530 Subject: [PATCH 0598/1012] Add rider ID to creatures --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/remotefortressreader.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index f2218dfb6..8e9fd4a58 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -336,6 +336,7 @@ message UnitDefinition optional UnitAppearance appearance = 16; optional int32 profession_id = 17; repeated string noble_positions = 18; + optional int32 rider_id = 19; } message UnitList diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 57f08d713..7fe879645 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -105,6 +105,8 @@ #include "df/plant_tree_tile.h" #endif +#include "df/unit_relationship_type.h" + #include "building_reader.h" using namespace DFHack; @@ -1574,6 +1576,8 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_unit->add_noble_positions(noble_positon.position->code); } } + + send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); } return CR_OK; } From a7d21fd62756fc28487041b88dfc384b2a52cf6d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 28 Jul 2017 02:28:16 -0500 Subject: [PATCH 0599/1012] move labormanager into a subdirectory --- plugins/CMakeLists.txt | 2 +- plugins/labormanager/CMakeLists.txt | 34 +++++++++++++++++++ plugins/{ => labormanager}/labormanager.cpp | 0 plugins/{ => labormanager}/labormanager.h | 0 .../labormanager_joblabormapper.cpp | 0 .../labormanager_joblabormapper.h | 0 6 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 plugins/labormanager/CMakeLists.txt rename plugins/{ => labormanager}/labormanager.cpp (100%) rename plugins/{ => labormanager}/labormanager.h (100%) rename plugins/{ => labormanager}/labormanager_joblabormapper.cpp (100%) rename plugins/{ => labormanager}/labormanager_joblabormapper.h (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b09651678..6981acb89 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -128,7 +128,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) - DFHACK_PLUGIN(labormanager labormanager.cpp labormanager.h labormanager_joblabormapper.cpp labormanager_joblabormapper.h) + add_subdirectory(labormanager) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) diff --git a/plugins/labormanager/CMakeLists.txt b/plugins/labormanager/CMakeLists.txt new file mode 100644 index 000000000..e5abb8c39 --- /dev/null +++ b/plugins/labormanager/CMakeLists.txt @@ -0,0 +1,34 @@ +PROJECT (labormanager) +# A list of source files +SET(PROJECT_SRCS + labormanager.cpp + labormanager_joblabormapper.cpp +) +# A list of headers +SET(PROJECT_HDRS + labormanager.h + labormanager_joblabormapper.h +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack + +DFHACK_PLUGIN(labormanager ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/labormanager.cpp b/plugins/labormanager/labormanager.cpp similarity index 100% rename from plugins/labormanager.cpp rename to plugins/labormanager/labormanager.cpp diff --git a/plugins/labormanager.h b/plugins/labormanager/labormanager.h similarity index 100% rename from plugins/labormanager.h rename to plugins/labormanager/labormanager.h diff --git a/plugins/labormanager_joblabormapper.cpp b/plugins/labormanager/labormanager_joblabormapper.cpp similarity index 100% rename from plugins/labormanager_joblabormapper.cpp rename to plugins/labormanager/labormanager_joblabormapper.cpp diff --git a/plugins/labormanager_joblabormapper.h b/plugins/labormanager/labormanager_joblabormapper.h similarity index 100% rename from plugins/labormanager_joblabormapper.h rename to plugins/labormanager/labormanager_joblabormapper.h From d810faa4a32b54befb2e1c0f96f8da94ac301c7d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 28 Jul 2017 02:43:32 -0500 Subject: [PATCH 0600/1012] whitespace --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6981acb89..005f9842c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -128,7 +128,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) - add_subdirectory(labormanager) + add_subdirectory(labormanager) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) From c5c80d5dc0a9c258202424df0df17f81addf367a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 28 Jul 2017 23:03:23 -0500 Subject: [PATCH 0601/1012] Rename labormanager_joblabormapper to joblabormapper --- plugins/labormanager/CMakeLists.txt | 4 ++-- ...rmanager_joblabormapper.cpp => joblabormapper.cpp} | 3 ++- ...labormanager_joblabormapper.h => joblabormapper.h} | 11 +++++++++++ plugins/labormanager/labormanager.cpp | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) rename plugins/labormanager/{labormanager_joblabormapper.cpp => joblabormapper.cpp} (99%) rename plugins/labormanager/{labormanager_joblabormapper.h => joblabormapper.h} (66%) diff --git a/plugins/labormanager/CMakeLists.txt b/plugins/labormanager/CMakeLists.txt index e5abb8c39..8e9e28b81 100644 --- a/plugins/labormanager/CMakeLists.txt +++ b/plugins/labormanager/CMakeLists.txt @@ -2,12 +2,12 @@ PROJECT (labormanager) # A list of source files SET(PROJECT_SRCS labormanager.cpp - labormanager_joblabormapper.cpp + joblabormapper.cpp ) # A list of headers SET(PROJECT_HDRS labormanager.h - labormanager_joblabormapper.h + joblabormapper.h ) SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/plugins/labormanager/labormanager_joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp similarity index 99% rename from plugins/labormanager/labormanager_joblabormapper.cpp rename to plugins/labormanager/joblabormapper.cpp index c3e21c962..051331137 100644 --- a/plugins/labormanager/labormanager_joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -60,7 +60,7 @@ using df::global::ui; using df::global::world; #include "labormanager.h" -#include "labormanager_joblabormapper.h" +#include "joblabormapper.h" static df::unit_labor hauling_labor_map[] = { @@ -635,6 +635,7 @@ JobLaborMapper::~JobLaborMapper() } job_to_labor_table[j] = 0; } + } JobLaborMapper::JobLaborMapper() diff --git a/plugins/labormanager/labormanager_joblabormapper.h b/plugins/labormanager/joblabormapper.h similarity index 66% rename from plugins/labormanager/labormanager_joblabormapper.h rename to plugins/labormanager/joblabormapper.h index adde5c340..37d405f54 100644 --- a/plugins/labormanager/labormanager_joblabormapper.h +++ b/plugins/labormanager/joblabormapper.h @@ -1,5 +1,16 @@ #pragma once +#include + +#include + +using namespace DFHack; +using namespace df::enums; + +#include "df/job.h" +#include "df/job_type.h" +#include "df/unit_labor.h" + class jlfunc; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index f791fe398..5fb8dc60e 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -69,7 +69,7 @@ #include #include "labormanager.h" -#include "labormanager_joblabormapper.h" +#include "joblabormapper.h" using namespace std; using std::string; From fe9454ff1c5a6d8de6191ad33a68ca0b33182175 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 29 Jul 2017 09:26:19 -0500 Subject: [PATCH 0602/1012] Fix #1103: do not idle for a meeting if the other participant is dead, asleep, resting, or on military duty --- plugins/labormanager/labormanager.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 5fb8dc60e..90f7f1e04 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1212,15 +1212,28 @@ private: { df::activity_info *act = ui->activities[i]; if (!act) continue; + bool p1 = act->unit_actor == dwarf->dwarf; bool p2 = act->unit_noble == dwarf->dwarf; if (p1 || p2) { - dwarf->clear_all = true; - if (print_debug) - out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); - break; + df::unit* other = p1 ? act->unit_noble : act->unit_actor; + if (other && !(other->flags1.bits.dead || + other->job.current_job->job_type == df::job_type::Sleep || + other->job.current_job->job_type == df::job_type::Rest || + ENUM_ATTR(profession, military, other->profession))) + { + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + else + { + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, but with someone who can't make the meeting.\n", dwarf->dwarf->name.first_name.c_str()); + } } } From 682d4b3144435fcb50f95cd5cde922da53dac920 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 29 Jul 2017 12:26:32 -0500 Subject: [PATCH 0603/1012] whitespace --- plugins/labormanager/labormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 90f7f1e04..a81021286 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1228,7 +1228,7 @@ private: if (print_debug) out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); break; - } + } else { if (print_debug) From d98fd020485c2b8c2dc2201241dfabc8742e5ca1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 29 Jul 2017 18:46:45 -0500 Subject: [PATCH 0604/1012] Weight CUTWOOD jobs by KILL_PLANT ethic; fix #1115 --- plugins/labormanager/labormanager.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 5fb8dc60e..c1d163cfb 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -67,6 +67,8 @@ #include #include #include +#include +#include #include "labormanager.h" #include "joblabormapper.h" @@ -1465,6 +1467,7 @@ private: for (int ma = 0; ma < 13; ma++) attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); } + } int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10) + attr_weight; @@ -1485,6 +1488,17 @@ private: score += 5000; } + // This should reweight assigning CUTWOOD jobs based on a citizen's ethic toward killing plants + + if (labor == df::unit_labor::CUTWOOD) + { + auto c_id = d->dwarf->cultural_identity; + auto culture = world->cultural_identities.all[c_id]; + auto ethics = culture->ethic[df::ethic_type::KILL_PLANT]; + if (ethics != df::ethic_response::NOT_APPLICABLE && ethics != df::ethic_response::REQUIRED) + score += 10000 * (df::ethic_response::ACCEPTABLE - ethics); + } + score -= Units::computeMovementSpeed(d->dwarf); return score; From 04733827a011c2e6a56bd28c8d6e9cabe5aac9e4 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 29 Jul 2017 19:54:51 -0500 Subject: [PATCH 0605/1012] Favor/disfavor RECOVER_WOUNDED based on ALTRUISM (fixes #1106) --- plugins/labormanager/labormanager.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 5fb8dc60e..a2c8a6fd7 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -67,6 +67,7 @@ #include #include #include +#include #include "labormanager.h" #include "joblabormapper.h" @@ -1485,6 +1486,17 @@ private: score += 5000; } + // Favor/disfavor RECOVER_WOUNDED based on ALTRUISM personality facet + + if (labor == df::unit_labor::RECOVER_WOUNDED) + { + int altruism = d->dwarf->status.current_soul->personality.traits[df::personality_facet_type::ALTRUISM]; + if (altruism >= 61) + score += 5000; + else if (altruism <= 24) + score -= 50000; + } + score -= Units::computeMovementSpeed(d->dwarf); return score; From b61859a55ed757af01139b427ca712cc9b8729b7 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 4 Aug 2017 11:29:48 -0500 Subject: [PATCH 0606/1012] labormanager/#1103: fix NPE in meeting test --- plugins/labormanager/labormanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index a81021286..e7b0cd5e8 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1220,10 +1220,10 @@ private: { df::unit* other = p1 ? act->unit_noble : act->unit_actor; if (other && !(other->flags1.bits.dead || - other->job.current_job->job_type == df::job_type::Sleep || - other->job.current_job->job_type == df::job_type::Rest || - ENUM_ATTR(profession, military, other->profession))) - { + (other->job.current_job && + (other->job.current_job->job_type == df::job_type::Sleep || + other->job.current_job->job_type == df::job_type::Rest)) || + ENUM_ATTR(profession, military, other->profession))) { dwarf->clear_all = true; if (print_debug) out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); From d280863bc8bd07616e675232f4f16257aa31f2f8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 Aug 2017 21:34:16 -0400 Subject: [PATCH 0607/1012] Avoid iterating over "has-bad-pointers" fields of IDTYPE_STRUCT and others dfhack/df-structures@4c224dd20567a9eda652ca9522bd0547be4bf358 --- library/LuaTypes.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 5b5a174fa..6e3c5d5cf 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1190,9 +1190,8 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo continue; case struct_field_info::POINTER: - // Skip class-typed pointers within unions - if ((fields[i].count & 2) != 0 && fields[i].type && - fields[i].type->type() == IDTYPE_CLASS) + // Skip class-typed pointers within unions and other bad pointers + if ((fields[i].count & 2) != 0 && fields[i].type) add_to_enum = false; break; From 16fb230ef61bc921add103e4c47552f6832a7e03 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 Aug 2017 21:38:18 -0400 Subject: [PATCH 0608/1012] getAnyItem: support viewscreen_textviewerst --- library/modules/Gui.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 5b56ee1ed..7de9a5e11 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -990,6 +990,18 @@ df::item *Gui::getAnyItem(df::viewscreen *top) using df::global::ui_building_item_cursor; using df::global::ui_sidebar_menus; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top)) + { + // return the main item if the parent screen is a viewscreen_itemst + if (VIRTUAL_CAST_VAR(parent_screen, df::viewscreen_itemst, screen->parent)) + return parent_screen->item; + + if (screen->parent) + return getAnyItem(screen->parent); + + return NULL; + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); From ab8fb9f44e1c2ce896dacaa967fa453258e2733b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 5 Aug 2017 21:48:41 -0500 Subject: [PATCH 0609/1012] whitespace --- plugins/labormanager/labormanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index e7b0cd5e8..cefdc90f9 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1220,8 +1220,8 @@ private: { df::unit* other = p1 ? act->unit_noble : act->unit_actor; if (other && !(other->flags1.bits.dead || - (other->job.current_job && - (other->job.current_job->job_type == df::job_type::Sleep || + (other->job.current_job && + (other->job.current_job->job_type == df::job_type::Sleep || other->job.current_job->job_type == df::job_type::Rest)) || ENUM_ATTR(profession, military, other->profession))) { dwarf->clear_all = true; From a383cc9a300e45aa9c79c6a7b46fcb0f188b92e3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 6 Aug 2017 21:01:36 -0400 Subject: [PATCH 0610/1012] Fix diggingInvaders compilation errors (#1145, GCC 4.8) --- plugins/CMakeLists.txt | 2 +- plugins/diggingInvaders/assignJob.cpp | 4 ++++ plugins/diggingInvaders/assignJob.h | 3 ++- plugins/diggingInvaders/edgeCost.cpp | 2 ++ plugins/diggingInvaders/edgeCost.h | 4 ++-- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d94dc52c6..9c3716314 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -108,7 +108,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) - # add_subdirectory(diggingInvaders) + add_subdirectory(diggingInvaders) DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index 6e3874cbf..cdd3e6fc5 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -20,6 +20,7 @@ #include "df/item_type.h" #include "df/item_weaponst.h" #include "df/job.h" +#include "df/job_list_link.h" #include "df/job_skill.h" #include "df/job_type.h" #include "df/reaction_product_itemst.h" @@ -27,8 +28,11 @@ #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/world.h" #include "df/world_site.h" +using namespace DFHack; + void getRidOfOldJob(df::unit* unit) { if ( unit->job.current_job == NULL ) { return; diff --git a/plugins/diggingInvaders/assignJob.h b/plugins/diggingInvaders/assignJob.h index 0ad6419d6..1e5750c03 100644 --- a/plugins/diggingInvaders/assignJob.h +++ b/plugins/diggingInvaders/assignJob.h @@ -2,6 +2,7 @@ #include "edgeCost.h" +#include "ColorText.h" #include "modules/MapCache.h" #include @@ -9,5 +10,5 @@ using namespace std; -int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache, DigAbilities& abilities); +int32_t assignJob(DFHack::color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache, DigAbilities& abilities); diff --git a/plugins/diggingInvaders/edgeCost.cpp b/plugins/diggingInvaders/edgeCost.cpp index c9e83fa14..b08227d94 100644 --- a/plugins/diggingInvaders/edgeCost.cpp +++ b/plugins/diggingInvaders/edgeCost.cpp @@ -19,6 +19,8 @@ #include +using namespace DFHack; + /* cost_t costWeight[] = { //Distance diff --git a/plugins/diggingInvaders/edgeCost.h b/plugins/diggingInvaders/edgeCost.h index 560fec109..ef4cb3168 100644 --- a/plugins/diggingInvaders/edgeCost.h +++ b/plugins/diggingInvaders/edgeCost.h @@ -93,6 +93,6 @@ struct PointHash { } }; -cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities); -std::vector* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities); +cost_t getEdgeCost(DFHack::color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities); +std::vector* getEdgeSet(DFHack::color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities); From 05b238c879cafcc559978ac922b43d685270ce29 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 7 Aug 2017 09:29:46 -0400 Subject: [PATCH 0611/1012] Fix another diggingInvaders compiler error Windows-specific, for some strange reason Ref #1145 --- plugins/diggingInvaders/diggingInvaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index 8be58fd32..4892c5e1d 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -199,7 +199,7 @@ public: } - int32_t operator()(df::coord p1, df::coord p2) { + int32_t operator()(df::coord p1, df::coord p2) const { if ( p1 == p2 ) return 0; auto i1 = pointCost->find(p1); auto i2 = pointCost->find(p2); From 95aa5bbb4742f2714bd9556b5cc99ae9ca5bebe3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 7 Aug 2017 15:06:00 -0400 Subject: [PATCH 0612/1012] zoom: fix MSVC warning --- plugins/devel/zoom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/devel/zoom.cpp b/plugins/devel/zoom.cpp index 3ee0566e8..d302d4900 100644 --- a/plugins/devel/zoom.cpp +++ b/plugins/devel/zoom.cpp @@ -77,5 +77,6 @@ command_result df_gzoom (color_ostream &out, std::vector & paramete Gui::setCursorCoords(x,y,z); } Gui::setViewCoords(x,y,z); + return CR_OK; } From 0b26e9aec705d7800b8259c8f3b82207fca7bcb6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 7 Aug 2017 14:13:22 -0500 Subject: [PATCH 0613/1012] Fix a couple of MSVC warnings Use intptr_t instead of long for a handle, and add an explict cast to eliminate a narrowing conversion warning --- library/Console-windows.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index c11661a30..28d98309b 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -172,7 +172,7 @@ namespace DFHack } void gotoxy(int x, int y) { - COORD coord = {x-1, y-1}; // Windows uses 0-based coordinates + COORD coord = {(SHORT)(x-1), (SHORT)(y-1)}; // Windows uses 0-based coordinates SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); } @@ -434,7 +434,7 @@ bool Console::init(bool) { d = new Private(); int hConHandle; - long lStdHandle; + intptr_t lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; DWORD oldMode, newMode; @@ -469,14 +469,14 @@ bool Console::init(bool) // redirect unbuffered STDOUT to the console d->console_out = GetStdHandle(STD_OUTPUT_HANDLE); - lStdHandle = (long)d->console_out; + lStdHandle = (intptr_t)d->console_out; hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); d->dfout_C = _fdopen( hConHandle, "w" ); setvbuf( d->dfout_C, NULL, _IONBF, 0 ); // redirect unbuffered STDIN to the console d->console_in = GetStdHandle(STD_INPUT_HANDLE); - lStdHandle = (long)d->console_in; + lStdHandle = (intptr_t)d->console_in; hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "r" ); *stdin = *fp; From 08656a3ca7d433c43e052920836e650880c18b90 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 8 Aug 2017 20:08:07 -0400 Subject: [PATCH 0614/1012] Strip DF folder from Ruby script paths Fixes #1146 (temporarily, see #1147) --- library/Core.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index fccf7f464..62d605fe6 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -388,6 +388,15 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, if (!plug_mgr->ruby || !plug_mgr->ruby->is_enabled()) return CR_FAILURE; + // ugly temporary patch for https://github.com/DFHack/dfhack/issues/1146 + string cwd = Filesystem::getcwd(); + if (filename.find(cwd) == 0) + { + filename = filename.substr(cwd.size()); + while (!filename.empty() && (filename[0] == '/' || filename[0] == '\\')) + filename = filename.substr(1); + } + std::string rbcmd = "$script_args = ["; for (size_t i = 0; i < args.size(); i++) rbcmd += "'" + args[i] + "', "; From 55d22855a02da72e8fb20526a61d3a22b972fcc0 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 11 Aug 2017 10:21:40 +0530 Subject: [PATCH 0615/1012] Add styling enums to RFR --- plugins/proto/RemoteFortressReader.proto | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 8e9fd4a58..d718b86d2 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -118,6 +118,15 @@ enum TileDigDesignation UP_STAIR_DIG = 6; } +enum HairStyle +{ + NEATLY_COMBED = 0; + BRAIDED = 1; + DOUBLE_BRAID = 2; + PONY_TAILS = 3; + CLEAN_SHAVEN = 4; +} + message Coord { optional int32 x = 1; From 6c5e25db13856d1d077fa874278ca6b60d2d5c43 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 11 Aug 2017 13:50:30 +0530 Subject: [PATCH 0616/1012] Add hair style to units in proto --- plugins/proto/RemoteFortressReader.proto | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index d718b86d2..b416b96ec 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -307,6 +307,12 @@ message MaterialList{ repeated MaterialDefinition material_list = 1; } +message Hair +{ + optional int32 length = 1; + optional HairStyle style = 2; +} + message BodySizeInfo { optional int32 size_cur = 1; @@ -323,6 +329,10 @@ message UnitAppearance repeated int32 bp_modifiers = 2; optional int32 size_modifier = 3; repeated int32 colors = 4; + optional Hair hair = 5; + optional Hair beard = 6; + optional Hair moustache = 7; + optional Hair sideburns = 8; } message UnitDefinition From 620cfcc8bd3bcfac46f8b717da1326ca47223fe6 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 12 Aug 2017 15:42:56 +0300 Subject: [PATCH 0617/1012] Fix luasocket receive with byte count Did not correctly detect when you typed in a number instead of pattern. --- plugins/lua/luasocket.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/luasocket.lua b/plugins/lua/luasocket.lua index 1debb4dd9..bf1e919d4 100644 --- a/plugins/lua/luasocket.lua +++ b/plugins/lua/luasocket.lua @@ -47,7 +47,7 @@ function client:receive( pattern ) local pattern=pattern or "*l" local bytes=-1 - if type(pattern)== number then + if type(pattern)== 'number' then bytes=pattern end From b7783ba8b9c453d268c2b3d75b6232d512b24fde Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 12 Aug 2017 19:10:35 +0530 Subject: [PATCH 0618/1012] Added unkept hair to style list --- plugins/proto/RemoteFortressReader.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index b416b96ec..963bf3bf8 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -120,6 +120,7 @@ enum TileDigDesignation enum HairStyle { + UNKEPT = -1; NEATLY_COMBED = 0; BRAIDED = 1; DOUBLE_BRAID = 2; From 5b5ac7088dbd1fd8504033d34653d367b7dd44ba Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 12 Aug 2017 19:12:14 +0530 Subject: [PATCH 0619/1012] correct spelling --- plugins/proto/RemoteFortressReader.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 963bf3bf8..bcbf46d71 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -120,7 +120,7 @@ enum TileDigDesignation enum HairStyle { - UNKEPT = -1; + UNKEMPT = -1; NEATLY_COMBED = 0; BRAIDED = 1; DOUBLE_BRAID = 2; From 737aefefea9d954f45ccc4c241ccdd56cac692bb Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 19 Aug 2017 09:38:56 +0530 Subject: [PATCH 0620/1012] send over beard styles --- .../remotefortressreader.cpp | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 7fe879645..e1f961577 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -83,6 +83,7 @@ #include "df/site_realization_building_info_trenchesst.h" #endif #include "df/tissue.h" +#include "df/tissue_style_raw.h" #include "df/ui.h" #include "df/unit.h" #include "df/viewscreen_choose_start_sitest.h" @@ -1578,6 +1579,42 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, } send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); + + auto creatureRaw = world->raws.creatures.all[unit->race]; + auto casteRaw = creatureRaw->caste[unit->caste]; + + for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) + { + auto type = unit->appearance.tissue_style_type[j]; + if (type < 0) + continue; + int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); + auto styleRaw = casteRaw->tissue_styles[style_raw_index]; + if (styleRaw->token == "HAIR") + { + auto send_style = appearance->mutable_hair(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "BEARD") + { + auto send_style = appearance->mutable_beard(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "MOUSTACHE") + { + auto send_style = appearance->mutable_moustache(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "SIDEBURNS") + { + auto send_style = appearance->mutable_sideburns(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + } } return CR_OK; } From 8e71cf9def0496f9977e8794b50d6c9983143637 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 19 Aug 2017 17:48:18 +0530 Subject: [PATCH 0621/1012] Send Unit inventories with RFR --- plugins/proto/RemoteFortressReader.proto | 40 +++++++++++++++++++ .../remotefortressreader.cpp | 9 +++++ 2 files changed, 49 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index bcbf46d71..88fd03c64 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -128,6 +128,39 @@ enum HairStyle CLEAN_SHAVEN = 4; } +enum InventoryMode +{ + Hauled = 0; + /** + * also shield, crutch + */ + Weapon = 1; + /** + * quiver + */ + Worn = 2; + Piercing = 3; + /** + * attached to clothing + */ + Flask = 4; + /** + * e.g. bandage + */ + WrappedAround = 5; + StuckIn = 6; + /** + * string descr like Worn + */ + InMouth = 7; + /** + * Left shoulder, right shoulder, or head, selected randomly using pet_seed + */ + Pet = 8; + SewnInto = 9; + Strapped = 10; +} + message Coord { optional int32 x = 1; @@ -336,6 +369,12 @@ message UnitAppearance optional Hair sideburns = 8; } +message InventoryItem +{ + optional InventoryMode mode = 1; + optional Item item = 2; +} + message UnitDefinition { required int32 id = 1; @@ -357,6 +396,7 @@ message UnitDefinition optional int32 profession_id = 17; repeated string noble_positions = 18; optional int32 rider_id = 19; + repeated InventoryItem inventory = 20; } message UnitList diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index e1f961577..035ab4322 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -86,6 +86,7 @@ #include "df/tissue_style_raw.h" #include "df/ui.h" #include "df/unit.h" +#include "df/unit_inventory_item.h" #include "df/viewscreen_choose_start_sitest.h" #include "df/world.h" #include "df/world_data.h" @@ -1615,6 +1616,14 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); } } + + for (int j = 0; j < unit->inventory.size(); j++) + { + auto inventory_item = unit->inventory[j]; + auto sent_item = send_unit->add_inventory(); + sent_item->set_mode((InventoryMode)inventory_item->mode); + CopyItem(sent_item->mutable_item(), inventory_item->item); + } } return CR_OK; } From a7b837f2d056ebae6fde4f97b6bc107508969ceb Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 20 Aug 2017 20:44:55 +0530 Subject: [PATCH 0622/1012] Increment version number --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 035ab4322..a07668778 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.17.1" +#define RFR_VERSION "0.18.0" #include #include From 4f38260e8c236e0cc59bb846b14aa581373feaf1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 21 Aug 2017 19:34:46 -0400 Subject: [PATCH 0623/1012] Pass the correct architecture to setarch on Linux Fixes #1129 --- CMakeLists.txt | 5 +++++ package/linux/dfhack | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e78340d5..79889dcb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,9 +81,11 @@ ENDIF() IF("${DFHACK_BUILD_ARCH}" STREQUAL "32") SET(DFHACK_BUILD_32 1) SET(DFHACK_BUILD_64 0) + set(DFHACK_SETARCH "i386") ELSEIF("${DFHACK_BUILD_ARCH}" STREQUAL "64") SET(DFHACK_BUILD_32 0) SET(DFHACK_BUILD_64 1) + set(DFHACK_SETARCH "x86_64") ADD_DEFINITIONS(-DDFHACK64) ELSE() MESSAGE(SEND_ERROR "Invalid build architecture (should be 32/64): ${DFHACK_BUILD_ARCH}") @@ -334,6 +336,9 @@ IF(BUILD_LIBRARY) install(FILES LICENSE.rst NEWS.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() +file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH}) +install(FILES "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" DESTINATION "${DFHACK_DATA_DESTINATION}") + install(DIRECTORY dfhack-config/ DESTINATION dfhack-config/default) #build the plugins diff --git a/package/linux/dfhack b/package/linux/dfhack index 7c01fcbd7..011df22eb 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -63,6 +63,8 @@ export LD_LIBRARY_PATH="./hack/libs:./hack:$LD_LIBRARY_PATH" PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}./hack/libdfhack.so" +setarch_arch=$(cat hack/dfhack_setarch.txt || printf i386) + case "$1" in -g | --gdb) shift @@ -74,21 +76,21 @@ case "$1" in ;; -h | --helgrind) shift - LD_PRELOAD="$PRELOAD_LIB" setarch i386 -R valgrind $DF_HELGRIND_OPTS --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress "$@" + LD_PRELOAD="$PRELOAD_LIB" setarch "$setarch_arch" -R valgrind $DF_HELGRIND_OPTS --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress "$@" ret=$? ;; -v | --valgrind) shift - LD_PRELOAD="$PRELOAD_LIB" setarch i386 -R valgrind $DF_VALGRIND_OPTS --log-file=valgrind.log ./libs/Dwarf_Fortress "$@" + LD_PRELOAD="$PRELOAD_LIB" setarch "$setarch_arch" -R valgrind $DF_VALGRIND_OPTS --log-file=valgrind.log ./libs/Dwarf_Fortress "$@" ret=$? ;; -c | --callgrind) shift - LD_PRELOAD="$PRELOAD_LIB" setarch i386 -R valgrind $DF_CALLGRIND_OPTS --tool=callgrind --separate-threads=yes --dump-instr=yes --instr-atstart=no --log-file=callgrind.log ./libs/Dwarf_Fortress "$@" + LD_PRELOAD="$PRELOAD_LIB" setarch "$setarch_arch" -R valgrind $DF_CALLGRIND_OPTS --tool=callgrind --separate-threads=yes --dump-instr=yes --instr-atstart=no --log-file=callgrind.log ./libs/Dwarf_Fortress "$@" ret=$? ;; *) - setarch i386 -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" + setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" ret=$? ;; esac From ca481fc69f177686a01fe3c89f59532c7ba2ac66 Mon Sep 17 00:00:00 2001 From: ViTuRaS Date: Mon, 28 Aug 2017 22:33:24 +0200 Subject: [PATCH 0624/1012] max_barrels was serialized on max_bins position When I save and load stockpile settings with stockpile management plugin, then saved barrels count will be loaded in bins and barrels are zero. So I think this change should correct it. --- plugins/stockpiles/StockpileSerializer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index fa3d648ba..e9a8143a5 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -465,8 +465,9 @@ int StockpileSerializer::other_mats_token ( const std::map oth void StockpileSerializer::write_general() { - mBuffer.set_max_bins ( mPile->max_barrels ); + mBuffer.set_max_bins ( mPile->max_bins ); mBuffer.set_max_wheelbarrows ( mPile->max_wheelbarrows ); + mBuffer.set_max_barrels ( mPile->max_barrels ); mBuffer.set_use_links_only ( mPile->use_links_only ); mBuffer.set_unknown1 ( mPile->settings.unk1 ); mBuffer.set_allow_inorganic ( mPile->settings.allow_inorganic ); From 80fe49b339e571eb3fe11a644608657225bba7fe Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 14:13:34 +0200 Subject: [PATCH 0625/1012] Add embark-assistant plugin --- plugins/CMakeLists.txt | 1 + plugins/embark-assistant/CMakeLists.txt | 48 + plugins/embark-assistant/biome_type.cpp | 752 +++++++++ plugins/embark-assistant/biome_type.h | 7 + plugins/embark-assistant/defs.h | 256 +++ plugins/embark-assistant/embark-assistant.cpp | 296 ++++ plugins/embark-assistant/embark-assistant.h | 1 + plugins/embark-assistant/finder_ui.cpp | 1143 +++++++++++++ plugins/embark-assistant/finder_ui.h | 17 + plugins/embark-assistant/help_ui.cpp | 314 ++++ plugins/embark-assistant/help_ui.h | 15 + plugins/embark-assistant/matcher.cpp | 1445 +++++++++++++++++ plugins/embark-assistant/matcher.h | 21 + plugins/embark-assistant/overlay.cpp | 439 +++++ plugins/embark-assistant/overlay.h | 37 + plugins/embark-assistant/screen.cpp | 26 + plugins/embark-assistant/screen.h | 7 + plugins/embark-assistant/survey.cpp | 1080 ++++++++++++ plugins/embark-assistant/survey.h | 38 + 19 files changed, 5943 insertions(+) create mode 100644 plugins/embark-assistant/CMakeLists.txt create mode 100644 plugins/embark-assistant/biome_type.cpp create mode 100644 plugins/embark-assistant/biome_type.h create mode 100644 plugins/embark-assistant/defs.h create mode 100644 plugins/embark-assistant/embark-assistant.cpp create mode 100644 plugins/embark-assistant/embark-assistant.h create mode 100644 plugins/embark-assistant/finder_ui.cpp create mode 100644 plugins/embark-assistant/finder_ui.h create mode 100644 plugins/embark-assistant/help_ui.cpp create mode 100644 plugins/embark-assistant/help_ui.h create mode 100644 plugins/embark-assistant/matcher.cpp create mode 100644 plugins/embark-assistant/matcher.h create mode 100644 plugins/embark-assistant/overlay.cpp create mode 100644 plugins/embark-assistant/overlay.h create mode 100644 plugins/embark-assistant/screen.cpp create mode 100644 plugins/embark-assistant/screen.h create mode 100644 plugins/embark-assistant/survey.cpp create mode 100644 plugins/embark-assistant/survey.h diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b6e05b749..e85e0aaf4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -111,6 +111,7 @@ if (BUILD_SUPPORTED) add_subdirectory(diggingInvaders) DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) + add_subdirectory(embark-assistant) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) diff --git a/plugins/embark-assistant/CMakeLists.txt b/plugins/embark-assistant/CMakeLists.txt new file mode 100644 index 000000000..e57561ec1 --- /dev/null +++ b/plugins/embark-assistant/CMakeLists.txt @@ -0,0 +1,48 @@ +PROJECT (embark-assistant) +# A list of source files +SET(PROJECT_SRCS + biome_type.cpp + embark-assistant.cpp + finder_ui.cpp + help_ui.cpp + matcher.cpp + overlay.cpp + screen.cpp + survey.cpp +) +# A list of headers +SET(PROJECT_HDRS + biome_type.h + defs.h + embark-assistant.h + finder_ui.h + help_ui.h + matcher.h + overlay.h + screen.h + survey.h +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +# option to use a thread for no particular reason +#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra windows libs here + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack +DFHACK_PLUGIN(embark-assistant ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/embark-assistant/biome_type.cpp b/plugins/embark-assistant/biome_type.cpp new file mode 100644 index 000000000..82603a004 --- /dev/null +++ b/plugins/embark-assistant/biome_type.cpp @@ -0,0 +1,752 @@ +/* The code is copied from Ragundo's repo referenced below. +The changes are: +- The addition of a .h file reference. +- The simplification of the code using ofsub to remove the use of (and + .h reference to) that function (analysis of the code showed the + simplified code is the result, as the ofsub expressions will never be + true given the range of the values it can be passed in these functions). +- The change of the main function to take a separate y coordinate for + use in the tropicality determination to allow proper determination of + the tropicality of mid level tiles ("region tiles") referencing a + neighboring world tile's biome. +*/ +/* +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +// You can always find the latest version of this plugin in Github +// https://github.com/ragundo/exportmaps + +#include "../../include/dfhack.h" +#include +#include +#include +#include + +#include "biome_type.h" + +/***************************************************************************** +Local functions forward declaration +*****************************************************************************/ +std::pair check_tropicality(df::region_map_entry& region, + int a1 +); + +int get_lake_biome(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude +); + +int get_ocean_biome(df::region_map_entry& region, + bool is_tropical_area_by_latitude +); + +int get_desert_biome(df::region_map_entry& region); + +int get_biome_grassland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_savanna(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_shrubland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_forest(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_swamp(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_shrubland_or_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +/***************************************************************************** +Module main function. +Return the biome type, given a position coordinate expressed in world_tiles +The world ref coordinates are used for tropicality determination and may refer +to a tile neighboring the "official" one. +*****************************************************************************/ +int get_biome_type(int world_coord_x, + int world_coord_y, + int world_ref_coord_y +) +{ + // Biome is per region, so get the region where this biome exists + df::region_map_entry& region = df::global::world->world_data->region_map[world_coord_x][world_coord_y]; + + // Check if the y reference position coordinate belongs to a tropical area + std::pair p = check_tropicality(region, + world_ref_coord_y + ); + bool is_possible_tropical_area_by_latitude = p.first; + bool is_tropical_area_by_latitude = p.second; + + // Begin the discrimination + if (region.flags.is_set(df::region_map_entry_flags::is_lake)) // is it a lake? + return get_lake_biome(region, + is_possible_tropical_area_by_latitude + ); + + // Not a lake. Check elevation + // Elevation greater then 149 means a mountain biome + // Elevation below 100 means a ocean biome + // Elevation between 100 and 149 are land biomes + + if (region.elevation >= 150) // is it a mountain? + return df::enums::biome_type::biome_type::MOUNTAIN; // 0 + + if (region.elevation < 100) // is it a ocean? + return get_ocean_biome(region, + is_possible_tropical_area_by_latitude + ); + + // land biome. Elevation between 100 and 149 + if (region.temperature <= -5) + { + if (region.drainage < 75) + return df::enums::biome_type::biome_type::TUNDRA; // 2 + else + return df::enums::biome_type::biome_type::GLACIER; // 1 + } + + // Not a lake, mountain, ocean, glacier or tundra + // Vegetation determines the biome type + if (region.vegetation < 66) + { + if (region.vegetation < 33) + return get_biome_desert_or_grassland_or_savanna(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x + ); + else // vegetation between 33 and 65 + return get_biome_shrubland_or_marsh(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x + ); + } + + // Not a lake, mountain, ocean, glacier, tundra, desert, grassland or savanna + // vegetation >= 66 + if (region.drainage >= 33) + return get_biome_forest(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x + ); + + // Not a lake, mountain, ocean, glacier, tundra, desert, grassland, savanna or forest + // vegetation >= 66, drainage < 33 + return get_biome_swamp(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x); +} + + + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_no_poles_world(df::region_map_entry& region, + int y_pos +) +{ + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + + // If there're no poles, tropical area is determined by temperature + if (region.temperature >= 75) + is_possible_tropical_area_by_latitude = true; + is_tropical_area_by_latitude = region.temperature >= 85; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_north_pole_only_world(df::region_map_entry& region, + int y_pos +) +{ + int v6; + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + df::world_data* wdata = df::global::world->world_data; + + // Scale the smaller worlds to the big one + if (wdata->world_height == 17) + v6 = 16 * y_pos; + else if (wdata->world_height == 33) + v6 = 8 * y_pos; + else if (wdata->world_height == 65) + v6 = 4 * y_pos; + else if (wdata->world_height == 129) + v6 = 2 * y_pos; + else + v6 = y_pos; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_south_pole_only_world(df::region_map_entry& region, + int y_pos +) +{ + int v6 = df::global::world->world_data->world_height - y_pos - 1; + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + df::world_data* wdata = df::global::world->world_data; + + if (wdata->world_height == 17) + v6 *= 16; + else if (wdata->world_height == 33) + v6 *= 8; + else if (wdata->world_height == 65) + v6 *= 4; + else if (wdata->world_height == 129) + v6 *= 2; + else + v6 *= 1; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_both_poles_world(df::region_map_entry& region, + int y_pos +) +{ + int v6; + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + df::world_data* wdata = df::global::world->world_data; + + if (y_pos < wdata->world_height / 2) + v6 = 2 * y_pos; + else + { + v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1; + if (v6 < 0) + v6 = 0; + if (v6 >= wdata->world_height) + v6 = wdata->world_height - 1; + } + + if (wdata->world_height == 17) + v6 *= 16; + else if (wdata->world_height == 33) + v6 *= 8; + else if (wdata->world_height == 65) + v6 *= 4; + else if (wdata->world_height == 129) + v6 *= 2; + else + v6 *= 1; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality(df::region_map_entry& region, + int y_pos +) +{ + int flip_latitude = df::global::world->world_data->flip_latitude; + + if (flip_latitude == -1) // NO POLES + return check_tropicality_no_poles_world(region, + y_pos + ); + + else if (flip_latitude == 0) // NORTH POLE ONLY + return check_tropicality_north_pole_only_world(region, + y_pos + ); + + else if (flip_latitude == 1) // SOUTH_POLE ONLY + return check_tropicality_south_pole_only_world(region, + y_pos + ); + + else if (flip_latitude == 2) // BOTH POLES + return check_tropicality_both_poles_world(region, + y_pos + ); + + return std::pair(false, false); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_parameter_percentage(int flip_latitude, + int y_pos, + int rainfall, + int world_height +) +{ + int result; + int ypos = y_pos; + + if (flip_latitude == -1) // NO POLES + return 100; + + else if (flip_latitude == 1) // SOUTH POLE + ypos = world_height - y_pos - 1; + else if (flip_latitude == 2) // NORTH & SOUTH POLE + { + if (ypos < world_height / 2) + ypos *= 2; + else + { + ypos = world_height + 2 * (world_height / 2 - ypos) - 1; + if (ypos < 0) + ypos = 0; + if (ypos >= world_height) + ypos = world_height - 1; + } + } + + int latitude; // 0 - 256 (size of a large world) + switch (world_height) + { + case 17: // Pocket world + latitude = 16 * ypos; + break; + case 33: // Smaller world + latitude = 8 * ypos; + break; + case 65: // Small world + latitude = 4 * ypos; + break; + case 129: // Medium world + latitude = 2 * ypos; + break; + default: // Large world + latitude = ypos; + break; + } + + // latitude > 220 + if ((latitude - 171) > 49) + return 100; + + + // Latitude between 191 and 200 + if ((latitude > 190) && (latitude < 201)) + return 0; + + // Latitude between 201 and 220 + if ((latitude > 190) && (latitude >= 201)) + result = rainfall + 16 * (latitude - 207); + else + // Latitude between 0 and 190 + result = (16 * (184 - latitude) - rainfall); + + if (result < 0) + return 0; + + if (result > 100) + return 100; + + return result; +} + +//----------------------------------------------------------------------------// +// Utility function +// +// return some unknow parameter as a percentage +//----------------------------------------------------------------------------// +int get_region_parameter(int y, + int x, + char a4 +) +{ + int result = 100; + + if ((df::global::cur_season && *df::global::cur_season != 1) || !a4) + { + int world_height = df::global::world->world_data->world_height; + if (world_height > 65) // Medium and large worlds + { + // access to region 2D array + df::region_map_entry& region = df::global::world->world_data->region_map[x][y]; + return get_parameter_percentage(df::global::world->world_data->flip_latitude, + y, + region.rainfall, + world_height + ); + } + } + return result; +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_lake_biome(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude +) +{ + // salinity values tell us the lake type + // greater than 66 is a salt water lake + // between 33 and 65 is a brackish water lake + // less than 33 is a fresh water lake + if (region.salinity < 66) + { + if (region.salinity < 33) + if (is_possible_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::LAKE_TROPICAL_FRESHWATER; // 39 + else + return df::enums::biome_type::biome_type::LAKE_TEMPERATE_FRESHWATER; // 36 + else // salinity >= 33 + if (is_possible_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::LAKE_TROPICAL_BRACKISHWATER; // 40 + else + return df::enums::biome_type::biome_type::LAKE_TEMPERATE_BRACKISHWATER; // 37 + } + else // salinity >= 66 + { + if (is_possible_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::LAKE_TROPICAL_SALTWATER;// 41 + else + return df::enums::biome_type::biome_type::LAKE_TEMPERATE_SALTWATER; // 38 + } +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_ocean_biome(df::region_map_entry& region, + bool is_tropical_area_by_latitude +) +{ + if (is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::OCEAN_TROPICAL; // 27 + else + if (region.temperature <= -5) + return df::enums::biome_type::biome_type::OCEAN_ARCTIC; // 29 + else + return df::enums::biome_type::biome_type::OCEAN_TEMPERATE; // 28 +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_desert_biome(df::region_map_entry& region) +{ + if (region.drainage < 66) + { + if (region.drainage < 33) + return df::enums::biome_type::biome_type::DESERT_SAND; // 26 + else // drainage between 33 and 65 + return df::enums::biome_type::biome_type::DESERT_ROCK; // 25 + } + // drainage >= 66 + return df::enums::biome_type::biome_type::DESERT_BADLAND; // 24 +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_grassland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::GRASSLAND_TROPICAL; // 21 + else + return df::enums::biome_type::biome_type::GRASSLAND_TEMPERATE; //18; +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_savanna(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) <= 6)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::SAVANNA_TROPICAL; // 22 + else + return df::enums::biome_type::biome_type::SAVANNA_TEMPERATE; //19; + +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (region.vegetation < 20) + { + if (region.vegetation < 10) + return get_desert_biome(region); + else // vegetation between 10 and 19 + return get_biome_grassland(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x); + } + // vegetation between 20 and 32 + return get_biome_savanna(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_shrubland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66 || is_tropical_area_by_latitude)) + return df::enums::biome_type::biome_type::SHRUBLAND_TROPICAL; // 23 + else + return df::enums::biome_type::biome_type::SHRUBLAND_TEMPERATE; // 20 +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (region.salinity < 66) + { + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::MARSH_TROPICAL_FRESHWATER; // 10 + else + return df::enums::biome_type::biome_type::MARSH_TEMPERATE_FRESHWATER; // 5 + } + else // drainage < 33, salinity >= 66 + { + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::MARSH_TROPICAL_SALTWATER; // 11 + else + return df::enums::biome_type::biome_type::MARSH_TEMPERATE_SALTWATER; // 6 + } +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_shrubland_or_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (region.drainage >= 33) + return get_biome_shrubland(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + y, + x + ); + // drainage < 33 + return get_biome_marsh(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + y, + x + ); +} + + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_forest(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + int parameter = get_region_parameter(y, x, 0); + + // drainage >= 33, not tropical area + if (!is_possible_tropical_area_by_latitude) + { + if ((region.rainfall < 75) || (region.temperature < 65)) + { + if (region.temperature >= 10) + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13 + else + return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12 + } + else + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14 + } + else // drainage >= 33, tropical area + { + if (((parameter < 66) || is_tropical_area_by_latitude) && (region.rainfall < 75)) + return df::enums::biome_type::biome_type::FOREST_TROPICAL_CONIFER; // 15 + if (parameter < 66) + return df::enums::biome_type::biome_type::FOREST_TROPICAL_DRY_BROADLEAF; // 16 + if (is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF; // 17 + else + { + if ((region.rainfall < 75) || (region.temperature < 65)) + { + if (region.temperature >= 10) + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13 + else + return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12 + } + else + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14 + } + } +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_swamp(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + int parameter = get_region_parameter(y, x, 0); + + if (is_possible_tropical_area_by_latitude) + { + if (region.salinity < 66) + { + if ((parameter < 66) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::SWAMP_TROPICAL_FRESHWATER; // 7 + else + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER;// 3 + } + else // elevation between 100 and 149, vegetation >= 66, drainage < 33, salinity >= 66 + { + if ((parameter < 66) || is_tropical_area_by_latitude) + { + if (region.drainage < 10) + return df::enums::biome_type::biome_type::SWAMP_MANGROVE; //9 + else // drainage >= 10 + return df::enums::biome_type::biome_type::SWAMP_TROPICAL_SALTWATER; // 8 + } + else + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4 + } + } + else // elevation between 100 and 149, vegetation >= 66, drainage < 33, not tropical area + { + if (region.salinity >= 66) + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4 + else + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER; // 3 + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/biome_type.h b/plugins/embark-assistant/biome_type.h new file mode 100644 index 000000000..9ea562749 --- /dev/null +++ b/plugins/embark-assistant/biome_type.h @@ -0,0 +1,7 @@ +// world_coord_x/y is the location of the tile "owning" the biome, while world_ref_coord_y is the +// location of the tile the biome appears on. They differ when a mid level tile ("region tile") +// refers to a neighboring tile for the biome parameters. The difference can affect the tropicality +// determination. Since Tropicality is determined by latitude, the x coordinate of the reference is +// omitted. +// +int get_biome_type(int world_coord_x, int world_coord_y, int world_ref_coord_y); diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h new file mode 100644 index 000000000..79ad5dd5b --- /dev/null +++ b/plugins/embark-assistant/defs.h @@ -0,0 +1,256 @@ +#pragma once + +#include +#include +#include + +using namespace std; +using std::array; +using std::ostringstream; +using std::string; +using std::vector; + +namespace embark_assist { + namespace defs { + // Survey types + // + enum class river_sizes { + None, + Brook, + Stream, + Minor, + Medium, + Major + }; + + struct mid_level_tile { + bool aquifer = false; + bool clay = false; + bool sand = false; + bool flux = false; + int8_t soil_depth; + int8_t offset; + int16_t elevation; + bool river_present = false; + int16_t river_elevation = 100; + int8_t biome_offset; + uint8_t savagery_level; // 0 - 2 + uint8_t evilness_level; // 0 - 2 + std::vector metals; + std::vector economics; + std::vector minerals; + }; + + typedef std::array, 16> mid_level_tiles; +// typedef mid_level_tile mid_level_tiles[16][16]; + + struct region_tile_datum { + bool surveyed = false; + uint16_t aquifer_count = 0; + uint16_t clay_count = 0; + uint16_t sand_count = 0; + uint16_t flux_count = 0; + uint8_t min_region_soil = 10; + uint8_t max_region_soil = 0; + bool waterfall = false; + + river_sizes river_size; + int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used + int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used + uint8_t biome_count; + bool evil_weather[10]; + bool evil_weather_possible; + bool evil_weather_full; + bool reanimating[10]; + bool reanimating_possible; + bool reanimating_full; + bool thralling[10]; + bool thralling_possible; + bool thralling_full; + uint16_t savagery_count[3]; + uint16_t evilness_count[3]; + std::vector metals; + std::vector economics; + std::vector minerals; + }; + + struct geo_datum { + uint8_t soil_size = 0; + bool top_soil_only = true; + bool top_soil_aquifer_only = true; + bool aquifer_absent = true; + bool clay_absent = true; + bool sand_absent = true; + bool flux_absent = true; + std::vector possible_metals; + std::vector possible_economics; + std::vector possible_minerals; + }; + + typedef std::vector geo_data; + + struct sites { + uint8_t x; + uint8_t y; + char type; + }; + + struct site_infos { + bool aquifer; + bool aquifer_full; + uint8_t min_soil; + uint8_t max_soil; + bool flat; + bool waterfall; + bool clay; + bool sand; + bool flux; + std::vector metals; + std::vector economics; + std::vector minerals; + // Could add savagery, evilness, and biomes, but DF provides those easily. + }; + + typedef std::vector site_lists; + + typedef std::vector> world_tile_data; + + typedef bool mlt_matches[16][16]; + // An embark region match is indicated by marking the top left corner + // tile as a match. Thus, the bottom and right side won't show matches + // unless the appropriate dimension has a width of 1. + + struct matches { + bool preliminary_match; + bool contains_match; + mlt_matches mlt_match; + }; + + typedef std::vector> match_results; + + // matcher types + // + enum class evil_savagery_values : int8_t { + NA = -1, + All, + Present, + Absent + }; + + enum class evil_savagery_ranges : int8_t { + Low, + Medium, + High + }; + + enum class aquifer_ranges : int8_t { + NA = -1, + All, + Present, + Partial, + Not_All, + Absent + }; + + enum class river_ranges : int8_t { + NA = -1, + None, + Brook, + Stream, + Minor, + Medium, + Major + }; + + enum class yes_no_ranges : int8_t { + NA = -1, + Yes, + No + }; + + enum class all_present_ranges : int8_t { + All, + Present + }; + enum class present_absent_ranges : int8_t { + NA = -1, + Present, + Absent + }; + + enum class soil_ranges : int8_t { + NA = -1, + None, + Very_Shallow, + Shallow, + Deep, + Very_Deep + }; + + /* // Future possible enhancement + enum class freezing_ranges : int8_t { + NA = -1, + Permanent, + At_Least_Partial, + Partial, + At_Most_Partial, + Never + }; + */ + + struct finders { + uint16_t x_dim; + uint16_t y_dim; + evil_savagery_values savagery[static_cast(evil_savagery_ranges::High) + 1]; + evil_savagery_values evilness[static_cast(evil_savagery_ranges::High) + 1]; + aquifer_ranges aquifer; + river_ranges min_river; + river_ranges max_river; + yes_no_ranges waterfall; + yes_no_ranges flat; + present_absent_ranges clay; + present_absent_ranges sand; + present_absent_ranges flux; + soil_ranges soil_min; + all_present_ranges soil_min_everywhere; + soil_ranges soil_max; + /*freezing_ranges freezing;*/ + yes_no_ranges evil_weather; // Will probably blow up with the magic release arcs... + yes_no_ranges reanimation; + yes_no_ranges thralling; + int8_t biome_count_min; // N/A(-1), 1-9 + int8_t biome_count_max; // N/A(-1), 1-9 + int8_t region_type_1; // N/A(-1), df::world_region_type + int8_t region_type_2; // N/A(-1), df::world_region_type + int8_t region_type_3; // N/A(-1), df::world_region_type + int8_t biome_1; // N/A(-1), df::biome_type + int8_t biome_2; // N/A(-1), df::biome_type + int8_t biome_3; // N/A(-1), df::biome_type + int16_t metal_1; // N/A(-1), 0-max_inorganic; + int16_t metal_2; // N/A(-1), 0-max_inorganic; + int16_t metal_3; // N/A(-1), 0-max_inorganic; + int16_t economic_1; // N/A(-1), 0-max_inorganic; + int16_t economic_2; // N/A(-1), 0-max_inorganic; + int16_t economic_3; // N/A(-1), 0-max_inorganic; + int16_t mineral_1; // N/A(-1), 0-max_inorganic; + int16_t mineral_2; // N/A(-1), 0-max_inorganic; + int16_t mineral_3; // N/A(-1), 0-max_inorganic; + }; + + struct match_iterators { + bool active; + uint16_t x; // x position of focus when iteration started so we can return it. + uint16_t y; // y + uint16_t i; + uint16_t k; + bool x_right; + bool y_down; + bool inhibit_x_turn; + bool inhibit_y_turn; + uint16_t count; + finders finder; + }; + + typedef void(*find_callbacks) (embark_assist::defs::finders finder); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp new file mode 100644 index 000000000..283b696a0 --- /dev/null +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -0,0 +1,296 @@ +#include "Core.h" +#include +#include +#include + +#include +#include +#include + +#include "DataDefs.h" +#include "df/coord2d.h" +#include "df/inorganic_flags.h" +#include "df/inorganic_raw.h" +#include "df/interfacest.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_raws.h" + +#include "defs.h" +#include "embark-assistant.h" +#include "finder_ui.h" +#include "matcher.h" +#include "overlay.h" +#include "survey.h" + +DFHACK_PLUGIN("embark-assistant"); + +using namespace DFHack; +using namespace df::enums; +using namespace Gui; + +REQUIRE_GLOBAL(world); + +namespace embark_assist { + namespace main { + struct states { + embark_assist::defs::geo_data geo_summary; + embark_assist::defs::world_tile_data survey_results; + embark_assist::defs::site_lists region_sites; + embark_assist::defs::site_infos site_info; + embark_assist::defs::match_results match_results; + embark_assist::defs::match_iterators match_iterator; + uint16_t max_inorganic; + }; + + static states *state = nullptr; + + void embark_update (); + void shutdown(); + + //=============================================================================== + + void embark_update() { + auto screen = Gui::getViewscreenByType(0); + embark_assist::defs::mid_level_tiles mlt; + embark_assist::survey::initiate(&mlt); + + embark_assist::survey::survey_mid_level_tile(&state->geo_summary, + &state->survey_results, + &mlt); + + embark_assist::survey::survey_embark(&mlt, &state->site_info, false); + embark_assist::overlay::set_embark(&state->site_info); + + embark_assist::survey::survey_region_sites(&state->region_sites); + embark_assist::overlay::set_sites(&state->region_sites); + + embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match); + } + + //=============================================================================== + + void match() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + + uint16_t count = embark_assist::matcher::find(&state->match_iterator, + &state->geo_summary, + &state->survey_results, + &state->match_results); + + embark_assist::overlay::match_progress(count, &state->match_results, !state->match_iterator.active); + + if (!state->match_iterator.active) { + auto screen = Gui::getViewscreenByType(0); + embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match); + } + } + + //=============================================================================== + + void clear_match() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (state->match_iterator.active) { + embark_assist::matcher::move_cursor(state->match_iterator.x, state->match_iterator.y); + } + embark_assist::survey::clear_results(&state->match_results); + embark_assist::overlay::clear_match_results(); + embark_assist::main::state->match_iterator.active = false; + } + + //=============================================================================== + + void find(embark_assist::defs::finders finder) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + + state->match_iterator.x = embark_assist::survey::get_last_pos().x; + state->match_iterator.y = embark_assist::survey::get_last_pos().y; + state->match_iterator.finder = finder; + embark_assist::overlay::initiate_match(); + } + + //=============================================================================== + + void shutdown() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::survey::shutdown(); + embark_assist::finder_ui::shutdown(); + embark_assist::overlay::shutdown(); + delete state; + state = nullptr; + } + } +} + +//======================================================================================= + +command_result embark_assistant (color_ostream &out, std::vector & parameters); + +//======================================================================================= + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "embark-assistant", "Embark site selection support.", + embark_assistant, true, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command starts the embark-assist plugin that provides embark site\n" + " selection help. It has to be called while th pre embark screen is\n" + " displayed and shows extended (and correct(?)) resource information for\n" + " the embark rectangle as well as normally undisplayed sites in the\n" + " current embark region. It also has a site selection tool with more\n" + " options than DF's vanilla search tool. For detailed help invoke the\n" + " in game info screen. Requires 42 lines to display properly.\n" + )); + return CR_OK; +} + +//======================================================================================= + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +//======================================================================================= + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case DFHack::SC_UNKNOWN: + break; + + case DFHack::SC_WORLD_LOADED: + break; + + case DFHack::SC_WORLD_UNLOADED: + case DFHack::SC_MAP_LOADED: + if (embark_assist::main::state) { + embark_assist::main::shutdown(); + } + break; + + case DFHack::SC_MAP_UNLOADED: + break; + + case DFHack::SC_VIEWSCREEN_CHANGED: + break; + + case DFHack::SC_CORE_INITIALIZED: + break; + + case DFHack::SC_BEGIN_UNLOAD: + break; + + case DFHack::SC_PAUSED: + break; + + case DFHack::SC_UNPAUSED: + break; + } + return CR_OK; +} + + +//======================================================================================= + +command_result embark_assistant(color_ostream &out, std::vector & parameters) +{ + if (!parameters.empty()) + return CR_WRONG_USAGE; + + CoreSuspender suspend; + + auto screen = Gui::getViewscreenByType(0); + if (!screen) { + out.printerr("This plugin works only in the embark site selection phase.\n"); + return CR_WRONG_USAGE; + } + + df::world_data *world_data = world->world_data; + + if (embark_assist::main::state) { + out.printerr("You can't invoke the embark assistant while it's already active.\n"); + return CR_WRONG_USAGE; + } + + embark_assist::main::state = new embark_assist::main::states; + + embark_assist::main::state->match_iterator.active = false; + + // Find the end of the normal inorganic definitions. + embark_assist::main::state->max_inorganic = 0; + for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) { + if (world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i; + } + embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement + + if (!embark_assist::overlay::setup(plugin_self, + embark_assist::main::embark_update, + embark_assist::main::match, + embark_assist::main::clear_match, + embark_assist::main::find, + embark_assist::main::shutdown, + embark_assist::main::state->max_inorganic)) { + return CR_FAILURE; + } + + embark_assist::survey::setup(embark_assist::main::state->max_inorganic); + embark_assist::main::state->geo_summary.resize(world_data->geo_biomes.size()); + embark_assist::main::state->survey_results.resize(world->worldgen.worldgen_parms.dim_x); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + embark_assist::main::state->survey_results[i].resize(world->worldgen.worldgen_parms.dim_y); + + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + embark_assist::main::state->survey_results[i][k].surveyed = false; + embark_assist::main::state->survey_results[i][k].aquifer_count = 0; + embark_assist::main::state->survey_results[i][k].clay_count = 0; + embark_assist::main::state->survey_results[i][k].sand_count = 0; + embark_assist::main::state->survey_results[i][k].flux_count = 0; + embark_assist::main::state->survey_results[i][k].min_region_soil = 10; + embark_assist::main::state->survey_results[i][k].max_region_soil = 0; + embark_assist::main::state->survey_results[i][k].waterfall = false; + embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None; + + for (uint8_t l = 1; l < 10; l++) { + embark_assist::main::state->survey_results[i][k].biome_index[l] = -1; + embark_assist::main::state->survey_results[i][k].biome[l] = -1; + embark_assist::main::state->survey_results[i][k].evil_weather[l] = false; + embark_assist::main::state->survey_results[i][k].reanimating[l] = false; + embark_assist::main::state->survey_results[i][k].thralling[l] = false; + } + + for (uint8_t l = 0; l < 2; l++) { + embark_assist::main::state->survey_results[i][k].savagery_count[l] = 0; + embark_assist::main::state->survey_results[i][k].evilness_count[l] = 0; + } + embark_assist::main::state->survey_results[i][k].metals.resize(embark_assist::main::state->max_inorganic); + embark_assist::main::state->survey_results[i][k].economics.resize(embark_assist::main::state->max_inorganic); + embark_assist::main::state->survey_results[i][k].minerals.resize(embark_assist::main::state->max_inorganic); + } + } + + embark_assist::survey::high_level_world_survey(&embark_assist::main::state->geo_summary, + &embark_assist::main::state->survey_results); + + embark_assist::main::state->match_results.resize(world->worldgen.worldgen_parms.dim_x); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + embark_assist::main::state->match_results[i].resize(world->worldgen.worldgen_parms.dim_y); + } + + embark_assist::survey::clear_results(&embark_assist::main::state->match_results); + embark_assist::survey::survey_region_sites(&embark_assist::main::state->region_sites); + embark_assist::overlay::set_sites(&embark_assist::main::state->region_sites); + + embark_assist::defs::mid_level_tiles mlt; + embark_assist::survey::survey_mid_level_tile(&embark_assist::main::state->geo_summary, &embark_assist::main::state->survey_results, &mlt); + embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->site_info, false); + embark_assist::overlay::set_embark(&embark_assist::main::state->site_info); + + return CR_OK; +} diff --git a/plugins/embark-assistant/embark-assistant.h b/plugins/embark-assistant/embark-assistant.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/plugins/embark-assistant/embark-assistant.h @@ -0,0 +1 @@ +#pragma once diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp new file mode 100644 index 000000000..3ebaa9e0f --- /dev/null +++ b/plugins/embark-assistant/finder_ui.cpp @@ -0,0 +1,1143 @@ +#include "Core.h" +#include + +#include + +#include "Types.h" + +#include "df/biome_type.h" +#include "df/inorganic_raw.h" +#include "df/material_flags.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_region_type.h" +#include "df/world_raws.h" + +#include "embark-assistant.h" +#include "finder_ui.h" +#include "screen.h" + +using df::global::world; + +namespace embark_assist { + namespace finder_ui { + + enum class fields : int8_t { + x_dim, + y_dim, + savagery_calm, + savagery_medium, + savagery_savage, + good, + neutral, + evil, + aquifer, + min_river, + max_river, + waterfall, + flat, + clay, + sand, + flux, + soil_min, + soil_min_everywhere, + soil_max, + evil_weather, + reanimation, + thralling, + biome_count_min, + biome_count_max, + region_type_1, + region_type_2, + region_type_3, + biome_1, + biome_2, + biome_3, + metal_1, + metal_2, + metal_3, + economic_1, + economic_2, + economic_3, + mineral_1, + mineral_2, + mineral_3 + }; + fields first_fields = fields::x_dim; + fields last_fields = fields::mineral_3; + + struct display_map_elements { + std::string text; + int16_t key; + }; + + typedef std::vector display_maps; + typedef std::vector name_lists; + typedef std::list< display_map_elements> sort_lists; + + struct ui_lists { + uint16_t current_display_value; // Not the value itself, but a reference to its index. + int16_t current_value; // The integer representation of the value (if an enum). + uint16_t current_index; // What's selected + uint16_t focus; // The value under the (possibly inactive) cursor + display_maps list; // The strings to be displayed together with keys + // to allow location of the actual elements (e.g. a raws.inorganics mat_index + // or underlying enum value). + }; + + typedef std::vector uis; + + const DFHack::Screen::Pen active_pen(' ', COLOR_YELLOW); + const DFHack::Screen::Pen passive_pen(' ', COLOR_DARKGREY); + const DFHack::Screen::Pen normal_pen(' ', COLOR_GREY); + const DFHack::Screen::Pen white_pen(' ', COLOR_WHITE); + const DFHack::Screen::Pen lr_pen(' ', COLOR_LIGHTRED); + + //========================================================================================================== + + struct states { + embark_assist::defs::find_callbacks find_callback; + uis ui; + display_maps finder_list; // Don't need the element key, but it's easier to use the same type. + uint16_t finder_list_focus; + bool finder_list_active; + uint16_t max_inorganic; + }; + + static states *state = 0; + + //========================================================================================================== + + bool compare(const display_map_elements& first, const display_map_elements& second) { + uint16_t i = 0; + while (i < first.text.length() && i < second.text.length()) { + if (first.text[i] < second.text[i]) { + return true; + } + else if (first.text[i] > second.text[i]) { + return false; + } + ++i; + } + return first.text.length() < second.text.length(); + } + + //========================================================================================================== + + void append(sort_lists *sort_list, display_map_elements element) { + sort_lists::iterator iterator; + for (iterator = sort_list->begin(); iterator != sort_list->end(); ++iterator) { + if (iterator->key == element.key) { + return; + } + } + sort_list->push_back(element); + } + + //========================================================================================================== + + void ui_setup(embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (!embark_assist::finder_ui::state) { + state = new(states); + state->finder_list_focus = 0; + state->finder_list_active = true; + state->find_callback = find_callback; + state->max_inorganic = max_inorganic; + } + + fields i = first_fields; + ui_lists *element; + + while (true) { + element = new ui_lists; + element->current_display_value = 0; + element->current_index = 0; + element->focus = 0; + + switch (i) { + case fields::x_dim: + for (int16_t k = 1; k < 16; k++) { + element->list.push_back({ std::to_string(k), k }); + } + + break; + + case fields::y_dim: + for (int16_t k = 1; k < 16; k++) { + element->list.push_back({ std::to_string(k), k }); + } + + break; + + case fields::savagery_calm: + case fields::savagery_medium: + case fields::savagery_savage: + case fields::good: + case fields::neutral: + case fields::evil: + { + embark_assist::defs::evil_savagery_values k = embark_assist::defs::evil_savagery_values::NA; + while (true) { + switch (k) { + case embark_assist::defs::evil_savagery_values::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::evil_savagery_values::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::aquifer: + { + embark_assist::defs::aquifer_ranges k = embark_assist::defs::aquifer_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::aquifer_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Partial: + element->list.push_back({ "Partial", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + element->list.push_back({ "Not All", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::aquifer_ranges::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::min_river: + case fields::max_river: + { + embark_assist::defs::river_ranges k = embark_assist::defs::river_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::river_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::None: + element->list.push_back({ "None", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Brook: + element->list.push_back({ "Brook", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Stream: + element->list.push_back({ "Stream", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Minor: + element->list.push_back({ "Minor", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Medium: + element->list.push_back({ "Medium", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Major: + element->list.push_back({ "Major", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::river_ranges::Major) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::waterfall: + case fields::flat: + case fields::evil_weather: + case fields::reanimation: + case fields::thralling: + { + embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::yes_no_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::yes_no_ranges::Yes: + element->list.push_back({ "Yes", static_cast(k) }); + break; + + case embark_assist::defs::yes_no_ranges::No: + element->list.push_back({ "No", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::yes_no_ranges::No) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::soil_min_everywhere: + { + embark_assist::defs::all_present_ranges k = embark_assist::defs::all_present_ranges::All; + while (true) { + switch (k) { + case embark_assist::defs::all_present_ranges::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::all_present_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::all_present_ranges::Present) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::clay: + case fields::sand: + case fields::flux: + { + embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::present_absent_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::present_absent_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::present_absent_ranges::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::present_absent_ranges::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::soil_min: + case fields::soil_max: + { + embark_assist::defs::soil_ranges k = embark_assist::defs::soil_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::soil_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::None: + element->list.push_back({ "None", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Very_Shallow: + element->list.push_back({ "Very Shallow", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Shallow: + element->list.push_back({ "Shallow", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Deep: + element->list.push_back({ "Deep", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + element->list.push_back({ "Very Deep", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::soil_ranges::Very_Deep) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::biome_count_min: + case fields::biome_count_max: + for (int16_t k = 0; k < 10; k++) { + if (k == 0) { + element->list.push_back({ "N/A", -1 }); + } + else { + element->list.push_back({ std::to_string(k), k }); + } + } + + break; + + case fields::region_type_1: + case fields::region_type_2: + case fields::region_type_3: + { + std::list name; + std::list::iterator iterator; + + FOR_ENUM_ITEMS(world_region_type, iter) { + name.push_back({ ENUM_KEY_STR(world_region_type, iter), static_cast(iter) }); + } + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::biome_1: + case fields::biome_2: + case fields::biome_3: + { + sort_lists name; + sort_lists::iterator iterator; + + FOR_ENUM_ITEMS(biome_type, iter) { + std::string s = ENUM_KEY_STR(biome_type, iter); + + if (s.substr(0, 4) != "POOL" && + s.substr(0, 5) != "RIVER" && + s.substr(0, 3) != "SUB") { + name.push_back({ s, static_cast(iter) }); + } + } + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::metal_1: + case fields::metal_2: + case fields::metal_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (uint16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + for (uint16_t l = 0; l < world->raws.inorganics[k]->metal_ore.mat_index.size(); l++) { + append(&name, { world->raws.inorganics[world->raws.inorganics[k]->metal_ore.mat_index[l]]->id, + world->raws.inorganics[k]->metal_ore.mat_index[l] }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::economic_1: + case fields::economic_2: + case fields::economic_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (int16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + if (world->raws.inorganics[k]->economic_uses.size() != 0 && + !world->raws.inorganics[k]->material.flags.is_set(df::material_flags::IS_METAL)) { + append(&name, { world->raws.inorganics[k]->id, k }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::mineral_1: + case fields::mineral_2: + case fields::mineral_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (int16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + if (world->raws.inorganics[k]->environment.location.size() != 0 || + world->raws.inorganics[k]->environment_spec.mat_index.size() != 0 || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::SEDIMENTARY) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::IGNEOUS_EXTRUSIVE) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::IGNEOUS_INTRUSIVE) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::METAMORPHIC) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::SOIL)) { + append(&name, { world->raws.inorganics[k]->id, k }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + } + + element->current_value = element->list[0].key; + + state->ui.push_back(element); + + switch (i) { + case fields::x_dim: + state->finder_list.push_back({ "X Dimension", static_cast(i) }); + break; + + case fields::y_dim: + state->finder_list.push_back({ "Y Dimension", static_cast(i) }); + break; + + case fields::savagery_calm: + state->finder_list.push_back({ "Low Savagery", static_cast(i) }); + break; + + case fields::savagery_medium: + state->finder_list.push_back({ "Medium Savagery", static_cast(i) }); + break; + + case fields::savagery_savage: + state->finder_list.push_back({ "High Savagery", static_cast(i) }); + break; + + case fields::good: + state->finder_list.push_back({ "Good", static_cast(i) }); + break; + + case fields::neutral: + state->finder_list.push_back({ "Neutral", static_cast(i) }); + break; + + case fields::evil: + state->finder_list.push_back({ "Evil", static_cast(i) }); + break; + + case fields::aquifer: + state->finder_list.push_back({ "Aquifer", static_cast(i) }); + break; + + case fields::min_river: + state->finder_list.push_back({ "Min River", static_cast(i) }); + break; + + case fields::max_river: + state->finder_list.push_back({ "Max River", static_cast(i) }); + break; + + case fields::waterfall: + state->finder_list.push_back({ "Waterfall", static_cast(i) }); + break; + + case fields::flat: + state->finder_list.push_back({ "Flat", static_cast(i) }); + break; + + case fields::soil_min_everywhere: + state->finder_list.push_back({ "Min Soil Everywhere", static_cast(i) }); + break; + + case fields::evil_weather: + state->finder_list.push_back({ "Evil Weather", static_cast(i) }); + break; + + case fields::reanimation: + state->finder_list.push_back({ "Reanimation", static_cast(i) }); + break; + + case fields::thralling: + state->finder_list.push_back({ "Thralling", static_cast(i) }); + break; + + case fields::clay: + state->finder_list.push_back({ "Clay", static_cast(i) }); + break; + + case fields::sand: + state->finder_list.push_back({ "Sand", static_cast(i) }); + break; + + case fields::flux: + state->finder_list.push_back({ "Flux", static_cast(i) }); + break; + + case fields::soil_min: + state->finder_list.push_back({ "Min Soil", static_cast(i) }); + break; + + case fields::soil_max: + state->finder_list.push_back({ "Max Soil", static_cast(i) }); + break; + + case fields::biome_count_min: + state->finder_list.push_back({ "Min Biome Count", static_cast(i) }); + break; + + case fields::biome_count_max: + state->finder_list.push_back({ "Max Biome Count", static_cast(i) }); + break; + + case fields::region_type_1: + state->finder_list.push_back({ "Region Type 1", static_cast(i) }); + break; + + case fields::region_type_2: + state->finder_list.push_back({ "Region Type 2", static_cast(i) }); + break; + + case fields::region_type_3: + state->finder_list.push_back({ "Region Type 3", static_cast(i) }); + break; + + case fields::biome_1: + state->finder_list.push_back({ "Biome 1", static_cast(i) }); + break; + + case fields::biome_2: + state->finder_list.push_back({ "Biome 2", static_cast(i) }); + break; + + case fields::biome_3: + state->finder_list.push_back({ "Biome 3", static_cast(i) }); + break; + + case fields::metal_1: + state->finder_list.push_back({ "Metal 1", static_cast(i) }); + break; + + case fields::metal_2: + state->finder_list.push_back({ "Metal 2", static_cast(i) }); + break; + + case fields::metal_3: + state->finder_list.push_back({ "Metal 3", static_cast(i) }); + break; + + case fields::economic_1: + state->finder_list.push_back({ "Economic 1", static_cast(i) }); + break; + + case fields::economic_2: + state->finder_list.push_back({ "Economic 2", static_cast(i) }); + break; + + case fields::economic_3: + state->finder_list.push_back({ "Economic 3", static_cast(i) }); + break; + + case fields::mineral_1: + state->finder_list.push_back({ "Mineral 1", static_cast(i) }); + break; + + case fields::mineral_2: + state->finder_list.push_back({ "Mineral 2", static_cast(i) }); + break; + + case fields::mineral_3: + state->finder_list.push_back({ "Mineral 3", static_cast(i) }); + break; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + // Default embark area size to that of the current selection. The "size" calculation is actually one + // off to compensate for the list starting with 1 at index 0. + // + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + state->ui[static_cast(fields::x_dim)]->current_display_value = + Gui::getViewscreenByType(0)->location.embark_pos_max.x - + Gui::getViewscreenByType(0)->location.embark_pos_min.x; + state->ui[static_cast(fields::x_dim)]->current_index = + state->ui[static_cast(fields::x_dim)]->current_display_value; + state->ui[static_cast(fields::x_dim)]->current_value = + state->ui[static_cast(fields::x_dim)]->current_display_value + 1; + + state->ui[static_cast(fields::y_dim)]->current_display_value = + Gui::getViewscreenByType(0)->location.embark_pos_max.y - + Gui::getViewscreenByType(0)->location.embark_pos_min.y; + state->ui[static_cast(fields::y_dim)]->current_index = + state->ui[static_cast(fields::y_dim)]->current_display_value; + state->ui[static_cast(fields::y_dim)]->current_value = + state->ui[static_cast(fields::y_dim)]->current_display_value + 1; + } + + + //========================================================================================================== + + void find() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::defs::finders finder; + fields i = first_fields; + + while (true) { + switch (i) { + case fields::x_dim: + finder.x_dim = state->ui[static_cast(i)]->current_value; + break; + + case fields::y_dim: + finder.y_dim = state->ui[static_cast(i)]->current_value; + break; + + case fields::savagery_calm: + finder.savagery[0] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::savagery_medium: + finder.savagery[1] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + case fields::savagery_savage: + finder.savagery[2] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::good: + finder.evilness[0] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::neutral: + finder.evilness[1] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::evil: + finder.evilness[2] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::aquifer: + finder.aquifer = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::min_river: + finder.min_river = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::max_river: + finder.max_river = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::waterfall: + finder.waterfall = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::flat: + finder.flat = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_min_everywhere: + finder.soil_min_everywhere = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::evil_weather: + finder.evil_weather = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::reanimation: + finder.reanimation = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::thralling: + finder.thralling = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::clay: + finder.clay = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::sand: + finder.sand = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::flux: + finder.flux = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_min: + finder.soil_min = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_max: + finder.soil_max = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::biome_count_min: + finder.biome_count_min = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_count_max: + finder.biome_count_max = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_1: + finder.region_type_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_2: + finder.region_type_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_3: + finder.region_type_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_1: + finder.biome_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_2: + finder.biome_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_3: + finder.biome_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_1: + finder.metal_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_2: + finder.metal_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_3: + finder.metal_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_1: + finder.economic_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_2: + finder.economic_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_3: + finder.economic_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_1: + finder.mineral_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_2: + finder.mineral_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_3: + finder.mineral_3 = state->ui[static_cast(i)]->current_value; + break; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + state->find_callback(finder); + } + + //========================================================================================================== + + class ViewscreenFindUi : public dfhack_viewscreen + { + public: + ViewscreenFindUi::ViewscreenFindUi(); + + void feed(std::set *input); + + void render(); + + std::string getFocusString() { return "Finder UI"; } + + private: + }; + + //=============================================================================== + + void ViewscreenFindUi::feed(std::set *input) { + if (input->count(df::interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + + } else if (input->count(df::interface_key::CURSOR_LEFT) || + input->count(df::interface_key::CURSOR_RIGHT)) { + state->finder_list_active = !state->finder_list_active; + + } else if (input->count(df::interface_key::CURSOR_UP)) { + if (state->finder_list_active) { + if (state->finder_list_focus > 0) { + state->finder_list_focus--; + } + else { + state->finder_list_focus = static_cast(last_fields); + } + } + else { + if (state->ui[state->finder_list_focus]->current_index > 0) { + state->ui[state->finder_list_focus]->current_index--; + } else { + state->ui[state->finder_list_focus]->current_index = static_cast(state->ui[state->finder_list_focus]->list.size()) - 1; + } + } + + } else if (input->count(df::interface_key::CURSOR_DOWN)) { + if (state->finder_list_active) { + if (state->finder_list_focus < static_cast(last_fields)) { + state->finder_list_focus++; + } else { + state->finder_list_focus = 0; + } + } + else { + if (state->ui[state->finder_list_focus]->current_index < state->ui[state->finder_list_focus]->list.size() - 1) { + state->ui[state->finder_list_focus]->current_index++; + } else { + state->ui[state->finder_list_focus]->current_index = 0; + } + } + } else if (input->count(df::interface_key::SELECT)) { + if (!state->finder_list_active) { + state->ui[state->finder_list_focus]->current_display_value = state->ui[state->finder_list_focus]->current_index; + state->ui[state->finder_list_focus]->current_value = state->ui[state->finder_list_focus]->list[state->ui[state->finder_list_focus]->current_index].key; + state->finder_list_active = true; + } + } else if (input->count(df::interface_key::CUSTOM_F)) { + input->clear(); + Screen::dismiss(this); + find(); + return; + } + } + + //=============================================================================== + + void ViewscreenFindUi::render() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen_size = DFHack::Screen::getWindowSize(); + const int list_column = 53; + uint16_t offset = 0; + + Screen::clear(); + Screen::drawBorder("Embark Assistant Site Finder"); + + embark_assist::screen::paintString(lr_pen, 1, 1, "4/6"); + embark_assist::screen::paintString(white_pen, 4, 1, ":Shift list"); + embark_assist::screen::paintString(lr_pen, 16, 1, "8/2"); + embark_assist::screen::paintString(white_pen, 19, 1, ":Up/down"); + embark_assist::screen::paintString(lr_pen, 28, 1, "ENTER"); + embark_assist::screen::paintString(white_pen, 33, 1, ":Select item"); + embark_assist::screen::paintString(lr_pen, 46, 1, "f"); + embark_assist::screen::paintString(white_pen, 47, 1, ":Find"); + embark_assist::screen::paintString(lr_pen, 53, 1, "ESC"); + embark_assist::screen::paintString(white_pen, 56, 1, ":Abort"); + + for (uint16_t i = 0; i < state->finder_list.size(); i++) { + if (i == state->finder_list_focus) { + if (state->finder_list_active) { + embark_assist::screen::paintString(active_pen, 1, 2 + i, state->finder_list[i].text); + } + else { + embark_assist::screen::paintString(passive_pen, 1, 2 + i, state->finder_list[i].text); + } + + embark_assist::screen::paintString(active_pen, + 21, + 2 + i, + state->ui[i]->list[state->ui[i]->current_display_value].text); + } + else { + embark_assist::screen::paintString(normal_pen, 1, 2 + i, state->finder_list[i].text); + + embark_assist::screen::paintString(white_pen, + 21, + 2 + i, + state->ui[i]->list[state->ui[i]->current_display_value].text); + } + + } + + // Implement scrolling lists if they don't fit on the screen. + if (state->ui[state->finder_list_focus]->list.size() > screen_size.y - 3) { + offset = (screen_size.y - 3) / 2; + if (state->ui[state->finder_list_focus]->current_index < offset) { + offset = 0; + } + else { + offset = state->ui[state->finder_list_focus]->current_index - offset; + } + + if (state->ui[state->finder_list_focus]->list.size() - offset < screen_size.y - 3) { + offset = static_cast(state->ui[state->finder_list_focus]->list.size()) - (screen_size.y - 3); + } + } + + for (uint16_t i = 0; i < state->ui[state->finder_list_focus]->list.size(); i++) { + if (i == state->ui[state->finder_list_focus]->current_index) { + if (!state->finder_list_active) { // Negated expression to get the display lines in the same order as above. + embark_assist::screen::paintString(active_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + else { + embark_assist::screen::paintString(passive_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + } + else { + embark_assist::screen::paintString(normal_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + } + + dfhack_viewscreen::render(); + } + + //=============================================================================== + + ViewscreenFindUi::ViewscreenFindUi() { + } + + //=============================================================================== + + void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { + if (!state) { // First call. Have to do the setup + ui_setup(find_callback, max_inorganic); + } + Screen::show(new ViewscreenFindUi(), plugin_self); + } + + //=============================================================================== + + void embark_assist::finder_ui::activate() { + } + + //=============================================================================== + + void embark_assist::finder_ui::shutdown() { + if (embark_assist::finder_ui::state) { + for (uint16_t i = 0; i < state->ui.size(); i++) { + delete state->ui[i]; + } + + delete state; + state = nullptr; + } + } + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/finder_ui.h b/plugins/embark-assistant/finder_ui.h new file mode 100644 index 000000000..70bf4ce42 --- /dev/null +++ b/plugins/embark-assistant/finder_ui.h @@ -0,0 +1,17 @@ +#pragma once + +#include "PluginManager.h" + +#include "DataDefs.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace finder_ui { + void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic); + void activate(); + void shutdown(); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp new file mode 100644 index 000000000..e1830883a --- /dev/null +++ b/plugins/embark-assistant/help_ui.cpp @@ -0,0 +1,314 @@ +#include "Core.h" +#include + +#include +#include +#include + +#include "Types.h" + +#include "help_ui.h" +#include "screen.h" + +using std::vector; + +namespace embark_assist{ + namespace help_ui { + enum class pages { + Intro, + General, + Finder, + Caveats + }; + + class ViewscreenHelpUi : public dfhack_viewscreen + { + public: + ViewscreenHelpUi::ViewscreenHelpUi(); + + void feed(std::set *input); + + void render(); + + std::string getFocusString() { return "Help UI"; } + + private: + pages current_page = pages::Intro; + }; + + //=============================================================================== + + void ViewscreenHelpUi::feed(std::set *input) { + if (input->count(df::interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + else if (input->count(df::interface_key::CHANGETAB)) { + switch (current_page) { + case pages::Intro: + current_page = pages::General; + break; + + case pages::General: + current_page = pages::Finder; + break; + + case pages::Finder: + current_page = pages::Caveats; + break; + + case pages::Caveats: + current_page = pages::Intro; + break; + } + } + else if (input->count(df::interface_key::SEC_CHANGETAB)) { + switch (current_page) { + case pages::Intro: + current_page = pages::Caveats; + break; + + case pages::General: + current_page = pages::Intro; + break; + + case pages::Finder: + current_page = pages::General; + break; + + case pages::Caveats: + current_page = pages::Intro; + break; + } + } + } + + //=============================================================================== + + void ViewscreenHelpUi::render() { + color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen_size = DFHack::Screen::getWindowSize(); + Screen::Pen pen(' ', COLOR_WHITE); + Screen::Pen site_pen = Screen::Pen(' ', COLOR_YELLOW, COLOR_BLACK, false); + Screen::Pen pen_lr(' ', COLOR_LIGHTRED); + + std::vector help_text; + + Screen::clear(); + + switch (current_page) { + case pages::Intro: + Screen::drawBorder("Embark Assistant Help/Info Introduction Page"); + + help_text.push_back("Embark Assistant is used on the embark selection screen to provide information"); + help_text.push_back("to help selecting a suitable embark site. It provides three services:"); + help_text.push_back("- Display of normally invisible sites overlayed on the region map."); + help_text.push_back("- Embark rectangle resources. More detailed and correct than vanilla DF."); + help_text.push_back("- Site find search. Richer set of selection criteria than the vanilla"); + help_text.push_back(" DF Find that Embark Assistant suppresses (by using the same key)."); + help_text.push_back(""); + help_text.push_back("The functionality requires a screen height of at least 42 lines to display"); + help_text.push_back("correctly (that's the height of the Finder screen), as fitting everything"); + help_text.push_back("onto a standard 80*25 screen would be too challenging. The help is adjusted"); + help_text.push_back("to fit into onto an 80*42 screen."); + help_text.push_back("This help/info is split over several screens, and you can move between them"); + help_text.push_back("using the TAB/Shift-TAB keys, and leave the help from any screen using ESC."); + help_text.push_back(""); + help_text.push_back("When the Embark Assistant is started it provides site information (if any)"); + help_text.push_back("as an overlay over the region map. Beneath that you will find a summary of"); + help_text.push_back("the resources in the current embark rectangle (explained in more detail on"); + help_text.push_back("the the next screen)."); + help_text.push_back("On the right side the command keys the Embark Assistant uses are listed"); + help_text.push_back("(this partially overwrites the DFHack Embark Tools information until the"); + help_text.push_back("screen is resized). It can also be mentioned that the DF 'f'ind key help"); + help_text.push_back("at the bottom of the screen is masked as the functionality is overridden by"); + help_text.push_back("the Embark Assistant."); + help_text.push_back("Main screen control keys used by the Embark Assistant:"); + help_text.push_back("i: Info/Help. Brings up this display."); + help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information."); + help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if"); + help_text.push_back(" one is under way."); + help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface."); + help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself"); + help_text.push_back(" when DF leaves the embark screen either through Abort Game or by"); + help_text.push_back(" embarking."); + help_text.push_back("Below this a Matching World Tiles count is displayed. It shows the number"); + help_text.push_back("of World Tiles that have at least one embark matching the Find criteria."); + + break; + + case pages::General: + Screen::drawBorder("Embark Assistant Help/Info General Page"); + + help_text.push_back("The Embark Assistant overlays the region map with characters indicating sites"); + help_text.push_back("normally not displayed by DF. The following key is used:"); + help_text.push_back("C: Camp"); + help_text.push_back("c: Cave. Only displayed if the DF worldgen parameter does not display caves."); + help_text.push_back("i: Important Location. The author doesn't actually know what those are."); + help_text.push_back("l: Lair"); + help_text.push_back("L: Labyrinth"); + help_text.push_back("M: Monument. The author is unsure how/if this is broken down further."); + help_text.push_back("S: Shrine"); + help_text.push_back("V: Vault"); + help_text.push_back("The Embark info below the region map differs from the vanilla DF display in a"); + help_text.push_back("few respects. Firstly, it shows resources in the embark rectangle, rather than"); + help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the"); + help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth"); + help_text.push_back("into consideration, so it can display resources that actually are cut away."); + help_text.push_back("(It can be noted that the DFHack Sand indicator does take these elements into"); + help_text.push_back("account)."); + help_text.push_back("The info the Embark Assistant displays is:"); + help_text.push_back("Sand, if present"); + help_text.push_back("Clay, if present"); + help_text.push_back("Min and Max soil depth in the embark rectangle."); + help_text.push_back("Flat indicator if all the tiles in the embark have the same elevation."); + help_text.push_back("Aquifer indicator, color coded as blue if all tiles have an aquifer and light"); + help_text.push_back("blue if some, but not all, tiles have one."); + help_text.push_back("Waterfall, if the embark has river elevation differences."); + help_text.push_back("Flux, if present"); + help_text.push_back("A list of all metals present in the embark."); + help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux"); + help_text.push_back("stones are economic, so they show up here as well."); + help_text.push_back("In addition to the above, the Find functionality can also produce blinking"); + help_text.push_back("overlays over the region map and the middle world map to indicate where"); + help_text.push_back("matching embarks are found. The region display marks the top left corner of"); + help_text.push_back("a matching embark rectangle as a matching tile."); + + break; + + case pages::Finder: + Screen::drawBorder("Embark Assistant Help/Info Find Page"); + + help_text.push_back("The Embark Assist Finder page is brought up with the f command key."); + help_text.push_back("The top of the Finder page lists the command keys available on the page:"); + help_text.push_back("4/6 or horizontal arrow keys to move between the element and element values."); + help_text.push_back("8/2 or vertical arrow keys to move up/down in the current list."); + help_text.push_back("ENTER to select a value in the value list, entering it among the selections."); + help_text.push_back("f to activate the Find functionality using the values in the middle column."); + help_text.push_back("ESC to leave the screen without activating a Find operation."); + help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF"); + help_text.push_back("itself these parameters are initiated to match the actual embark rectangle"); + help_text.push_back("when a new search is initiated (prior results are cleared."); + help_text.push_back("The 6 Savagery and Evilness parameters takes some getting used to. They"); + help_text.push_back("allow for searching for embarks with e.g. has both Good and Evil tiles."); + help_text.push_back("All as a parameter means every embark tile has to have this value."); + help_text.push_back("Present means at least one embark tile has to have this value."); + help_text.push_back("Absent means the feature mustn't exist in any of the embark tiles."); + help_text.push_back("N/A means no restrictions are applied."); + help_text.push_back("The Aquifer criterion introduces some new parameters:"); + help_text.push_back("Partial means at least one tile has to have an aquifer, but it also has"); + help_text.push_back("to be absent from at least one tile. Not All means an aquifer is tolerated"); + help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have"); + help_text.push_back("any at all."); + help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of"); + help_text.push_back("Waterfall, Flat, etc. means one has to be Present and Absent respectivey."); + help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil"); + help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and"); + help_text.push_back("and Present."); + help_text.push_back("The parameters for biomes, regions, etc. all require that the required"); + help_text.push_back("feature is Present in the embark, and entering the same value multiple"); + help_text.push_back("times does nothing (the first match ticks all requirements off). It can be"); + help_text.push_back("noted that all the Economic materials are found in the much longer Mineral"); + help_text.push_back("list. Note that Find is a fairly time consuming task (is it is in vanilla)."); + break; + + case pages::Caveats: + Screen::drawBorder("Embark Assistant Help/Info Caveats Page"); + + help_text.push_back("Find searching first does a sanity check (e.g. max < min) and then a rough"); + help_text.push_back("world tile match to find tiles that may have matching embarks. This results"); + help_text.push_back("in an overlay of inverted yellow X on top of the middle world map. Then"); + help_text.push_back("those tiles are scanned in detail, one feature shell (16*16 world tile"); + help_text.push_back("block) at a time, and the results are displayed as green inverted X on"); + help_text.push_back("the same map (replacing or erasing the yellow ones). region map overlay"); + help_text.push_back("data is generated as well."); + help_text.push_back(""); + help_text.push_back("Caveats & technical stuff:"); + help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it"); + help_text.push_back(" to load feature shells and detailed region data, and this costs the"); + help_text.push_back(" least when done one feature shell at a time."); + help_text.push_back("- The search strategy causes detailed region data to update surveyed"); + help_text.push_back(" world info, and this can cause a subsequent search to generate a smaller"); + help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search."); + help_text.push_back(" However, this is a bug only if it causes the search to fail to find"); + help_text.push_back(" actual existing matches."); + help_text.push_back("- The site info is deduced by the author, so there may be errors and"); + help_text.push_back(" there are probably site types that end up not being identified."); + help_text.push_back("- Aquifer indications are based on the author's belief that they occur"); + help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or"); + help_text.push_back(" more."); + help_text.push_back("- The biome determination logic comes from code provided by Ragundo,"); + help_text.push_back(" with only marginal changes by the author. References can be found in"); + help_text.push_back(" the source file."); + help_text.push_back("- Thralling is determined by weather material interactions causing"); + help_text.push_back(" blinking, which the author believes is one of 4 thralling changes."); + help_text.push_back("- The geo information is gathered by code which is essentially a"); + help_text.push_back(" copy of parts of prospector's code adapted for this plugin."); + help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS."); + help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); + help_text.push_back("- Right world map overlay not implemented as author has failed to"); + help_text.push_back(" emulate the sizing logic exactly."); + help_text.push_back("Version 0.1 2017-08-30"); + + break; + } + + // Add control keys to first line. + embark_assist::screen::paintString(pen_lr, 1, 1, "TAB/Shift-TAB"); + embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page"); + embark_assist::screen::paintString(pen_lr, 34, 1, "ESC"); + embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help"); + + for (uint16_t i = 0; i < help_text.size(); i++) { + embark_assist::screen::paintString(pen, 1, 2 + i, help_text[i]); + } + + switch (current_page) { + case pages::Intro: + embark_assist::screen::paintString(pen_lr, 1, 26, "i"); + embark_assist::screen::paintString(pen_lr, 1, 27, "f"); + embark_assist::screen::paintString(pen_lr, 1, 28, "c"); + embark_assist::screen::paintString(pen_lr, 1, 30, "q"); + break; + + case pages::General: + embark_assist::screen::paintString(site_pen, 1, 4, "C"); + embark_assist::screen::paintString(site_pen, 1, 5, "c"); + embark_assist::screen::paintString(site_pen, 1, 6, "i"); + embark_assist::screen::paintString(site_pen, 1, 7, "l"); + embark_assist::screen::paintString(site_pen, 1, 8, "L"); + embark_assist::screen::paintString(site_pen, 1, 9, "M"); + embark_assist::screen::paintString(site_pen, 1, 10, "S"); + embark_assist::screen::paintString(site_pen, 1, 11, "V"); + break; + + case pages::Finder: + embark_assist::screen::paintString(pen_lr, 1, 4, "4/6"); + embark_assist::screen::paintString(pen_lr, 1, 5, "8/2"); + embark_assist::screen::paintString(pen_lr, 1, 6, "ENTER"); + embark_assist::screen::paintString(pen_lr, 1, 7, "f"); + embark_assist::screen::paintString(pen_lr, 1, 8, "ESC"); + break; + + case pages::Caveats: + break; + } + dfhack_viewscreen::render(); + } + + //=============================================================================== + + ViewscreenHelpUi::ViewscreenHelpUi() { + } + + //=============================================================================== + // Exported operations + //=============================================================================== + + void init(DFHack::Plugin *plugin_self) { + Screen::show(new ViewscreenHelpUi(), plugin_self); + } + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/help_ui.h b/plugins/embark-assistant/help_ui.h new file mode 100644 index 000000000..65a2e4f1d --- /dev/null +++ b/plugins/embark-assistant/help_ui.h @@ -0,0 +1,15 @@ +#pragma once + +#include "PluginManager.h" + +#include "DataDefs.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace help_ui { + void init(DFHack::Plugin *plugin_self); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp new file mode 100644 index 000000000..b4d3f438e --- /dev/null +++ b/plugins/embark-assistant/matcher.cpp @@ -0,0 +1,1445 @@ +#include + +#include + +#include "DataDefs.h" +#include "df/biome_type.h" +#include "df/inorganic_raw.h" +#include "df/region_map_entry.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_raws.h" +#include "df/world_region.h" +#include "df/world_region_type.h" + +#include "matcher.h" +#include "survey.h" + +using df::global::world; + +namespace embark_assist { + namespace matcher { + + //======================================================================================= + + //======================================================================================= + + bool embark_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + uint16_t start_x, + uint16_t start_y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + bool savagery_found[3] = { false, false, false }; + bool evilness_found[3] = { false, false, false }; + uint16_t aquifer_count = 0; + bool river_found = false; + bool waterfall_found = false; + uint16_t river_elevation; + uint16_t elevation = mlt->at(start_x).at(start_y).elevation; + bool clay_found = false; + bool sand_found = false; + bool flux_found = false; + uint8_t max_soil = 0; + bool uneven = false; + bool evil_weather_found = false; + bool reanimation_found = false; + bool thralling_found = false; + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + bool region_types[ENUM_LAST_ITEM(world_region_type) + 1]; + uint8_t biome_count; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + const uint16_t embark_size = finder->x_dim * finder->y_dim; + + if (finder->biome_count_min != -1 || + finder->biome_count_max != -1 || + finder->biome_1 != -1 || + finder->biome_2 != -1 || + finder->biome_3 != -1) { + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) biomes[i] = false; + } + + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(world_region_type); i++) region_types[i] = false; + + for (uint16_t i = start_x; i < start_x + finder->x_dim; i++) { + for (uint16_t k = start_y; k < start_y + finder->y_dim; k++) { + + // Savagery & Evilness + { + savagery_found[mlt->at(i).at(k).savagery_level] = true; + evilness_found[mlt->at(i).at(k).evilness_level] = true; + + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + while (true) { + if (mlt->at(i).at(k).savagery_level == static_cast(l)) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) return false; + } + else { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) return false; + } + + if (mlt->at(i).at(k).evilness_level == static_cast(l)) { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) return false; + } + else { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) return false; + } + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; + + case embark_assist::defs::aquifer_ranges::All: + if (!mlt->at(i).at(k).aquifer) return false; + aquifer_count++; + break; + + case embark_assist::defs::aquifer_ranges::Present: + case embark_assist::defs::aquifer_ranges::Partial: + case embark_assist::defs::aquifer_ranges::Not_All: + if (mlt->at(i).at(k).aquifer) aquifer_count++; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (mlt->at(i).at(k).aquifer) return false; + break; + } + + // River & Waterfall + if (mlt->at(i).at(k).river_present) { + // Actual size values were checked on the world tile level for min rivers + if (finder->max_river != embark_assist::defs::river_ranges::NA && + finder->max_river < static_cast(survey_results->at(x).at(y).river_size)) return false; + + if (river_found && river_elevation != mlt->at(i).at(k).river_elevation) { + if (finder->waterfall == embark_assist::defs::yes_no_ranges::No) return false; + waterfall_found = true; + } + river_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; + } + + // Flat + if (finder->flat == embark_assist::defs::yes_no_ranges::Yes && + elevation != mlt->at(i).at(k).elevation) return false; + + if (elevation != mlt->at(i).at(k).elevation) uneven = true; + + // Clay + if (mlt->at(i).at(k).clay) { + if (finder->clay == embark_assist::defs::present_absent_ranges::Absent) return false; + clay_found = true; + } + + // Sand + if (mlt->at(i).at(k).sand) { + if (finder->sand == embark_assist::defs::present_absent_ranges::Absent) return false; + sand_found = true; + } + + // Flux + if (mlt->at(i).at(k).flux) { + if (finder->flux == embark_assist::defs::present_absent_ranges::Absent) return false; + flux_found = true; + } + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + mlt->at(i).at(k).soil_depth < static_cast(finder->soil_min) && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::All) return false; + + if (max_soil < mlt->at(i).at(k).soil_depth) { + max_soil = mlt->at(i).at(k).soil_depth; + } + + // Max Soil + if (finder->soil_max != embark_assist::defs::soil_ranges::NA && + mlt->at(i).at(k).soil_depth > static_cast(finder->soil_max)) return false; + + // Evil Weather + if (survey_results->at(x).at(y).evil_weather[mlt->at(i).at(k).biome_offset]) { + if (finder->evil_weather == embark_assist::defs::yes_no_ranges::No) return false; + evil_weather_found = true; + } + + // Reanmation + if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) { + if (finder->reanimation == embark_assist::defs::yes_no_ranges::No) return false; + reanimation_found = true; + } + + // Thralling + if (survey_results->at(x).at(y).thralling[mlt->at(i).at(k).biome_offset]) { + if (finder->thralling == embark_assist::defs::yes_no_ranges::No) return false; + thralling_found = true; + } + + // Biomes + biomes[survey_results->at(x).at(y).biome[mlt->at(i).at(k).biome_offset]] = true; + + // Region Type + region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset]]->type] = true; + + // Metals + metal_1 = metal_1 || mlt->at(i).at(k).metals[finder->metal_1]; + metal_2 = metal_2 || mlt->at(i).at(k).metals[finder->metal_2]; + metal_3 = metal_3 || mlt->at(i).at(k).metals[finder->metal_3]; + + // Economics + economic_1 = economic_1 || mlt->at(i).at(k).economics[finder->economic_1]; + economic_2 = economic_2 || mlt->at(i).at(k).economics[finder->economic_2]; + economic_3 = economic_3 || mlt->at(i).at(k).economics[finder->economic_3]; + + // Minerals + mineral_1 = mineral_1 || mlt->at(i).at(k).minerals[finder->mineral_1]; + mineral_2 = mineral_2 || mlt->at(i).at(k).minerals[finder->mineral_2]; + mineral_3 = mineral_3 || mlt->at(i).at(k).minerals[finder->mineral_3]; + } + } + + // Summary section, for all the stuff that require the complete picture + // + // Savagery & Evilness + { + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + + while (true) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Present && + !savagery_found[static_cast(l)]) return false; + + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Present && + !evilness_found[static_cast(l)]) return false; + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + case embark_assist::defs::aquifer_ranges::All: // Checked above + case embark_assist::defs::aquifer_ranges::Absent: // Ditto + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (aquifer_count == 0 || aquifer_count == embark_size) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (aquifer_count == embark_size) return false; + break; + } + + // River & Waterfall + if (!river_found && finder->min_river > embark_assist::defs::river_ranges::None) return false; + if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && !waterfall_found) return false; + + // Flat + if (!uneven && finder->flat == embark_assist::defs::yes_no_ranges::No) return false; + + // Clay + if (finder->clay == embark_assist::defs::present_absent_ranges::Present && !clay_found) return false; + + // Sand + if (finder->sand == embark_assist::defs::present_absent_ranges::Present && !sand_found) return false; + + // Flux + if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false; + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && + max_soil < static_cast(finder->soil_min)) return false; + + // Evil Weather + if (finder->evil_weather == embark_assist::defs::yes_no_ranges::Yes && !evil_weather_found) return false; + + // Reanimation + if (finder->reanimation == embark_assist::defs::yes_no_ranges::Yes && !reanimation_found) return false; + + // Thralling + if (finder->thralling == embark_assist::defs::yes_no_ranges::Yes && !thralling_found) return false; + + // Biomes + if (finder->biome_count_min != -1 || + finder->biome_count_max != -1) { + biome_count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) biome_count++; + } + + if (biome_count < finder->biome_count_min || + (finder->biome_count_max != -1 && + finder->biome_count_max < biome_count)) return false; + } + + if (finder->biome_1 != -1 && !biomes[finder->biome_1]) return false; + if (finder->biome_2 != -1 && !biomes[finder->biome_2]) return false; + if (finder->biome_3 != -1 && !biomes[finder->biome_3]) return false; + + // Region Type + if (finder->region_type_1 != -1 && !region_types[finder->region_type_1]) return false; + if (finder->region_type_2 != -1 && !region_types[finder->region_type_2]) return false; + if (finder->region_type_3 != -1 && !region_types[finder->region_type_3]) return false; + + // Metals, Economics, and Minerals + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + + return true; + } + + //======================================================================================= + + void mid_level_tile_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + bool match = false; + + for (uint16_t i = 0; i < 16; i++) { + for (uint16_t k = 0; k < 16; k++) { + if (i < 16 - finder->x_dim + 1 && k < 16 - finder->y_dim + 1) { + match_results->at(x).at(y).mlt_match[i][k] = embark_match(survey_results, mlt, x, y, i, k, finder); + match = match || match_results->at(x).at(y).mlt_match[i][k]; + } + else { + match_results->at(x).at(y).mlt_match[i][k] = false; + } + } + } + match_results->at(x).at(y).contains_match = match; + match_results->at(x).at(y).preliminary_match = false; + } + + //======================================================================================= + + bool world_tile_match(embark_assist::defs::world_tile_data *survey_results, + uint16_t x, + uint16_t y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + const uint16_t embark_size = finder->x_dim * finder->y_dim; + uint16_t count; + bool found; + + if (tile->surveyed) { + // Savagery + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->savagery[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->savagery_count[i] < embark_size) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->savagery_count[i] > 256 - embark_size) return false; + break; + } + } + + // Evilness + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->evilness[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->evilness_count[i] < embark_size) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->evilness_count[i] > 256 - embark_size) return false; + break; + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; // No restriction + + case embark_assist::defs::aquifer_ranges::All: + if (tile->aquifer_count < 256 - embark_size) return false; + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (tile->aquifer_count == 0 || + tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (tile->aquifer_count > 256 - embark_size) return false; + break; + } + + // River size. Every tile has riverless tiles, so max rivers has to be checked on the detailed level. + switch (tile->river_size) { + case embark_assist::defs::river_sizes::None: + if (finder->min_river > embark_assist::defs::river_ranges::None) return false; + break; + + case embark_assist::defs::river_sizes::Brook: + if (finder->min_river > embark_assist::defs::river_ranges::Brook) return false; + break; + + case embark_assist::defs::river_sizes::Stream: + if (finder->min_river > embark_assist::defs::river_ranges::Stream) return false; + break; + + case embark_assist::defs::river_sizes::Minor: + if (finder->min_river > embark_assist::defs::river_ranges::Minor) return false; + break; + + case embark_assist::defs::river_sizes::Medium: + if (finder->min_river > embark_assist::defs::river_ranges::Medium) return false; + break; + + case embark_assist::defs::river_sizes::Major: + if (finder->max_river != embark_assist::defs::river_ranges::NA) return false; + break; + } + + // Waterfall + switch (finder->waterfall) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->waterfall) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->waterfall && + embark_size == 256) return false; + break; + } + + // Flat. No world tile checks. Need to look at the details + + // Clay + switch (finder->clay) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->clay_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->clay_count > 256 - embark_size) return false; + break; + } + + // Sand + switch (finder->sand) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->sand_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->sand_count > 256 - embark_size) return false; + break; + } + + // Flux + switch (finder->flux) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->flux_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->flux_count > 256 - embark_size) return false; + break; + } + + // Soil Min + switch (finder->soil_min) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::None: + break; // No restriction + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->max_region_soil < 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->max_region_soil < 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->max_region_soil < 3) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + if (tile->max_region_soil < 4) return false; + break; + } + + // soil_min_everywhere only applies on the detailed level + + // Soil Max + switch (finder->soil_max) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::Very_Deep: + break; // No restriction + + case embark_assist::defs::soil_ranges::None: + if (tile->min_region_soil > 0) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->min_region_soil > 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->min_region_soil > 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->min_region_soil > 3) return false; + break; + } + + // Evil Weather + switch (finder->evil_weather) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->evil_weather_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->evil_weather_full) return false; + break; + } + + // Reanimating + switch (finder->reanimation) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->reanimating_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->reanimating_full) return false; + break; + } + + // Thralling + switch (finder->thralling) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->thralling_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->thralling_full) return false; + break; + } + + // Biome Count Min (Can't do anything with Max at this level) + if (finder->biome_count_min > tile->biome_count) return false; + + // Region Type 1 + if (finder->region_type_1 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_1) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 2 + if (finder->region_type_2 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_2) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 3 + if (finder->region_type_3 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_3) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Biome 1 + if (finder->biome_1 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_1) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 2 + if (finder->biome_2 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_2) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 3 + if (finder->biome_3 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_3) { + found = true; + break; + } + } + + if (!found) return false; + } + + if (finder->metal_1 != -1 || + finder->metal_2 != -1 || + finder->metal_3 != -1 || + finder->economic_1 != -1 || + finder->economic_2 != -1 || + finder->economic_3 != -1 || + finder->mineral_1 != -1 || + finder->mineral_2 != -1 || + finder->mineral_3 != -1) { + count = 0; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + metal_1 = metal_1 || tile->metals[finder->metal_1]; + metal_2 = metal_2 || tile->metals[finder->metal_2]; + metal_3 = metal_3 || tile->metals[finder->metal_3]; + economic_1 = economic_1 || tile->economics[finder->economic_1]; + economic_2 = economic_2 || tile->economics[finder->economic_2]; + economic_3 = economic_3 || tile->economics[finder->economic_3]; + mineral_1 = mineral_1 || tile->minerals[finder->mineral_1]; + mineral_2 = mineral_2 || tile->minerals[finder->mineral_2]; + mineral_3 = mineral_3 || tile->minerals[finder->mineral_3]; + + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + } + } + else { // Not surveyed + // Savagery + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->savagery[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->savagery_count[i] == 256) return false; + break; + } + } + + // Evilness + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->evilness[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->evilness_count[i] == 256) return false; + break; + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; // No restriction + + case embark_assist::defs::aquifer_ranges::All: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (tile->aquifer_count == 0 || + tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (tile->aquifer_count == 256) return false; + break; + } + + // River size + switch (tile->river_size) { + case embark_assist::defs::river_sizes::None: + if (finder->min_river > embark_assist::defs::river_ranges::None) return false; + break; + + case embark_assist::defs::river_sizes::Brook: + if (finder->min_river > embark_assist::defs::river_ranges::Brook) return false; + break; + + case embark_assist::defs::river_sizes::Stream: + if (finder->min_river > embark_assist::defs::river_ranges::Stream) return false; + break; + + case embark_assist::defs::river_sizes::Minor: + if (finder->min_river > embark_assist::defs::river_ranges::Minor) return false; + break; + + case embark_assist::defs::river_sizes::Medium: + if (finder->min_river > embark_assist::defs::river_ranges::Medium) return false; + break; + + case embark_assist::defs::river_sizes::Major: + if (finder->max_river != embark_assist::defs::river_ranges::NA) return false; + break; + } + + // Waterfall + if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && + tile->river_size == embark_assist::defs::river_sizes::None) return false; + + // Flat. No world tile checks. Need to look at the details + + // Clay + switch (finder->clay) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->clay_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->clay_count == 256) return false; + break; + } + + // Sand + switch (finder->sand) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->sand_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->sand_count == 256) return false; + break; + } + + // Flux + switch (finder->flux) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->flux_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->flux_count == 256) return false; + break; + } + + // Soil Min + switch (finder->soil_min) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::None: + break; // No restriction + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->max_region_soil < 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->max_region_soil < 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->max_region_soil < 3) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + if (tile->max_region_soil < 4) return false; + break; + } + + // soil_min_everywhere only applies on the detailed level + + // Soil Max + // Can't say anything as the preliminary data isn't reliable + + // Evil Weather + switch (finder->evil_weather) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->evil_weather_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->evil_weather_full) return false; + break; + } + + // Reanimating + switch (finder->reanimation) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->reanimating_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->reanimating_full) return false; + break; + } + + // Thralling + switch (finder->thralling) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->thralling_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->thralling_full) return false; + break; + } + + // Biome Count Min (Can't do anything with Max at this level) + if (finder->biome_count_min > tile->biome_count) return false; + + // Region Type 1 + if (finder->region_type_1 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_1) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 2 + if (finder->region_type_2 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_2) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 3 + if (finder->region_type_3 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_3) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Biome 1 + if (finder->biome_1 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_1) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 2 + if (finder->biome_2 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_2) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 3 + if (finder->biome_3 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_3) { + found = true; + break; + } + } + + if (!found) return false; + } + + if (finder->metal_1 != -1 || + finder->metal_2 != -1 || + finder->metal_3 != -1 || + finder->economic_1 != -1 || + finder->economic_2 != -1 || + finder->economic_3 != -1 || + finder->mineral_1 != -1 || + finder->mineral_2 != -1 || + finder->mineral_3 != -1) { + count = 0; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + metal_1 = metal_1 || tile->metals[finder->metal_1]; + metal_2 = metal_2 || tile->metals[finder->metal_2]; + metal_3 = metal_3 || tile->metals[finder->metal_3]; + economic_1 = economic_1 || tile->economics[finder->economic_1]; + economic_2 = economic_2 || tile->economics[finder->economic_2]; + economic_3 = economic_3 || tile->economics[finder->economic_3]; + mineral_1 = mineral_1 || tile->minerals[finder->mineral_1]; + mineral_2 = mineral_2 || tile->minerals[finder->mineral_2]; + mineral_3 = mineral_3 || tile->minerals[finder->mineral_3]; + + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + } + } + return true; + } + + //======================================================================================= + + uint32_t preliminary_world_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + uint32_t count = 0; + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + match_results->at(i).at(k).preliminary_match = + world_tile_match(survey_results, i, k, finder); + if (match_results->at(i).at(k).preliminary_match) count++; + match_results->at(i).at(k).contains_match = false; + } + } + + return count; + } + + //======================================================================================= + + void match_world_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results, + uint16_t x, + uint16_t y) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::defs::mid_level_tiles mlt; + + embark_assist::survey::survey_mid_level_tile(geo_summary, + survey_results, + &mlt); + + mid_level_tile_match(survey_results, + &mlt, + x, + y, + finder, + match_results); + } + + //======================================================================================= + // Visible operations + //======================================================================================= + + void move_cursor(uint16_t x, uint16_t y) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t original_x = screen->location.region_pos.x; + uint16_t original_y = screen->location.region_pos.y; + + uint16_t large_x = std::abs(original_x - x) / 10; + uint16_t small_x = std::abs(original_x - x) % 10; + uint16_t large_y = std::abs(original_y - y) / 10; + uint16_t small_y = std::abs(original_y - y) % 10; + + while (large_x > 0 || large_y > 0) { + if (large_x > 0 && large_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT_FAST); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT_FAST); + } + large_x--; + large_y--; + } + else if (large_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT_FAST); + } + large_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN_FAST); + } + large_y--; + } + } + + while (small_x > 0 || small_y > 0) { + if (small_x > 0 && small_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT); + } + small_x--; + small_y--; + } + else if (small_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + small_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + small_y--; + } + } + } + + //======================================================================================= + + uint16_t find(embark_assist::defs::match_iterators *iterator, + embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::match_results *match_results) { + + color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t x_end; + uint16_t y_end; + bool turn; + uint16_t count; + uint16_t preliminary_matches; + + if (!iterator->active) { + embark_assist::survey::clear_results(match_results); + + // Static check for impossible requirements + // + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.evilness[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } + + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All evilness requirements\n"); + return 0; + } + + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.savagery[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } + + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All savagery requirements\n"); + return 0; + } + + if (iterator->finder.max_river < iterator->finder.min_river && + iterator->finder.max_river != embark_assist::defs::river_ranges::NA) { + out.printerr("matcher::find: Will never find any due to max river < min river\n"); + return 0; + } + + if (iterator->finder.waterfall == embark_assist::defs::yes_no_ranges::Yes && + iterator->finder.max_river == embark_assist::defs::river_ranges::None) { + out.printerr("matcher::find: Will never find any waterfalls with None as max river\n"); + return 0; + } + + if (iterator->finder.soil_max < iterator->finder.soil_min && + iterator->finder.soil_max != embark_assist::defs::soil_ranges::NA) { + out.printerr("matcher::find: Will never find any matches with max soil < min soil\n"); + return 0; + } + + if (iterator->finder.biome_count_max < iterator->finder.biome_count_min && + iterator->finder.biome_count_max != -1) { + out.printerr("matcher::find: Will never find any matches with max biomes < min biomes\n"); + return 0; + } + + preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); + + if (preliminary_matches == 0) { + out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + return 0; + } + else { + out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + } + + while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + iterator->active = true; + iterator->i = 0; + iterator->k = 0; + iterator->x_right = true; + iterator->y_down = true; + iterator->inhibit_x_turn = false; + iterator->inhibit_y_turn = false; + iterator->count = 0; + } + + if ((iterator->k == world->worldgen.worldgen_parms.dim_x / 16 && iterator->x_right) || + (iterator->k == 0 && !iterator->x_right)) { + x_end = 0; + } + else { + x_end = 15; + } + + if (iterator->i == world->worldgen.worldgen_parms.dim_y / 16) { + y_end = 0; + } + else { + y_end = 15; + } + + for (uint16_t l = 0; l <= x_end; l++) { + for (uint16_t m = 0; m <= y_end; m++) { + // This is where the payload goes + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).preliminary_match) { + match_world_tile(geo_summary, + survey_results, + &iterator->finder, + match_results, + screen->location.region_pos.x, + screen->location.region_pos.y); + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).contains_match) { + iterator->count++; + } + } + else { + for (uint16_t n = 0; n < 16; n++) { + for (uint16_t p = 0; p < 16; p++) { + match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match[n][p] = false; + } + } + } + // End of payload section + + if (m != y_end) { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } + } + else { + if (screen->location.region_pos.x != 0 && + screen->location.region_pos.x != world->worldgen.worldgen_parms.dim_x - 1) { + turn = true; + } + else { + iterator->inhibit_y_turn = !iterator->inhibit_y_turn; + turn = iterator->inhibit_y_turn; + } + + if (turn) { + iterator->y_down = !iterator->y_down; + } + else { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } + } + } + } + + if (iterator->x_right) { // Won't do anything at the edge, so we don't bother filter those cases. + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + + if (!iterator->x_right && + screen->location.region_pos.x == 0) { + turn = !turn; + + if (turn) { + iterator->x_right = true; + } + } + else if (iterator->x_right && + screen->location.region_pos.x == world->worldgen.worldgen_parms.dim_x - 1) { + turn = !turn; + + if (turn) { + iterator->x_right = false; + } + } + } + // } + + iterator->k++; + if (iterator->k > world->worldgen.worldgen_parms.dim_x / 16) + { + iterator->k = 0; + iterator->i++; + iterator->active = !(iterator->i > world->worldgen.worldgen_parms.dim_y / 16); + + if (!iterator->active) { + move_cursor(iterator->x, iterator->y); + } + } + + return iterator->count; + } + } +} diff --git a/plugins/embark-assistant/matcher.h b/plugins/embark-assistant/matcher.h new file mode 100644 index 000000000..9887ffbd0 --- /dev/null +++ b/plugins/embark-assistant/matcher.h @@ -0,0 +1,21 @@ +#pragma once + +#include "DataDefs.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace matcher { + void move_cursor(uint16_t x, uint16_t y); + + // Used to iterate over the whole world to generate a map of world tiles + // that contain matching embarks. + // + uint16_t find(embark_assist::defs::match_iterators *iterator, + embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::match_results *match_results); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp new file mode 100644 index 000000000..6b63fa70d --- /dev/null +++ b/plugins/embark-assistant/overlay.cpp @@ -0,0 +1,439 @@ +#include + +#include "df/coord2d.h" +#include "df/inorganic_raw.h" +#include "df/dfhack_material_category.h" +#include "df/interface_key.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_raws.h" + +#include "finder_ui.h" +#include "help_ui.h" +#include "overlay.h" +#include "screen.h" + +using df::global::world; + +namespace embark_assist { + namespace overlay { + DFHack::Plugin *plugin_self; + const Screen::Pen empty_pen = Screen::Pen('\0', COLOR_YELLOW, COLOR_BLACK, false); + const Screen::Pen yellow_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_YELLOW, false); + const Screen::Pen green_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_GREEN, false); + + struct display_strings { + Screen::Pen pen; + std::string text; + }; + + typedef Screen::Pen *pen_column; + + struct states { + int blink_count = 0; + bool show = true; + + bool matching = false; + bool match_active = false; + + embark_update_callbacks embark_update; + match_callbacks match_callback; + clear_match_callbacks clear_match_callback; + embark_assist::defs::find_callbacks find_callback; + shutdown_callbacks shutdown_callback; + + Screen::Pen site_grid[16][16]; + uint8_t current_site_grid = 0; + + std::vector embark_info; + + Screen::Pen region_match_grid[16][16]; + + pen_column *world_match_grid = nullptr; + uint16_t match_count = 0; + + uint16_t max_inorganic; + }; + + static states *state = nullptr; + + //==================================================================== + +/* // Attempt to replicate the DF logic for sizing the right world map. This + // code seems to compute the values correctly, but the author hasn't been + // able to apply them at the same time as DF does to 100%. + // DF seems to round down on 0.5 values. + df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) { + uint16_t result; + + for (uint16_t factor = 1; factor < 17; factor++) { + result = map_size / factor; + if ((map_size - result * factor) * 2 != factor) { + result = (map_size + factor / 2) / factor; + } + + if (result <= available_screen) { + return {result, factor}; + } + } + return{16, 16}; // Should never get here. + } +*/ + //==================================================================== + + class ViewscreenOverlay : public df::viewscreen_choose_start_sitest + { + public: + typedef df::viewscreen_choose_start_sitest interpose_base; + + void send_key(const df::interface_key &key) + { + std::set< df::interface_key > keys; + keys.insert(key); + this->feed(&keys); + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (input->count(df::interface_key::CUSTOM_Q)) { + state->shutdown_callback(); + return; + + } + else if (input->count(df::interface_key::SETUP_LOCAL_X_MUP) || + input->count(df::interface_key::SETUP_LOCAL_X_MDOWN) || + input->count(df::interface_key::SETUP_LOCAL_Y_MUP) || + input->count(df::interface_key::SETUP_LOCAL_Y_MDOWN) || + input->count(df::interface_key::SETUP_LOCAL_X_UP) || + input->count(df::interface_key::SETUP_LOCAL_X_DOWN) || + input->count(df::interface_key::SETUP_LOCAL_Y_UP) || + input->count(df::interface_key::SETUP_LOCAL_Y_DOWN)) { + INTERPOSE_NEXT(feed)(input); + state->embark_update(); + } + else if (input->count(df::interface_key::CUSTOM_C)) { + state->match_active = false; + state->matching = false; + state->clear_match_callback(); + } + else if (input->count(df::interface_key::CUSTOM_F)) { + if (!state->match_active && !state->matching) { + embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic); + } + } + else if (input->count(df::interface_key::CUSTOM_I)) { + embark_assist::help_ui::init(embark_assist::overlay::plugin_self); + } + else { + INTERPOSE_NEXT(feed)(input); + } + } + + //==================================================================== + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto current_screen = Gui::getViewscreenByType(0); + int16_t x = current_screen->location.region_pos.x; + int16_t y = current_screen->location.region_pos.y; + auto width = Screen::getWindowSize().x; + auto height = Screen::getWindowSize().y; + + state->blink_count++; + if (state->blink_count == 35) { + state->blink_count = 0; + state->show = !state->show; + } + + if (state->matching) state->show = true; + + Screen::drawBorder("Embark Assistant"); + + Screen::Pen pen_lr(' ', COLOR_LIGHTRED); + Screen::Pen pen_w(' ', COLOR_WHITE); + + Screen::paintString(pen_lr, width - 28, 20, "i", false); + Screen::paintString(pen_w, width - 27, 20, ":Embark Assistant Info", false); + Screen::paintString(pen_lr, width - 28, 21, "f", false); + Screen::paintString(pen_w, width - 27, 21, ":Find Embark ", false); + Screen::paintString(pen_lr, width - 28, 22, "c", false); + Screen::paintString(pen_w, width - 27, 22, ":Cancel/Clear Find", false); + Screen::paintString(pen_lr, width - 28, 23, "q", false); + Screen::paintString(pen_w, width - 27, 23, ":Quit Embark Assistant", false); + Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles", false); + Screen::paintString(empty_pen, width - 7, 25, to_string(state->match_count), false); + + if (height > 25) { // Mask the vanilla DF find help as it's overridden. + Screen::paintString(pen_w, 50, height - 2, " ", false); + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (state->site_grid[i][k].ch) { + Screen::paintTile(state->site_grid[i][k], i + 1, k + 2); + } + } + } + + for (auto i = 0; i < state->embark_info.size(); i++) { + embark_assist::screen::paintString(state->embark_info[i].pen, 1, i + 19, state->embark_info[i].text, false); + } + + if (state->show) { + int16_t left_x = x - (width / 2 - 7 - 18 + 1) / 2; + int16_t right_x; + int16_t top_y = y - (height - 8 - 2 + 1) / 2; + int16_t bottom_y; + + if (left_x < 0) { left_x = 0; } + + if (top_y < 0) { top_y = 0; } + + right_x = left_x + width / 2 - 7 - 18; + bottom_y = top_y + height - 8 - 2; + + if (right_x >= world->worldgen.worldgen_parms.dim_x) { + right_x = world->worldgen.worldgen_parms.dim_x - 1; + left_x = right_x - (width / 2 - 7 - 18); + } + + if (bottom_y >= world->worldgen.worldgen_parms.dim_y) { + bottom_y = world->worldgen.worldgen_parms.dim_y - 1; + top_y = bottom_y - (height - 8 - 2); + } + + if (left_x < 0) { left_x = 0; } + + if (top_y < 0) { top_y = 0; } + + + for (uint16_t i = left_x; i <= right_x; i++) { + for (uint16_t k = top_y; k <= bottom_y; k++) { + if (state->world_match_grid[i][k].ch) { + Screen::paintTile(state->world_match_grid[i][k], i - left_x + 18, k - top_y + 2); + } + } + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (state->region_match_grid[i][k].ch) { + Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2); + } + } + } + +/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there. + Screen::Pen pen(' ', COLOR_YELLOW); + // Boundaries of the top level world map + Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant +// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area. +// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area. +// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area. + + uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map. + uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map. + df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x); + df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y); + + Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false); + Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false); + Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false); + */ + } + + if (state->matching) { + embark_assist::overlay::state->match_callback(); + } + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, feed); + IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, render); + } +} + +//==================================================================== + +bool embark_assist::overlay::setup(DFHack::Plugin *plugin_self, + embark_update_callbacks embark_update_callback, + match_callbacks match_callback, + clear_match_callbacks clear_match_callback, + embark_assist::defs::find_callbacks find_callback, + shutdown_callbacks shutdown_callback, + uint16_t max_inorganic) +{ +// color_ostream_proxy out(Core::getInstance().getConsole()); + state = new(states); + + embark_assist::overlay::plugin_self = plugin_self; + embark_assist::overlay::state->embark_update = embark_update_callback; + embark_assist::overlay::state->match_callback = match_callback; + embark_assist::overlay::state->clear_match_callback = clear_match_callback; + embark_assist::overlay::state->find_callback = find_callback; + embark_assist::overlay::state->shutdown_callback = shutdown_callback; + embark_assist::overlay::state->max_inorganic = max_inorganic; + embark_assist::overlay::state->match_active = false; + + state->world_match_grid = new pen_column[world->worldgen.worldgen_parms.dim_x]; + if (!state->world_match_grid) { + return false; // Out of memory + } + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + state->world_match_grid[i] = new Screen::Pen[world->worldgen.worldgen_parms.dim_y]; + if (!state->world_match_grid[i]) { // Out of memory. + return false; + } + } + + clear_match_results(); + + return INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, feed).apply(true) && + INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, render).apply(true); +} + +//==================================================================== + +void embark_assist::overlay::set_sites(embark_assist::defs::site_lists *site_list) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + state->site_grid[i][k] = empty_pen; + } + } + + for (uint16_t i = 0; i < site_list->size(); i++) { + state->site_grid[site_list->at(i).x][site_list->at(i).y].ch = site_list->at(i).type; + } +} + +//==================================================================== + +void embark_assist::overlay::initiate_match() { + embark_assist::overlay::state->matching = true; +} + +//==================================================================== + +void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + state->matching = !done; + state->match_count = count; + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + if (match_results->at(i).at(k).preliminary_match) { + state->world_match_grid[i][k] = yellow_x_pen; + + } else if (match_results->at(i).at(k).contains_match) { + state->world_match_grid[i][k] = green_x_pen; + } + else { + state->world_match_grid[i][k] = empty_pen; + } + } + } +} + +//==================================================================== + +void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) { + state->embark_info.clear(); + + if (site_info->sand) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" }); + } + + if (site_info->clay) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" }); + } + + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) }); + + if (site_info->flat) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Flat" }); + } + + if (site_info->aquifer) { + if (site_info->aquifer_full) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" }); + + } + else { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aquifer" }); + } + } + + if (site_info->waterfall) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" }); + } + + if (site_info->flux) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), "Flux" }); + } + + for (auto const& i : site_info->metals) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), world->raws.inorganics[i]->id }); + } + + for (auto const& i : site_info->economics) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), world->raws.inorganics[i]->id }); + } +} + +//==================================================================== + +void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (mlt_matches[i][k]) { + state->region_match_grid[i][k] = green_x_pen; + + } + else { + state->region_match_grid[i][k] = empty_pen; + } + } + } +} + +//==================================================================== + +void embark_assist::overlay::clear_match_results() { + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + state->world_match_grid[i][k] = empty_pen; + } + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + state->region_match_grid[i][k] = empty_pen; + } + } +} + +//==================================================================== + +void embark_assist::overlay::shutdown() { + if (state && + state->world_match_grid) { + INTERPOSE_HOOK(ViewscreenOverlay, render).remove(); + INTERPOSE_HOOK(ViewscreenOverlay, feed).remove(); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + delete[] state->world_match_grid[i]; + } + + delete[] state->world_match_grid; + } + + if (state) { + state->embark_info.clear(); + delete state; + state = nullptr; + } +} diff --git a/plugins/embark-assistant/overlay.h b/plugins/embark-assistant/overlay.h new file mode 100644 index 000000000..d66d6d7fd --- /dev/null +++ b/plugins/embark-assistant/overlay.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include "PluginManager.h" + +#include "DataDefs.h" +#include "df/viewscreen_choose_start_sitest.h" + +#include "defs.h" + +using df::global::enabler; +using df::global::gps; + +namespace embark_assist { + namespace overlay { + typedef void(*embark_update_callbacks)(); + typedef void(*match_callbacks)(); + typedef void(*clear_match_callbacks)(); + typedef void(*shutdown_callbacks)(); + + bool setup(DFHack::Plugin *plugin_self, + embark_update_callbacks embark_update_callback, + match_callbacks match_callback, + clear_match_callbacks clear_match_callback, + embark_assist::defs::find_callbacks find_callback, + shutdown_callbacks shutdown_callback, + uint16_t max_inorganic); + + void set_sites(embark_assist::defs::site_lists *site_list); + void initiate_match(); + void match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done); + void set_embark(embark_assist::defs::site_infos *site_info); + void set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches); + void clear_match_results(); + void shutdown(); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/screen.cpp b/plugins/embark-assistant/screen.cpp new file mode 100644 index 000000000..dc634049e --- /dev/null +++ b/plugins/embark-assistant/screen.cpp @@ -0,0 +1,26 @@ +#include "screen.h" + +namespace embark_assist { + namespace screen { + bool paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map) { + auto screen_size = DFHack::Screen::getWindowSize(); + + if (y < 1 || y + 1 >= screen_size.y || x < 1) + { + return false; // Won't paint outside of the screen or on the frame + } + + if (x + text.length() - 1 < screen_size.x - 2) { + DFHack::Screen::paintString(pen, x, y, text, map); + } + else if (x < screen_size.x - 2) { + DFHack::Screen::paintString(pen, x, y, text.substr(0, screen_size.x - 2 - x + 1), map); + } + else { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/screen.h b/plugins/embark-assistant/screen.h new file mode 100644 index 000000000..06b723f24 --- /dev/null +++ b/plugins/embark-assistant/screen.h @@ -0,0 +1,7 @@ +#include "modules/Screen.h" + +namespace embark_assist { + namespace screen { + bool paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map = false); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp new file mode 100644 index 000000000..308ee78d1 --- /dev/null +++ b/plugins/embark-assistant/survey.cpp @@ -0,0 +1,1080 @@ +#include + +#include "Core.h" +#include +#include +#include + +#include +#include "modules/Materials.h" + +#include "DataDefs.h" +#include "df/coord2d.h" +#include "df/creature_interaction_effect.h" +#include "df/creature_interaction_effect_display_symbolst.h" +#include "df/creature_interaction_effect_type.h" +#include "df/feature_init.h" +#include "df/inorganic_flags.h" +#include "df/inorganic_raw.h" +#include "df/interaction.h" +#include "df/interaction_instance.h" +#include "df/interaction_source.h" +#include "df/interaction_source_regionst.h" +#include "df/interaction_source_type.h" +#include "df/interaction_target.h" +#include "df/interaction_target_corpsest.h" +#include "df/interaction_target_materialst.h" +#include "df/material_common.h" +#include "df/reaction.h" +#include "df/region_map_entry.h" +#include "df/syndrome.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_raws.h" +#include "df/world_region.h" +#include "df/world_region_details.h" +#include "df/world_region_feature.h" +#include "df/world_river.h" +#include "df/world_site.h" +#include "df/world_site_type.h" +#include "df/world_underground_region.h" + +#include "biome_type.h" +#include "defs.h" +#include "survey.h" + +using namespace DFHack; +using namespace df::enums; +using namespace Gui; + +using df::global::world; + +namespace embark_assist { + namespace survey { + struct states { + uint16_t clay_reaction = -1; + uint16_t flux_reaction = -1; + uint16_t x; + uint16_t y; + uint8_t local_min_x; + uint8_t local_min_y; + uint8_t local_max_x; + uint8_t local_max_y; + uint16_t max_inorganic; + }; + + static states *state; + + //======================================================================================= + + bool geo_survey(embark_assist::defs::geo_data *geo_summary) { + color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + auto reactions = world->raws.reactions; + bool non_soil_found; + uint16_t size; + + for (uint16_t i = 0; i < reactions.size(); i++) { + if (reactions[i]->code == "MAKE_CLAY_BRICKS") { + state->clay_reaction = i; + } + + if (reactions[i]->code == "PIG_IRON_MAKING") { + state->flux_reaction = i; + } + } + + if (state->clay_reaction == -1) { + out.printerr("The reaction 'MAKE_CLAY_BRICKS' was not found, so clay can't be identified.\n"); + } + + if (state->flux_reaction == -1) { + out.printerr("The reaction 'PIG_IRON_MAKING' was not found, so flux can't be identified.\n"); + } + + for (uint16_t i = 0; i < world_data->geo_biomes.size(); i++) { + geo_summary->at(i).possible_metals.resize(state->max_inorganic); + geo_summary->at(i).possible_economics.resize(state->max_inorganic); + geo_summary->at(i).possible_minerals.resize(state->max_inorganic); + + non_soil_found = true; + df::world_geo_biome *geo = world_data->geo_biomes[i]; + + for (uint16_t k = 0; k < geo->layers.size() && k < 16; k++) { + df::world_geo_layer *layer = geo->layers[k]; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + geo_summary->at(i).soil_size += layer->top_height - layer->bottom_height + 1; + + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + geo_summary->at(i).sand_absent = false; + } + + if (non_soil_found) { + geo_summary->at(i).top_soil_only = false; + } + } + else { + non_soil_found = true; + } + + geo_summary->at(i).possible_minerals[layer->mat_index] = true; + + size = (uint16_t)world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size(); + + for (uint16_t l = 0; l < size; l++) { + geo_summary->at(i).possible_metals.at(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[l]) = true; + } + + size = (uint16_t)world->raws.inorganics[layer->mat_index]->economic_uses.size(); + if (size != 0) { + geo_summary->at(i).possible_economics[layer->mat_index] = true; + + for (uint16_t l = 0; l < size; l++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[l] == state->clay_reaction) { + geo_summary->at(i).clay_absent = false; + } + + if (world->raws.inorganics[layer->mat_index]->economic_uses[l] == state->flux_reaction) { + geo_summary->at(i).flux_absent = false; + } + } + } + + size = (uint16_t)layer->vein_mat.size(); + + for (uint16_t l = 0; l < size; l++) { + auto vein = layer->vein_mat[l]; + geo_summary->at(i).possible_minerals[vein] = true; + + for (uint16_t m = 0; m < world->raws.inorganics[vein]->metal_ore.mat_index.size(); m++) { + geo_summary->at(i).possible_metals.at(world->raws.inorganics[vein]->metal_ore.mat_index[m]) = true; + } + + if (world->raws.inorganics[vein]->economic_uses.size() != 0) { + geo_summary->at(i).possible_economics[vein] = true; + + for (uint16_t m = 0; m < world->raws.inorganics[vein]->economic_uses.size(); m++) { + if (world->raws.inorganics[vein]->economic_uses[m] == state->clay_reaction) { + geo_summary->at(i).clay_absent = false; + } + + if (world->raws.inorganics[vein]->economic_uses[m] == state->flux_reaction) { + geo_summary->at(i).flux_absent = false; + } + } + } + } + + if (layer->bottom_height <= -3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + geo_summary->at(i).aquifer_absent = false; + } + + if (non_soil_found == true) { + geo_summary->at(i).top_soil_aquifer_only = false; + } + } + } + return true; + } + + + //================================================================================= + + void survey_rivers(embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + int16_t x; + int16_t y; + + for (uint16_t i = 0; i < world_data->rivers.size(); i++) { + for (uint16_t k = 0; k < world_data->rivers[i]->path.x.size(); k++) { + x = world_data->rivers[i]->path.x[k]; + y = world_data->rivers[i]->path.y[k]; + + if (world_data->rivers[i]->unk_8c[k] < 5000) { + if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; + } + } + else if (world_data->rivers[i]->unk_8c[k] < 10000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; + } + else if (world_data->rivers[i]->unk_8c[k] < 20000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Major; + } + } + + x = world_data->rivers[i]->end_pos.x; + y = world_data->rivers[i]->end_pos.y; + + // Make the guess the river size for the end is the same as the tile next to the end. Note that DF + // doesn't actually recognize this tile as part of the river in the pre embark river name display. + // We also assume the is_river/is_brook flags are actually set properly for the end tile. + // + if (x >= 0 && y >= 0 && x < world->worldgen.worldgen_parms.dim_x && y < world->worldgen.worldgen_parms.dim_y) { + if (survey_results->at(x).at(y).river_size == embark_assist::defs::river_sizes::None) { + if (world_data->rivers[i]->path.x.size() && + world_data->rivers[i]->unk_8c[world_data->rivers[i]->path.x.size() - 1] < 5000) { + if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; + } + } + else if (world_data->rivers[i]->unk_8c[world_data->rivers[i]->path.x.size() - 1] < 10000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; + } + else if (world_data->rivers[i]->unk_8c[world_data->rivers[i]->path.x.size() - 1] < 20000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Major; + } + } + } + } + } + + //================================================================================= + + void survey_evil_weather(embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + + for (uint16_t i = 0; i < world->interaction_instances.all.size(); i++) { + auto interaction = world->raws.interactions[world->interaction_instances.all[i]->interaction_id]; + uint16_t region_index = world->interaction_instances.all[i]->region_index; + bool thralling = false; + bool reanimating = false; + + if (interaction->sources.size() && + interaction->sources[0]->getType() == df::interaction_source_type::REGION) { + for (uint16_t k = 0; k < interaction->targets.size(); k++) { + if (interaction->targets[k]->getType() == 0) { // Returns wrong type. Should be df::interaction_target_type::CORPSE + reanimating = true; + } + else if (interaction->targets[k]->getType() == 2) {// Returns wrong type.. Should be df::interaction_target_type::MATERIAL + df::interaction_target_materialst* material = static_cast(interaction->targets[k]); + if (DFHack::MaterialInfo::MaterialInfo(material->anon_1, material->anon_2).isInorganic()) { + for (uint16_t l = 0; l < world->raws.inorganics[material->anon_2]->material.syndrome.size(); l++) { + for (uint16_t m = 0; m < world->raws.inorganics[material->anon_2]->material.syndrome[l]->ce.size(); m++) { + if (world->raws.inorganics[material->anon_2]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { + // Using this as a proxy. There seems to be a group of 4 effects for thralls: + // display symbol, flash symbol, phys att change and one more. + thralling = true; + } + } + } + } + } + } + } + + for (uint16_t k = 0; k < world_data->regions[region_index]->region_coords.size(); k++) { + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).evil_weather[5] = true; + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).reanimating[5] = reanimating; + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).thralling[5] = thralling; + } + } + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + survey_results->at(i).at(k).evil_weather_possible = false; + survey_results->at(i).at(k).reanimating_possible = false; + survey_results->at(i).at(k).thralling_possible = false; + survey_results->at(i).at(k).evil_weather_full = true; + survey_results->at(i).at(k).reanimating_full = true; + survey_results->at(i).at(k).thralling_full = true; + + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome_index[l] != -1) { + df::coord2d adjusted = apply_offset(i, k, l); + survey_results->at(i).at(k).evil_weather[l] = survey_results->at(adjusted.x).at(adjusted.y).evil_weather[5]; + survey_results->at(i).at(k).reanimating[l] = survey_results->at(adjusted.x).at(adjusted.y).reanimating[5]; + survey_results->at(i).at(k).thralling[l] = survey_results->at(adjusted.x).at(adjusted.y).thralling[5]; + + if (survey_results->at(i).at(k).evil_weather[l]) { + survey_results->at(i).at(k).evil_weather_possible = true; + } + else { + survey_results->at(i).at(k).evil_weather_full = false; + } + + if (survey_results->at(i).at(k).reanimating[l]) { + survey_results->at(i).at(k).reanimating_possible = true; + } + else { + survey_results->at(i).at(k).reanimating_full = false; + } + + if (survey_results->at(i).at(k).thralling[l]) { + survey_results->at(i).at(k).thralling_possible = true; + } + else { + survey_results->at(i).at(k).thralling_full = false; + } + } + } + } + } + } + + //================================================================================= + // Exported operations + //================================================================================= + + void setup(uint16_t max_inorganic) { + state = new(states); + state->max_inorganic = max_inorganic; + } + + //================================================================================= + + df::coord2d get_last_pos() { + return{state->x, state->y}; + } + + //================================================================================= + + void initiate(embark_assist::defs::mid_level_tiles *mlt) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } + } + + //================================================================================= + + void clear_results(embark_assist::defs::match_results *match_results) { + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + match_results->at(i).at(k).preliminary_match = false; + match_results->at(i).at(k).contains_match = false; + + for (uint16_t l = 0; l < 16; l++) { + for (uint16_t m = 0; m < 16; m++) { + match_results->at(i).at(k).mlt_match[l][m] = false; + } + } + } + } + } + + //================================================================================= + + void high_level_world_survey(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + + geo_survey(geo_summary); + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + uint16_t geo_index; + uint16_t sav_ev; + uint8_t offset_count = 0; + survey_results->at(i).at(k).surveyed = false; + survey_results->at(i).at(k).aquifer_count = 0; + survey_results->at(i).at(k).clay_count = 0; + survey_results->at(i).at(k).sand_count = 0; + survey_results->at(i).at(k).flux_count = 0; + survey_results->at(i).at(k).min_region_soil = 10; + survey_results->at(i).at(k).max_region_soil = 0; + survey_results->at(i).at(k).waterfall = false; + survey_results->at(i).at(k).savagery_count[0] = 0; + survey_results->at(i).at(k).savagery_count[1] = 0; + survey_results->at(i).at(k).savagery_count[2] = 0; + survey_results->at(i).at(k).evilness_count[0] = 0; + survey_results->at(i).at(k).evilness_count[1] = 0; + survey_results->at(i).at(k).evilness_count[2] = 0; + survey_results->at(i).at(k).metals.resize(state->max_inorganic); + survey_results->at(i).at(k).economics.resize(state->max_inorganic); + survey_results->at(i).at(k).minerals.resize(state->max_inorganic); + // Evil weather and rivers are handled in later operations. Should probably be merged into one. + + for (uint8_t l = 1; l < 10; l++) + { + adjusted = apply_offset(i, k, l); + if (adjusted.x != i || adjusted.y != k || l == 5) { + offset_count++; + + survey_results->at(i).at(k).biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; + survey_results->at(i).at(k).biome[l] = get_biome_type(adjusted.x, adjusted.y, k); + geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index; + + if (!geo_summary->at(geo_index).aquifer_absent) survey_results->at(i).at(k).aquifer_count++; + if (!geo_summary->at(geo_index).clay_absent) survey_results->at(i).at(k).clay_count++; + if (!geo_summary->at(geo_index).sand_absent) survey_results->at(i).at(k).sand_count++; + if (!geo_summary->at(geo_index).flux_absent) survey_results->at(i).at(k).flux_count++; + + if (geo_summary->at(geo_index).soil_size < survey_results->at(i).at(k).min_region_soil) + survey_results->at(i).at(k).min_region_soil = geo_summary->at(geo_index).soil_size; + + if (geo_summary->at(geo_index).soil_size > survey_results->at(i).at(k).max_region_soil) + survey_results->at(i).at(k).max_region_soil = geo_summary->at(geo_index).soil_size; + + sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).savagery_count[sav_ev]++; + + sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).evilness_count[sav_ev]++; + + for (uint16_t m = 0; m < state->max_inorganic; m++) { + if (geo_summary->at(geo_index).possible_metals[m]) survey_results->at(i).at(k).metals[m] = true; + if (geo_summary->at(geo_index).possible_economics[m]) survey_results->at(i).at(k).economics[m] = true; + if (geo_summary->at(geo_index).possible_minerals[m]) survey_results->at(i).at(k).minerals[m] = true; + } + } + else { + survey_results->at(i).at(k).biome_index[l] = -1; + survey_results->at(i).at(k).biome[l] = -1; + } + } + + survey_results->at(i).at(k).biome_count = 0; + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome[l] != -1) survey_results->at(i).at(k).biome_count++; + } + + if (survey_results->at(i).at(k).aquifer_count == offset_count) survey_results->at(i).at(k).aquifer_count = 256; + if (survey_results->at(i).at(k).clay_count == offset_count) survey_results->at(i).at(k).clay_count = 256; + if (survey_results->at(i).at(k).sand_count == offset_count) survey_results->at(i).at(k).sand_count = 256; + if (survey_results->at(i).at(k).flux_count == offset_count) survey_results->at(i).at(k).flux_count = 256; + for (uint8_t l = 0; l < 3; l++) { + if (survey_results->at(i).at(k).savagery_count[l] == offset_count) survey_results->at(i).at(k).savagery_count[l] = 256; + if (survey_results->at(i).at(k).evilness_count[l] == offset_count) survey_results->at(i).at(k).evilness_count[l] = 256; + } + } + } + + survey_rivers(survey_results); + survey_evil_weather(survey_results); + } + + //================================================================================= + + void survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + int8_t max_soil_depth; + int8_t offset; + int16_t elevation; + int16_t last_bottom; + int16_t top_z; + int16_t base_z; + int16_t min_z = 0; // Initialized to silence warning about potential usage of uninitialized data. + int16_t bottom_z; + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + df::world_region_details *details = world_data->region_details[0]; + df::region_map_entry *world_tile = &world_data->region_map[x][y]; + std::vector features; + uint8_t soil_erosion; + uint16_t end_check_l; + uint16_t end_check_m; + uint16_t end_check_n; + + for (uint16_t i = 0; i < state->max_inorganic; i++) { + tile->metals[i] = 0; + tile->economics[i] = 0; + tile->minerals[i] = 0; + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } + + for (uint8_t i = 1; i < 10; i++) survey_results->at(x).at(y).biome_index[i] = -1; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + max_soil_depth = -1; + + offset = details->biome[i][k]; + adjusted = apply_offset(x, y, offset); + + if (adjusted.x != x || adjusted.y != y) + { + mlt->at(i).at(k).biome_offset = offset; + } + else + { + mlt->at(i).at(k).biome_offset = 5; + }; + + survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] = + world_data->region_map[adjusted.x][adjusted.y].region_id; + + mlt->at(i).at(k).savagery_level = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (mlt->at(i).at(k).savagery_level == 3) { + mlt->at(i).at(k).savagery_level = 2; + } + mlt->at(i).at(k).evilness_level = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (mlt->at(i).at(k).evilness_level == 3) { + mlt->at(i).at(k).evilness_level = 2; + } + + elevation = details->elevation[i][k]; + + // Special biome adjustments + if (!world_data->region_map[adjusted.x][adjusted.y].flags.is_set(region_map_entry_flags::is_lake)) { + if (world_data->region_map[adjusted.x][adjusted.y].elevation >= 150) { // Mountain + max_soil_depth = 0; + + } + else if (world_data->region_map[adjusted.x][adjusted.y].elevation < 100) { // Ocean + if (elevation == 99) { + elevation = 98; + } + + if ((world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 4 || + world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 5) && + details->unk12e8 < 500) { + max_soil_depth = 0; + } + } + } + + base_z = elevation - 1; + features = details->features[i][k]; + std::map layer_bottom, layer_top; + + end_check_l = static_cast(features.size()); + for (size_t l = 0; l < end_check_l; l++) { + auto feature = features[l]; + + if (feature->layer != -1 && + feature->min_z != -30000) { + auto layer = world_data->underground_regions[feature->layer]; + + layer_bottom[layer->layer_depth] = feature->min_z; + layer_top[layer->layer_depth] = feature->max_z; + base_z = std::min((int)base_z, (int)feature->min_z); + + if (layer->type == df::world_underground_region::MagmaSea) { + min_z = feature->min_z; // The features are individual per region tile + break; + } + } + } + + // Compute shifts for layers in the stack. + + if (max_soil_depth == -1) { // Not set to zero by the biome + max_soil_depth = std::max((154 - elevation) / 5, 1); + } + + soil_erosion = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - + std::min((int)geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size, (int)max_soil_depth); + int16_t layer_shift[16]; + int16_t cur_shift = elevation + soil_erosion - 1; + + mlt->at(i).at(k).aquifer = false; + mlt->at(i).at(k).clay = false; + mlt->at(i).at(k).sand = false; + mlt->at(i).at(k).flux = false; + if (max_soil_depth == 0) { + mlt->at(i).at(k).soil_depth = 0; + } + else { + mlt->at(i).at(k).soil_depth = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - soil_erosion; + } + mlt->at(i).at(k).offset = offset; + mlt->at(i).at(k).elevation = details->elevation[i][k]; + mlt->at(i).at(k).river_present = false; + mlt->at(i).at(k).river_elevation = 100; + + if (details->rivers_vertical.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_vertical.elevation[i][k]; + } + else if (details->rivers_horizontal.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_horizontal.elevation[i][k]; + } + + if (tile->min_region_soil > mlt->at(i).at(k).soil_depth) { + tile->min_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (tile->max_region_soil < mlt->at(i).at(k).soil_depth) { + tile->max_region_soil = mlt->at(i).at(k).soil_depth; + } + + end_check_l = static_cast(world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers.size()); + if (end_check_l > 16) end_check_l = 16; + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + layer_shift[l] = cur_shift; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + int16_t size = layer->top_height - layer->bottom_height - 1; + // Comment copied from prospector.cpp(like the logic)... + // This is to replicate the behavior of a probable bug in the + // map generation code : if a layer is partially eroded, the + // removed levels are in fact transferred to the layer below, + // because unlike the case of removing the whole layer, the code + // does not execute a loop to shift the lower part of the stack up. + if (size > soil_erosion) { + cur_shift = cur_shift - soil_erosion; + } + + soil_erosion -= std::min((int)soil_erosion, (int)size); + } + } + + last_bottom = elevation; + // Don't have to set up the end_check as we can reuse the one above. + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + top_z = last_bottom - 1; + bottom_z = std::max((int)layer->bottom_height + layer_shift[l], (int)min_z); + + if (l == 15) { + bottom_z = min_z; // stretch layer if needed + } + + if (top_z >= bottom_z) { + mlt->at(i).at(k).minerals[layer->mat_index] = true; + + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size()); + + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[m]] = true; + } + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + mlt->at(i).at(k).sand = true; + } + } + + if (world->raws.inorganics[layer->mat_index]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->mat_index] = true; + + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->economic_uses.size()); + for (uint16_t m = 0; m < end_check_m; m++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } + + else if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + + end_check_m = static_cast(layer->vein_mat.size()); + + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).minerals[layer->vein_mat[m]] = true; + + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index.size()); + + for (uint16_t n = 0; n < end_check_n; n++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index[n]] = true; + } + + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->vein_mat[m]] = true; + + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size()); + for (uint16_t n = 0; n < end_check_n; n++) { + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } + + else if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + } + + if (bottom_z <= elevation - 3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + mlt->at(i).at(k).aquifer = true; + } + } + } + } + } + + survey_results->at(x).at(y).aquifer_count = 0; + survey_results->at(x).at(y).clay_count = 0; + survey_results->at(x).at(y).sand_count = 0; + survey_results->at(x).at(y).flux_count = 0; + survey_results->at(x).at(y).min_region_soil = 10; + survey_results->at(x).at(y).max_region_soil = 0; + survey_results->at(x).at(y).savagery_count[0] = 0; + survey_results->at(x).at(y).savagery_count[1] = 0; + survey_results->at(x).at(y).savagery_count[2] = 0; + survey_results->at(x).at(y).evilness_count[0] = 0; + survey_results->at(x).at(y).evilness_count[1] = 0; + survey_results->at(x).at(y).evilness_count[2] = 0; + + bool river_elevation_found = false; + int16_t river_elevation; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; } + if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; } + if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; } + if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; } + + if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { + survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).soil_depth > survey_results->at(x).at(y).max_region_soil) { + survey_results->at(x).at(y).max_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).river_present) { + if (river_elevation_found) { + if (mlt->at(i).at(k).river_elevation != river_elevation) + { + survey_results->at(x).at(y).waterfall = true; + } + } + else { + river_elevation_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; + } + } + + // River size surveyed separately + // biome_index handled above + // biome handled below + // evil weather handled separately + // reanimating handled separately + // thralling handled separately + + survey_results->at(x).at(y).savagery_count[mlt->at(i).at(k).savagery_level]++; + survey_results->at(x).at(y).evilness_count[mlt->at(i).at(k).evilness_level]++; + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (mlt->at(i).at(k).metals[l]) { survey_results->at(x).at(y).metals[l] = true; } + if (mlt->at(i).at(k).economics[l]) { survey_results->at(x).at(y).economics[l] = true; } + if (mlt->at(i).at(k).minerals[l]) { survey_results->at(x).at(y).minerals[l] = true; } + } + } + } + + for (uint8_t i = 1; i < 10; i++) { + if (survey_results->at(x).at(y).biome_index[i] == -1) { + survey_results->at(x).at(y).biome[i] = -1; + } + } + + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + biomes[i] = false; + } + + for (uint8_t i = 1; i < 10; i++) + { + if (survey_results->at(x).at(y).biome[i] != -1) { + biomes[survey_results->at(x).at(y).biome[i]] = true; + } + } + int count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) count++; + } + + tile->biome_count = count; + tile->surveyed = true; + } + //================================================================================= + + df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset) { + df::coord2d result; + result.x = x; + result.y = y; + + switch (offset) { + case 1: + result.x--; + result.y++; + break; + + case 2: + result.y++; + break; + + case 3: + result.x++; + result.y++; + break; + + case 4: + result.x--; + break; + + case 5: + break; // Center. No change + + case 6: + result.x++; + break; + + case 7: + result.x--; + result.y--; + break; + + case 8: + result.y--; + break; + + case 9: + result.x++; + result.y--; + break; + + default: + // Bug. Just act as if it's the center... + break; + } + + if (result.x < 0) { + result.x = 0; + } + else if (result.x >= world->worldgen.worldgen_parms.dim_x) { + result.x = world->worldgen.worldgen_parms.dim_x - 1; + } + + if (result.y < 0) { + result.y = 0; + } + else if (result.y >= world->worldgen.worldgen_parms.dim_y) { + result.y = world->worldgen.worldgen_parms.dim_y - 1; + } + + return result; + } + + //================================================================================= + + void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists *site_list) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + df::world_data *world_data = world->world_data; + int8_t index = 0; + + site_list->clear(); + + for (uint32_t i = 0; i < world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites.size(); i++) { + auto site = world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites[i]; + switch (site->type) { + case df::world_site_type::PlayerFortress: + case df::world_site_type::DarkFortress: + case df::world_site_type::MountainHalls: + case df::world_site_type::ForestRetreat: + case df::world_site_type::Town: + case df::world_site_type::Fortress: + break; // Already visible + + case df::world_site_type::Cave: + if (!world->worldgen.worldgen_parms.all_caves_visible) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'c' }); // Cave + } + break; + + case df::world_site_type::Monument: + if (site->subtype_info->lair_type != -1 || + site->subtype_info->is_monument == 0) { // Not Tomb, which is visible already + } + else if (site->subtype_info->lair_type == -1) { + site_list->push_back( { (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'V' }); // Vault + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'M' }); // Any other Monument type. Pyramid? + } + break; + + case df::world_site_type::ImportantLocation: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'i' }); // Don't really know what that is... + break; + + case df::world_site_type::LairShrine: + if (site->subtype_info->lair_type == 0 || + site->subtype_info->lair_type == 1 || + site->subtype_info->lair_type == 4) { // Only Rocs seen. Mountain lair? + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'l' }); // Lair + } + else if (site->subtype_info->lair_type == 2) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'L' }); // Labyrinth + } + else if (site->subtype_info->lair_type == 3) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'S' }); // Shrine + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '?' }); // Can these exist? + } + break; + + case df::world_site_type::Camp: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'C' }); // Camp + break; + + default: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '!' }); // Not even in the enum... + break; + } + } + } + + //================================================================================= + + void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::site_infos *site_info, + bool use_cache) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t elevation; + uint16_t x = screen->location.region_pos.x; + uint16_t y = screen->location.region_pos.y; + bool river_found = false; + int16_t river_elevation; + std::vector metals(state->max_inorganic); + std::vector economics(state->max_inorganic); + std::vector minerals(state->max_inorganic); + + if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...). + state->local_min_x = screen->location.embark_pos_min.x; + state->local_min_y = screen->location.embark_pos_min.y; + state->local_max_x = screen->location.embark_pos_max.x; + state->local_max_y = screen->location.embark_pos_max.y; + } + + state->x = x; + state->y = y; + + site_info->aquifer = false; + site_info->aquifer_full = true; + site_info->min_soil = 10; + site_info->max_soil = 0; + site_info->flat = true; + site_info->waterfall = false; + site_info->clay = false; + site_info->sand = false; + site_info->flux = false; + site_info->metals.clear(); + site_info->economics.clear(); + site_info->metals.clear(); + + for (uint8_t i = state->local_min_x; i <= state->local_max_x; i++) { + for (uint8_t k = state->local_min_y; k <= state->local_max_y; k++) { + if (mlt->at(i).at(k).aquifer) { + site_info->aquifer = true; + } + else { + site_info->aquifer_full = false; + } + + if (mlt->at(i).at(k).soil_depth < site_info->min_soil) { + site_info->min_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).soil_depth > site_info->max_soil) { + site_info->max_soil = mlt->at(i).at(k).soil_depth; + } + + if (i == state->local_min_x && k == state->local_min_y) { + elevation = mlt->at(i).at(k).elevation; + + } + else if (elevation != mlt->at(i).at(k).elevation) { + site_info->flat = false; + } + + if (mlt->at(i).at(k).river_present) { + if (river_found) { + if (river_elevation != mlt->at(i).at(k).river_elevation) { + site_info->waterfall = true; + } + } + else { + river_elevation = mlt->at(i).at(k).river_elevation; + river_found = true; + } + } + + if (mlt->at(i).at(k).clay) { + site_info->clay = true; + } + + if (mlt->at(i).at(k).sand) { + site_info->sand = true; + } + + if (mlt->at(i).at(k).flux) { + site_info->flux = true; + } + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + metals[l] = metals [l] || mlt->at(i).at(k).metals[l]; + economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; + minerals[l] = minerals[l] || mlt->at(i).at(k).minerals[l]; + } + } + } + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (metals[l]) { + site_info->metals.push_back(l); + } + + if (economics[l]) { + site_info->economics.push_back(l); + } + + if (minerals[l]) { + site_info->minerals.push_back(l); + } + } + } + + //================================================================================= + + void embark_assist::survey::shutdown() { + delete state; + } + } +} diff --git a/plugins/embark-assistant/survey.h b/plugins/embark-assistant/survey.h new file mode 100644 index 000000000..03d2d9a03 --- /dev/null +++ b/plugins/embark-assistant/survey.h @@ -0,0 +1,38 @@ +#pragma once +#include + +#include "DataDefs.h" +#include "df/coord2d.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace survey { + void setup(uint16_t max_inorganic); + + df::coord2d get_last_pos(); + + void initiate(embark_assist::defs::mid_level_tiles *mlt); + + void clear_results(embark_assist::defs::match_results *match_results); + + void high_level_world_survey(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results); + + void survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt); + + df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset); + + void survey_region_sites(embark_assist::defs::site_lists *site_list); + + void survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::site_infos *site_info, + bool use_cache); + + void shutdown(); + } +} \ No newline at end of file From 831fa07fb2fdd29c432bb0478e0f3b3e9484f43b Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 15:10:53 +0200 Subject: [PATCH 0626/1012] Removed path from dfhack.h include. --- plugins/embark-assistant/biome_type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/embark-assistant/biome_type.cpp b/plugins/embark-assistant/biome_type.cpp index 82603a004..20ae086bf 100644 --- a/plugins/embark-assistant/biome_type.cpp +++ b/plugins/embark-assistant/biome_type.cpp @@ -29,7 +29,7 @@ misrepresented as being the original software. // You can always find the latest version of this plugin in Github // https://github.com/ragundo/exportmaps -#include "../../include/dfhack.h" +#include "dfhack.h" #include #include #include From 83061cffcae347b941b92b02409c6836e3545b09 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 15:37:40 +0200 Subject: [PATCH 0627/1012] Replaced include of dfhack.h with and DataDefs.h --- plugins/embark-assistant/biome_type.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/embark-assistant/biome_type.cpp b/plugins/embark-assistant/biome_type.cpp index 20ae086bf..01bc0044a 100644 --- a/plugins/embark-assistant/biome_type.cpp +++ b/plugins/embark-assistant/biome_type.cpp @@ -29,7 +29,9 @@ misrepresented as being the original software. // You can always find the latest version of this plugin in Github // https://github.com/ragundo/exportmaps -#include "dfhack.h" +#include + +#include "DataDefs.h" #include #include #include From 84eaf0414870826fb6a925794cf1a39c881f80d6 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 16:22:10 +0200 Subject: [PATCH 0628/1012] Removed function qualifications to make Travis happy and Visual Studio unhappy. --- plugins/embark-assistant/finder_ui.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 3ebaa9e0f..bdf460270 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -959,7 +959,7 @@ namespace embark_assist { class ViewscreenFindUi : public dfhack_viewscreen { public: - ViewscreenFindUi::ViewscreenFindUi(); + ViewscreenFindUi(); void feed(std::set *input); @@ -1115,7 +1115,7 @@ namespace embark_assist { //=============================================================================== - void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { + void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { if (!state) { // First call. Have to do the setup ui_setup(find_callback, max_inorganic); } @@ -1124,12 +1124,12 @@ namespace embark_assist { //=============================================================================== - void embark_assist::finder_ui::activate() { + void activate() { } //=============================================================================== - void embark_assist::finder_ui::shutdown() { + void shutdown() { if (embark_assist::finder_ui::state) { for (uint16_t i = 0; i < state->ui.size(); i++) { delete state->ui[i]; From 596296358fc1fce58e43685b36a6c2d53d779e85 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 17:00:14 +0200 Subject: [PATCH 0629/1012] Moved all externally visible operation implementations out of name spaces to satisfy both Travis and Visual Studio. --- plugins/embark-assistant/finder_ui.cpp | 44 +- plugins/embark-assistant/help_ui.cpp | 16 +- plugins/embark-assistant/matcher.cpp | 506 +++++----- plugins/embark-assistant/screen.cpp | 37 +- plugins/embark-assistant/survey.cpp | 1237 ++++++++++++------------ 5 files changed, 922 insertions(+), 918 deletions(-) diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index bdf460270..feea89199 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -1112,32 +1112,34 @@ namespace embark_assist { ViewscreenFindUi::ViewscreenFindUi() { } + } +} - //=============================================================================== - - void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { - if (!state) { // First call. Have to do the setup - ui_setup(find_callback, max_inorganic); - } - Screen::show(new ViewscreenFindUi(), plugin_self); - } +//=============================================================================== +// Exported operations +//=============================================================================== - //=============================================================================== +void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { + if (!embark_assist::finder_ui::state) { // First call. Have to do the setup + embark_assist::finder_ui::ui_setup(find_callback, max_inorganic); + } + Screen::show(new ViewscreenFindUi(), plugin_self); +} - void activate() { - } +//=============================================================================== - //=============================================================================== +void embark_assist::finder_ui::activate() { +} - void shutdown() { - if (embark_assist::finder_ui::state) { - for (uint16_t i = 0; i < state->ui.size(); i++) { - delete state->ui[i]; - } +//=============================================================================== - delete state; - state = nullptr; - } +void embark_assist::finder_ui::shutdown() { + if (embark_assist::finder_ui::state) { + for (uint16_t i = 0; i < embark_assist::finder_ui::state->ui.size(); i++) { + delete embark_assist::finder_ui::state->ui[i]; } + + delete embark_assist::finder_ui::state; + embark_assist::finder_ui::state = nullptr; } -} \ No newline at end of file +} diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index e1830883a..9fe79e210 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -302,13 +302,13 @@ namespace embark_assist{ ViewscreenHelpUi::ViewscreenHelpUi() { } + } +} - //=============================================================================== - // Exported operations - //=============================================================================== +//=============================================================================== +// Exported operations +//=============================================================================== - void init(DFHack::Plugin *plugin_self) { - Screen::show(new ViewscreenHelpUi(), plugin_self); - } - } -} \ No newline at end of file +void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) { + Screen::show(new embark_assist::help_ui::ViewscreenHelpUi(), plugin_self); +} diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index b4d3f438e..484ee3cf8 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -1149,297 +1149,297 @@ namespace embark_assist { finder, match_results); } + } +} - //======================================================================================= - // Visible operations - //======================================================================================= - - void move_cursor(uint16_t x, uint16_t y) { -// color_ostream_proxy out(Core::getInstance().getConsole()); - auto screen = Gui::getViewscreenByType(0); - uint16_t original_x = screen->location.region_pos.x; - uint16_t original_y = screen->location.region_pos.y; - - uint16_t large_x = std::abs(original_x - x) / 10; - uint16_t small_x = std::abs(original_x - x) % 10; - uint16_t large_y = std::abs(original_y - y) / 10; - uint16_t small_y = std::abs(original_y - y) % 10; - - while (large_x > 0 || large_y > 0) { - if (large_x > 0 && large_y > 0) { - if (original_x - x > 0 && original_y - y > 0) { - screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); - } - else if (original_x - x > 0 && original_y - y < 0) { - screen->feed_key(df::interface_key::CURSOR_DOWNLEFT_FAST); - } - else if (original_y - y > 0) { - screen->feed_key(df::interface_key::CURSOR_UPRIGHT_FAST); - } - else { - screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT_FAST); - } - large_x--; - large_y--; - } - else if (large_x > 0) { - if (original_x - x > 0) { - screen->feed_key(df::interface_key::CURSOR_LEFT_FAST); - } - else { - screen->feed_key(df::interface_key::CURSOR_RIGHT_FAST); - } - large_x--; - } - else { - if (original_y - y > 0) { - screen->feed_key(df::interface_key::CURSOR_UP_FAST); - } - else { - screen->feed_key(df::interface_key::CURSOR_DOWN_FAST); - } - large_y--; - } +//======================================================================================= +// Visible operations +//======================================================================================= + +void embark_assist::matcher::move_cursor(uint16_t x, uint16_t y) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t original_x = screen->location.region_pos.x; + uint16_t original_y = screen->location.region_pos.y; + + uint16_t large_x = std::abs(original_x - x) / 10; + uint16_t small_x = std::abs(original_x - x) % 10; + uint16_t large_y = std::abs(original_y - y) / 10; + uint16_t small_y = std::abs(original_y - y) % 10; + + while (large_x > 0 || large_y > 0) { + if (large_x > 0 && large_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT_FAST); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT_FAST); } + large_x--; + large_y--; + } + else if (large_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT_FAST); + } + large_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN_FAST); + } + large_y--; + } + } - while (small_x > 0 || small_y > 0) { - if (small_x > 0 && small_y > 0) { - if (original_x - x > 0 && original_y - y > 0) { - screen->feed_key(df::interface_key::CURSOR_UPLEFT); - } - else if (original_x - x > 0 && original_y - y < 0) { - screen->feed_key(df::interface_key::CURSOR_DOWNLEFT); - } - else if (original_y - y > 0) { - screen->feed_key(df::interface_key::CURSOR_UPRIGHT); - } - else { - screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT); - } - small_x--; - small_y--; - } - else if (small_x > 0) { - if (original_x - x > 0) { - screen->feed_key(df::interface_key::CURSOR_LEFT); - } - else { - screen->feed_key(df::interface_key::CURSOR_RIGHT); - } - small_x--; - } - else { - if (original_y - y > 0) { - screen->feed_key(df::interface_key::CURSOR_UP); - } - else { - screen->feed_key(df::interface_key::CURSOR_DOWN); - } - small_y--; - } + while (small_x > 0 || small_y > 0) { + if (small_x > 0 && small_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT); } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT); + } + small_x--; + small_y--; } + else if (small_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + small_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + small_y--; + } + } +} - //======================================================================================= +//======================================================================================= + +uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iterator, + embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::match_results *match_results) { + + color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t x_end; + uint16_t y_end; + bool turn; + uint16_t count; + uint16_t preliminary_matches; + + if (!iterator->active) { + embark_assist::survey::clear_results(match_results); + + // Static check for impossible requirements + // + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.evilness[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } - uint16_t find(embark_assist::defs::match_iterators *iterator, - embark_assist::defs::geo_data *geo_summary, - embark_assist::defs::world_tile_data *survey_results, - embark_assist::defs::match_results *match_results) { - - color_ostream_proxy out(Core::getInstance().getConsole()); - auto screen = Gui::getViewscreenByType(0); - uint16_t x_end; - uint16_t y_end; - bool turn; - uint16_t count; - uint16_t preliminary_matches; + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All evilness requirements\n"); + return 0; + } - if (!iterator->active) { - embark_assist::survey::clear_results(match_results); + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.savagery[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } - // Static check for impossible requirements - // - count = 0; - for (uint8_t i = 0; i < 3; i++) { - if (iterator->finder.evilness[i] == embark_assist::defs::evil_savagery_values::All) { - count++; - } - } + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All savagery requirements\n"); + return 0; + } - if (count > 1) { - out.printerr("matcher::find: Will never find any due to multiple All evilness requirements\n"); - return 0; - } + if (iterator->finder.max_river < iterator->finder.min_river && + iterator->finder.max_river != embark_assist::defs::river_ranges::NA) { + out.printerr("matcher::find: Will never find any due to max river < min river\n"); + return 0; + } - count = 0; - for (uint8_t i = 0; i < 3; i++) { - if (iterator->finder.savagery[i] == embark_assist::defs::evil_savagery_values::All) { - count++; - } - } + if (iterator->finder.waterfall == embark_assist::defs::yes_no_ranges::Yes && + iterator->finder.max_river == embark_assist::defs::river_ranges::None) { + out.printerr("matcher::find: Will never find any waterfalls with None as max river\n"); + return 0; + } - if (count > 1) { - out.printerr("matcher::find: Will never find any due to multiple All savagery requirements\n"); - return 0; - } + if (iterator->finder.soil_max < iterator->finder.soil_min && + iterator->finder.soil_max != embark_assist::defs::soil_ranges::NA) { + out.printerr("matcher::find: Will never find any matches with max soil < min soil\n"); + return 0; + } - if (iterator->finder.max_river < iterator->finder.min_river && - iterator->finder.max_river != embark_assist::defs::river_ranges::NA) { - out.printerr("matcher::find: Will never find any due to max river < min river\n"); - return 0; - } + if (iterator->finder.biome_count_max < iterator->finder.biome_count_min && + iterator->finder.biome_count_max != -1) { + out.printerr("matcher::find: Will never find any matches with max biomes < min biomes\n"); + return 0; + } - if (iterator->finder.waterfall == embark_assist::defs::yes_no_ranges::Yes && - iterator->finder.max_river == embark_assist::defs::river_ranges::None) { - out.printerr("matcher::find: Will never find any waterfalls with None as max river\n"); - return 0; - } + preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); - if (iterator->finder.soil_max < iterator->finder.soil_min && - iterator->finder.soil_max != embark_assist::defs::soil_ranges::NA) { - out.printerr("matcher::find: Will never find any matches with max soil < min soil\n"); - return 0; - } + if (preliminary_matches == 0) { + out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + return 0; + } + else { + out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + } - if (iterator->finder.biome_count_max < iterator->finder.biome_count_min && - iterator->finder.biome_count_max != -1) { - out.printerr("matcher::find: Will never find any matches with max biomes < min biomes\n"); - return 0; - } + while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + iterator->active = true; + iterator->i = 0; + iterator->k = 0; + iterator->x_right = true; + iterator->y_down = true; + iterator->inhibit_x_turn = false; + iterator->inhibit_y_turn = false; + iterator->count = 0; + } - preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); + if ((iterator->k == world->worldgen.worldgen_parms.dim_x / 16 && iterator->x_right) || + (iterator->k == 0 && !iterator->x_right)) { + x_end = 0; + } + else { + x_end = 15; + } - if (preliminary_matches == 0) { - out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); - return 0; - } - else { - out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); - } + if (iterator->i == world->worldgen.worldgen_parms.dim_y / 16) { + y_end = 0; + } + else { + y_end = 15; + } - while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { - screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + for (uint16_t l = 0; l <= x_end; l++) { + for (uint16_t m = 0; m <= y_end; m++) { + // This is where the payload goes + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).preliminary_match) { + match_world_tile(geo_summary, + survey_results, + &iterator->finder, + match_results, + screen->location.region_pos.x, + screen->location.region_pos.y); + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).contains_match) { + iterator->count++; } - iterator->active = true; - iterator->i = 0; - iterator->k = 0; - iterator->x_right = true; - iterator->y_down = true; - iterator->inhibit_x_turn = false; - iterator->inhibit_y_turn = false; - iterator->count = 0; - } - - if ((iterator->k == world->worldgen.worldgen_parms.dim_x / 16 && iterator->x_right) || - (iterator->k == 0 && !iterator->x_right)) { - x_end = 0; } else { - x_end = 15; + for (uint16_t n = 0; n < 16; n++) { + for (uint16_t p = 0; p < 16; p++) { + match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match[n][p] = false; + } + } } + // End of payload section - if (iterator->i == world->worldgen.worldgen_parms.dim_y / 16) { - y_end = 0; + if (m != y_end) { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } } else { - y_end = 15; - } - - for (uint16_t l = 0; l <= x_end; l++) { - for (uint16_t m = 0; m <= y_end; m++) { - // This is where the payload goes - if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).preliminary_match) { - match_world_tile(geo_summary, - survey_results, - &iterator->finder, - match_results, - screen->location.region_pos.x, - screen->location.region_pos.y); - if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).contains_match) { - iterator->count++; - } - } - else { - for (uint16_t n = 0; n < 16; n++) { - for (uint16_t p = 0; p < 16; p++) { - match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match[n][p] = false; - } - } - } - // End of payload section - - if (m != y_end) { - if (iterator->y_down) { - screen->feed_key(df::interface_key::CURSOR_DOWN); - } - else { - screen->feed_key(df::interface_key::CURSOR_UP); - } - } - else { - if (screen->location.region_pos.x != 0 && - screen->location.region_pos.x != world->worldgen.worldgen_parms.dim_x - 1) { - turn = true; - } - else { - iterator->inhibit_y_turn = !iterator->inhibit_y_turn; - turn = iterator->inhibit_y_turn; - } - - if (turn) { - iterator->y_down = !iterator->y_down; - } - else { - if (iterator->y_down) { - screen->feed_key(df::interface_key::CURSOR_DOWN); - } - else { - screen->feed_key(df::interface_key::CURSOR_UP); - } - } - } - } - - if (iterator->x_right) { // Won't do anything at the edge, so we don't bother filter those cases. - screen->feed_key(df::interface_key::CURSOR_RIGHT); + if (screen->location.region_pos.x != 0 && + screen->location.region_pos.x != world->worldgen.worldgen_parms.dim_x - 1) { + turn = true; } else { - screen->feed_key(df::interface_key::CURSOR_LEFT); + iterator->inhibit_y_turn = !iterator->inhibit_y_turn; + turn = iterator->inhibit_y_turn; } - if (!iterator->x_right && - screen->location.region_pos.x == 0) { - turn = !turn; - - if (turn) { - iterator->x_right = true; - } + if (turn) { + iterator->y_down = !iterator->y_down; } - else if (iterator->x_right && - screen->location.region_pos.x == world->worldgen.worldgen_parms.dim_x - 1) { - turn = !turn; - - if (turn) { - iterator->x_right = false; + else { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); } } } - // } + } - iterator->k++; - if (iterator->k > world->worldgen.worldgen_parms.dim_x / 16) - { - iterator->k = 0; - iterator->i++; - iterator->active = !(iterator->i > world->worldgen.worldgen_parms.dim_y / 16); + if (iterator->x_right) { // Won't do anything at the edge, so we don't bother filter those cases. + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } - if (!iterator->active) { - move_cursor(iterator->x, iterator->y); - } + if (!iterator->x_right && + screen->location.region_pos.x == 0) { + turn = !turn; + + if (turn) { + iterator->x_right = true; } + } + else if (iterator->x_right && + screen->location.region_pos.x == world->worldgen.worldgen_parms.dim_x - 1) { + turn = !turn; - return iterator->count; + if (turn) { + iterator->x_right = false; + } } } + // } + + iterator->k++; + if (iterator->k > world->worldgen.worldgen_parms.dim_x / 16) + { + iterator->k = 0; + iterator->i++; + iterator->active = !(iterator->i > world->worldgen.worldgen_parms.dim_y / 16); + + if (!iterator->active) { + embark_assist::matcher::move_cursor(iterator->x, iterator->y); + } + } + + return iterator->count; } diff --git a/plugins/embark-assistant/screen.cpp b/plugins/embark-assistant/screen.cpp index dc634049e..477e5c63c 100644 --- a/plugins/embark-assistant/screen.cpp +++ b/plugins/embark-assistant/screen.cpp @@ -2,25 +2,26 @@ namespace embark_assist { namespace screen { - bool paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map) { - auto screen_size = DFHack::Screen::getWindowSize(); + } +} - if (y < 1 || y + 1 >= screen_size.y || x < 1) - { - return false; // Won't paint outside of the screen or on the frame - } +bool embark_assist::screen::paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map) { + auto screen_size = DFHack::Screen::getWindowSize(); - if (x + text.length() - 1 < screen_size.x - 2) { - DFHack::Screen::paintString(pen, x, y, text, map); - } - else if (x < screen_size.x - 2) { - DFHack::Screen::paintString(pen, x, y, text.substr(0, screen_size.x - 2 - x + 1), map); - } - else { - return false; - } + if (y < 1 || y + 1 >= screen_size.y || x < 1) + { + return false; // Won't paint outside of the screen or on the frame + } - return true; - } + if (x + text.length() - 1 < screen_size.x - 2) { + DFHack::Screen::paintString(pen, x, y, text, map); + } + else if (x < screen_size.x - 2) { + DFHack::Screen::paintString(pen, x, y, text.substr(0, screen_size.x - 2 - x + 1), map); } -} \ No newline at end of file + else { + return false; + } + + return true; +} diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 308ee78d1..5c0ff95af 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -332,749 +332,750 @@ namespace embark_assist { } } } + } +} - //================================================================================= - // Exported operations - //================================================================================= +//================================================================================= +// Exported operations +//================================================================================= - void setup(uint16_t max_inorganic) { - state = new(states); - state->max_inorganic = max_inorganic; - } +void embark_assist::survey::setup(uint16_t max_inorganic) { + embark_assist::survey::state = new(embark_assist::survey::states); + embark_assist::survey::state->max_inorganic = max_inorganic; +} - //================================================================================= +//================================================================================= - df::coord2d get_last_pos() { - return{state->x, state->y}; - } +df::coord2d embark_assist::survey::get_last_pos() { + return{ embark_assist::survey::state->x, embark_assist::survey::state->y }; +} - //================================================================================= +//================================================================================= - void initiate(embark_assist::defs::mid_level_tiles *mlt) { - for (uint8_t i = 0; i < 16; i++) { - for (uint8_t k = 0; k < 16; k++) { - mlt->at(i).at(k).metals.resize(state->max_inorganic); - mlt->at(i).at(k).economics.resize(state->max_inorganic); - mlt->at(i).at(k).minerals.resize(state->max_inorganic); - } - } +void embark_assist::survey::initiate(embark_assist::defs::mid_level_tiles *mlt) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); } + } +} - //================================================================================= +//================================================================================= - void clear_results(embark_assist::defs::match_results *match_results) { - for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { - for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { - match_results->at(i).at(k).preliminary_match = false; - match_results->at(i).at(k).contains_match = false; +void embark_assist::survey::clear_results(embark_assist::defs::match_results *match_results) { + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + match_results->at(i).at(k).preliminary_match = false; + match_results->at(i).at(k).contains_match = false; - for (uint16_t l = 0; l < 16; l++) { - for (uint16_t m = 0; m < 16; m++) { - match_results->at(i).at(k).mlt_match[l][m] = false; - } - } + for (uint16_t l = 0; l < 16; l++) { + for (uint16_t m = 0; m < 16; m++) { + match_results->at(i).at(k).mlt_match[l][m] = false; } } } + } +} - //================================================================================= - - void high_level_world_survey(embark_assist::defs::geo_data *geo_summary, - embark_assist::defs::world_tile_data *survey_results) { -// color_ostream_proxy out(Core::getInstance().getConsole()); - - geo_survey(geo_summary); - for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { - for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { - df::coord2d adjusted; - df::world_data *world_data = world->world_data; - uint16_t geo_index; - uint16_t sav_ev; - uint8_t offset_count = 0; - survey_results->at(i).at(k).surveyed = false; - survey_results->at(i).at(k).aquifer_count = 0; - survey_results->at(i).at(k).clay_count = 0; - survey_results->at(i).at(k).sand_count = 0; - survey_results->at(i).at(k).flux_count = 0; - survey_results->at(i).at(k).min_region_soil = 10; - survey_results->at(i).at(k).max_region_soil = 0; - survey_results->at(i).at(k).waterfall = false; - survey_results->at(i).at(k).savagery_count[0] = 0; - survey_results->at(i).at(k).savagery_count[1] = 0; - survey_results->at(i).at(k).savagery_count[2] = 0; - survey_results->at(i).at(k).evilness_count[0] = 0; - survey_results->at(i).at(k).evilness_count[1] = 0; - survey_results->at(i).at(k).evilness_count[2] = 0; - survey_results->at(i).at(k).metals.resize(state->max_inorganic); - survey_results->at(i).at(k).economics.resize(state->max_inorganic); - survey_results->at(i).at(k).minerals.resize(state->max_inorganic); - // Evil weather and rivers are handled in later operations. Should probably be merged into one. - - for (uint8_t l = 1; l < 10; l++) - { - adjusted = apply_offset(i, k, l); - if (adjusted.x != i || adjusted.y != k || l == 5) { - offset_count++; +//================================================================================= - survey_results->at(i).at(k).biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; - survey_results->at(i).at(k).biome[l] = get_biome_type(adjusted.x, adjusted.y, k); - geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index; +void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results) { + // color_ostream_proxy out(Core::getInstance().getConsole()); - if (!geo_summary->at(geo_index).aquifer_absent) survey_results->at(i).at(k).aquifer_count++; - if (!geo_summary->at(geo_index).clay_absent) survey_results->at(i).at(k).clay_count++; - if (!geo_summary->at(geo_index).sand_absent) survey_results->at(i).at(k).sand_count++; - if (!geo_summary->at(geo_index).flux_absent) survey_results->at(i).at(k).flux_count++; + embark_assist::survey::geo_survey(geo_summary); + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + uint16_t geo_index; + uint16_t sav_ev; + uint8_t offset_count = 0; + survey_results->at(i).at(k).surveyed = false; + survey_results->at(i).at(k).aquifer_count = 0; + survey_results->at(i).at(k).clay_count = 0; + survey_results->at(i).at(k).sand_count = 0; + survey_results->at(i).at(k).flux_count = 0; + survey_results->at(i).at(k).min_region_soil = 10; + survey_results->at(i).at(k).max_region_soil = 0; + survey_results->at(i).at(k).waterfall = false; + survey_results->at(i).at(k).savagery_count[0] = 0; + survey_results->at(i).at(k).savagery_count[1] = 0; + survey_results->at(i).at(k).savagery_count[2] = 0; + survey_results->at(i).at(k).evilness_count[0] = 0; + survey_results->at(i).at(k).evilness_count[1] = 0; + survey_results->at(i).at(k).evilness_count[2] = 0; + survey_results->at(i).at(k).metals.resize(state->max_inorganic); + survey_results->at(i).at(k).economics.resize(state->max_inorganic); + survey_results->at(i).at(k).minerals.resize(state->max_inorganic); + // Evil weather and rivers are handled in later operations. Should probably be merged into one. + + for (uint8_t l = 1; l < 10; l++) + { + adjusted = apply_offset(i, k, l); + if (adjusted.x != i || adjusted.y != k || l == 5) { + offset_count++; - if (geo_summary->at(geo_index).soil_size < survey_results->at(i).at(k).min_region_soil) - survey_results->at(i).at(k).min_region_soil = geo_summary->at(geo_index).soil_size; + survey_results->at(i).at(k).biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; + survey_results->at(i).at(k).biome[l] = get_biome_type(adjusted.x, adjusted.y, k); + geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index; - if (geo_summary->at(geo_index).soil_size > survey_results->at(i).at(k).max_region_soil) - survey_results->at(i).at(k).max_region_soil = geo_summary->at(geo_index).soil_size; + if (!geo_summary->at(geo_index).aquifer_absent) survey_results->at(i).at(k).aquifer_count++; + if (!geo_summary->at(geo_index).clay_absent) survey_results->at(i).at(k).clay_count++; + if (!geo_summary->at(geo_index).sand_absent) survey_results->at(i).at(k).sand_count++; + if (!geo_summary->at(geo_index).flux_absent) survey_results->at(i).at(k).flux_count++; - sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; - if (sav_ev == 3) sav_ev = 2; - survey_results->at(i).at(k).savagery_count[sav_ev]++; + if (geo_summary->at(geo_index).soil_size < survey_results->at(i).at(k).min_region_soil) + survey_results->at(i).at(k).min_region_soil = geo_summary->at(geo_index).soil_size; - sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; - if (sav_ev == 3) sav_ev = 2; - survey_results->at(i).at(k).evilness_count[sav_ev]++; + if (geo_summary->at(geo_index).soil_size > survey_results->at(i).at(k).max_region_soil) + survey_results->at(i).at(k).max_region_soil = geo_summary->at(geo_index).soil_size; - for (uint16_t m = 0; m < state->max_inorganic; m++) { - if (geo_summary->at(geo_index).possible_metals[m]) survey_results->at(i).at(k).metals[m] = true; - if (geo_summary->at(geo_index).possible_economics[m]) survey_results->at(i).at(k).economics[m] = true; - if (geo_summary->at(geo_index).possible_minerals[m]) survey_results->at(i).at(k).minerals[m] = true; - } - } - else { - survey_results->at(i).at(k).biome_index[l] = -1; - survey_results->at(i).at(k).biome[l] = -1; - } - } + sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).savagery_count[sav_ev]++; - survey_results->at(i).at(k).biome_count = 0; - for (uint8_t l = 1; l < 10; l++) { - if (survey_results->at(i).at(k).biome[l] != -1) survey_results->at(i).at(k).biome_count++; - } + sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).evilness_count[sav_ev]++; - if (survey_results->at(i).at(k).aquifer_count == offset_count) survey_results->at(i).at(k).aquifer_count = 256; - if (survey_results->at(i).at(k).clay_count == offset_count) survey_results->at(i).at(k).clay_count = 256; - if (survey_results->at(i).at(k).sand_count == offset_count) survey_results->at(i).at(k).sand_count = 256; - if (survey_results->at(i).at(k).flux_count == offset_count) survey_results->at(i).at(k).flux_count = 256; - for (uint8_t l = 0; l < 3; l++) { - if (survey_results->at(i).at(k).savagery_count[l] == offset_count) survey_results->at(i).at(k).savagery_count[l] = 256; - if (survey_results->at(i).at(k).evilness_count[l] == offset_count) survey_results->at(i).at(k).evilness_count[l] = 256; + for (uint16_t m = 0; m < state->max_inorganic; m++) { + if (geo_summary->at(geo_index).possible_metals[m]) survey_results->at(i).at(k).metals[m] = true; + if (geo_summary->at(geo_index).possible_economics[m]) survey_results->at(i).at(k).economics[m] = true; + if (geo_summary->at(geo_index).possible_minerals[m]) survey_results->at(i).at(k).minerals[m] = true; } } + else { + survey_results->at(i).at(k).biome_index[l] = -1; + survey_results->at(i).at(k).biome[l] = -1; + } } - survey_rivers(survey_results); - survey_evil_weather(survey_results); - } - - //================================================================================= - - void survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, - embark_assist::defs::world_tile_data *survey_results, - embark_assist::defs::mid_level_tiles *mlt) { -// color_ostream_proxy out(Core::getInstance().getConsole()); - auto screen = Gui::getViewscreenByType(0); - int16_t x = screen->location.region_pos.x; - int16_t y = screen->location.region_pos.y; - embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); - int8_t max_soil_depth; - int8_t offset; - int16_t elevation; - int16_t last_bottom; - int16_t top_z; - int16_t base_z; - int16_t min_z = 0; // Initialized to silence warning about potential usage of uninitialized data. - int16_t bottom_z; - df::coord2d adjusted; - df::world_data *world_data = world->world_data; - df::world_region_details *details = world_data->region_details[0]; - df::region_map_entry *world_tile = &world_data->region_map[x][y]; - std::vector features; - uint8_t soil_erosion; - uint16_t end_check_l; - uint16_t end_check_m; - uint16_t end_check_n; - - for (uint16_t i = 0; i < state->max_inorganic; i++) { - tile->metals[i] = 0; - tile->economics[i] = 0; - tile->minerals[i] = 0; + survey_results->at(i).at(k).biome_count = 0; + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome[l] != -1) survey_results->at(i).at(k).biome_count++; } - for (uint8_t i = 0; i < 16; i++) { - for (uint8_t k = 0; k < 16; k++) { - mlt->at(i).at(k).metals.resize(state->max_inorganic); - mlt->at(i).at(k).economics.resize(state->max_inorganic); - mlt->at(i).at(k).minerals.resize(state->max_inorganic); - } + if (survey_results->at(i).at(k).aquifer_count == offset_count) survey_results->at(i).at(k).aquifer_count = 256; + if (survey_results->at(i).at(k).clay_count == offset_count) survey_results->at(i).at(k).clay_count = 256; + if (survey_results->at(i).at(k).sand_count == offset_count) survey_results->at(i).at(k).sand_count = 256; + if (survey_results->at(i).at(k).flux_count == offset_count) survey_results->at(i).at(k).flux_count = 256; + for (uint8_t l = 0; l < 3; l++) { + if (survey_results->at(i).at(k).savagery_count[l] == offset_count) survey_results->at(i).at(k).savagery_count[l] = 256; + if (survey_results->at(i).at(k).evilness_count[l] == offset_count) survey_results->at(i).at(k).evilness_count[l] = 256; } + } + } - for (uint8_t i = 1; i < 10; i++) survey_results->at(x).at(y).biome_index[i] = -1; - - for (uint8_t i = 0; i < 16; i++) { - for (uint8_t k = 0; k < 16; k++) { - max_soil_depth = -1; - - offset = details->biome[i][k]; - adjusted = apply_offset(x, y, offset); - - if (adjusted.x != x || adjusted.y != y) - { - mlt->at(i).at(k).biome_offset = offset; - } - else - { - mlt->at(i).at(k).biome_offset = 5; - }; + embark_assist::survey::survey_rivers(survey_results); + embark_assist::survey::survey_evil_weather(survey_results); +} - survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] = - world_data->region_map[adjusted.x][adjusted.y].region_id; +//================================================================================= + +void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + int8_t max_soil_depth; + int8_t offset; + int16_t elevation; + int16_t last_bottom; + int16_t top_z; + int16_t base_z; + int16_t min_z = 0; // Initialized to silence warning about potential usage of uninitialized data. + int16_t bottom_z; + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + df::world_region_details *details = world_data->region_details[0]; + df::region_map_entry *world_tile = &world_data->region_map[x][y]; + std::vector features; + uint8_t soil_erosion; + uint16_t end_check_l; + uint16_t end_check_m; + uint16_t end_check_n; + + for (uint16_t i = 0; i < state->max_inorganic; i++) { + tile->metals[i] = 0; + tile->economics[i] = 0; + tile->minerals[i] = 0; + } - mlt->at(i).at(k).savagery_level = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; - if (mlt->at(i).at(k).savagery_level == 3) { - mlt->at(i).at(k).savagery_level = 2; - } - mlt->at(i).at(k).evilness_level = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; - if (mlt->at(i).at(k).evilness_level == 3) { - mlt->at(i).at(k).evilness_level = 2; - } + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } - elevation = details->elevation[i][k]; + for (uint8_t i = 1; i < 10; i++) survey_results->at(x).at(y).biome_index[i] = -1; - // Special biome adjustments - if (!world_data->region_map[adjusted.x][adjusted.y].flags.is_set(region_map_entry_flags::is_lake)) { - if (world_data->region_map[adjusted.x][adjusted.y].elevation >= 150) { // Mountain - max_soil_depth = 0; + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + max_soil_depth = -1; - } - else if (world_data->region_map[adjusted.x][adjusted.y].elevation < 100) { // Ocean - if (elevation == 99) { - elevation = 98; - } + offset = details->biome[i][k]; + adjusted = apply_offset(x, y, offset); - if ((world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 4 || - world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 5) && - details->unk12e8 < 500) { - max_soil_depth = 0; - } - } - } + if (adjusted.x != x || adjusted.y != y) + { + mlt->at(i).at(k).biome_offset = offset; + } + else + { + mlt->at(i).at(k).biome_offset = 5; + }; - base_z = elevation - 1; - features = details->features[i][k]; - std::map layer_bottom, layer_top; + survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] = + world_data->region_map[adjusted.x][adjusted.y].region_id; - end_check_l = static_cast(features.size()); - for (size_t l = 0; l < end_check_l; l++) { - auto feature = features[l]; + mlt->at(i).at(k).savagery_level = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (mlt->at(i).at(k).savagery_level == 3) { + mlt->at(i).at(k).savagery_level = 2; + } + mlt->at(i).at(k).evilness_level = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (mlt->at(i).at(k).evilness_level == 3) { + mlt->at(i).at(k).evilness_level = 2; + } - if (feature->layer != -1 && - feature->min_z != -30000) { - auto layer = world_data->underground_regions[feature->layer]; + elevation = details->elevation[i][k]; - layer_bottom[layer->layer_depth] = feature->min_z; - layer_top[layer->layer_depth] = feature->max_z; - base_z = std::min((int)base_z, (int)feature->min_z); + // Special biome adjustments + if (!world_data->region_map[adjusted.x][adjusted.y].flags.is_set(region_map_entry_flags::is_lake)) { + if (world_data->region_map[adjusted.x][adjusted.y].elevation >= 150) { // Mountain + max_soil_depth = 0; - if (layer->type == df::world_underground_region::MagmaSea) { - min_z = feature->min_z; // The features are individual per region tile - break; - } - } + } + else if (world_data->region_map[adjusted.x][adjusted.y].elevation < 100) { // Ocean + if (elevation == 99) { + elevation = 98; } - // Compute shifts for layers in the stack. - - if (max_soil_depth == -1) { // Not set to zero by the biome - max_soil_depth = std::max((154 - elevation) / 5, 1); + if ((world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 4 || + world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 5) && + details->unk12e8 < 500) { + max_soil_depth = 0; } + } + } - soil_erosion = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - - std::min((int)geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size, (int)max_soil_depth); - int16_t layer_shift[16]; - int16_t cur_shift = elevation + soil_erosion - 1; - - mlt->at(i).at(k).aquifer = false; - mlt->at(i).at(k).clay = false; - mlt->at(i).at(k).sand = false; - mlt->at(i).at(k).flux = false; - if (max_soil_depth == 0) { - mlt->at(i).at(k).soil_depth = 0; - } - else { - mlt->at(i).at(k).soil_depth = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - soil_erosion; - } - mlt->at(i).at(k).offset = offset; - mlt->at(i).at(k).elevation = details->elevation[i][k]; - mlt->at(i).at(k).river_present = false; - mlt->at(i).at(k).river_elevation = 100; - - if (details->rivers_vertical.active[i][k] == 1) { - mlt->at(i).at(k).river_present = true; - mlt->at(i).at(k).river_elevation = details->rivers_vertical.elevation[i][k]; - } - else if (details->rivers_horizontal.active[i][k] == 1) { - mlt->at(i).at(k).river_present = true; - mlt->at(i).at(k).river_elevation = details->rivers_horizontal.elevation[i][k]; - } + base_z = elevation - 1; + features = details->features[i][k]; + std::map layer_bottom, layer_top; - if (tile->min_region_soil > mlt->at(i).at(k).soil_depth) { - tile->min_region_soil = mlt->at(i).at(k).soil_depth; - } + end_check_l = static_cast(features.size()); + for (size_t l = 0; l < end_check_l; l++) { + auto feature = features[l]; - if (tile->max_region_soil < mlt->at(i).at(k).soil_depth) { - tile->max_region_soil = mlt->at(i).at(k).soil_depth; - } + if (feature->layer != -1 && + feature->min_z != -30000) { + auto layer = world_data->underground_regions[feature->layer]; - end_check_l = static_cast(world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers.size()); - if (end_check_l > 16) end_check_l = 16; - - for (uint16_t l = 0; l < end_check_l; l++) { - auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; - layer_shift[l] = cur_shift; - - if (layer->type == df::geo_layer_type::SOIL || - layer->type == df::geo_layer_type::SOIL_SAND) { - int16_t size = layer->top_height - layer->bottom_height - 1; - // Comment copied from prospector.cpp(like the logic)... - // This is to replicate the behavior of a probable bug in the - // map generation code : if a layer is partially eroded, the - // removed levels are in fact transferred to the layer below, - // because unlike the case of removing the whole layer, the code - // does not execute a loop to shift the lower part of the stack up. - if (size > soil_erosion) { - cur_shift = cur_shift - soil_erosion; - } + layer_bottom[layer->layer_depth] = feature->min_z; + layer_top[layer->layer_depth] = feature->max_z; + base_z = std::min((int)base_z, (int)feature->min_z); - soil_erosion -= std::min((int)soil_erosion, (int)size); - } + if (layer->type == df::world_underground_region::MagmaSea) { + min_z = feature->min_z; // The features are individual per region tile + break; } + } + } - last_bottom = elevation; - // Don't have to set up the end_check as we can reuse the one above. - - for (uint16_t l = 0; l < end_check_l; l++) { - auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; - top_z = last_bottom - 1; - bottom_z = std::max((int)layer->bottom_height + layer_shift[l], (int)min_z); + // Compute shifts for layers in the stack. - if (l == 15) { - bottom_z = min_z; // stretch layer if needed - } + if (max_soil_depth == -1) { // Not set to zero by the biome + max_soil_depth = std::max((154 - elevation) / 5, 1); + } - if (top_z >= bottom_z) { - mlt->at(i).at(k).minerals[layer->mat_index] = true; + soil_erosion = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - + std::min((int)geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size, (int)max_soil_depth); + int16_t layer_shift[16]; + int16_t cur_shift = elevation + soil_erosion - 1; + + mlt->at(i).at(k).aquifer = false; + mlt->at(i).at(k).clay = false; + mlt->at(i).at(k).sand = false; + mlt->at(i).at(k).flux = false; + if (max_soil_depth == 0) { + mlt->at(i).at(k).soil_depth = 0; + } + else { + mlt->at(i).at(k).soil_depth = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - soil_erosion; + } + mlt->at(i).at(k).offset = offset; + mlt->at(i).at(k).elevation = details->elevation[i][k]; + mlt->at(i).at(k).river_present = false; + mlt->at(i).at(k).river_elevation = 100; + + if (details->rivers_vertical.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_vertical.elevation[i][k]; + } + else if (details->rivers_horizontal.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_horizontal.elevation[i][k]; + } - end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size()); + if (tile->min_region_soil > mlt->at(i).at(k).soil_depth) { + tile->min_region_soil = mlt->at(i).at(k).soil_depth; + } - for (uint16_t m = 0; m < end_check_m; m++) { - mlt->at(i).at(k).metals[world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[m]] = true; - } + if (tile->max_region_soil < mlt->at(i).at(k).soil_depth) { + tile->max_region_soil = mlt->at(i).at(k).soil_depth; + } - if (layer->type == df::geo_layer_type::SOIL || - layer->type == df::geo_layer_type::SOIL_SAND) { - if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { - mlt->at(i).at(k).sand = true; - } - } + end_check_l = static_cast(world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers.size()); + if (end_check_l > 16) end_check_l = 16; + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + layer_shift[l] = cur_shift; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + int16_t size = layer->top_height - layer->bottom_height - 1; + // Comment copied from prospector.cpp(like the logic)... + // This is to replicate the behavior of a probable bug in the + // map generation code : if a layer is partially eroded, the + // removed levels are in fact transferred to the layer below, + // because unlike the case of removing the whole layer, the code + // does not execute a loop to shift the lower part of the stack up. + if (size > soil_erosion) { + cur_shift = cur_shift - soil_erosion; + } - if (world->raws.inorganics[layer->mat_index]->economic_uses.size() > 0) { - mlt->at(i).at(k).economics[layer->mat_index] = true; + soil_erosion -= std::min((int)soil_erosion, (int)size); + } + } - end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->economic_uses.size()); - for (uint16_t m = 0; m < end_check_m; m++) { - if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->clay_reaction) { - mlt->at(i).at(k).clay = true; - } + last_bottom = elevation; + // Don't have to set up the end_check as we can reuse the one above. - else if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->flux_reaction) { - mlt->at(i).at(k).flux = true; - } - } - } + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + top_z = last_bottom - 1; + bottom_z = std::max((int)layer->bottom_height + layer_shift[l], (int)min_z); - end_check_m = static_cast(layer->vein_mat.size()); + if (l == 15) { + bottom_z = min_z; // stretch layer if needed + } - for (uint16_t m = 0; m < end_check_m; m++) { - mlt->at(i).at(k).minerals[layer->vein_mat[m]] = true; + if (top_z >= bottom_z) { + mlt->at(i).at(k).minerals[layer->mat_index] = true; - end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index.size()); + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size()); - for (uint16_t n = 0; n < end_check_n; n++) { - mlt->at(i).at(k).metals[world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index[n]] = true; - } + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[m]] = true; + } - if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size() > 0) { - mlt->at(i).at(k).economics[layer->vein_mat[m]] = true; + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + mlt->at(i).at(k).sand = true; + } + } - end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size()); - for (uint16_t n = 0; n < end_check_n; n++) { - if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->clay_reaction) { - mlt->at(i).at(k).clay = true; - } + if (world->raws.inorganics[layer->mat_index]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->mat_index] = true; - else if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->flux_reaction) { - mlt->at(i).at(k).flux = true; - } - } - } + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->economic_uses.size()); + for (uint16_t m = 0; m < end_check_m; m++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; } - if (bottom_z <= elevation - 3 && - world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { - mlt->at(i).at(k).aquifer = true; + else if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; } } } - } - } - survey_results->at(x).at(y).aquifer_count = 0; - survey_results->at(x).at(y).clay_count = 0; - survey_results->at(x).at(y).sand_count = 0; - survey_results->at(x).at(y).flux_count = 0; - survey_results->at(x).at(y).min_region_soil = 10; - survey_results->at(x).at(y).max_region_soil = 0; - survey_results->at(x).at(y).savagery_count[0] = 0; - survey_results->at(x).at(y).savagery_count[1] = 0; - survey_results->at(x).at(y).savagery_count[2] = 0; - survey_results->at(x).at(y).evilness_count[0] = 0; - survey_results->at(x).at(y).evilness_count[1] = 0; - survey_results->at(x).at(y).evilness_count[2] = 0; - - bool river_elevation_found = false; - int16_t river_elevation; - - for (uint8_t i = 0; i < 16; i++) { - for (uint8_t k = 0; k < 16; k++) { - if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; } - if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; } - if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; } - if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; } - - if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { - survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; - } + end_check_m = static_cast(layer->vein_mat.size()); - if (mlt->at(i).at(k).soil_depth > survey_results->at(x).at(y).max_region_soil) { - survey_results->at(x).at(y).max_region_soil = mlt->at(i).at(k).soil_depth; - } + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).minerals[layer->vein_mat[m]] = true; - if (mlt->at(i).at(k).river_present) { - if (river_elevation_found) { - if (mlt->at(i).at(k).river_elevation != river_elevation) - { - survey_results->at(x).at(y).waterfall = true; - } - } - else { - river_elevation_found = true; - river_elevation = mlt->at(i).at(k).river_elevation; + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index.size()); + + for (uint16_t n = 0; n < end_check_n; n++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index[n]] = true; } - } - // River size surveyed separately - // biome_index handled above - // biome handled below - // evil weather handled separately - // reanimating handled separately - // thralling handled separately + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->vein_mat[m]] = true; - survey_results->at(x).at(y).savagery_count[mlt->at(i).at(k).savagery_level]++; - survey_results->at(x).at(y).evilness_count[mlt->at(i).at(k).evilness_level]++; + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size()); + for (uint16_t n = 0; n < end_check_n; n++) { + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } - for (uint16_t l = 0; l < state->max_inorganic; l++) { - if (mlt->at(i).at(k).metals[l]) { survey_results->at(x).at(y).metals[l] = true; } - if (mlt->at(i).at(k).economics[l]) { survey_results->at(x).at(y).economics[l] = true; } - if (mlt->at(i).at(k).minerals[l]) { survey_results->at(x).at(y).minerals[l] = true; } + else if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + } + + if (bottom_z <= elevation - 3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + mlt->at(i).at(k).aquifer = true; } } } + } + } - for (uint8_t i = 1; i < 10; i++) { - if (survey_results->at(x).at(y).biome_index[i] == -1) { - survey_results->at(x).at(y).biome[i] = -1; - } + survey_results->at(x).at(y).aquifer_count = 0; + survey_results->at(x).at(y).clay_count = 0; + survey_results->at(x).at(y).sand_count = 0; + survey_results->at(x).at(y).flux_count = 0; + survey_results->at(x).at(y).min_region_soil = 10; + survey_results->at(x).at(y).max_region_soil = 0; + survey_results->at(x).at(y).savagery_count[0] = 0; + survey_results->at(x).at(y).savagery_count[1] = 0; + survey_results->at(x).at(y).savagery_count[2] = 0; + survey_results->at(x).at(y).evilness_count[0] = 0; + survey_results->at(x).at(y).evilness_count[1] = 0; + survey_results->at(x).at(y).evilness_count[2] = 0; + + bool river_elevation_found = false; + int16_t river_elevation; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; } + if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; } + if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; } + if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; } + + if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { + survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; } - bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; - for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { - biomes[i] = false; + if (mlt->at(i).at(k).soil_depth > survey_results->at(x).at(y).max_region_soil) { + survey_results->at(x).at(y).max_region_soil = mlt->at(i).at(k).soil_depth; } - for (uint8_t i = 1; i < 10; i++) - { - if (survey_results->at(x).at(y).biome[i] != -1) { - biomes[survey_results->at(x).at(y).biome[i]] = true; + if (mlt->at(i).at(k).river_present) { + if (river_elevation_found) { + if (mlt->at(i).at(k).river_elevation != river_elevation) + { + survey_results->at(x).at(y).waterfall = true; + } + } + else { + river_elevation_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; } } - int count = 0; - for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { - if (biomes[i]) count++; - } - - tile->biome_count = count; - tile->surveyed = true; - } - //================================================================================= - df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset) { - df::coord2d result; - result.x = x; - result.y = y; - - switch (offset) { - case 1: - result.x--; - result.y++; - break; - - case 2: - result.y++; - break; - - case 3: - result.x++; - result.y++; - break; - - case 4: - result.x--; - break; - - case 5: - break; // Center. No change - - case 6: - result.x++; - break; - - case 7: - result.x--; - result.y--; - break; - - case 8: - result.y--; - break; - - case 9: - result.x++; - result.y--; - break; - - default: - // Bug. Just act as if it's the center... - break; - } + // River size surveyed separately + // biome_index handled above + // biome handled below + // evil weather handled separately + // reanimating handled separately + // thralling handled separately - if (result.x < 0) { - result.x = 0; - } - else if (result.x >= world->worldgen.worldgen_parms.dim_x) { - result.x = world->worldgen.worldgen_parms.dim_x - 1; - } + survey_results->at(x).at(y).savagery_count[mlt->at(i).at(k).savagery_level]++; + survey_results->at(x).at(y).evilness_count[mlt->at(i).at(k).evilness_level]++; - if (result.y < 0) { - result.y = 0; - } - else if (result.y >= world->worldgen.worldgen_parms.dim_y) { - result.y = world->worldgen.worldgen_parms.dim_y - 1; + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (mlt->at(i).at(k).metals[l]) { survey_results->at(x).at(y).metals[l] = true; } + if (mlt->at(i).at(k).economics[l]) { survey_results->at(x).at(y).economics[l] = true; } + if (mlt->at(i).at(k).minerals[l]) { survey_results->at(x).at(y).minerals[l] = true; } } + } + } - return result; + for (uint8_t i = 1; i < 10; i++) { + if (survey_results->at(x).at(y).biome_index[i] == -1) { + survey_results->at(x).at(y).biome[i] = -1; } + } - //================================================================================= + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + biomes[i] = false; + } - void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists *site_list) { -// color_ostream_proxy out(Core::getInstance().getConsole()); - auto screen = Gui::getViewscreenByType(0); - df::world_data *world_data = world->world_data; - int8_t index = 0; - - site_list->clear(); - - for (uint32_t i = 0; i < world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites.size(); i++) { - auto site = world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites[i]; - switch (site->type) { - case df::world_site_type::PlayerFortress: - case df::world_site_type::DarkFortress: - case df::world_site_type::MountainHalls: - case df::world_site_type::ForestRetreat: - case df::world_site_type::Town: - case df::world_site_type::Fortress: - break; // Already visible - - case df::world_site_type::Cave: - if (!world->worldgen.worldgen_parms.all_caves_visible) { - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'c' }); // Cave - } - break; + for (uint8_t i = 1; i < 10; i++) + { + if (survey_results->at(x).at(y).biome[i] != -1) { + biomes[survey_results->at(x).at(y).biome[i]] = true; + } + } + int count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) count++; + } - case df::world_site_type::Monument: - if (site->subtype_info->lair_type != -1 || - site->subtype_info->is_monument == 0) { // Not Tomb, which is visible already - } - else if (site->subtype_info->lair_type == -1) { - site_list->push_back( { (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'V' }); // Vault - } - else { - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'M' }); // Any other Monument type. Pyramid? - } - break; + tile->biome_count = count; + tile->surveyed = true; +} +//================================================================================= + +df::coord2d embark_assist::survey::apply_offset(uint16_t x, uint16_t y, int8_t offset) { + df::coord2d result; + result.x = x; + result.y = y; + + switch (offset) { + case 1: + result.x--; + result.y++; + break; + + case 2: + result.y++; + break; + + case 3: + result.x++; + result.y++; + break; + + case 4: + result.x--; + break; + + case 5: + break; // Center. No change + + case 6: + result.x++; + break; + + case 7: + result.x--; + result.y--; + break; + + case 8: + result.y--; + break; + + case 9: + result.x++; + result.y--; + break; + + default: + // Bug. Just act as if it's the center... + break; + } - case df::world_site_type::ImportantLocation: - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'i' }); // Don't really know what that is... - break; + if (result.x < 0) { + result.x = 0; + } + else if (result.x >= world->worldgen.worldgen_parms.dim_x) { + result.x = world->worldgen.worldgen_parms.dim_x - 1; + } - case df::world_site_type::LairShrine: - if (site->subtype_info->lair_type == 0 || - site->subtype_info->lair_type == 1 || - site->subtype_info->lair_type == 4) { // Only Rocs seen. Mountain lair? - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'l' }); // Lair - } - else if (site->subtype_info->lair_type == 2) { - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'L' }); // Labyrinth - } - else if (site->subtype_info->lair_type == 3) { - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'S' }); // Shrine - } - else { - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '?' }); // Can these exist? - } - break; + if (result.y < 0) { + result.y = 0; + } + else if (result.y >= world->worldgen.worldgen_parms.dim_y) { + result.y = world->worldgen.worldgen_parms.dim_y - 1; + } - case df::world_site_type::Camp: - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'C' }); // Camp - break; + return result; +} - default: - site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '!' }); // Not even in the enum... - break; - } +//================================================================================= + +void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists *site_list) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + df::world_data *world_data = world->world_data; + int8_t index = 0; + + site_list->clear(); + + for (uint32_t i = 0; i < world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites.size(); i++) { + auto site = world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites[i]; + switch (site->type) { + case df::world_site_type::PlayerFortress: + case df::world_site_type::DarkFortress: + case df::world_site_type::MountainHalls: + case df::world_site_type::ForestRetreat: + case df::world_site_type::Town: + case df::world_site_type::Fortress: + break; // Already visible + + case df::world_site_type::Cave: + if (!world->worldgen.worldgen_parms.all_caves_visible) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'c' }); // Cave } - } + break; - //================================================================================= + case df::world_site_type::Monument: + if (site->subtype_info->lair_type != -1 || + site->subtype_info->is_monument == 0) { // Not Tomb, which is visible already + } + else if (site->subtype_info->lair_type == -1) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'V' }); // Vault + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'M' }); // Any other Monument type. Pyramid? + } + break; - void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *mlt, - embark_assist::defs::site_infos *site_info, - bool use_cache) { + case df::world_site_type::ImportantLocation: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'i' }); // Don't really know what that is... + break; -// color_ostream_proxy out(Core::getInstance().getConsole()); - auto screen = Gui::getViewscreenByType(0); - int16_t elevation; - uint16_t x = screen->location.region_pos.x; - uint16_t y = screen->location.region_pos.y; - bool river_found = false; - int16_t river_elevation; - std::vector metals(state->max_inorganic); - std::vector economics(state->max_inorganic); - std::vector minerals(state->max_inorganic); - - if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...). - state->local_min_x = screen->location.embark_pos_min.x; - state->local_min_y = screen->location.embark_pos_min.y; - state->local_max_x = screen->location.embark_pos_max.x; - state->local_max_y = screen->location.embark_pos_max.y; + case df::world_site_type::LairShrine: + if (site->subtype_info->lair_type == 0 || + site->subtype_info->lair_type == 1 || + site->subtype_info->lair_type == 4) { // Only Rocs seen. Mountain lair? + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'l' }); // Lair } + else if (site->subtype_info->lair_type == 2) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'L' }); // Labyrinth + } + else if (site->subtype_info->lair_type == 3) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'S' }); // Shrine + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '?' }); // Can these exist? + } + break; - state->x = x; - state->y = y; - - site_info->aquifer = false; - site_info->aquifer_full = true; - site_info->min_soil = 10; - site_info->max_soil = 0; - site_info->flat = true; - site_info->waterfall = false; - site_info->clay = false; - site_info->sand = false; - site_info->flux = false; - site_info->metals.clear(); - site_info->economics.clear(); - site_info->metals.clear(); - - for (uint8_t i = state->local_min_x; i <= state->local_max_x; i++) { - for (uint8_t k = state->local_min_y; k <= state->local_max_y; k++) { - if (mlt->at(i).at(k).aquifer) { - site_info->aquifer = true; - } - else { - site_info->aquifer_full = false; - } - - if (mlt->at(i).at(k).soil_depth < site_info->min_soil) { - site_info->min_soil = mlt->at(i).at(k).soil_depth; - } + case df::world_site_type::Camp: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'C' }); // Camp + break; - if (mlt->at(i).at(k).soil_depth > site_info->max_soil) { - site_info->max_soil = mlt->at(i).at(k).soil_depth; - } + default: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '!' }); // Not even in the enum... + break; + } + } +} - if (i == state->local_min_x && k == state->local_min_y) { - elevation = mlt->at(i).at(k).elevation; +//================================================================================= + +void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::site_infos *site_info, + bool use_cache) { + + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t elevation; + uint16_t x = screen->location.region_pos.x; + uint16_t y = screen->location.region_pos.y; + bool river_found = false; + int16_t river_elevation; + std::vector metals(state->max_inorganic); + std::vector economics(state->max_inorganic); + std::vector minerals(state->max_inorganic); + + if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...). + state->local_min_x = screen->location.embark_pos_min.x; + state->local_min_y = screen->location.embark_pos_min.y; + state->local_max_x = screen->location.embark_pos_max.x; + state->local_max_y = screen->location.embark_pos_max.y; + } - } - else if (elevation != mlt->at(i).at(k).elevation) { - site_info->flat = false; - } + state->x = x; + state->y = y; + + site_info->aquifer = false; + site_info->aquifer_full = true; + site_info->min_soil = 10; + site_info->max_soil = 0; + site_info->flat = true; + site_info->waterfall = false; + site_info->clay = false; + site_info->sand = false; + site_info->flux = false; + site_info->metals.clear(); + site_info->economics.clear(); + site_info->metals.clear(); + + for (uint8_t i = state->local_min_x; i <= state->local_max_x; i++) { + for (uint8_t k = state->local_min_y; k <= state->local_max_y; k++) { + if (mlt->at(i).at(k).aquifer) { + site_info->aquifer = true; + } + else { + site_info->aquifer_full = false; + } - if (mlt->at(i).at(k).river_present) { - if (river_found) { - if (river_elevation != mlt->at(i).at(k).river_elevation) { - site_info->waterfall = true; - } - } - else { - river_elevation = mlt->at(i).at(k).river_elevation; - river_found = true; - } - } + if (mlt->at(i).at(k).soil_depth < site_info->min_soil) { + site_info->min_soil = mlt->at(i).at(k).soil_depth; + } - if (mlt->at(i).at(k).clay) { - site_info->clay = true; - } + if (mlt->at(i).at(k).soil_depth > site_info->max_soil) { + site_info->max_soil = mlt->at(i).at(k).soil_depth; + } - if (mlt->at(i).at(k).sand) { - site_info->sand = true; - } + if (i == state->local_min_x && k == state->local_min_y) { + elevation = mlt->at(i).at(k).elevation; - if (mlt->at(i).at(k).flux) { - site_info->flux = true; - } + } + else if (elevation != mlt->at(i).at(k).elevation) { + site_info->flat = false; + } - for (uint16_t l = 0; l < state->max_inorganic; l++) { - metals[l] = metals [l] || mlt->at(i).at(k).metals[l]; - economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; - minerals[l] = minerals[l] || mlt->at(i).at(k).minerals[l]; + if (mlt->at(i).at(k).river_present) { + if (river_found) { + if (river_elevation != mlt->at(i).at(k).river_elevation) { + site_info->waterfall = true; } } - } - for (uint16_t l = 0; l < state->max_inorganic; l++) { - if (metals[l]) { - site_info->metals.push_back(l); + else { + river_elevation = mlt->at(i).at(k).river_elevation; + river_found = true; } + } - if (economics[l]) { - site_info->economics.push_back(l); - } + if (mlt->at(i).at(k).clay) { + site_info->clay = true; + } - if (minerals[l]) { - site_info->minerals.push_back(l); - } + if (mlt->at(i).at(k).sand) { + site_info->sand = true; + } + + if (mlt->at(i).at(k).flux) { + site_info->flux = true; } + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + metals[l] = metals[l] || mlt->at(i).at(k).metals[l]; + economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; + minerals[l] = minerals[l] || mlt->at(i).at(k).minerals[l]; + } + } + } + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (metals[l]) { + site_info->metals.push_back(l); } - //================================================================================= + if (economics[l]) { + site_info->economics.push_back(l); + } - void embark_assist::survey::shutdown() { - delete state; + if (minerals[l]) { + site_info->minerals.push_back(l); } } } + +//================================================================================= + +void embark_assist::survey::shutdown() { + delete state; +} + From 8a81f6b5f717ad65f0f658c9c3a8ad36086c4eba Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 17:14:21 +0200 Subject: [PATCH 0630/1012] Removed extra qualification to make Travis happy. --- plugins/embark-assistant/help_ui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index 9fe79e210..d75d00ce2 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -24,7 +24,7 @@ namespace embark_assist{ class ViewscreenHelpUi : public dfhack_viewscreen { public: - ViewscreenHelpUi::ViewscreenHelpUi(); + ViewscreenHelpUi(); void feed(std::set *input); From c2b36f0979b9ffefdbc933fb514ec59a69e5614b Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 1 Sep 2017 17:29:55 +0200 Subject: [PATCH 0631/1012] Removed extra qualification to make Travis happy. --- plugins/embark-assistant/survey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 5c0ff95af..ace97e5b1 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -269,7 +269,7 @@ namespace embark_assist { } else if (interaction->targets[k]->getType() == 2) {// Returns wrong type.. Should be df::interaction_target_type::MATERIAL df::interaction_target_materialst* material = static_cast(interaction->targets[k]); - if (DFHack::MaterialInfo::MaterialInfo(material->anon_1, material->anon_2).isInorganic()) { + if (DFHack::MaterialInfo(material->anon_1, material->anon_2).isInorganic()) { for (uint16_t l = 0; l < world->raws.inorganics[material->anon_2]->material.syndrome.size(); l++) { for (uint16_t m = 0; m < world->raws.inorganics[material->anon_2]->material.syndrome[l]->ce.size(); m++) { if (world->raws.inorganics[material->anon_2]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { From 8dffd48f70e55710891ab046b9474cd063a70b54 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 2 Oct 2017 19:55:16 +0530 Subject: [PATCH 0632/1012] Add a flag for functions considered safe for a remote computer to call. --- library/RemoteServer.cpp | 6 +++++- library/include/RemoteServer.h | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index df65e8e5a..699176549 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -284,7 +284,11 @@ void ServerConnection::threadFn() } else { - if (!fn->in()->ParseFromArray(buf.get(), header.size)) + if (((fn->flags & SF_ALLOW_REMOTE) != SF_ALLOW_REMOTE) && strcmp(socket->GetClientAddr(), "127.0.0.1") != 0) + { + stream.printerr("In call to %s: forbidden host: %s\n", fn->name, socket->GetClientAddr()); + } + else if (!fn->in()->ParseFromArray(buf.get(), header.size)) { stream.printerr("In call to %s: could not decode input args.\n", fn->name); } diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index df2f42712..d01eb0a19 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -46,7 +46,10 @@ namespace DFHack SF_CALLED_ONCE = 1, // Don't automatically suspend the core around the call. // The function is supposed to manage locking itself. - SF_DONT_SUSPEND = 2 + SF_DONT_SUSPEND = 2, + // The function is considered safe to call from a remote computer. + // All other functions cannot be allowed for security reasons. + SF_ALLOW_REMOTE = 4 }; class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase { From cbeb2414359ca801e1806f874929a86309d07bdf Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 2 Oct 2017 20:48:05 +0530 Subject: [PATCH 0633/1012] Listen on any address --- library/RemoteServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 699176549..f27bb36ee 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -379,7 +379,7 @@ bool ServerMain::listen(int port) socket->Initialize(); - if (!socket->Listen("127.0.0.1", port)) + if (!socket->Listen(NULL, port)) return false; thread = new tthread::thread(threadFn, this); From 1d02ecff6f6b1dfafc3d317c026535b532580d42 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 3 Oct 2017 18:56:49 +0530 Subject: [PATCH 0634/1012] Add safe for remote flag to select default functions. --- library/RemoteTools.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 77374ea0c..763049a04 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -652,29 +652,29 @@ CoreService::CoreService() { suspend_depth = 0; // These 2 methods must be first, so that they get id 0 and 1 - addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND); + addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND); // Add others here: - addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); - addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); + addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); + addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); addMethod("RunLua", &CoreService::RunLua); // Functions: - addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); - addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); + addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); + addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); - addFunction("GetWorldInfo", GetWorldInfo); + addFunction("GetWorldInfo", GetWorldInfo, SF_ALLOW_REMOTE); - addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND); - addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND); + addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE); + addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE); - addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); - addFunction("ListUnits", ListUnits); - addFunction("ListSquads", ListSquads); + addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE | SF_ALLOW_REMOTE); + addFunction("ListUnits", ListUnits, SF_ALLOW_REMOTE); + addFunction("ListSquads", ListSquads, SF_ALLOW_REMOTE); - addFunction("SetUnitLabors", SetUnitLabors); + addFunction("SetUnitLabors", SetUnitLabors, SF_ALLOW_REMOTE); } CoreService::~CoreService() From bb1eb0b48a650afa42cd1f4aab9f9f95dc071bd6 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 3 Oct 2017 19:48:37 +0530 Subject: [PATCH 0635/1012] Use a config file to enable outside connections. --- dfhack-config/remote-server.cfg | 2 ++ library/RemoteServer.cpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 dfhack-config/remote-server.cfg diff --git a/dfhack-config/remote-server.cfg b/dfhack-config/remote-server.cfg new file mode 100644 index 000000000..877f06179 --- /dev/null +++ b/dfhack-config/remote-server.cfg @@ -0,0 +1,2 @@ +#change this to true to allow remote RPC connections from other devices. +allow-remote-connections=false \ No newline at end of file diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index f27bb36ee..4a4787006 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -379,8 +379,33 @@ bool ServerMain::listen(int port) socket->Initialize(); - if (!socket->Listen(NULL, port)) - return false; + bool allow_remote = false; + std::string filename("dfhack-config/remote-server.cfg"); + + std::ifstream configFile(filename); + if (configFile.is_open()) + { + std::string line; + while (std::getline(configFile, line)) + { + if (line.compare(0, 1, "#") == 0) + continue; + if (line.compare(0, 24, "allow-remote-connections") == 0) + { + allow_remote = (line.compare(25, std::string::npos, "true") == 0); + } + } + } + if (allow_remote) + { + if (!socket->Listen(NULL, port)) + return false; + } + else + { + if (!socket->Listen("127.0.0.1", port)) + return false; + } thread = new tthread::thread(threadFn, this); thread->detach(); From 3c5483df03db4d301681bcbc8b5087d54feffb9e Mon Sep 17 00:00:00 2001 From: Japa Illo Date: Wed, 4 Oct 2017 09:48:12 +0530 Subject: [PATCH 0636/1012] Change tabs to spaces to satisfy travis. --- library/RemoteServer.cpp | 62 +++++++++++++++++----------------- library/include/RemoteServer.h | 6 ++-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 4a4787006..05fd141f2 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -284,10 +284,10 @@ void ServerConnection::threadFn() } else { - if (((fn->flags & SF_ALLOW_REMOTE) != SF_ALLOW_REMOTE) && strcmp(socket->GetClientAddr(), "127.0.0.1") != 0) - { - stream.printerr("In call to %s: forbidden host: %s\n", fn->name, socket->GetClientAddr()); - } + if (((fn->flags & SF_ALLOW_REMOTE) != SF_ALLOW_REMOTE) && strcmp(socket->GetClientAddr(), "127.0.0.1") != 0) + { + stream.printerr("In call to %s: forbidden host: %s\n", fn->name, socket->GetClientAddr()); + } else if (!fn->in()->ParseFromArray(buf.get(), header.size)) { stream.printerr("In call to %s: could not decode input args.\n", fn->name); @@ -379,33 +379,33 @@ bool ServerMain::listen(int port) socket->Initialize(); - bool allow_remote = false; - std::string filename("dfhack-config/remote-server.cfg"); - - std::ifstream configFile(filename); - if (configFile.is_open()) - { - std::string line; - while (std::getline(configFile, line)) - { - if (line.compare(0, 1, "#") == 0) - continue; - if (line.compare(0, 24, "allow-remote-connections") == 0) - { - allow_remote = (line.compare(25, std::string::npos, "true") == 0); - } - } - } - if (allow_remote) - { - if (!socket->Listen(NULL, port)) - return false; - } - else - { - if (!socket->Listen("127.0.0.1", port)) - return false; - } + bool allow_remote = false; + std::string filename("dfhack-config/remote-server.cfg"); + + std::ifstream configFile(filename); + if (configFile.is_open()) + { + std::string line; + while (std::getline(configFile, line)) + { + if (line.compare(0, 1, "#") == 0) + continue; + if (line.compare(0, 24, "allow-remote-connections") == 0) + { + allow_remote = (line.compare(25, std::string::npos, "true") == 0); + } + } + } + if (allow_remote) + { + if (!socket->Listen(NULL, port)) + return false; + } + else + { + if (!socket->Listen("127.0.0.1", port)) + return false; + } thread = new tthread::thread(threadFn, this); thread->detach(); diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index d01eb0a19..514f91250 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -47,9 +47,9 @@ namespace DFHack // Don't automatically suspend the core around the call. // The function is supposed to manage locking itself. SF_DONT_SUSPEND = 2, - // The function is considered safe to call from a remote computer. - // All other functions cannot be allowed for security reasons. - SF_ALLOW_REMOTE = 4 + // The function is considered safe to call from a remote computer. + // All other functions cannot be allowed for security reasons. + SF_ALLOW_REMOTE = 4 }; class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase { From d3f3ebf77868d344c09b911137c8e26e4c92c2b3 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 4 Oct 2017 14:37:04 -0500 Subject: [PATCH 0637/1012] auto_ptr is deprecated - use unique_ptr instead --- library/RemoteServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index df65e8e5a..bc30daff3 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -261,7 +261,7 @@ void ServerConnection::threadFn() break; } - std::auto_ptr buf(new uint8_t[header.size]); + std::unique_ptr buf(new uint8_t[header.size]); if (!readFullBuffer(socket, buf.get(), header.size)) { From c080da3750d923bd546f94c40c0a1b9ee88f28cc Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 8 Oct 2017 19:54:51 -0600 Subject: [PATCH 0638/1012] listcolumn - adjust add(ListEntry) to take const ref, more compatible on Linux --- plugins/listcolumn.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 9914c8ea7..4e4815b69 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -70,7 +70,7 @@ public: display_max_rows = gps->dimy - 4 - bottom_margin; } - void add(ListEntry &entry) + void add(const ListEntry &entry) { list.push_back(entry); if (entry.text.length() > max_item_width) From cc595d7a4ed3d72f654e4474fd62563f9930983e Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 16 Oct 2017 14:01:23 -0500 Subject: [PATCH 0639/1012] Fix fencepost error in orders import. Fixes #1177. --- plugins/orders.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 53c1844ac..4a2e3b0c0 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -146,15 +146,22 @@ static void json_array_to_bitfield(B & bits, Json::Value & arr) return; } - for (Json::ArrayIndex i = arr.size() - 1; i != 0; i--) + for (Json::ArrayIndex i = arr.size(); i != 0; i--) { + if (!arr[i - 1].isString()) + { + continue; + } + + std::string str(arr[i - 1].asString()); + int current; - if (get_bitfield_field(¤t, bits, arr[i].asString())) + if (get_bitfield_field(¤t, bits, str)) { - if (!current && set_bitfield_field(&bits, arr[i].asString(), 1)) + if (!current && set_bitfield_field(&bits, str, 1)) { Json::Value removed; - arr.removeIndex(i, &removed); + arr.removeIndex(i - 1, &removed); } } } From d0090223113823b759681b46f30ecf12f7574723 Mon Sep 17 00:00:00 2001 From: David Timm Date: Tue, 17 Oct 2017 13:43:20 -0600 Subject: [PATCH 0640/1012] Add tree product exclusions to autochop --- plugins/autochop.cpp | 162 ++++++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 50 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index dbc79af42..48f0a7626 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -16,6 +16,7 @@ #include "df/items_other_id.h" #include "df/job.h" #include "df/map_block.h" +#include "df/material.h" #include "df/plant.h" #include "df/plant_raw.h" #include "df/tile_dig_designation.h" @@ -48,6 +49,9 @@ static bool autochop_enabled = false; static int min_logs, max_logs; static const int LOG_CAP_MAX = 99999; static bool wait_for_threshold; +static bool skip_fruit_trees; +static bool skip_food_trees; +static bool skip_cook_trees; static PersistentDataItem config_autochop; @@ -179,6 +183,9 @@ static void save_config() config_autochop.ival(1) = min_logs; config_autochop.ival(2) = max_logs; config_autochop.ival(3) = wait_for_threshold; + config_autochop.ival(5) = skip_fruit_trees; + config_autochop.ival(6) = skip_food_trees; + config_autochop.ival(7) = skip_cook_trees; } static void initialize() @@ -188,6 +195,9 @@ static void initialize() min_logs = 80; max_logs = 100; wait_for_threshold = false; + skip_fruit_trees = false; + skip_food_trees = false; + skip_cook_trees = false; config_autochop = World::GetPersistentData("autochop/config"); if (config_autochop.isValid()) @@ -197,6 +207,9 @@ static void initialize() min_logs = config_autochop.ival(1); max_logs = config_autochop.ival(2); wait_for_threshold = config_autochop.ival(3); + skip_fruit_trees = config_autochop.ival(4); + skip_food_trees = config_autochop.ival(5); + skip_cook_trees = config_autochop.ival(6); } else { @@ -206,29 +219,63 @@ static void initialize() } } +static bool skip_plant(const df::plant * plant) +{ + // Skip all non-trees immediately. + if (plant->flags.bits.is_shrub) + return true; + + // Skip plants with invalid tile. + df::map_block *cur = Maps::getTileBlock(plant->pos); + if (!cur) + return true; + + int x = plant->pos.x % 16; + int y = plant->pos.y % 16; + + // Skip all unrevealed plants. + if (cur->designation[x][y].bits.hidden) + return true; + + const df::plant_raw *plant_raw = df::plant_raw::find(plant->material); + + // Skip fruit trees if set. + if (skip_fruit_trees && plant_raw->material_defs.type_drink != -1) + return true; + + if (skip_food_trees || skip_cook_trees) + { + df::material * mat; + for (int idx = 0; idx < plant_raw->material.size(); idx++) + { + mat = plant_raw->material[idx]; + if (skip_food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) + return true; + + if (skip_cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) + return true; + } + } + + df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); + if (material != tiletype_material::TREE) + return true; + + return false; +} + static int do_chop_designation(bool chop, bool count_only) { int count = 0; for (size_t i = 0; i < world->plants.all.size(); i++) { const df::plant *plant = world->plants.all[i]; - df::map_block *cur = Maps::getTileBlock(plant->pos); - if (!cur) - continue; - int x = plant->pos.x % 16; - int y = plant->pos.y % 16; - - if (plant->flags.bits.is_shrub) - continue; - if (cur->designation[x][y].bits.hidden) - continue; - df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); - if (material != tiletype_material::TREE) - continue; + if (skip_plant(plant)) + continue; - if (!count_only && !watchedBurrows.isValidPos(plant->pos)) - continue; + if (!count_only && !watchedBurrows.isValidPos(plant->pos)) + continue; if (chop && !Designations::isPlantMarked(plant)) { @@ -554,6 +601,18 @@ public: { change_max_logs(10); } + else if (input->count(interface_key::CUSTOM_F)) + { + skip_fruit_trees = !skip_fruit_trees; + } + else if (input->count(interface_key::CUSTOM_E)) + { + skip_food_trees = !skip_food_trees; + } + else if (input->count(interface_key::CUSTOM_C)) + { + skip_cook_trees = !skip_cook_trees; + } else if (enabler->tracking_on && enabler->mouse_lbut) { if (burrows_column.setHighlightByMouse()) @@ -602,41 +661,44 @@ public: OutputHotkeyString(x, y, "Designate Now", "d", true, left_margin); OutputHotkeyString(x, y, "Undesignate Now", "u", true, left_margin); OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin); - if (autochop_enabled) - { - using namespace df::enums::interface_key; - const struct { - const char *caption; - int count; - bool in_edit; - df::interface_key key; - df::interface_key skeys[4]; - } rows[] = { - {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}}, - {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}} - }; - for (size_t i = 0; i < sizeof(rows)/sizeof(rows[0]); ++i) - { - auto row = rows[i]; - OutputHotkeyString(x, y, row.caption, row.key); - auto prev_x = x; - if (row.in_edit) - OutputString(COLOR_LIGHTCYAN, x, y, int_to_string(row.count) + "_"); - else if (row.count <= LOG_CAP_MAX) - OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(row.count)); - else - OutputString(COLOR_LIGHTBLUE, x, y, "Unlimited"); - if (edit_mode == EDIT_NONE) - { - x = std::max(x, prev_x + 10); - for (size_t j = 0; j < sizeof(row.skeys)/sizeof(row.skeys[0]); ++j) - OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j])); - OutputString(COLOR_WHITE, x, y, ": Step"); - } - OutputString(COLOR_WHITE, x, y, "", true, left_margin); - } - OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); - } + if (autochop_enabled) + { + using namespace df::enums::interface_key; + const struct { + const char *caption; + int count; + bool in_edit; + df::interface_key key; + df::interface_key skeys[4]; + } rows[] = { + {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}}, + {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}} + }; + for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); ++i) + { + auto row = rows[i]; + OutputHotkeyString(x, y, row.caption, row.key); + auto prev_x = x; + if (row.in_edit) + OutputString(COLOR_LIGHTCYAN, x, y, int_to_string(row.count) + "_"); + else if (row.count <= LOG_CAP_MAX) + OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(row.count)); + else + OutputString(COLOR_LIGHTBLUE, x, y, "Unlimited"); + if (edit_mode == EDIT_NONE) + { + x = std::max(x, prev_x + 10); + for (size_t j = 0; j < sizeof(row.skeys) / sizeof(row.skeys[0]); ++j) + OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j])); + OutputString(COLOR_WHITE, x, y, ": Step"); + } + OutputString(COLOR_WHITE, x, y, "", true, left_margin); + } + OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); + OutputToggleString(x, y, "Skip Fruit Trees: ", "f", skip_fruit_trees, true, left_margin); + OutputToggleString(x, y, "Skip Edible Product Trees: ", "e", skip_food_trees, true, left_margin); + OutputToggleString(x, y, "Skip Cookable Product Trees: ", "c", skip_cook_trees, true, left_margin); + } ++y; OutputString(COLOR_BROWN, x, y, "Current Counts", true, left_margin); From 3c564c64ba85f1dab9e13eb4b2bc2d9ebe7e3c04 Mon Sep 17 00:00:00 2001 From: David Timm Date: Tue, 17 Oct 2017 15:17:35 -0600 Subject: [PATCH 0641/1012] Fix tabs. --- plugins/autochop.cpp | 178 +++++++++++++++++++++---------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 48f0a7626..312f5ba42 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -195,9 +195,9 @@ static void initialize() min_logs = 80; max_logs = 100; wait_for_threshold = false; - skip_fruit_trees = false; - skip_food_trees = false; - skip_cook_trees = false; + skip_fruit_trees = false; + skip_food_trees = false; + skip_cook_trees = false; config_autochop = World::GetPersistentData("autochop/config"); if (config_autochop.isValid()) @@ -221,47 +221,47 @@ static void initialize() static bool skip_plant(const df::plant * plant) { - // Skip all non-trees immediately. - if (plant->flags.bits.is_shrub) - return true; - - // Skip plants with invalid tile. - df::map_block *cur = Maps::getTileBlock(plant->pos); - if (!cur) - return true; - - int x = plant->pos.x % 16; - int y = plant->pos.y % 16; - - // Skip all unrevealed plants. - if (cur->designation[x][y].bits.hidden) - return true; - - const df::plant_raw *plant_raw = df::plant_raw::find(plant->material); - - // Skip fruit trees if set. - if (skip_fruit_trees && plant_raw->material_defs.type_drink != -1) - return true; - - if (skip_food_trees || skip_cook_trees) - { - df::material * mat; - for (int idx = 0; idx < plant_raw->material.size(); idx++) - { - mat = plant_raw->material[idx]; - if (skip_food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) - return true; - - if (skip_cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) - return true; - } - } - - df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); - if (material != tiletype_material::TREE) - return true; - - return false; + // Skip all non-trees immediately. + if (plant->flags.bits.is_shrub) + return true; + + // Skip plants with invalid tile. + df::map_block *cur = Maps::getTileBlock(plant->pos); + if (!cur) + return true; + + int x = plant->pos.x % 16; + int y = plant->pos.y % 16; + + // Skip all unrevealed plants. + if (cur->designation[x][y].bits.hidden) + return true; + + const df::plant_raw *plant_raw = df::plant_raw::find(plant->material); + + // Skip fruit trees if set. + if (skip_fruit_trees && plant_raw->material_defs.type_drink != -1) + return true; + + if (skip_food_trees || skip_cook_trees) + { + df::material * mat; + for (int idx = 0; idx < plant_raw->material.size(); idx++) + { + mat = plant_raw->material[idx]; + if (skip_food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) + return true; + + if (skip_cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) + return true; + } + } + + df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); + if (material != tiletype_material::TREE) + return true; + + return false; } static int do_chop_designation(bool chop, bool count_only) @@ -271,11 +271,11 @@ static int do_chop_designation(bool chop, bool count_only) { const df::plant *plant = world->plants.all[i]; - if (skip_plant(plant)) - continue; + if (skip_plant(plant)) + continue; - if (!count_only && !watchedBurrows.isValidPos(plant->pos)) - continue; + if (!count_only && !watchedBurrows.isValidPos(plant->pos)) + continue; if (chop && !Designations::isPlantMarked(plant)) { @@ -603,15 +603,15 @@ public: } else if (input->count(interface_key::CUSTOM_F)) { - skip_fruit_trees = !skip_fruit_trees; + skip_fruit_trees = !skip_fruit_trees; } else if (input->count(interface_key::CUSTOM_E)) { - skip_food_trees = !skip_food_trees; + skip_food_trees = !skip_food_trees; } else if (input->count(interface_key::CUSTOM_C)) { - skip_cook_trees = !skip_cook_trees; + skip_cook_trees = !skip_cook_trees; } else if (enabler->tracking_on && enabler->mouse_lbut) { @@ -661,44 +661,44 @@ public: OutputHotkeyString(x, y, "Designate Now", "d", true, left_margin); OutputHotkeyString(x, y, "Undesignate Now", "u", true, left_margin); OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin); - if (autochop_enabled) - { - using namespace df::enums::interface_key; - const struct { - const char *caption; - int count; - bool in_edit; - df::interface_key key; - df::interface_key skeys[4]; - } rows[] = { - {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}}, - {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}} - }; - for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); ++i) - { - auto row = rows[i]; - OutputHotkeyString(x, y, row.caption, row.key); - auto prev_x = x; - if (row.in_edit) - OutputString(COLOR_LIGHTCYAN, x, y, int_to_string(row.count) + "_"); - else if (row.count <= LOG_CAP_MAX) - OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(row.count)); - else - OutputString(COLOR_LIGHTBLUE, x, y, "Unlimited"); - if (edit_mode == EDIT_NONE) - { - x = std::max(x, prev_x + 10); - for (size_t j = 0; j < sizeof(row.skeys) / sizeof(row.skeys[0]); ++j) - OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j])); - OutputString(COLOR_WHITE, x, y, ": Step"); - } - OutputString(COLOR_WHITE, x, y, "", true, left_margin); - } - OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); - OutputToggleString(x, y, "Skip Fruit Trees: ", "f", skip_fruit_trees, true, left_margin); - OutputToggleString(x, y, "Skip Edible Product Trees: ", "e", skip_food_trees, true, left_margin); - OutputToggleString(x, y, "Skip Cookable Product Trees: ", "c", skip_cook_trees, true, left_margin); - } + if (autochop_enabled) + { + using namespace df::enums::interface_key; + const struct { + const char *caption; + int count; + bool in_edit; + df::interface_key key; + df::interface_key skeys[4]; + } rows[] = { + {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}}, + {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}} + }; + for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); ++i) + { + auto row = rows[i]; + OutputHotkeyString(x, y, row.caption, row.key); + auto prev_x = x; + if (row.in_edit) + OutputString(COLOR_LIGHTCYAN, x, y, int_to_string(row.count) + "_"); + else if (row.count <= LOG_CAP_MAX) + OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(row.count)); + else + OutputString(COLOR_LIGHTBLUE, x, y, "Unlimited"); + if (edit_mode == EDIT_NONE) + { + x = std::max(x, prev_x + 10); + for (size_t j = 0; j < sizeof(row.skeys) / sizeof(row.skeys[0]); ++j) + OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j])); + OutputString(COLOR_WHITE, x, y, ": Step"); + } + OutputString(COLOR_WHITE, x, y, "", true, left_margin); + } + OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); + OutputToggleString(x, y, "Skip Fruit Trees: ", "f", skip_fruit_trees, true, left_margin); + OutputToggleString(x, y, "Skip Edible Product Trees: ", "e", skip_food_trees, true, left_margin); + OutputToggleString(x, y, "Skip Cookable Product Trees: ", "c", skip_cook_trees, true, left_margin); + } ++y; OutputString(COLOR_BROWN, x, y, "Current Counts", true, left_margin); From 322964f0e6349c0da71ea5f89ebe04afe61c2e44 Mon Sep 17 00:00:00 2001 From: David Timm Date: Tue, 17 Oct 2017 16:06:33 -0600 Subject: [PATCH 0642/1012] Switch to explicit `interface_key` values instead of char. --- plugins/autochop.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 312f5ba42..3b75068f6 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -657,13 +657,14 @@ public: } ++y; - OutputToggleString(x, y, "Autochop", "a", autochop_enabled, true, left_margin); - OutputHotkeyString(x, y, "Designate Now", "d", true, left_margin); - OutputHotkeyString(x, y, "Undesignate Now", "u", true, left_margin); + + using namespace df::enums::interface_key; + OutputToggleString(x, y, "Autochop", CUSTOM_A, autochop_enabled, true, left_margin); + OutputHotkeyString(x, y, "Designate Now", CUSTOM_D, true, left_margin); + OutputHotkeyString(x, y, "Undesignate Now", CUSTOM_U, true, left_margin); OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin); if (autochop_enabled) { - using namespace df::enums::interface_key; const struct { const char *caption; int count; @@ -695,9 +696,9 @@ public: OutputString(COLOR_WHITE, x, y, "", true, left_margin); } OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); - OutputToggleString(x, y, "Skip Fruit Trees: ", "f", skip_fruit_trees, true, left_margin); - OutputToggleString(x, y, "Skip Edible Product Trees: ", "e", skip_food_trees, true, left_margin); - OutputToggleString(x, y, "Skip Cookable Product Trees: ", "c", skip_cook_trees, true, left_margin); + OutputToggleString(x, y, "Skip Fruit Trees: ", CUSTOM_F, skip_fruit_trees, true, left_margin); + OutputToggleString(x, y, "Skip Edible Product Trees: ", CUSTOM_E, skip_food_trees, true, left_margin); + OutputToggleString(x, y, "Skip Cookable Product Trees: ", CUSTOM_C, skip_cook_trees, true, left_margin); } ++y; From b480cd6b3863e87b62344d4b01ad5ef397b96f90 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 20 Oct 2017 19:07:47 +0530 Subject: [PATCH 0643/1012] Mark RFR functions as being safe for network use. --- .../remotefortressreader.cpp | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index a07668778..2ccf0cd27 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -247,33 +247,33 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addFunction("GetMaterialList", GetMaterialList); - svc->addFunction("GetGrowthList", GetGrowthList); - svc->addFunction("GetBlockList", GetBlockList); - svc->addFunction("CheckHashes", CheckHashes); - svc->addFunction("GetTiletypeList", GetTiletypeList); - svc->addFunction("GetPlantList", GetPlantList); - svc->addFunction("GetUnitList", GetUnitList); - svc->addFunction("GetViewInfo", GetViewInfo); - svc->addFunction("GetMapInfo", GetMapInfo); - svc->addFunction("ResetMapHashes", ResetMapHashes); - svc->addFunction("GetItemList", GetItemList); - svc->addFunction("GetBuildingDefList", GetBuildingDefList); - svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetWorldMapNew", GetWorldMapNew); - svc->addFunction("GetRegionMaps", GetRegionMaps); - svc->addFunction("GetRegionMapsNew", GetRegionMapsNew); - svc->addFunction("GetCreatureRaws", GetCreatureRaws); - svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws); - svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); - svc->addFunction("GetPlantRaws", GetPlantRaws); - svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws); - svc->addFunction("CopyScreen", CopyScreen); - svc->addFunction("PassKeyboardEvent", PassKeyboardEvent); - svc->addFunction("SendDigCommand", SendDigCommand); - svc->addFunction("SetPauseState", SetPauseState); - svc->addFunction("GetPauseState", GetPauseState); - svc->addFunction("GetVersionInfo", GetVersionInfo); + svc->addFunction("GetMaterialList", GetMaterialList, SF_ALLOW_REMOTE); + svc->addFunction("GetGrowthList", GetGrowthList, SF_ALLOW_REMOTE); + svc->addFunction("GetBlockList", GetBlockList, SF_ALLOW_REMOTE); + svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); + svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); + svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); + svc->addFunction("GetBuildingDefList", GetBuildingDefList, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMap", GetWorldMap, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapNew", GetWorldMapNew, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMaps", GetRegionMaps, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMapsNew", GetRegionMapsNew, SF_ALLOW_REMOTE); + svc->addFunction("GetCreatureRaws", GetCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapCenter", GetWorldMapCenter, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantRaws", GetPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("CopyScreen", CopyScreen, SF_ALLOW_REMOTE); + svc->addFunction("PassKeyboardEvent", PassKeyboardEvent, SF_ALLOW_REMOTE); + svc->addFunction("SendDigCommand", SendDigCommand, SF_ALLOW_REMOTE); + svc->addFunction("SetPauseState", SetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); return svc; } From e51ae78a46f9a54cc8e6bb2f0e95b1136796852f Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 22 Oct 2017 13:37:23 +0530 Subject: [PATCH 0644/1012] Always follow the current adventure in adventure mode. --- plugins/remotefortressreader/remotefortressreader.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 2ccf0cd27..a5d503086 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1656,7 +1656,11 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, out->set_cursor_pos_x(cx); out->set_cursor_pos_y(cy); out->set_cursor_pos_z(cz); - out->set_follow_unit_id(ui->follow_unit); + + if(gamemode && *gamemode == GameMode::ADVENTURE) + out->set_follow_unit_id(world->units.active[0]->id); + else + out->set_follow_unit_id(ui->follow_unit); out->set_follow_item_id(ui->follow_item); return CR_OK; } From 117b6e0427dc5fcc8b0e19f48159e3a1ca175dba Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 30 Oct 2017 19:00:03 +0530 Subject: [PATCH 0645/1012] Send reports over RFR. --- plugins/proto/RemoteFortressReader.proto | 23 +++++++++- .../remotefortressreader.cpp | 42 ++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 88fd03c64..8af324ceb 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -842,4 +842,25 @@ message ListRequest { optional int32 list_start = 1; optional int32 list_end = 2; -} \ No newline at end of file +} + +message Report +{ + optional int32 type = 1; + optional string text = 2; + optional ColorDefinition color = 3; + optional int32 duration = 4; + optional bool continuation = 5; + optional bool unconscious = 6; + optional bool announcement = 7; + optional int32 repeat_count = 8; + optional Coord pos = 9; + optional int32 id = 10; + optional int32 year = 11; + optional int32 time = 12; +} + +message Status +{ + repeated Report reports = 1; +} diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index a5d503086..795f2911a 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -76,6 +76,7 @@ #include "df/plant.h" #include "df/plant_raw_flags.h" #include "df/region_map_entry.h" +#include "df/report.h" #include "df/site_realization_building.h" #include "df/site_realization_building_info_castle_towerst.h" #include "df/site_realization_building_info_castle_wallst.h" @@ -157,6 +158,7 @@ static command_result SetPauseState(color_ostream & stream, const SingleBool * i static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); +static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -244,6 +246,10 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addFunction("SetPauseState", SetPauseState, SF_ALLOW_REMOTE); svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); return svc; } @@ -331,6 +338,13 @@ void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefiniti out->set_blue(color->blue * 255); } +void ConvertDFCoord(DFCoord in, RemoteFortressReader::Coord * out) +{ + out->set_x(in.x); + out->set_y(in.y); + out->set_z(in.z); +} + RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { switch (material) @@ -2795,7 +2809,7 @@ static command_result GetPauseState(color_ostream &stream, const EmptyMessage *i return CR_OK; } -command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) +static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) { out->set_dfhack_version(DFHACK_VERSION); #if DF_VERSION_INT == 34011 @@ -2804,5 +2818,29 @@ command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, R out->set_dwarf_fortress_version(DF_VERSION); #endif out->set_remote_fortress_reader_version(RFR_VERSION); - return command_result(); + return CR_OK; +} + +static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out) +{ + for (int i = 0; i < world->status.reports.size(); i++) + { + auto local_rep = world->status.reports[i]; + if (!local_rep) + continue; + auto send_rep = out->add_reports(); + send_rep->set_type(local_rep->type); + send_rep->set_text(local_rep->text); + ConvertDfColor(local_rep->color | (local_rep->bright ? 8 : 0), send_rep->mutable_color()); + send_rep->set_duration(local_rep->duration); + send_rep->set_continuation(local_rep->flags.bits.continuation); + send_rep->set_unconscious(local_rep->flags.bits.unconscious); + send_rep->set_announcement(local_rep->flags.bits.announcement); + send_rep->set_repeat_count(local_rep->repeat_count); + ConvertDFCoord(local_rep->pos, send_rep->mutable_pos()); + send_rep->set_id(local_rep->id); + send_rep->set_year(local_rep->year); + send_rep->set_time(local_rep->time); + } + return CR_OK; } From b449446526b74e679cd873684b9926851b7105bd Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 30 Oct 2017 19:23:17 +0530 Subject: [PATCH 0646/1012] Send announcements in Unicode. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 795f2911a..d86277850 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -2830,7 +2830,7 @@ static command_result GetReports(color_ostream & stream, const EmptyMessage * in continue; auto send_rep = out->add_reports(); send_rep->set_type(local_rep->type); - send_rep->set_text(local_rep->text); + send_rep->set_text(DF2UTF(local_rep->text)); ConvertDfColor(local_rep->color | (local_rep->bright ? 8 : 0), send_rep->mutable_color()); send_rep->set_duration(local_rep->duration); send_rep->set_continuation(local_rep->flags.bits.continuation); From f91123610e98d0f11726ffdb6a9ea67ffc5ba14a Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 30 Oct 2017 21:51:54 +0530 Subject: [PATCH 0647/1012] Update structures. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 3322beb2e..3b5214bca 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3322beb2e7f4b28ff8e573e9bec738c77026b8e9 +Subproject commit 3b5214bcad3349bdf3418a1dba71dbeea4a723d1 From 33a43c5bfd0bebd902b4194ed077e9666df49f32 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 4 Nov 2017 07:50:47 -0600 Subject: [PATCH 0648/1012] update structures --- library/modules/Maps.cpp | 2 +- library/xml | 2 +- plugins/prospector.cpp | 2 +- plugins/reveal.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index af2cd5183..2b2311a7c 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -91,7 +91,7 @@ const char * DFHack::sa_feature(df::feature_type index) return "Cavern"; case feature_type::magma_core_from_layer: return "Magma sea"; - case feature_type::feature_underworld_from_layer: + case feature_type::underworld_from_layer: return "Underworld"; default: return "Unknown/Error"; diff --git a/library/xml b/library/xml index 3322beb2e..888f24622 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3322beb2e7f4b28ff8e573e9bec738c77026b8e9 +Subproject commit 888f24622990c11db5c392f5e241e0de28d2bb9e diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 522630afe..32e360a3a 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -721,7 +721,7 @@ command_result prospector (color_ostream &con, vector & parameters) } if (showSlade && blockFeatureGlobal.type != -1 && des.bits.feature_global - && blockFeatureGlobal.type == feature_type::feature_underworld_from_layer + && blockFeatureGlobal.type == feature_type::underworld_from_layer && blockFeatureGlobal.main_material == 0) // stone { layerMats[blockFeatureGlobal.sub_material].add(global_z); diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 314259fd4..c2479bedc 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -44,7 +44,7 @@ bool isSafe(df::coord c) if (local_feature.type == feature_type::deep_special_tube || local_feature.type == feature_type::deep_surface_portal) return false; // And Hell *is* Hell. - if (global_feature.type == feature_type::feature_underworld_from_layer) + if (global_feature.type == feature_type::underworld_from_layer) return false; // otherwise it's safe. return true; From 53b5a5cc6f3111ae811328dd5f755937676518fa Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 4 Nov 2017 20:14:24 +0530 Subject: [PATCH 0649/1012] Added a function to only get details for units within the area of interest. --- plugins/proto/RemoteFortressReader.proto | 3 + .../remotefortressreader.cpp | 113 +++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 8af324ceb..04be9bda5 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -397,6 +397,9 @@ message UnitDefinition repeated string noble_positions = 18; optional int32 rider_id = 19; repeated InventoryItem inventory = 20; + optional float subpos_x = 21; + optional float subpos_y = 22; + optional float subpos_z = 23; } message UnitList diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index d86277850..599f5816b 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -138,6 +138,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out); static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in); static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out); +static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out); static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out); static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out); static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); @@ -259,8 +260,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); - svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); - svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); + svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); @@ -1642,6 +1644,113 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, return CR_OK; } +static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) +{ + auto world = df::global::world; + for (int i = 0; i < world->units.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + auto send_unit = out->add_creature_list(); + send_unit->set_id(unit->id); + send_unit->set_pos_x(unit->pos.x); + send_unit->set_pos_y(unit->pos.y); + send_unit->set_pos_z(unit->pos.z); + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) + continue; + if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) + continue; + if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) + continue; + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); + auto size_info = send_unit->mutable_size_info(); + size_info->set_size_cur(unit->body.size_info.size_cur); + size_info->set_size_base(unit->body.size_info.size_base); + size_info->set_area_cur(unit->body.size_info.area_cur); + size_info->set_area_base(unit->body.size_info.area_base); + size_info->set_length_cur(unit->body.size_info.length_cur); + size_info->set_length_base(unit->body.size_info.length_base); + if (unit->name.has_name) + { + send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); + } + + auto appearance = send_unit->mutable_appearance(); + for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) + appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); + for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) + appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); + for (int j = 0; j < unit->appearance.colors.size(); j++) + appearance->add_colors(unit->appearance.colors[j]); + appearance->set_size_modifier(unit->appearance.size_modifier); + + send_unit->set_profession_id(unit->profession); + + std::vector pvec; + + if (Units::getNoblePositions(&pvec, unit)) + { + for (int j = 0; j < pvec.size(); j++) + { + auto noble_positon = pvec[j]; + send_unit->add_noble_positions(noble_positon.position->code); + } + } + + send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); + + auto creatureRaw = world->raws.creatures.all[unit->race]; + auto casteRaw = creatureRaw->caste[unit->caste]; + + for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) + { + auto type = unit->appearance.tissue_style_type[j]; + if (type < 0) + continue; + int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); + auto styleRaw = casteRaw->tissue_styles[style_raw_index]; + if (styleRaw->token == "HAIR") + { + auto send_style = appearance->mutable_hair(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "BEARD") + { + auto send_style = appearance->mutable_beard(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "MOUSTACHE") + { + auto send_style = appearance->mutable_moustache(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "SIDEBURNS") + { + auto send_style = appearance->mutable_sideburns(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + } + + for (int j = 0; j < unit->inventory.size(); j++) + { + auto inventory_item = unit->inventory[j]; + auto sent_item = send_unit->add_inventory(); + sent_item->set_mode((InventoryMode)inventory_item->mode); + CopyItem(sent_item->mutable_item(), inventory_item->item); + } + } + return CR_OK; +} + static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out) { int x, y, z, w, h, cx, cy, cz; From e3a41a323d453cd4e01ed5e5b3eb61ff062caf4f Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 5 Nov 2017 16:06:51 +0530 Subject: [PATCH 0650/1012] Update Units --- .../remotefortressreader.cpp | 131 ++++-------------- 1 file changed, 28 insertions(+), 103 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 599f5816b..fe86aa2d6 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -30,6 +30,7 @@ #include "modules/Units.h" #include "modules/World.h" +#include "df/unit_action.h" #if DF_VERSION_INT > 34011 #include "df/army.h" #include "df/army_flags.h" @@ -75,6 +76,8 @@ #include "df/physical_attribute_type.h" #include "df/plant.h" #include "df/plant_raw_flags.h" +#include "df/projectile.h" +#include "df/proj_unitst.h" #include "df/region_map_entry.h" #include "df/report.h" #include "df/site_realization_building.h" @@ -1545,103 +1548,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out) { - auto world = df::global::world; - for (int i = 0; i < world->units.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - auto send_unit = out->add_creature_list(); - send_unit->set_id(unit->id); - send_unit->set_pos_x(unit->pos.x); - send_unit->set_pos_y(unit->pos.y); - send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); - send_unit->set_flags1(unit->flags1.whole); - send_unit->set_flags2(unit->flags2.whole); - send_unit->set_flags3(unit->flags3.whole); - send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); - auto size_info = send_unit->mutable_size_info(); - size_info->set_size_cur(unit->body.size_info.size_cur); - size_info->set_size_base(unit->body.size_info.size_base); - size_info->set_area_cur(unit->body.size_info.area_cur); - size_info->set_area_base(unit->body.size_info.area_base); - size_info->set_length_cur(unit->body.size_info.length_cur); - size_info->set_length_base(unit->body.size_info.length_base); - if (unit->name.has_name) - { - send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); - } - - auto appearance = send_unit->mutable_appearance(); - for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) - appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); - for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) - appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); - for (int j = 0; j < unit->appearance.colors.size(); j++) - appearance->add_colors(unit->appearance.colors[j]); - appearance->set_size_modifier(unit->appearance.size_modifier); - - send_unit->set_profession_id(unit->profession); - - std::vector pvec; - - if (Units::getNoblePositions(&pvec, unit)) - { - for (int j = 0; j < pvec.size(); j++) - { - auto noble_positon = pvec[j]; - send_unit->add_noble_positions(noble_positon.position->code); - } - } - - send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); - - auto creatureRaw = world->raws.creatures.all[unit->race]; - auto casteRaw = creatureRaw->caste[unit->caste]; - - for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) - { - auto type = unit->appearance.tissue_style_type[j]; - if (type < 0) - continue; - int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); - auto styleRaw = casteRaw->tissue_styles[style_raw_index]; - if (styleRaw->token == "HAIR") - { - auto send_style = appearance->mutable_hair(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "BEARD") - { - auto send_style = appearance->mutable_beard(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "MOUSTACHE") - { - auto send_style = appearance->mutable_moustache(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "SIDEBURNS") - { - auto send_style = appearance->mutable_sideburns(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - } - - for (int j = 0; j < unit->inventory.size(); j++) - { - auto inventory_item = unit->inventory[j]; - auto sent_item = send_unit->add_inventory(); - sent_item->set_mode((InventoryMode)inventory_item->mode); - CopyItem(sent_item->mutable_item(), inventory_item->item); - } - } - return CR_OK; + return GetUnitListInside(stream, NULL, out); } static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) @@ -1657,12 +1564,15 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques send_unit->set_pos_z(unit->pos.z); send_unit->mutable_race()->set_mat_type(unit->race); send_unit->mutable_race()->set_mat_index(unit->caste); - if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) - continue; - if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) - continue; - if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) - continue; + if (in != NULL) + { + if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) + continue; + if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) + continue; + if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) + continue; + } ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); send_unit->set_flags1(unit->flags1.whole); send_unit->set_flags2(unit->flags2.whole); @@ -1747,6 +1657,21 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques sent_item->set_mode((InventoryMode)inventory_item->mode); CopyItem(sent_item->mutable_item(), inventory_item->item); } + + if (unit->flags1.bits.projectile) + { + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); + if (item == NULL) + continue; + if (item->unit != unit) + continue; + send_unit->set_subpos_x(item->pos_x / 100000.0); + send_unit->set_subpos_y(item->pos_y / 100000.0); + send_unit->set_subpos_z(item->pos_z / 140000.0); + } + } } return CR_OK; } From e06644cdf4320732922b2329f5bdadbf39f30922 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 23 Nov 2017 10:55:37 -0600 Subject: [PATCH 0651/1012] Fix labormanager crash when a dwarf doesn't have a cultural identity --- plugins/labormanager/labormanager.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index d7d4ffc6b..1a5323d8a 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1175,6 +1175,8 @@ private: void collect_dwarf_list() { + state_count.clear(); + state_count.resize(NUM_STATE); for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) { @@ -1373,6 +1375,8 @@ private: if (print_debug) out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); + state_count[dwarf->state]++; + // determine if dwarf has medical needs if (dwarf->dwarf->health && !( // on-duty military will not necessarily break to get minor injuries attended @@ -1516,11 +1520,12 @@ private: if (labor == df::unit_labor::CUTWOOD) { - auto c_id = d->dwarf->cultural_identity; - auto culture = world->cultural_identities.all[c_id]; - auto ethics = culture->ethic[df::ethic_type::KILL_PLANT]; - if (ethics != df::ethic_response::NOT_APPLICABLE && ethics != df::ethic_response::REQUIRED) - score += 10000 * (df::ethic_response::ACCEPTABLE - ethics); + if (auto culture = df::cultural_identity::find(d->dwarf->cultural_identity)) + { + auto ethics = culture->ethic[df::ethic_type::KILL_PLANT]; + if (ethics != df::ethic_response::NOT_APPLICABLE && ethics != df::ethic_response::REQUIRED) + score += 10000 * (df::ethic_response::ACCEPTABLE - ethics); + } } score -= Units::computeMovementSpeed(d->dwarf); From 111df1312bac8ea9bb0a60bd572f82493d6eedac Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 15:50:58 -0500 Subject: [PATCH 0652/1012] Update xml to 1cdab5a (last for 0.43.05) --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 888f24622..1cdab5a16 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 888f24622990c11db5c392f5e241e0de28d2bb9e +Subproject commit 1cdab5a16467c73080553e94a651bbc2b48380a6 From 2473476f98717c04191a23582adde6140f0ae57e Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 15:54:34 -0500 Subject: [PATCH 0653/1012] Update Authors.rst (#1156) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 128d327ba..cb915e036 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -124,6 +124,7 @@ Travis Hoppe thoppe orthographic-pedant txtsd txtsd U-glouglou\\simon Valentin Ochs Cat-Ion +ViTuRaS ViTuRaS Vjek Warmist warmist Wes Malone wesQ3 From a3288b4b05e724551b9ae40a1caacb553b4c7a50 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 15:54:45 -0500 Subject: [PATCH 0654/1012] Update Authors.rst (dfhack/scripts#32) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index cb915e036..948d956ba 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -26,6 +26,7 @@ Caldfir caldfir Carter Bray Qartar Chris Dombroski cdombroski Clayton Hughes +Clément Vuchener cvuchener David Corbett dscorbett David Seguin dseguin Deon From 5795a370bc7f08ce5c3073fc7038de28c439c29f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 16:01:27 -0500 Subject: [PATCH 0655/1012] Update xml again to fix GCC build error --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 1cdab5a16..860a9041a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1cdab5a16467c73080553e94a651bbc2b48380a6 +Subproject commit 860a9041a75305609643d465123a4b598140dd7f From c546e5e52c9a1940ae576c4139d77522b3a387a1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 16:06:19 -0500 Subject: [PATCH 0656/1012] Update Authors.rst (dfhack/scripts#31) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 948d956ba..a693646bf 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -79,6 +79,7 @@ Nick Rart nickrart comestible Nikolay Amiantov abbradar nocico nocico Omniclasm +OwnageIsMagic OwnageIsMagic Patrik Lundell PatrikLundell Paul Fenwick pjf PeridexisErrant PeridexisErrant From 1b706bc1626c27475f728890bff524d07d4f1d36 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 16:34:22 -0500 Subject: [PATCH 0657/1012] Add anchor for luasocket --- docs/Lua API.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index d9bbfed55..66c2a5a07 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3621,6 +3621,8 @@ Or with auto_gears:: auto_gears=true } +.. _luasocket: + Luasocket ========= From fb940b8836601f6a9a38807e04e3dc79496f9173 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 16:34:28 -0500 Subject: [PATCH 0658/1012] Update NEWS.rst --- NEWS.rst | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 9d8b05a78..e55ca92c5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -36,6 +36,47 @@ Changelog .. contents:: :depth: 2 +DFHack 0.43.05-r3 +================= + +Internals +--------- +- Fixed an uncommon crash that could occur when printing text to the console +- Added lots of previously-missing DF classes +- More names for fields: https://github.com/DFHack/df-structures/compare/0.43.05-r2...0.43.05 + +Fixes +----- +- Linux: fixed argument to ``setarch`` in the ``dfhack`` launcher script +- Ruby: fixed an error that occurred when the DF path contained an apostrophe +- `diggingInvaders` now compiles again and is included +- `labormanager`: + + - stopped waiting for on-duty military dwarves with minor injuries to obtain care + - stopped waiting for meetings when participant(s) are dead + - fixed a crash for dwarves with no cultural identity + +- `luasocket`: fixed ``receive()`` with a byte count +- `orders`: fixed an error when importing orders with material categories +- `siren`: fixed an error +- `stockpiles`: fixed serialization of barrel and bin counts +- `view-item-info`: fixed a ``CHEESE_MAT``-related error + +Misc Improvements +----------------- +- `devel/export-dt-ini`: added more offsets for new DT versions +- `digfort`: added support for changing z-levels +- `exportlegends`: suppressed ABSTRACT_BUILDING warning +- `gui/dfstatus`: excluded logs in constructions +- `labormanager`: + + - stopped assigning woodcutting jobs to elves + - "recover wounded" jobs now weighted based on altruism + +- `remotefortressreader`: added support for buildings, grass, riders, and + hair/beard styles + + DFHack 0.43.05-r2 ================= From e2f705c76d34e858023e9a77071a2184a27bb3b9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Nov 2017 16:38:05 -0500 Subject: [PATCH 0659/1012] Update version in CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79889dcb0..6faa01870 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "r2") +SET(DFHACK_RELEASE "r3") SET(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 69eb10a3e552bba0c9185737dd00fc0af4e369e3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Nov 2017 23:40:58 -0500 Subject: [PATCH 0660/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3baa24fec..09cf8dde9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3baa24fec93461218b5b658de94884ebff0a0b23 +Subproject commit 09cf8dde929e0478a869fa09ec329a27c9631113 From 7508126beffbfcf2420ff96c2e9453d6ef9a5294 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 25 Nov 2017 00:50:22 -0500 Subject: [PATCH 0661/1012] Change version to 0.44.02-r0 --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6faa01870..3c7838949 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,9 +140,9 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.43.05") -SET(DFHACK_RELEASE "r3") -SET(DFHACK_PRERELEASE FALSE) +set(DF_VERSION "0.44.02") +SET(DFHACK_RELEASE "r0") +SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 2c95ac411e5bca1fc8cfba3008886e0c01a03b5b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 25 Nov 2017 00:59:59 -0500 Subject: [PATCH 0662/1012] Update xml and all uses of job_handler --- library/include/df/custom/job_handler.methods.inc | 1 + library/modules/Buildings.cpp | 2 +- library/modules/Designations.cpp | 4 ++-- library/modules/EventManager.cpp | 4 ++-- library/modules/Job.cpp | 12 ++++++------ library/xml | 2 +- plugins/digFlood.cpp | 2 +- plugins/diggingInvaders/diggingInvaders.cpp | 2 +- plugins/labormanager/labormanager.cpp | 4 ++-- .../remotefortressreader/remotefortressreader.cpp | 8 ++++---- plugins/ruby/job.rb | 4 ++-- plugins/ruby/map.rb | 4 ++-- plugins/showmood.cpp | 2 +- plugins/stockflow.cpp | 2 +- plugins/workflow.cpp | 10 +++++----- 15 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 library/include/df/custom/job_handler.methods.inc diff --git a/library/include/df/custom/job_handler.methods.inc b/library/include/df/custom/job_handler.methods.inc new file mode 100644 index 000000000..c160f5470 --- /dev/null +++ b/library/include/df/custom/job_handler.methods.inc @@ -0,0 +1 @@ +friend struct df::world; diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 759e3acd4..e3d8e1d69 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -138,7 +138,7 @@ void buildings_onUpdate(color_ostream &out) { buildings_do_onupdate = false; - df::job_list_link *link = world->job_list.next; + df::job_list_link *link = world->jobs.list.next; for (; link; link = link->next) { df::job *job = link->item; diff --git a/library/modules/Designations.cpp b/library/modules/Designations.cpp index 3fb325096..1d41cfb49 100644 --- a/library/modules/Designations.cpp +++ b/library/modules/Designations.cpp @@ -66,7 +66,7 @@ bool Designations::isPlantMarked(const df::plant *plant) if (block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig == tile_dig_designation::Default) return true; - for (auto *link = world->job_list.next; link; link = link->next) + for (auto *link = world->jobs.list.next; link; link = link->next) { df::job *job = link->item; if (!job) @@ -128,7 +128,7 @@ bool Designations::unmarkPlant(const df::plant *plant) block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig = tile_dig_designation::No; block->flags.bits.designated = true; - auto *link = world->job_list.next; + auto *link = world->jobs.list.next; while (link) { auto *next = link->next; diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index e2baf9bfb..8ce2c97f4 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -381,7 +381,7 @@ static void manageJobInitiatedEvent(color_ostream& out) { } multimap copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end()); - for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &df::global::world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; if ( link->item->id <= lastJobId ) @@ -411,7 +411,7 @@ static void manageJobCompletedEvent(color_ostream& out) { multimap copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); map nowJobs; - for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &df::global::world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; nowJobs[link->item->id] = link->item; diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 3f12ea2c2..132dcfc4b 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -490,10 +490,10 @@ bool DFHack::Job::linkIntoWorld(df::job *job, bool new_id) job->list_link = new df::job_list_link(); job->list_link->item = job; - linked_list_append(&world->job_list, job->list_link); + linked_list_append(&world->jobs.list, job->list_link); return true; } else { - df::job_list_link *ins_pos = &world->job_list; + df::job_list_link *ins_pos = &world->jobs.list; while (ins_pos->next && ins_pos->next->item->id < job->id) ins_pos = ins_pos->next; @@ -514,15 +514,15 @@ bool DFHack::Job::removePostings(df::job *job, bool remove_all) bool removed = false; if (!remove_all) { - if (job->posting_index >= 0 && job->posting_index < world->job_postings.size()) + if (job->posting_index >= 0 && job->posting_index < world->jobs.postings.size()) { - world->job_postings[job->posting_index]->flags.bits.dead = true; + world->jobs.postings[job->posting_index]->flags.bits.dead = true; removed = true; } } else { - for (auto it = world->job_postings.begin(); it != world->job_postings.end(); ++it) + for (auto it = world->jobs.postings.begin(); it != world->jobs.postings.end(); ++it) { if ((**it).job == job) { @@ -553,7 +553,7 @@ bool DFHack::Job::listNewlyCreated(std::vector *pvec, int *id_var) pvec->reserve(std::min(20,cur_id - old_id)); - df::job_list_link *link = world->job_list.next; + df::job_list_link *link = world->jobs.list.next; for (; link; link = link->next) { int id = link->item->id; diff --git a/library/xml b/library/xml index 860a9041a..fd6e70de5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 860a9041a75305609643d465123a4b598140dd7f +Subproject commit fd6e70de5047a48b163ced482c8a925b6a021891 diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 6668f9994..ada6213a6 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -89,7 +89,7 @@ void onDig(color_ostream& out, void* ptr) { return; set jobLocations; - for ( df::job_list_link* link = &world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index 4892c5e1d..9bbc5191e 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -598,7 +598,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { lastInvasionDigger = firstInvader->id; lastInvasionJob = firstInvader->job.current_job ? firstInvader->job.current_job->id : -1; invaderJobs.erase(lastInvasionJob); - for ( df::job_list_link* link = &world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; df::job* job = link->item; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 1a5323d8a..3a026d1ea 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1155,7 +1155,7 @@ private: void collect_job_list() { - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) + for (df::job_list_link* jll = world->jobs.list.next; jll; jll = jll->next) { df::job* j = jll->item; if (!j) @@ -1163,7 +1163,7 @@ private: process_job(j); } - for (auto jp = world->job_postings.begin(); jp != world->job_postings.end(); jp++) + for (auto jp = world->jobs.postings.begin(); jp != world->jobs.postings.end(); jp++) { if ((*jp)->flags.bits.dead) continue; diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index a07668778..b543a2ab3 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1127,9 +1127,9 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N } } #if DF_VERSION_INT > 34011 - for (int i = 0; i < world->job_postings.size(); i++) + for (int i = 0; i < world->jobs.postings.size(); i++) { - auto job = world->job_postings[i]->job; + auto job = world->jobs.postings[i]->job; if (job == nullptr) continue; if ( @@ -1616,7 +1616,7 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); } } - + for (int j = 0; j < unit->inventory.size(); j++) { auto inventory_item = unit->inventory[j]; @@ -2738,7 +2738,7 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in #if DF_VERSION_INT >= 43005 //remove and job postings related. - for (df::job_list_link * listing = &(world->job_list); listing != NULL; listing = listing->next) + for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next) { if (listing->item == NULL) continue; diff --git a/plugins/ruby/job.rb b/plugins/ruby/job.rb index e489dcc91..bf033fbb3 100644 --- a/plugins/ruby/job.rb +++ b/plugins/ruby/job.rb @@ -1,9 +1,9 @@ module DFHack class << self # link a job to the world - # allocate & set job.id, allocate a JobListLink, link to job & world.job_list + # allocate & set job.id, allocate a JobListLink, link to job & world.jobs.list def job_link(job) - lastjob = world.job_list + lastjob = world.jobs.list lastjob = lastjob.next while lastjob.next joblink = JobListLink.cpp_new joblink.prev = lastjob diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index f6f69de75..5c6295946 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -269,7 +269,7 @@ module DFHack def dig(mode=:Default) if mode == :Smooth if (tilemat == :STONE or tilemat == :MINERAL) and caption !~ /smooth|pillar|fortification/i and # XXX caption.. - designation.smooth == 0 and (designation.hidden or not df.world.job_list.find { |j| + designation.smooth == 0 and (designation.hidden or not df.world.jobs.list.find { |j| # the game removes 'smooth' designation as soon as it assigns a job, if we # re-set it the game may queue another :DetailWall that will carve a fortification (j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self) @@ -279,7 +279,7 @@ module DFHack mapblock.flags.designated = true end else - return if mode != :No and designation.dig == :No and not designation.hidden and df.world.job_list.find { |j| + return if mode != :No and designation.dig == :No and not designation.hidden and df.world.jobs.list.find { |j| # someone already enroute to dig here, avoid 'Inappropriate dig square' spam JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self) } diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 05e9a67ad..d2ae73f08 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -43,7 +43,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) CoreSuspender suspend; bool found = false; - for (df::job_list_link *cur = world->job_list.next; cur != NULL; cur = cur->next) + for (df::job_list_link *cur = world->jobs.list.next; cur != NULL; cur = cur->next) { df::job *job = cur->item; if ((job->job_type < job_type::StrangeMoodCrafter) || (job->job_type > job_type::StrangeMoodMechanics)) diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index ff87bdddc..7cfc8ce0d 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -69,7 +69,7 @@ public: } else { // Gather orders when the bookkeeper starts updating stockpile records, // and enqueue them when the job is done. - for (df::job_list_link* link = &world->job_list; link != NULL; link = link->next) { + for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next) { if (link->item == NULL) continue; if (link->item->job_type == job_type::UpdateStockpileRecords) { found = true; diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index f8f9e8231..cc3ac8cd7 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -429,15 +429,15 @@ public: static int fix_job_postings (color_ostream *out, bool dry_run) { int count = 0; - df::job_list_link *link = &world->job_list; + df::job_list_link *link = &world->jobs.list; while (link) { df::job *job = link->item; if (job) { - for (size_t i = 0; i < world->job_postings.size(); ++i) + for (size_t i = 0; i < world->jobs.postings.size(); ++i) { - df::world::T_job_postings *posting = world->job_postings[i]; + df::job_handler::T_postings *posting = world->jobs.postings[i]; if (posting->job == job && i != job->posting_index && !posting->flags.bits.dead) { ++count; @@ -673,7 +673,7 @@ static void check_lost_jobs(color_ostream &out, int ticks) ProtectedJob::cur_tick_idx++; if (ticks < 0) ticks = 0; - df::job_list_link *p = world->job_list.next; + df::job_list_link *p = world->jobs.list.next; for (; p; p = p->next) { df::job *job = p->item; @@ -707,7 +707,7 @@ static void check_lost_jobs(color_ostream &out, int ticks) static void update_job_data(color_ostream &out) { - df::job_list_link *p = world->job_list.next; + df::job_list_link *p = world->jobs.list.next; for (; p; p = p->next) { ProtectedJob *pj = get_known(p->item->id); From 69a88e1641928fac2931912e4b7049665e5024fd Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sat, 25 Nov 2017 11:34:02 +0530 Subject: [PATCH 0663/1012] Use json for server config. --- dfhack-config/remote-server.cfg | 2 -- library/RemoteServer.cpp | 38 +++++++++++++++++++++------------ library/xml | 2 +- scripts | 2 +- 4 files changed, 26 insertions(+), 18 deletions(-) delete mode 100644 dfhack-config/remote-server.cfg diff --git a/dfhack-config/remote-server.cfg b/dfhack-config/remote-server.cfg deleted file mode 100644 index 877f06179..000000000 --- a/dfhack-config/remote-server.cfg +++ /dev/null @@ -1,2 +0,0 @@ -#change this to true to allow remote RPC connections from other devices. -allow-remote-connections=false \ No newline at end of file diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 9cd017993..64be8221c 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -63,6 +63,8 @@ using namespace DFHack; #include "tinythread.h" using namespace tthread; +#include "jsoncpp.h" + using dfproto::CoreTextNotification; using dfproto::CoreTextFragment; using google::protobuf::MessageLite; @@ -379,23 +381,31 @@ bool ServerMain::listen(int port) socket->Initialize(); - bool allow_remote = false; - std::string filename("dfhack-config/remote-server.cfg"); + std::string filename("dfhack-config/remote-server.json"); + + Json::Value configJson; - std::ifstream configFile(filename); - if (configFile.is_open()) + std::ifstream inFile(filename, std::ios_base::in); + if (inFile.is_open()) { - std::string line; - while (std::getline(configFile, line)) - { - if (line.compare(0, 1, "#") == 0) - continue; - if (line.compare(0, 24, "allow-remote-connections") == 0) - { - allow_remote = (line.compare(25, std::string::npos, "true") == 0); - } - } + inFile >> configJson; + inFile.close(); } + + bool allow_remote = configJson.get("allow_remote", "false").asBool(); + port = configJson.get("port", port).asInt(); + + configJson["allow_remote"] = allow_remote; + configJson["port"] = port; + + std::ofstream outFile(filename, std::ios_base::trunc); + + if (outFile.is_open()) + { + outFile << configJson; + outFile.close(); + } + if (allow_remote) { if (!socket->Listen(NULL, port)) diff --git a/library/xml b/library/xml index 860a9041a..3b5214bca 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 860a9041a75305609643d465123a4b598140dd7f +Subproject commit 3b5214bcad3349bdf3418a1dba71dbeea4a723d1 diff --git a/scripts b/scripts index 09cf8dde9..3baa24fec 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 09cf8dde929e0478a869fa09ec329a27c9631113 +Subproject commit 3baa24fec93461218b5b658de94884ebff0a0b23 From 4a52f322c3d5a956a25aa7cc035d7e1d4cc28d1d Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sat, 25 Nov 2017 11:37:54 +0530 Subject: [PATCH 0664/1012] Remove tabs. --- plugins/proto/RemoteFortressReader.proto | 6 +- .../remotefortressreader.cpp | 248 +++++++++--------- 2 files changed, 127 insertions(+), 127 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 04be9bda5..ae1d7dcb4 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -397,9 +397,9 @@ message UnitDefinition repeated string noble_positions = 18; optional int32 rider_id = 19; repeated InventoryItem inventory = 20; - optional float subpos_x = 21; - optional float subpos_y = 22; - optional float subpos_z = 23; + optional float subpos_x = 21; + optional float subpos_y = 22; + optional float subpos_z = 23; } message UnitList diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index fe86aa2d6..9c89a44b8 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -263,9 +263,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); - svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); - svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); - svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); + svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); @@ -1553,127 +1553,127 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) { - auto world = df::global::world; - for (int i = 0; i < world->units.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - auto send_unit = out->add_creature_list(); - send_unit->set_id(unit->id); - send_unit->set_pos_x(unit->pos.x); - send_unit->set_pos_y(unit->pos.y); - send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - if (in != NULL) - { - if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) - continue; - if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) - continue; - if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) - continue; - } - ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); - send_unit->set_flags1(unit->flags1.whole); - send_unit->set_flags2(unit->flags2.whole); - send_unit->set_flags3(unit->flags3.whole); - send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); - auto size_info = send_unit->mutable_size_info(); - size_info->set_size_cur(unit->body.size_info.size_cur); - size_info->set_size_base(unit->body.size_info.size_base); - size_info->set_area_cur(unit->body.size_info.area_cur); - size_info->set_area_base(unit->body.size_info.area_base); - size_info->set_length_cur(unit->body.size_info.length_cur); - size_info->set_length_base(unit->body.size_info.length_base); - if (unit->name.has_name) - { - send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); - } - - auto appearance = send_unit->mutable_appearance(); - for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) - appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); - for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) - appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); - for (int j = 0; j < unit->appearance.colors.size(); j++) - appearance->add_colors(unit->appearance.colors[j]); - appearance->set_size_modifier(unit->appearance.size_modifier); - - send_unit->set_profession_id(unit->profession); - - std::vector pvec; - - if (Units::getNoblePositions(&pvec, unit)) - { - for (int j = 0; j < pvec.size(); j++) - { - auto noble_positon = pvec[j]; - send_unit->add_noble_positions(noble_positon.position->code); - } - } - - send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); - - auto creatureRaw = world->raws.creatures.all[unit->race]; - auto casteRaw = creatureRaw->caste[unit->caste]; - - for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) - { - auto type = unit->appearance.tissue_style_type[j]; - if (type < 0) - continue; - int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); - auto styleRaw = casteRaw->tissue_styles[style_raw_index]; - if (styleRaw->token == "HAIR") - { - auto send_style = appearance->mutable_hair(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "BEARD") - { - auto send_style = appearance->mutable_beard(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "MOUSTACHE") - { - auto send_style = appearance->mutable_moustache(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "SIDEBURNS") - { - auto send_style = appearance->mutable_sideburns(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - } - - for (int j = 0; j < unit->inventory.size(); j++) - { - auto inventory_item = unit->inventory[j]; - auto sent_item = send_unit->add_inventory(); - sent_item->set_mode((InventoryMode)inventory_item->mode); - CopyItem(sent_item->mutable_item(), inventory_item->item); - } - - if (unit->flags1.bits.projectile) - { - for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) - { - STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); - if (item == NULL) - continue; - if (item->unit != unit) - continue; - send_unit->set_subpos_x(item->pos_x / 100000.0); - send_unit->set_subpos_y(item->pos_y / 100000.0); - send_unit->set_subpos_z(item->pos_z / 140000.0); - } - } - } - return CR_OK; + auto world = df::global::world; + for (int i = 0; i < world->units.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + auto send_unit = out->add_creature_list(); + send_unit->set_id(unit->id); + send_unit->set_pos_x(unit->pos.x); + send_unit->set_pos_y(unit->pos.y); + send_unit->set_pos_z(unit->pos.z); + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + if (in != NULL) + { + if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) + continue; + if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) + continue; + if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) + continue; + } + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); + auto size_info = send_unit->mutable_size_info(); + size_info->set_size_cur(unit->body.size_info.size_cur); + size_info->set_size_base(unit->body.size_info.size_base); + size_info->set_area_cur(unit->body.size_info.area_cur); + size_info->set_area_base(unit->body.size_info.area_base); + size_info->set_length_cur(unit->body.size_info.length_cur); + size_info->set_length_base(unit->body.size_info.length_base); + if (unit->name.has_name) + { + send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); + } + + auto appearance = send_unit->mutable_appearance(); + for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) + appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); + for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) + appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); + for (int j = 0; j < unit->appearance.colors.size(); j++) + appearance->add_colors(unit->appearance.colors[j]); + appearance->set_size_modifier(unit->appearance.size_modifier); + + send_unit->set_profession_id(unit->profession); + + std::vector pvec; + + if (Units::getNoblePositions(&pvec, unit)) + { + for (int j = 0; j < pvec.size(); j++) + { + auto noble_positon = pvec[j]; + send_unit->add_noble_positions(noble_positon.position->code); + } + } + + send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); + + auto creatureRaw = world->raws.creatures.all[unit->race]; + auto casteRaw = creatureRaw->caste[unit->caste]; + + for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) + { + auto type = unit->appearance.tissue_style_type[j]; + if (type < 0) + continue; + int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); + auto styleRaw = casteRaw->tissue_styles[style_raw_index]; + if (styleRaw->token == "HAIR") + { + auto send_style = appearance->mutable_hair(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "BEARD") + { + auto send_style = appearance->mutable_beard(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "MOUSTACHE") + { + auto send_style = appearance->mutable_moustache(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "SIDEBURNS") + { + auto send_style = appearance->mutable_sideburns(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + } + + for (int j = 0; j < unit->inventory.size(); j++) + { + auto inventory_item = unit->inventory[j]; + auto sent_item = send_unit->add_inventory(); + sent_item->set_mode((InventoryMode)inventory_item->mode); + CopyItem(sent_item->mutable_item(), inventory_item->item); + } + + if (unit->flags1.bits.projectile) + { + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); + if (item == NULL) + continue; + if (item->unit != unit) + continue; + send_unit->set_subpos_x(item->pos_x / 100000.0); + send_unit->set_subpos_y(item->pos_y / 100000.0); + send_unit->set_subpos_z(item->pos_z / 140000.0); + } + } + } + return CR_OK; } static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out) From 7fd2586f62a8ee6ebfa8d3427500f2b8ac1dcb48 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sat, 25 Nov 2017 11:39:15 +0530 Subject: [PATCH 0665/1012] Remove trailing whitespace --- plugins/proto/RemoteFortressReader.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index ae1d7dcb4..62ea5ee54 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -865,5 +865,5 @@ message Report message Status { - repeated Report reports = 1; + repeated Report reports = 1; } From 35b0e962ce53835092a2218262ba8ddfb2c5abb7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 25 Nov 2017 01:10:47 -0500 Subject: [PATCH 0666/1012] Fix crash on empty symbol tables --- library/VersionInfoFactory.cpp | 4 ++++ library/xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 707c2809f..cdfdbf774 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -116,6 +116,10 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) // process additional entries //cout << "Entry " << cstr_version << " " << cstr_os << endl; + if (!entry->FirstChildElement()) { + cerr << "Empty symbol table: " << entry->Attribute("name") << endl; + return; + } pMemEntry = entry->FirstChildElement()->ToElement(); for(;pMemEntry;pMemEntry=pMemEntry->NextSiblingElement()) { diff --git a/library/xml b/library/xml index fd6e70de5..24b87c746 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fd6e70de5047a48b163ced482c8a925b6a021891 +Subproject commit 24b87c74675df5059b848c4a1b8764a293fdda1b From ea8347e2ca52b8a464ad91ec213870aabbe76c3a Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sat, 25 Nov 2017 11:45:30 +0530 Subject: [PATCH 0667/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 3b5214bca..24b87c746 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3b5214bcad3349bdf3418a1dba71dbeea4a723d1 +Subproject commit 24b87c74675df5059b848c4a1b8764a293fdda1b From 7e55efa3f7eeffbaab216a234ffd260fc2c67cae Mon Sep 17 00:00:00 2001 From: Phillip Spiess Date: Mon, 27 Nov 2017 13:10:09 -0800 Subject: [PATCH 0668/1012] Autocomplete built in commands (die, etc.) --- library/Core.cpp | 29 +++++++++-------------------- library/include/Core.h | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 62d605fe6..4472fe9f4 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -433,6 +433,11 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: { std::vector possible; + // Check for possible built in commands to autocomplete first + for (auto iter = built_in_commands.begin(); iter != built_in_commands.end(); ++iter) + if (iter->substr(0, first.size()) == first) + possible.push_back(*iter); + auto plug_mgr = Core::getInstance().getPluginManager(); for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { @@ -612,28 +617,12 @@ static std::string sc_event_name (state_change_event id) { string getBuiltinCommand(std::string cmd) { std::string builtin = ""; - if (cmd == "ls" || - cmd == "help" || - cmd == "type" || - cmd == "load" || - cmd == "unload" || - cmd == "reload" || - cmd == "enable" || - cmd == "disable" || - cmd == "plug" || - cmd == "keybinding" || - cmd == "alias" || - cmd == "fpause" || - cmd == "cls" || - cmd == "die" || - cmd == "kill-lua" || - cmd == "script" || - cmd == "hide" || - cmd == "show" || - cmd == "sc-script" - ) + + // Check our list of builtin commands from the header + if (built_in_commands.count(cmd)) builtin = cmd; + // Check for some common aliases for built in commands else if (cmd == "?" || cmd == "man") builtin = "help"; diff --git a/library/include/Core.h b/library/include/Core.h index fa65645a1..ef90550b4 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -30,6 +30,7 @@ distribution. #include #include #include +#include #include #include "Console.h" #include "modules/Graphic.h" @@ -84,6 +85,29 @@ namespace DFHack SC_UNPAUSED = 8 }; + // List of built in commands + const std::set built_in_commands = { + "ls" , + "help" , + "type" , + "load" , + "unload" , + "reload" , + "enable" , + "disable" , + "plug" , + "keybinding" , + "alias" , + "fpause" , + "cls" , + "die" , + "kill-lua" , + "script" , + "hide" , + "show" , + "sc-script" + }; + class DFHACK_EXPORT StateChangeScript { public: From bbdf157a5257ec3e92df1db69d80f54f3d4e7c4f Mon Sep 17 00:00:00 2001 From: Phillip Spiess Date: Mon, 27 Nov 2017 13:45:58 -0800 Subject: [PATCH 0669/1012] Fixup autocomplete built ins per IRC --- library/Core.cpp | 29 ++++++++++++++++++++++++++--- library/include/Core.h | 24 ------------------------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 4472fe9f4..47bddbab8 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -429,14 +429,37 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } +// List of built in commands +static const std::set built_in_commands = { + "ls" , + "help" , + "type" , + "load" , + "unload" , + "reload" , + "enable" , + "disable" , + "plug" , + "keybinding" , + "alias" , + "fpause" , + "cls" , + "die" , + "kill-lua" , + "script" , + "hide" , + "show" , + "sc-script" +}; + static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) { std::vector possible; // Check for possible built in commands to autocomplete first - for (auto iter = built_in_commands.begin(); iter != built_in_commands.end(); ++iter) - if (iter->substr(0, first.size()) == first) - possible.push_back(*iter); + for (auto const &command : built_in_commands) + if (command.substr(0, first.size()) == first) + possible.push_back(command); auto plug_mgr = Core::getInstance().getPluginManager(); for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) diff --git a/library/include/Core.h b/library/include/Core.h index ef90550b4..fa65645a1 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -30,7 +30,6 @@ distribution. #include #include #include -#include #include #include "Console.h" #include "modules/Graphic.h" @@ -85,29 +84,6 @@ namespace DFHack SC_UNPAUSED = 8 }; - // List of built in commands - const std::set built_in_commands = { - "ls" , - "help" , - "type" , - "load" , - "unload" , - "reload" , - "enable" , - "disable" , - "plug" , - "keybinding" , - "alias" , - "fpause" , - "cls" , - "die" , - "kill-lua" , - "script" , - "hide" , - "show" , - "sc-script" - }; - class DFHACK_EXPORT StateChangeScript { public: From d1c1dfe2cdf95c941bd55ec0c6028dd53259679c Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 30 Nov 2017 19:46:03 +0530 Subject: [PATCH 0670/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 24b87c746..3208cff2a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 24b87c74675df5059b848c4a1b8764a293fdda1b +Subproject commit 3208cff2a041fa915260d085655a69fb15565537 From e174135133172b7875ba33cde7c133bc54242e99 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 2 Dec 2017 21:06:29 -0600 Subject: [PATCH 0671/1012] update structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 24b87c746..c1e116acc 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 24b87c74675df5059b848c4a1b8764a293fdda1b +Subproject commit c1e116acc0cb6f4a968503215237310d29947502 From 4edc7a6f43959758250c17d4322960c038785984 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 2 Dec 2017 21:41:44 -0600 Subject: [PATCH 0672/1012] update DataFuncs to support vmethods with 12-13 parameters (#1192) --- library/include/DataFuncs.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index bf8e6b244..6541ae900 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -221,6 +221,29 @@ INSTANTIATE_WRAPPERS(11, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11), LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11);) #undef FW_TARGS +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11, class A12 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12)) +INSTANTIATE_WRAPPERS(12, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8); + LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11); LOAD_ARG(A12);) +#undef FW_TARGS + +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11, class A12, class A13 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13)) +INSTANTIATE_WRAPPERS(13, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12,vA13), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12,vA13), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8); + LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11); LOAD_ARG(A12); + LOAD_ARG(A13);) +#undef FW_TARGS + #undef FW_TARGSC #undef INSTANTIATE_WRAPPERS #undef INSTANTIATE_WRAPPERS2 From 1489e7db824cc6323de14772fbf0f121ecd0860b Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 2 Dec 2017 22:14:04 -0600 Subject: [PATCH 0673/1012] update structures - fix unit_personality and historical_figure_info.personality --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index c1e116acc..487eb45e2 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c1e116acc0cb6f4a968503215237310d29947502 +Subproject commit 487eb45e2d083aee1ca104c6fb1a33942b2448d2 From ec06ed09c900a9381cb3a72ff7829bfcd5b835be Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 3 Dec 2017 13:49:57 -0600 Subject: [PATCH 0674/1012] update structures - add remaining Linux 64-bit offsets --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 487eb45e2..3abfb36a1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 487eb45e2d083aee1ca104c6fb1a33942b2448d2 +Subproject commit 3abfb36a129add730599cf27684b2c65ff7ba6bb From c72ae8d8a77958cbe8bc9447ae0e14fff53b9f8b Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 3 Dec 2017 20:05:08 -0600 Subject: [PATCH 0675/1012] Merge "announcements" global into d_init where it belongs --- library/modules/Gui.cpp | 8 ++++---- library/xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 7de9a5e11..c3fad806f 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -48,12 +48,12 @@ using namespace DFHack; #include "DataDefs.h" #include "df/announcement_flags.h" -#include "df/announcements.h" #include "df/assign_trade_status.h" #include "df/building_civzonest.h" #include "df/building_furnacest.h" #include "df/building_trapst.h" #include "df/building_workshopst.h" +#include "df/d_init.h" #include "df/game_mode.h" #include "df/general_ref.h" #include "df/global_objects.h" @@ -1428,13 +1428,13 @@ void Gui::showAutoAnnouncement( df::announcement_type type, df::coord pos, std::string message, int color, bool bright, df::unit *unit1, df::unit *unit2 ) { - using df::global::announcements; + using df::global::d_init; df::announcement_flags flags; flags.bits.D_DISPLAY = flags.bits.A_DISPLAY = true; - if (is_valid_enum_item(type) && announcements) - flags = announcements->flags[type]; + if (is_valid_enum_item(type) && d_init) + flags = d_init->announcements.flags[type]; int id = makeAnnouncement(type, flags, pos, message, color, bright); diff --git a/library/xml b/library/xml index 3abfb36a1..9ffd5971b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3abfb36a129add730599cf27684b2c65ff7ba6bb +Subproject commit 9ffd5971bd4709abec914715cee1a23963b7ee91 From 88c7e493b8c4ed4c1449e047a466556e05f5c9f4 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 3 Dec 2017 20:34:59 -0600 Subject: [PATCH 0676/1012] Merge ui_area_map_width into ui_menu_width, now a 2-byte array --- library/lua/gui/dwarfmode.lua | 4 ++-- library/modules/Gui.cpp | 13 ++++++------- library/xml | 2 +- plugins/fix-armory.cpp | 2 -- plugins/rendermax/renderer_light.cpp | 4 ++-- plugins/rendermax/rendermax.cpp | 2 -- plugins/ruby/ui.rb | 6 +++--- plugins/tweak/tweak.cpp | 2 -- plugins/zone.cpp | 5 ++--- 9 files changed, 16 insertions(+), 24 deletions(-) diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 3764c4df7..e98ba2080 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -18,8 +18,8 @@ refreshSidebar = dfhack.gui.refreshSidebar function getPanelLayout() local dims = dfhack.gui.getDwarfmodeViewDims() - local area_pos = df.global.ui_area_map_width - local menu_pos = df.global.ui_menu_width + local area_pos = df.global.ui_menu_width[1] + local menu_pos = df.global.ui_menu_width[0] if dims.menu_forced then menu_pos = area_pos - 1 diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index c3fad806f..0631e0ed7 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -108,7 +108,6 @@ using df::global::ui; using df::global::world; using df::global::selection_rect; using df::global::ui_menu_width; -using df::global::ui_area_map_width; using df::global::gamemode; static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx) @@ -1508,8 +1507,8 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default() dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1; dims.menu_forced = false; - int menu_pos = (ui_menu_width ? *ui_menu_width : 2); - int area_pos = (ui_area_map_width ? *ui_area_map_width : 3); + int menu_pos = (ui_menu_width ? (*ui_menu_width)[0] : 2); + int area_pos = (ui_menu_width ? (*ui_menu_width)[1] : 3); if (ui && ui->main.mode && menu_pos >= area_pos) { @@ -1715,14 +1714,14 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height) bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width) { - menu_width = *df::global::ui_menu_width; - area_map_width = *df::global::ui_area_map_width; + menu_width = (*df::global::ui_menu_width)[0]; + area_map_width = (*df::global::ui_menu_width)[1]; return true; } bool Gui::setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width) { - *df::global::ui_menu_width = menu_width; - *df::global::ui_area_map_width = area_map_width; + (*df::global::ui_menu_width)[0] = menu_width; + (*df::global::ui_menu_width)[1] = area_map_width; return true; } diff --git a/library/xml b/library/xml index 9ffd5971b..0a9a57154 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9ffd5971bd4709abec914715cee1a23963b7ee91 +Subproject commit 0a9a571545eaf1fd960366fd12dffe6b30add756 diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 8b062290b..0f63b4d93 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -57,8 +57,6 @@ using df::global::ui; using df::global::world; using df::global::gamemode; using df::global::ui_build_selector; -using df::global::ui_menu_width; -using df::global::ui_area_map_width; using namespace DFHack::Gui; using Screen::Pen; diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index f0f3c92cf..199e174af 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -88,8 +88,8 @@ rect2d getMapViewport() int menu_x1=area_x2-MENU_WIDTH-1; int view_rb=w-1; - int area_pos=*df::global::ui_area_map_width; - int menu_pos=*df::global::ui_menu_width; + int area_pos=(*df::global::ui_menu_width)[1]; + int menu_pos=(*df::global::ui_menu_width)[0]; if(area_pos<3) { view_rb=area_x2; diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index 2140743ca..be8be07ba 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -36,8 +36,6 @@ REQUIRE_GLOBAL(enabler); REQUIRE_GLOBAL(gametype); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(ui); -REQUIRE_GLOBAL(ui_area_map_width); -REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(window_x); REQUIRE_GLOBAL(window_y); REQUIRE_GLOBAL(window_z); diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index a9dd05437..9268dcb9e 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -15,15 +15,15 @@ module DFHack x, y, z = x.x, x.y, x.z if x.respond_to?(:x) # compute screen 'map' size (tiles) - menuwidth = ui_menu_width + menuwidth = ui_menu_width[0] # ui_menu_width shows only the 'tab' status - menuwidth = 1 if menuwidth == 2 and ui_area_map_width == 2 and cursor.x != -30000 + menuwidth = 1 if menuwidth == 2 and ui_menu_width[1] == 2 and cursor.x != -30000 menuwidth = 2 if menuwidth == 3 and cursor.x != -30000 w_w = gps.dimx - 2 w_h = gps.dimy - 2 case menuwidth when 1; w_w -= 55 - when 2; w_w -= (ui_area_map_width == 2 ? 24 : 31) + when 2; w_w -= (ui_menu_width[1] == 2 ? 24 : 31) end # center view diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index bf044dd58..2970f3792 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -117,12 +117,10 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(enabler); REQUIRE_GLOBAL(ui); -REQUIRE_GLOBAL(ui_area_map_width); REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_building_in_resize); 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); diff --git a/plugins/zone.cpp b/plugins/zone.cpp index e41704044..608216092 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -108,7 +108,6 @@ REQUIRE_GLOBAL(ui_building_assign_items); REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_menu_width); -REQUIRE_GLOBAL(ui_area_map_width); using namespace DFHack::Gui; @@ -3926,8 +3925,8 @@ public: return; int left_margin = gps->dimx - 30; - int8_t a = *ui_menu_width; - int8_t b = *ui_area_map_width; + int8_t a = (*ui_menu_width)[0]; + int8_t b = (*ui_menu_width)[1]; if ((a == 1 && b > 1) || (a == 2 && b == 2)) left_margin -= 24; From 5c9318654d56089abb221a65ba48537f9a52b561 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Dec 2017 13:31:42 -0500 Subject: [PATCH 0677/1012] Change release to alpha0 to avoid error from prerelease-warning --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c7838949..b43de8400 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.02") -SET(DFHACK_RELEASE "r0") +SET(DFHACK_RELEASE "alpha0") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From fc952bc67f47f3298d45b20cce29f54df565ff9f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 7 Dec 2017 14:36:44 -0500 Subject: [PATCH 0678/1012] Update NEWS-dev.rst --- docs/NEWS-dev.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 2be774672..2f0c3ea97 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,23 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.02-alpha1 +===================== + +Fixes +----- +- Fixed a crash that could occur if a symbol table in symbols.xml had no content +- The Lua API can now wrap functions with 12 or 13 parameters + +Structures +---------- +- The ``ui_menu_width`` global is now a 2-byte array; the second item is the + former ``ui_area_map_width`` global, which is now removed +- The former ``announcements`` global is now a field in ``d_init`` +- ``world`` fields formerly beginning with ``job_`` are now fields of + ``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list`` + + DFHack 0.43.05-beta2 ==================== From 23bb8c4f3ddb5814acfdd49b08a01da16e70db67 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 7 Dec 2017 14:37:15 -0500 Subject: [PATCH 0679/1012] Restore REQUIRE_GLOBAL(ui_menu_width) - used in tweak stable-cursor --- plugins/tweak/tweak.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 2970f3792..417784efb 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -122,6 +122,7 @@ REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_building_in_resize); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_look_cursor); +REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(ui_unit_view_mode); REQUIRE_GLOBAL(ui_workshop_in_add); From 622a8dacb61d7eb5165784e5a98c8512db495760 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 7 Dec 2017 14:39:15 -0500 Subject: [PATCH 0680/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 0a9a57154..e2e256066 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 0a9a571545eaf1fd960366fd12dffe6b30add756 +Subproject commit e2e256066cc4a5c427172d9d27db25b7823e4e86 diff --git a/scripts b/scripts index 09cf8dde9..252a5adbf 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 09cf8dde929e0478a869fa09ec329a27c9631113 +Subproject commit 252a5adbf1a5cae45cf7e623d3c5ce6f3aa1195d From 7721a142d8c18562ce0b7223e38240be8620cfe5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 7 Dec 2017 14:43:27 -0500 Subject: [PATCH 0681/1012] Add a basic Lua console API --- NEWS.rst | 7 +++++++ docs/Lua API.rst | 12 ++++++++++++ docs/NEWS-dev.rst | 3 +++ library/LuaApi.cpp | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index e55ca92c5..673ae31f9 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -36,6 +36,13 @@ Changelog .. contents:: :depth: 2 +DFHack future +============= + +Lua +--- +- Added a new ``dfhack.console`` API + DFHack 0.43.05-r3 ================= diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 66c2a5a07..a6a2ab8cd 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1966,6 +1966,18 @@ unless otherwise noted. ``listdir_recursive()`` returns the initial path and all components following it for each entry. +Console API +----------- + +* ``dfhack.console.clear()`` + + Clears the console; equivalent to the ``cls`` built-in command. + +* ``dfhack.console.flush()`` + + Flushes all output to the console. This can be useful when printing text that + does not end in a newline but should still be displayed. + Internal API ------------ diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 2f0c3ea97..8dfe2866b 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -53,6 +53,9 @@ Structures - ``world`` fields formerly beginning with ``job_`` are now fields of ``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list`` +API Changes +----------- +- Lua: Added a new ``dfhack.console`` API DFHack 0.43.05-beta2 ==================== diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 39b4b41cb..0bf19e93e 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2429,6 +2429,23 @@ static const luaL_Reg dfhack_designations_funcs[] = { {NULL, NULL} }; +/***** Console module *****/ + +namespace console { + void clear() { + Core::getInstance().getConsole().clear(); + } + void flush() { + Core::getInstance().getConsole() << std::flush; + } +} + +static const LuaWrapper::FunctionReg dfhack_console_module[] = { + WRAPM(console, clear), + WRAPM(console, flush), + { NULL, NULL } +}; + /***** Internal module *****/ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) @@ -2964,5 +2981,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs); + OpenModule(state, "console", dfhack_console_module); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } From 43b19c893aed218d3abb75ba3960402dc700fa49 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 7 Dec 2017 14:43:50 -0500 Subject: [PATCH 0682/1012] Bump version to alpha1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b43de8400..d55cd9e43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.02") -SET(DFHACK_RELEASE "alpha0") +SET(DFHACK_RELEASE "alpha1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From ad2c939fea4292efc45abb9ff29b4f97bed3eb2b Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 8 Dec 2017 01:19:36 -0500 Subject: [PATCH 0683/1012] Update Authors.rst: dfhack/scripts#33 --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index a693646bf..2537abfd1 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -84,6 +84,7 @@ Patrik Lundell PatrikLundell Paul Fenwick pjf PeridexisErrant PeridexisErrant Petr Mrázek peterix +Pfhreak Pfhreak potato Priit Laes plaes Putnam Putnam3145 From cf7dbd478aac33f23d752b9075cb9e0f6eb35885 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Thu, 14 Dec 2017 12:45:35 +0100 Subject: [PATCH 0684/1012] Made prompt respect color reset --- library/Console-windows.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 28d98309b..99f7ace39 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -214,8 +214,11 @@ namespace DFHack { COORD pos = { (SHORT)x, (SHORT)y }; DWORD count = 0; - WriteConsoleOutputCharacterA(console_out, str, len, pos, &count); - } + CONSOLE_SCREEN_BUFFER_INFO inf = { 0 }; + GetConsoleScreenBufferInfo(console_out, &inf); + SetConsoleCursorPosition(console_out, pos); + WriteConsoleA(console_out, str, len, &count, NULL); + } void prompt_refresh() { From 14996a9d6794a8f3ecf4fc056f5662240a481b35 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Thu, 14 Dec 2017 12:46:52 +0100 Subject: [PATCH 0685/1012] Made prompt respect color reset --- library/Console-windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 99f7ace39..3dc687642 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -218,7 +218,7 @@ namespace DFHack GetConsoleScreenBufferInfo(console_out, &inf); SetConsoleCursorPosition(console_out, pos); WriteConsoleA(console_out, str, len, &count, NULL); - } + } void prompt_refresh() { From b1e77fa2601e6ad76c0f495e4f5a9b647e73dc5f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 16 Dec 2017 16:04:32 -0500 Subject: [PATCH 0686/1012] createitem: fix an issue with items teleporting to the location of another unit This was due to moveToGround() being called twice, first with the location of world.units.active[0], which caused the item to teleport to that location later. Fixes #1198 --- plugins/createitem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index e65d19793..18ce7674a 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -114,10 +114,10 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it if (!Items::moveToBuilding(mc, out_items[i], (df::building_actual *)building, 0)) out_items[i]->moveToGround(building->centerx, building->centery, building->z); } - if (on_ground) - out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); if (move_to_cursor) out_items[i]->moveToGround(cursor->x, cursor->y, cursor->z); + else if (on_ground) + out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); if (is_gloves) { // if the reaction creates gloves without handedness, then create 2 sets (left and right) From f59a91a0dbdce7884b6c10affdcd0441c06d799d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 16 Dec 2017 16:17:11 -0500 Subject: [PATCH 0687/1012] Add more warnings about Lua's delete() Closes #1170 --- docs/Lua API.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index a6a2ab8cd..520ac243a 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -125,12 +125,18 @@ All typed objects have the following built-in features: * ``ref:delete()`` - Destroys the object with the C++ ``delete`` operator. - If destructor is not available, returns *false*. + Destroys the object with the C++ ``delete`` operator. If the destructor is not + available, returns *false*. (This typically only occurs when trying to delete + an instance of a DF class with virtual methods whose vtable address has not + been found; it is impossible for ``delete()`` to determine the validity of + ``ref``.) .. warning:: - the lua reference object remains as a dangling - pointer, like a raw C++ pointer would. + ``ref`` **must** be an object allocated with ``new``, like in C++. Calling + ``obj.field:delete()`` where ``obj`` was allocated with ``new`` will not + work. After ``delete()`` returns, ``ref`` remains as a dangling pointer, + like a raw C++ pointer would. Any accesses to ``ref`` after ``ref:delete()`` + has been called are undefined behavior. * ``ref:assign(object)`` From bbd823f5b072ce93664f80d7019a78c0fb08c55f Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 16 Dec 2017 15:40:39 -0600 Subject: [PATCH 0688/1012] Simplify logic in createitem, to avoid similar errors in the future --- plugins/createitem.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 18ce7674a..1f2e8a5c4 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -99,24 +99,21 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it for (size_t i = 0; i < out_items.size(); i++) { - bool on_ground = true; if (container) { - on_ground = false; out_items[i]->flags.bits.removed = 1; if (!Items::moveToContainer(mc, out_items[i], container)) out_items[i]->moveToGround(container->pos.x, container->pos.y, container->pos.z); } - if (building) + else if (building) { - on_ground = false; out_items[i]->flags.bits.removed = 1; if (!Items::moveToBuilding(mc, out_items[i], (df::building_actual *)building, 0)) out_items[i]->moveToGround(building->centerx, building->centery, building->z); } - if (move_to_cursor) + else if (move_to_cursor) out_items[i]->moveToGround(cursor->x, cursor->y, cursor->z); - else if (on_ground) + else out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); if (is_gloves) { From 29282a238ba02f74356a70fdde8893c1d4d5359f Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 17 Dec 2017 18:05:38 +0530 Subject: [PATCH 0689/1012] Bring in changes from the remoteServerUnsafe branch. --- plugins/proto/RemoteFortressReader.proto | 26 +- plugins/remotefortressreader/df_version_int.h | 2 +- .../remotefortressreader.cpp | 4590 +++++++++-------- 3 files changed, 2359 insertions(+), 2259 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 88fd03c64..62ea5ee54 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -397,6 +397,9 @@ message UnitDefinition repeated string noble_positions = 18; optional int32 rider_id = 19; repeated InventoryItem inventory = 20; + optional float subpos_x = 21; + optional float subpos_y = 22; + optional float subpos_z = 23; } message UnitList @@ -842,4 +845,25 @@ message ListRequest { optional int32 list_start = 1; optional int32 list_end = 2; -} \ No newline at end of file +} + +message Report +{ + optional int32 type = 1; + optional string text = 2; + optional ColorDefinition color = 3; + optional int32 duration = 4; + optional bool continuation = 5; + optional bool unconscious = 6; + optional bool announcement = 7; + optional int32 repeat_count = 8; + optional Coord pos = 9; + optional int32 id = 10; + optional int32 year = 11; + optional int32 time = 12; +} + +message Status +{ + repeated Report reports = 1; +} diff --git a/plugins/remotefortressreader/df_version_int.h b/plugins/remotefortressreader/df_version_int.h index d6d0bd750..35c9ed851 100644 --- a/plugins/remotefortressreader/df_version_int.h +++ b/plugins/remotefortressreader/df_version_int.h @@ -1,2 +1,2 @@ #pragma once -#define DF_VERSION_INT 43005 +#define DF_VERSION_INT 44002 diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index b543a2ab3..704c91ae1 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.18.0" +#define RFR_VERSION "0.19.0" #include #include @@ -30,6 +30,7 @@ #include "modules/Units.h" #include "modules/World.h" +#include "df/unit_action.h" #if DF_VERSION_INT > 34011 #include "df/army.h" #include "df/army_flags.h" @@ -75,7 +76,10 @@ #include "df/physical_attribute_type.h" #include "df/plant.h" #include "df/plant_raw_flags.h" +#include "df/projectile.h" +#include "df/proj_unitst.h" #include "df/region_map_entry.h" +#include "df/report.h" #include "df/site_realization_building.h" #include "df/site_realization_building_info_castle_towerst.h" #include "df/site_realization_building_info_castle_wallst.h" @@ -137,6 +141,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out); static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in); static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out); +static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out); static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out); static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out); static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); @@ -157,460 +162,474 @@ static command_result SetPauseState(color_ostream & stream, const SingleBool * i static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); +static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); const char* growth_locations[] = { - "TWIGS", - "LIGHT_BRANCHES", - "HEAVY_BRANCHES", - "TRUNK", - "ROOTS", - "CAP", - "SAPLING", - "SHRUB" + "TWIGS", + "LIGHT_BRANCHES", + "HEAVY_BRANCHES", + "TRUNK", + "ROOTS", + "CAP", + "SAPLING", + "SHRUB" }; #define GROWTH_LOCATIONS_SIZE 8 command_result dump_bp_mods(color_ostream &out, vector & parameters) { - remove("bp_appearance_mods.csv"); - ofstream output; - output.open("bp_appearance_mods.csv"); - - output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; - - for (int creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) - { - auto creatureRaw = world->raws.creatures.all[creatureIndex]; - for (int casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) - { - df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; - for (int partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) - { - output << creatureIndex << ";"; - output << creatureRaw->creature_id << ";"; - output << casteRaw->caste_id << ";"; - output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";"; - output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";"; - int layer = casteRaw->bp_appearance.layer_idx[partIndex]; - if (layer < 0) - output << "N/A;"; - else - output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; - output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; - auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; + remove("bp_appearance_mods.csv"); + ofstream output; + output.open("bp_appearance_mods.csv"); + + output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; + + for (int creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) + { + auto creatureRaw = world->raws.creatures.all[creatureIndex]; + for (int casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) + { + df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; + for (int partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) + { + output << creatureIndex << ";"; + output << creatureRaw->creature_id << ";"; + output << casteRaw->caste_id << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";"; + int layer = casteRaw->bp_appearance.layer_idx[partIndex]; + if (layer < 0) + output << "N/A;"; + else + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; + output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; + auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; #if DF_VERSION_INT > 34011 - if (appMod->growth_rate > 0) - { - output << appMod->growth_min << " - " << appMod->growth_max << "\n"; - } - else + if (appMod->growth_rate > 0) + { + output << appMod->growth_min << " - " << appMod->growth_max << "\n"; + } + else #endif - { - output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; - output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; - } - } - } - } + { + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; + } + } + } + } - output.close(); + output.close(); - return CR_OK; + return CR_OK; } command_result RemoteFortressReader_version(color_ostream &out, vector ¶meters) { - out.print(RFR_VERSION); - return CR_OK; + out.print(RFR_VERSION); + return CR_OK; } // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - //// Fill the command list with your commands. - commands.push_back(PluginCommand( - "dump_bp_mods", "Dump bodypart mods for debugging", - dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " This command does nothing at all.\n" - "Example:\n" - " isoworldremote\n" - " Does nothing.\n" - )); - commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); - return CR_OK; + //// Fill the command list with your commands. + commands.push_back(PluginCommand( + "dump_bp_mods", "Dump bodypart mods for debugging", + dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " isoworldremote\n" + " Does nothing.\n" + )); + commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); + return CR_OK; } +#ifndef SF_ALLOW_REMOTE +#define SF_ALLOW_REMOTE 0 +#endif // !SF_ALLOW_REMOTE + DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) { - RPCService *svc = new RPCService(); - svc->addFunction("GetMaterialList", GetMaterialList); - svc->addFunction("GetGrowthList", GetGrowthList); - svc->addFunction("GetBlockList", GetBlockList); - svc->addFunction("CheckHashes", CheckHashes); - svc->addFunction("GetTiletypeList", GetTiletypeList); - svc->addFunction("GetPlantList", GetPlantList); - svc->addFunction("GetUnitList", GetUnitList); - svc->addFunction("GetViewInfo", GetViewInfo); - svc->addFunction("GetMapInfo", GetMapInfo); - svc->addFunction("ResetMapHashes", ResetMapHashes); - svc->addFunction("GetItemList", GetItemList); - svc->addFunction("GetBuildingDefList", GetBuildingDefList); - svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetWorldMapNew", GetWorldMapNew); - svc->addFunction("GetRegionMaps", GetRegionMaps); - svc->addFunction("GetRegionMapsNew", GetRegionMapsNew); - svc->addFunction("GetCreatureRaws", GetCreatureRaws); - svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws); - svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); - svc->addFunction("GetPlantRaws", GetPlantRaws); - svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws); - svc->addFunction("CopyScreen", CopyScreen); - svc->addFunction("PassKeyboardEvent", PassKeyboardEvent); - svc->addFunction("SendDigCommand", SendDigCommand); - svc->addFunction("SetPauseState", SetPauseState); - svc->addFunction("GetPauseState", GetPauseState); - svc->addFunction("GetVersionInfo", GetVersionInfo); - return svc; + RPCService *svc = new RPCService(); + svc->addFunction("GetMaterialList", GetMaterialList, SF_ALLOW_REMOTE); + svc->addFunction("GetGrowthList", GetGrowthList, SF_ALLOW_REMOTE); + svc->addFunction("GetBlockList", GetBlockList, SF_ALLOW_REMOTE); + svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); + svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); + svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); + svc->addFunction("GetBuildingDefList", GetBuildingDefList, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMap", GetWorldMap, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapNew", GetWorldMapNew, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMaps", GetRegionMaps, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMapsNew", GetRegionMapsNew, SF_ALLOW_REMOTE); + svc->addFunction("GetCreatureRaws", GetCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapCenter", GetWorldMapCenter, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantRaws", GetPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("CopyScreen", CopyScreen, SF_ALLOW_REMOTE); + svc->addFunction("PassKeyboardEvent", PassKeyboardEvent, SF_ALLOW_REMOTE); + svc->addFunction("SendDigCommand", SendDigCommand, SF_ALLOW_REMOTE); + svc->addFunction("SetPauseState", SetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); + return svc; } // This is called right before the plugin library is removed from memory. DFhackCExport command_result plugin_shutdown(color_ostream &out) { - // You *MUST* kill all threads you created before this returns. - // If everything fails, just return CR_FAILURE. Your plugin will be - // in a zombie state, but things won't crash. - return CR_OK; + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; } uint16_t fletcher16(uint8_t const *data, size_t bytes) { - uint16_t sum1 = 0xff, sum2 = 0xff; - - while (bytes) { - size_t tlen = bytes > 20 ? 20 : bytes; - bytes -= tlen; - do { - sum2 += sum1 += *data++; - } while (--tlen); - sum1 = (sum1 & 0xff) + (sum1 >> 8); - sum2 = (sum2 & 0xff) + (sum2 >> 8); - } - /* Second reduction step to reduce sums to 8 bits */ - sum1 = (sum1 & 0xff) + (sum1 >> 8); - sum2 = (sum2 & 0xff) + (sum2 >> 8); - return sum2 << 8 | sum1; + uint16_t sum1 = 0xff, sum2 = 0xff; + + while (bytes) { + size_t tlen = bytes > 20 ? 20 : bytes; + bytes -= tlen; + do { + sum2 += sum1 += *data++; + } while (--tlen); + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + } + /* Second reduction step to reduce sums to 8 bits */ + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + return sum2 << 8 | sum1; } void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) { - if (!df::global::enabler) - return; + if (!df::global::enabler) + return; - auto enabler = df::global::enabler; + auto enabler = df::global::enabler; - out->set_red((int)(enabler->ccolor[index][0] * 255)); - out->set_green((int)(enabler->ccolor[index][1] * 255)); - out->set_blue((int)(enabler->ccolor[index][2] * 255)); + out->set_red((int)(enabler->ccolor[index][0] * 255)); + out->set_green((int)(enabler->ccolor[index][1] * 255)); + out->set_blue((int)(enabler->ccolor[index][2] * 255)); } void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) { - int index = in[0] | (8 * in[2]); - ConvertDfColor(index, out); + int index = in[0] | (8 * in[2]); + ConvertDfColor(index, out); } void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out) { - df::descriptor_color *color = world->raws.language.colors[index]; - out->set_red(color->red * 255); - out->set_green(color->green * 255); - out->set_blue(color->blue * 255); + df::descriptor_color *color = world->raws.language.colors[index]; + out->set_red(color->red * 255); + out->set_green(color->green * 255); + out->set_blue(color->blue * 255); +} + +void ConvertDFCoord(DFCoord in, RemoteFortressReader::Coord * out) +{ + out->set_x(in.x); + out->set_y(in.y); + out->set_z(in.z); } RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { - switch (material) - { - case df::enums::tiletype_material::NONE: - return RemoteFortressReader::NO_MATERIAL; - break; - case df::enums::tiletype_material::AIR: - return RemoteFortressReader::AIR; - break; - case df::enums::tiletype_material::SOIL: - return RemoteFortressReader::SOIL; - break; - case df::enums::tiletype_material::STONE: - return RemoteFortressReader::STONE; - break; - case df::enums::tiletype_material::FEATURE: - return RemoteFortressReader::FEATURE; - break; - case df::enums::tiletype_material::LAVA_STONE: - return RemoteFortressReader::LAVA_STONE; - break; - case df::enums::tiletype_material::MINERAL: - return RemoteFortressReader::MINERAL; - break; - case df::enums::tiletype_material::FROZEN_LIQUID: - return RemoteFortressReader::FROZEN_LIQUID; - break; - case df::enums::tiletype_material::CONSTRUCTION: - return RemoteFortressReader::CONSTRUCTION; - break; - case df::enums::tiletype_material::GRASS_LIGHT: - return RemoteFortressReader::GRASS_LIGHT; - break; - case df::enums::tiletype_material::GRASS_DARK: - return RemoteFortressReader::GRASS_DARK; - break; - case df::enums::tiletype_material::GRASS_DRY: - return RemoteFortressReader::GRASS_DRY; - break; - case df::enums::tiletype_material::GRASS_DEAD: - return RemoteFortressReader::GRASS_DEAD; - break; - case df::enums::tiletype_material::PLANT: - return RemoteFortressReader::PLANT; - break; - case df::enums::tiletype_material::HFS: - return RemoteFortressReader::HFS; - break; - case df::enums::tiletype_material::CAMPFIRE: - return RemoteFortressReader::CAMPFIRE; - break; - case df::enums::tiletype_material::FIRE: - return RemoteFortressReader::FIRE; - break; - case df::enums::tiletype_material::ASHES: - return RemoteFortressReader::ASHES; - break; - case df::enums::tiletype_material::MAGMA: - return RemoteFortressReader::MAGMA; - break; - case df::enums::tiletype_material::DRIFTWOOD: - return RemoteFortressReader::DRIFTWOOD; - break; - case df::enums::tiletype_material::POOL: - return RemoteFortressReader::POOL; - break; - case df::enums::tiletype_material::BROOK: - return RemoteFortressReader::BROOK; - break; - case df::enums::tiletype_material::RIVER: - return RemoteFortressReader::RIVER; - break; + switch (material) + { + case df::enums::tiletype_material::NONE: + return RemoteFortressReader::NO_MATERIAL; + break; + case df::enums::tiletype_material::AIR: + return RemoteFortressReader::AIR; + break; + case df::enums::tiletype_material::SOIL: + return RemoteFortressReader::SOIL; + break; + case df::enums::tiletype_material::STONE: + return RemoteFortressReader::STONE; + break; + case df::enums::tiletype_material::FEATURE: + return RemoteFortressReader::FEATURE; + break; + case df::enums::tiletype_material::LAVA_STONE: + return RemoteFortressReader::LAVA_STONE; + break; + case df::enums::tiletype_material::MINERAL: + return RemoteFortressReader::MINERAL; + break; + case df::enums::tiletype_material::FROZEN_LIQUID: + return RemoteFortressReader::FROZEN_LIQUID; + break; + case df::enums::tiletype_material::CONSTRUCTION: + return RemoteFortressReader::CONSTRUCTION; + break; + case df::enums::tiletype_material::GRASS_LIGHT: + return RemoteFortressReader::GRASS_LIGHT; + break; + case df::enums::tiletype_material::GRASS_DARK: + return RemoteFortressReader::GRASS_DARK; + break; + case df::enums::tiletype_material::GRASS_DRY: + return RemoteFortressReader::GRASS_DRY; + break; + case df::enums::tiletype_material::GRASS_DEAD: + return RemoteFortressReader::GRASS_DEAD; + break; + case df::enums::tiletype_material::PLANT: + return RemoteFortressReader::PLANT; + break; + case df::enums::tiletype_material::HFS: + return RemoteFortressReader::HFS; + break; + case df::enums::tiletype_material::CAMPFIRE: + return RemoteFortressReader::CAMPFIRE; + break; + case df::enums::tiletype_material::FIRE: + return RemoteFortressReader::FIRE; + break; + case df::enums::tiletype_material::ASHES: + return RemoteFortressReader::ASHES; + break; + case df::enums::tiletype_material::MAGMA: + return RemoteFortressReader::MAGMA; + break; + case df::enums::tiletype_material::DRIFTWOOD: + return RemoteFortressReader::DRIFTWOOD; + break; + case df::enums::tiletype_material::POOL: + return RemoteFortressReader::POOL; + break; + case df::enums::tiletype_material::BROOK: + return RemoteFortressReader::BROOK; + break; + case df::enums::tiletype_material::RIVER: + return RemoteFortressReader::RIVER; + break; #if DF_VERSION_INT > 40001 - case df::enums::tiletype_material::ROOT: - return RemoteFortressReader::ROOT; - break; - case df::enums::tiletype_material::TREE: - return RemoteFortressReader::TREE_MATERIAL; - break; - case df::enums::tiletype_material::MUSHROOM: - return RemoteFortressReader::MUSHROOM; - break; - case df::enums::tiletype_material::UNDERWORLD_GATE: - return RemoteFortressReader::UNDERWORLD_GATE; - break; + case df::enums::tiletype_material::ROOT: + return RemoteFortressReader::ROOT; + break; + case df::enums::tiletype_material::TREE: + return RemoteFortressReader::TREE_MATERIAL; + break; + case df::enums::tiletype_material::MUSHROOM: + return RemoteFortressReader::MUSHROOM; + break; + case df::enums::tiletype_material::UNDERWORLD_GATE: + return RemoteFortressReader::UNDERWORLD_GATE; + break; #endif - default: - return RemoteFortressReader::NO_MATERIAL; - break; - } - return RemoteFortressReader::NO_MATERIAL; + default: + return RemoteFortressReader::NO_MATERIAL; + break; + } + return RemoteFortressReader::NO_MATERIAL; } RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special special) { - switch (special) - { - case df::enums::tiletype_special::NONE: - return RemoteFortressReader::NO_SPECIAL; - break; - case df::enums::tiletype_special::NORMAL: - return RemoteFortressReader::NORMAL; - break; - case df::enums::tiletype_special::RIVER_SOURCE: - return RemoteFortressReader::RIVER_SOURCE; - break; - case df::enums::tiletype_special::WATERFALL: - return RemoteFortressReader::WATERFALL; - break; - case df::enums::tiletype_special::SMOOTH: - return RemoteFortressReader::SMOOTH; - break; - case df::enums::tiletype_special::FURROWED: - return RemoteFortressReader::FURROWED; - break; - case df::enums::tiletype_special::WET: - return RemoteFortressReader::WET; - break; - case df::enums::tiletype_special::DEAD: - return RemoteFortressReader::DEAD; - break; - case df::enums::tiletype_special::WORN_1: - return RemoteFortressReader::WORN_1; - break; - case df::enums::tiletype_special::WORN_2: - return RemoteFortressReader::WORN_2; - break; - case df::enums::tiletype_special::WORN_3: - return RemoteFortressReader::WORN_3; - break; - case df::enums::tiletype_special::TRACK: - return RemoteFortressReader::TRACK; - break; + switch (special) + { + case df::enums::tiletype_special::NONE: + return RemoteFortressReader::NO_SPECIAL; + break; + case df::enums::tiletype_special::NORMAL: + return RemoteFortressReader::NORMAL; + break; + case df::enums::tiletype_special::RIVER_SOURCE: + return RemoteFortressReader::RIVER_SOURCE; + break; + case df::enums::tiletype_special::WATERFALL: + return RemoteFortressReader::WATERFALL; + break; + case df::enums::tiletype_special::SMOOTH: + return RemoteFortressReader::SMOOTH; + break; + case df::enums::tiletype_special::FURROWED: + return RemoteFortressReader::FURROWED; + break; + case df::enums::tiletype_special::WET: + return RemoteFortressReader::WET; + break; + case df::enums::tiletype_special::DEAD: + return RemoteFortressReader::DEAD; + break; + case df::enums::tiletype_special::WORN_1: + return RemoteFortressReader::WORN_1; + break; + case df::enums::tiletype_special::WORN_2: + return RemoteFortressReader::WORN_2; + break; + case df::enums::tiletype_special::WORN_3: + return RemoteFortressReader::WORN_3; + break; + case df::enums::tiletype_special::TRACK: + return RemoteFortressReader::TRACK; + break; #if DF_VERSION_INT > 40001 - case df::enums::tiletype_special::SMOOTH_DEAD: - return RemoteFortressReader::SMOOTH_DEAD; - break; + case df::enums::tiletype_special::SMOOTH_DEAD: + return RemoteFortressReader::SMOOTH_DEAD; + break; #endif - default: - return RemoteFortressReader::NO_SPECIAL; - break; - } - return RemoteFortressReader::NO_SPECIAL; + default: + return RemoteFortressReader::NO_SPECIAL; + break; + } + return RemoteFortressReader::NO_SPECIAL; } RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) { - switch (shape) - { - case df::enums::tiletype_shape::NONE: - return RemoteFortressReader::NO_SHAPE; - break; - case df::enums::tiletype_shape::EMPTY: - return RemoteFortressReader::EMPTY; - break; - case df::enums::tiletype_shape::FLOOR: - return RemoteFortressReader::FLOOR; - break; - case df::enums::tiletype_shape::BOULDER: - return RemoteFortressReader::BOULDER; - break; - case df::enums::tiletype_shape::PEBBLES: - return RemoteFortressReader::PEBBLES; - break; - case df::enums::tiletype_shape::WALL: - return RemoteFortressReader::WALL; - break; - case df::enums::tiletype_shape::FORTIFICATION: - return RemoteFortressReader::FORTIFICATION; - break; - case df::enums::tiletype_shape::STAIR_UP: - return RemoteFortressReader::STAIR_UP; - break; - case df::enums::tiletype_shape::STAIR_DOWN: - return RemoteFortressReader::STAIR_DOWN; - break; - case df::enums::tiletype_shape::STAIR_UPDOWN: - return RemoteFortressReader::STAIR_UPDOWN; - break; - case df::enums::tiletype_shape::RAMP: - return RemoteFortressReader::RAMP; - break; - case df::enums::tiletype_shape::RAMP_TOP: - return RemoteFortressReader::RAMP_TOP; - break; - case df::enums::tiletype_shape::BROOK_BED: - return RemoteFortressReader::BROOK_BED; - break; - case df::enums::tiletype_shape::BROOK_TOP: - return RemoteFortressReader::BROOK_TOP; - break; + switch (shape) + { + case df::enums::tiletype_shape::NONE: + return RemoteFortressReader::NO_SHAPE; + break; + case df::enums::tiletype_shape::EMPTY: + return RemoteFortressReader::EMPTY; + break; + case df::enums::tiletype_shape::FLOOR: + return RemoteFortressReader::FLOOR; + break; + case df::enums::tiletype_shape::BOULDER: + return RemoteFortressReader::BOULDER; + break; + case df::enums::tiletype_shape::PEBBLES: + return RemoteFortressReader::PEBBLES; + break; + case df::enums::tiletype_shape::WALL: + return RemoteFortressReader::WALL; + break; + case df::enums::tiletype_shape::FORTIFICATION: + return RemoteFortressReader::FORTIFICATION; + break; + case df::enums::tiletype_shape::STAIR_UP: + return RemoteFortressReader::STAIR_UP; + break; + case df::enums::tiletype_shape::STAIR_DOWN: + return RemoteFortressReader::STAIR_DOWN; + break; + case df::enums::tiletype_shape::STAIR_UPDOWN: + return RemoteFortressReader::STAIR_UPDOWN; + break; + case df::enums::tiletype_shape::RAMP: + return RemoteFortressReader::RAMP; + break; + case df::enums::tiletype_shape::RAMP_TOP: + return RemoteFortressReader::RAMP_TOP; + break; + case df::enums::tiletype_shape::BROOK_BED: + return RemoteFortressReader::BROOK_BED; + break; + case df::enums::tiletype_shape::BROOK_TOP: + return RemoteFortressReader::BROOK_TOP; + break; #if DF_VERSION_INT > 40001 - case df::enums::tiletype_shape::BRANCH: - return RemoteFortressReader::BRANCH; - break; + case df::enums::tiletype_shape::BRANCH: + return RemoteFortressReader::BRANCH; + break; #endif #if DF_VERSION_INT < 40001 - case df::enums::tiletype_shape::TREE: - return RemoteFortressReader::TREE_SHAPE; - break; + case df::enums::tiletype_shape::TREE: + return RemoteFortressReader::TREE_SHAPE; + break; #endif #if DF_VERSION_INT > 40001 - case df::enums::tiletype_shape::TRUNK_BRANCH: - return RemoteFortressReader::TRUNK_BRANCH; - break; - case df::enums::tiletype_shape::TWIG: - return RemoteFortressReader::TWIG; - break; + case df::enums::tiletype_shape::TRUNK_BRANCH: + return RemoteFortressReader::TRUNK_BRANCH; + break; + case df::enums::tiletype_shape::TWIG: + return RemoteFortressReader::TWIG; + break; #endif - case df::enums::tiletype_shape::SAPLING: - return RemoteFortressReader::SAPLING; - break; - case df::enums::tiletype_shape::SHRUB: - return RemoteFortressReader::SHRUB; - break; - case df::enums::tiletype_shape::ENDLESS_PIT: - return RemoteFortressReader::EMPTY; - break; - default: - return RemoteFortressReader::NO_SHAPE; - break; - } - return RemoteFortressReader::NO_SHAPE; + case df::enums::tiletype_shape::SAPLING: + return RemoteFortressReader::SAPLING; + break; + case df::enums::tiletype_shape::SHRUB: + return RemoteFortressReader::SHRUB; + break; + case df::enums::tiletype_shape::ENDLESS_PIT: + return RemoteFortressReader::EMPTY; + break; + default: + return RemoteFortressReader::NO_SHAPE; + break; + } + return RemoteFortressReader::NO_SHAPE; } RemoteFortressReader::TiletypeVariant TranslateVariant(df::tiletype_variant variant) { - switch (variant) - { - case df::enums::tiletype_variant::NONE: - return RemoteFortressReader::NO_VARIANT; - break; - case df::enums::tiletype_variant::VAR_1: - return RemoteFortressReader::VAR_1; - break; - case df::enums::tiletype_variant::VAR_2: - return RemoteFortressReader::VAR_2; - break; - case df::enums::tiletype_variant::VAR_3: - return RemoteFortressReader::VAR_3; - break; - case df::enums::tiletype_variant::VAR_4: - return RemoteFortressReader::VAR_4; - break; - default: - return RemoteFortressReader::NO_VARIANT; - break; - } - return RemoteFortressReader::NO_VARIANT; + switch (variant) + { + case df::enums::tiletype_variant::NONE: + return RemoteFortressReader::NO_VARIANT; + break; + case df::enums::tiletype_variant::VAR_1: + return RemoteFortressReader::VAR_1; + break; + case df::enums::tiletype_variant::VAR_2: + return RemoteFortressReader::VAR_2; + break; + case df::enums::tiletype_variant::VAR_3: + return RemoteFortressReader::VAR_3; + break; + case df::enums::tiletype_variant::VAR_4: + return RemoteFortressReader::VAR_4; + break; + default: + return RemoteFortressReader::NO_VARIANT; + break; + } + return RemoteFortressReader::NO_VARIANT; } static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) { - clock_t start = clock(); - for (int i = 0; i < world->map.map_blocks.size(); i++) - { - df::map_block * block = world->map.map_blocks[i]; - fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); - } - clock_t end = clock(); - double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; - stream.print("Checking all hashes took %f seconds.", elapsed_secs); - return CR_OK; + clock_t start = clock(); + for (int i = 0; i < world->map.map_blocks.size(); i++) + { + df::map_block * block = world->map.map_blocks[i]; + fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); + } + clock_t end = clock(); + double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; + stream.print("Checking all hashes took %f seconds.", elapsed_secs); + return CR_OK; } void CopyMat(RemoteFortressReader::MatPair * mat, int type, int index) { - if (type >= MaterialInfo::FIGURE_BASE && type < MaterialInfo::PLANT_BASE) - { - df::historical_figure * figure = df::historical_figure::find(index); - if (figure) - { - type -= MaterialInfo::GROUP_SIZE; - index = figure->race; - } - } - mat->set_mat_type(type); - mat->set_mat_index(index); + if (type >= MaterialInfo::FIGURE_BASE && type < MaterialInfo::PLANT_BASE) + { + df::historical_figure * figure = df::historical_figure::find(index); + if (figure) + { + type -= MaterialInfo::GROUP_SIZE; + index = figure->race; + } + } + mat->set_mat_type(type); + mat->set_mat_index(index); } @@ -618,2187 +637,2244 @@ map hashes; bool IsTiletypeChanged(DFCoord pos) { - uint16_t hash; - df::map_block * block = Maps::getBlock(pos); - if (block) - hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); - else - hash = 0; - if (hashes[pos] != hash) - { - hashes[pos] = hash; - return true; - } - return false; + uint16_t hash; + df::map_block * block = Maps::getBlock(pos); + if (block) + hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); + else + hash = 0; + if (hashes[pos] != hash) + { + hashes[pos] = hash; + return true; + } + return false; } map waterHashes; bool IsDesignationChanged(DFCoord pos) { - uint16_t hash; - df::map_block * block = Maps::getBlock(pos); - if (block) - hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); - else - hash = 0; - if (waterHashes[pos] != hash) - { - waterHashes[pos] = hash; - return true; - } - return false; + uint16_t hash; + df::map_block * block = Maps::getBlock(pos); + if (block) + hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); + else + hash = 0; + if (waterHashes[pos] != hash) + { + waterHashes[pos] = hash; + return true; + } + return false; } map buildingHashes; bool IsBuildingChanged(DFCoord pos) { - df::map_block * block = Maps::getBlock(pos); - bool changed = false; - for (int x = 0; x < 16; x++) - for (int y = 0; y < 16; y++) - { - DFCoord localPos = DFCoord(pos.x * 16 + x, pos.y * 16 + y, pos.z); - auto bld = block->occupancy[x][y].bits.building; - if (buildingHashes[pos] != bld) - { - buildingHashes[pos] = bld; - changed = true; - } - } - return changed; + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + DFCoord localPos = DFCoord(pos.x * 16 + x, pos.y * 16 + y, pos.z); + auto bld = block->occupancy[x][y].bits.building; + if (buildingHashes[pos] != bld) + { + buildingHashes[pos] = bld; + changed = true; + } + } + return changed; } map spatterHashes; bool IsspatterChanged(DFCoord pos) { - df::map_block * block = Maps::getBlock(pos); - bool changed = false; - std::vector materials; + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + std::vector materials; #if DF_VERSION_INT > 34011 - std::vector items; - if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) - return false; + std::vector items; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return false; #else - if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL)) - return false; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL)) + return false; #endif - uint16_t hash = 0; + uint16_t hash = 0; - for (int i = 0; i < materials.size(); i++) - { - auto mat = materials[i]; - hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); - } + for (int i = 0; i < materials.size(); i++) + { + auto mat = materials[i]; + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); + } #if DF_VERSION_INT > 34011 - for (int i = 0; i < items.size(); i++) - { - auto item = items[i]; - hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); - } + for (int i = 0; i < items.size(); i++) + { + auto item = items[i]; + hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); + } #endif - if (spatterHashes[pos] != hash) - { - spatterHashes[pos] = hash; - return true; - } - return false; + if (spatterHashes[pos] != hash) + { + spatterHashes[pos] = hash; + return true; + } + return false; } map itemHashes; bool isItemChanged(int i) { - uint16_t hash = 0; - auto item = df::item::find(i); - if (item) - { - hash = fletcher16((uint8_t*)item, sizeof(df::item)); - } - if (itemHashes[i] != hash) - { - itemHashes[i] = hash; - return true; - } - return false; + uint16_t hash = 0; + auto item = df::item::find(i); + if (item) + { + hash = fletcher16((uint8_t*)item, sizeof(df::item)); + } + if (itemHashes[i] != hash) + { + itemHashes[i] = hash; + return true; + } + return false; } bool areItemsChanged(vector * items) { - bool result = false; - for (int i = 0; i < items->size(); i++) - { - if (isItemChanged(items->at(i))) - result = true; - } - return result; + bool result = false; + for (int i = 0; i < items->size(); i++) + { + if (isItemChanged(items->at(i))) + result = true; + } + return result; } static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { - hashes.clear(); - waterHashes.clear(); - buildingHashes.clear(); - spatterHashes.clear(); - itemHashes.clear(); - return CR_OK; + hashes.clear(); + waterHashes.clear(); + buildingHashes.clear(); + spatterHashes.clear(); + itemHashes.clear(); + return CR_OK; } df::matter_state GetState(df::material * mat, uint16_t temp = 10015) { - df::matter_state state = matter_state::Solid; - if (temp >= mat->heat.melting_point) - state = df::matter_state::Liquid; - if (temp >= mat->heat.boiling_point) - state = matter_state::Gas; - return state; + df::matter_state state = matter_state::Solid; + if (temp >= mat->heat.melting_point) + state = df::matter_state::Liquid; + if (temp >= mat->heat.boiling_point) + state = matter_state::Gas; + return state; } static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - - - - df::world_raws *raws = &world->raws; - df::world_history *history = &world->history; - MaterialInfo mat; - for (int i = 0; i < raws->inorganics.size(); i++) - { - mat.decode(0, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(0); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); - } - } - for (int i = 0; i < 19; i++) - { - int k = -1; - if (i == 7) - k = 1;// for coal. - for (int j = -1; j <= k; j++) - { - mat.decode(i, j); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(i); - mat_def->mutable_mat_pair()->set_mat_index(j); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); - } - } - } - for (int i = 0; i < raws->creatures.all.size(); i++) - { - df::creature_raw * creature = raws->creatures.all[i]; - for (int j = 0; j < creature->material.size(); j++) - { - mat.decode(j + MaterialInfo::CREATURE_BASE, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + 19); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); - } - } - } - //for (int i = 0; i < history->figures.size(); i++) - //{ - // df::historical_figure * figure = history->figures[i]; - // if (figure->race < 0) - // continue; - // df::creature_raw * creature = raws->creatures.all[figure->race]; - // for (int j = 0; j < creature->material.size(); j++) - // { - // mat.decode(j + MaterialInfo::FIGURE_BASE, i); - // MaterialDefinition *mat_def = out->add_material_list(); - // mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); - // mat_def->mutable_mat_pair()->set_mat_index(i); - // stringstream id; - // id << "HF" << i << mat.getToken(); - // mat_def->set_id(id.str()); - // mat_def->set_name(mat.toString()); //find the name at cave temperature; - // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) - // { - // df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; - // mat_def->mutable_state_color()->set_red(color->red * 255); - // mat_def->mutable_state_color()->set_green(color->green * 255); - // mat_def->mutable_state_color()->set_blue(color->blue * 255); - // } - // } - //} - for (int i = 0; i < raws->plants.all.size(); i++) - { - df::plant_raw * plant = raws->plants.all[i]; - for (int j = 0; j < plant->material.size(); j++) - { - mat.decode(j + 419, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + 419); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); - } - } - } - return CR_OK; + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + + + + df::world_raws *raws = &world->raws; + df::world_history *history = &world->history; + MaterialInfo mat; + for (int i = 0; i < raws->inorganics.size(); i++) + { + mat.decode(0, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(0); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); + } + } + for (int i = 0; i < 19; i++) + { + int k = -1; + if (i == 7) + k = 1;// for coal. + for (int j = -1; j <= k; j++) + { + mat.decode(i, j); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(i); + mat_def->mutable_mat_pair()->set_mat_index(j); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); + } + } + } + for (int i = 0; i < raws->creatures.all.size(); i++) + { + df::creature_raw * creature = raws->creatures.all[i]; + for (int j = 0; j < creature->material.size(); j++) + { + mat.decode(j + MaterialInfo::CREATURE_BASE, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + 19); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); + } + } + } + //for (int i = 0; i < history->figures.size(); i++) + //{ + // df::historical_figure * figure = history->figures[i]; + // if (figure->race < 0) + // continue; + // df::creature_raw * creature = raws->creatures.all[figure->race]; + // for (int j = 0; j < creature->material.size(); j++) + // { + // mat.decode(j + MaterialInfo::FIGURE_BASE, i); + // MaterialDefinition *mat_def = out->add_material_list(); + // mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); + // mat_def->mutable_mat_pair()->set_mat_index(i); + // stringstream id; + // id << "HF" << i << mat.getToken(); + // mat_def->set_id(id.str()); + // mat_def->set_name(mat.toString()); //find the name at cave temperature; + // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + // { + // df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; + // mat_def->mutable_state_color()->set_red(color->red * 255); + // mat_def->mutable_state_color()->set_green(color->green * 255); + // mat_def->mutable_state_color()->set_blue(color->blue * 255); + // } + // } + //} + for (int i = 0; i < raws->plants.all.size(); i++) + { + df::plant_raw * plant = raws->plants.all[i]; + for (int j = 0; j < plant->material.size(); j++) + { + mat.decode(j + 419, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + 419); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); + } + } + } + return CR_OK; } static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - FOR_ENUM_ITEMS(item_type, it) - { - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(-1); - mat_def->set_id(ENUM_KEY_STR(item_type, it)); - if (it == item_type::BOX) - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(0); - mat_def->set_id("BOX_CHEST"); - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(1); - mat_def->set_id("BOX_BAG"); - } - int subtypes = Items::getSubtypeCount(it); - if (subtypes >= 0) - { - for (int i = 0; i < subtypes; i++) - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(i); - df::itemdef * item = Items::getSubtypeDef(it, i); - mat_def->set_id(item->id); - } - } - } - - - return CR_OK; + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + FOR_ENUM_ITEMS(item_type, it) + { + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(-1); + mat_def->set_id(ENUM_KEY_STR(item_type, it)); + if (it == item_type::BOX) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("BOX_CHEST"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("BOX_BAG"); + } + int subtypes = Items::getSubtypeCount(it); + if (subtypes >= 0) + { + for (int i = 0; i < subtypes; i++) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + df::itemdef * item = Items::getSubtypeDef(it, i); + mat_def->set_id(item->id); + } + } + } + + + return CR_OK; } static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } - df::world_raws *raws = &world->raws; - if (!raws) - return CR_OK;//'. + df::world_raws *raws = &world->raws; + if (!raws) + return CR_OK;//'. - for (int i = 0; i < raws->plants.all.size(); i++) - { - df::plant_raw * pp = raws->plants.all[i]; - if (!pp) - continue; - MaterialDefinition * basePlant = out->add_material_list(); - basePlant->set_id(pp->id + ":BASE"); - basePlant->set_name(pp->name); - basePlant->mutable_mat_pair()->set_mat_type(-1); - basePlant->mutable_mat_pair()->set_mat_index(i); + for (int i = 0; i < raws->plants.all.size(); i++) + { + df::plant_raw * pp = raws->plants.all[i]; + if (!pp) + continue; + MaterialDefinition * basePlant = out->add_material_list(); + basePlant->set_id(pp->id + ":BASE"); + basePlant->set_name(pp->name); + basePlant->mutable_mat_pair()->set_mat_type(-1); + basePlant->mutable_mat_pair()->set_mat_index(i); #if DF_VERSION_INT > 40001 - for (int g = 0; g < pp->growths.size(); g++) - { - df::plant_growth* growth = pp->growths[g]; - if (!growth) - continue; - for (int l = 0; l < GROWTH_LOCATIONS_SIZE; l++) - { - MaterialDefinition * out_growth = out->add_material_list(); - out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); - out_growth->set_name(growth->name); - out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); - out_growth->mutable_mat_pair()->set_mat_index(i); - } - } + for (int g = 0; g < pp->growths.size(); g++) + { + df::plant_growth* growth = pp->growths[g]; + if (!growth) + continue; + for (int l = 0; l < GROWTH_LOCATIONS_SIZE; l++) + { + MaterialDefinition * out_growth = out->add_material_list(); + out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); + out_growth->set_name(growth->name); + out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); + out_growth->mutable_mat_pair()->set_mat_index(i); + } + } #endif - } - return CR_OK; + } + return CR_OK; } void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - - MapExtras::Block * block = MC->BlockAtTile(DfBlock->map_pos); - - int trunk_percent[16][16]; - int tree_x[16][16]; - int tree_y[16][16]; - int tree_z[16][16]; - for (int xx = 0; xx < 16; xx++) - for (int yy = 0; yy < 16; yy++) - { - trunk_percent[xx][yy] = 255; - tree_x[xx][yy] = -3000; - tree_y[xx][yy] = -3000; - tree_z[xx][yy] = -3000; - } + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + + MapExtras::Block * block = MC->BlockAtTile(DfBlock->map_pos); + + int trunk_percent[16][16]; + int tree_x[16][16]; + int tree_y[16][16]; + int tree_z[16][16]; + for (int xx = 0; xx < 16; xx++) + for (int yy = 0; yy < 16; yy++) + { + trunk_percent[xx][yy] = 255; + tree_x[xx][yy] = -3000; + tree_y[xx][yy] = -3000; + tree_z[xx][yy] = -3000; + } #if DF_VERSION_INT > 34011 - df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; - for (int i = 0; i < column->plants.size(); i++) - { - df::plant* plant = column->plants[i]; - if (plant->tree_info == NULL) - continue; - df::plant_tree_info * tree_info = plant->tree_info; - if ( - plant->pos.z - tree_info->roots_depth > DfBlock->map_pos.z - || (plant->pos.z + tree_info->body_height) <= DfBlock->map_pos.z - || (plant->pos.x - tree_info->dim_x / 2) > (DfBlock->map_pos.x + 16) - || (plant->pos.x + tree_info->dim_x / 2) < (DfBlock->map_pos.x) - || (plant->pos.y - tree_info->dim_y / 2) > (DfBlock->map_pos.y + 16) - || (plant->pos.y + tree_info->dim_y / 2) < (DfBlock->map_pos.y) - ) - continue; - DFCoord localPos = plant->pos - DfBlock->map_pos; - for (int xx = 0; xx < tree_info->dim_x; xx++) - for (int yy = 0; yy < tree_info->dim_y; yy++) - { - int xxx = localPos.x - (tree_info->dim_x / 2) + xx; - int yyy = localPos.y - (tree_info->dim_y / 2) + yy; - if (xxx < 0 - || yyy < 0 - || xxx >= 16 - || yyy >= 16 - ) - continue; - df::plant_tree_tile tile; - if (-localPos.z < 0) - { - tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; - } - else - { - tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; - } - if (!tile.whole || tile.bits.blocked) - continue; - if (tree_info->body_height <= 1) - trunk_percent[xxx][yyy] = 0; - else - trunk_percent[xxx][yyy] = -localPos.z * 100 / (tree_info->body_height - 1); - tree_x[xxx][yyy] = xx - tree_info->dim_x / 2; - tree_y[xxx][yyy] = yy - tree_info->dim_y / 2; - tree_z[xxx][yyy] = localPos.z; - } - } + df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; + for (int i = 0; i < column->plants.size(); i++) + { + df::plant* plant = column->plants[i]; + if (plant->tree_info == NULL) + continue; + df::plant_tree_info * tree_info = plant->tree_info; + if ( + plant->pos.z - tree_info->roots_depth > DfBlock->map_pos.z + || (plant->pos.z + tree_info->body_height) <= DfBlock->map_pos.z + || (plant->pos.x - tree_info->dim_x / 2) > (DfBlock->map_pos.x + 16) + || (plant->pos.x + tree_info->dim_x / 2) < (DfBlock->map_pos.x) + || (plant->pos.y - tree_info->dim_y / 2) > (DfBlock->map_pos.y + 16) + || (plant->pos.y + tree_info->dim_y / 2) < (DfBlock->map_pos.y) + ) + continue; + DFCoord localPos = plant->pos - DfBlock->map_pos; + for (int xx = 0; xx < tree_info->dim_x; xx++) + for (int yy = 0; yy < tree_info->dim_y; yy++) + { + int xxx = localPos.x - (tree_info->dim_x / 2) + xx; + int yyy = localPos.y - (tree_info->dim_y / 2) + yy; + if (xxx < 0 + || yyy < 0 + || xxx >= 16 + || yyy >= 16 + ) + continue; + df::plant_tree_tile tile; + if (-localPos.z < 0) + { + tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; + } + else + { + tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; + } + if (!tile.whole || tile.bits.blocked) + continue; + if (tree_info->body_height <= 1) + trunk_percent[xxx][yyy] = 0; + else + trunk_percent[xxx][yyy] = -localPos.z * 100 / (tree_info->body_height - 1); + tree_x[xxx][yyy] = xx - tree_info->dim_x / 2; + tree_y[xxx][yyy] = yy - tree_info->dim_y / 2; + tree_z[xxx][yyy] = localPos.z; + } + } #endif - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - df::tiletype tile = DfBlock->tiletype[xx][yy]; - NetBlock->add_tiles(tile); - df::coord2d p = df::coord2d(xx, yy); - t_matpair baseMat = block->baseMaterialAt(p); - t_matpair staticMat = block->staticMaterialAt(p); - switch (tileMaterial(tile)) - { - case tiletype_material::FROZEN_LIQUID: - staticMat.mat_type = builtin_mats::WATER; - staticMat.mat_index = -1; - break; - default: - break; - } - CopyMat(NetBlock->add_materials(), staticMat.mat_type, staticMat.mat_index); - CopyMat(NetBlock->add_layer_materials(), 0, block->layerMaterialAt(p)); - CopyMat(NetBlock->add_vein_materials(), 0, block->veinMaterialAt(p)); - CopyMat(NetBlock->add_base_materials(), baseMat.mat_type, baseMat.mat_index); - RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); - CopyMat(constructionItem, -1, -1); - if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) - { - df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); - if (con) - { - CopyMat(constructionItem, con->item_type, con->item_subtype); - } - } - NetBlock->add_tree_percent(trunk_percent[xx][yy]); - NetBlock->add_tree_x(tree_x[xx][yy]); - NetBlock->add_tree_y(tree_y[xx][yy]); - NetBlock->add_tree_z(tree_z[xx][yy]); - } + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + df::tiletype tile = DfBlock->tiletype[xx][yy]; + NetBlock->add_tiles(tile); + df::coord2d p = df::coord2d(xx, yy); + t_matpair baseMat = block->baseMaterialAt(p); + t_matpair staticMat = block->staticMaterialAt(p); + switch (tileMaterial(tile)) + { + case tiletype_material::FROZEN_LIQUID: + staticMat.mat_type = builtin_mats::WATER; + staticMat.mat_index = -1; + break; + default: + break; + } + CopyMat(NetBlock->add_materials(), staticMat.mat_type, staticMat.mat_index); + CopyMat(NetBlock->add_layer_materials(), 0, block->layerMaterialAt(p)); + CopyMat(NetBlock->add_vein_materials(), 0, block->veinMaterialAt(p)); + CopyMat(NetBlock->add_base_materials(), baseMat.mat_type, baseMat.mat_index); + RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); + CopyMat(constructionItem, -1, -1); + if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) + { + df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); + if (con) + { + CopyMat(constructionItem, con->item_type, con->item_subtype); + } + } + NetBlock->add_tree_percent(trunk_percent[xx][yy]); + NetBlock->add_tree_x(tree_x[xx][yy]); + NetBlock->add_tree_y(tree_y[xx][yy]); + NetBlock->add_tree_z(tree_z[xx][yy]); + } } void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - df::tile_designation designation = DfBlock->designation[xx][yy]; - df::tile_occupancy occupancy = DfBlock->occupancy[xx][yy]; - int lava = 0; - int water = 0; - if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) - lava = designation.bits.flow_size; - else - water = designation.bits.flow_size; - NetBlock->add_magma(lava); - NetBlock->add_water(water); - NetBlock->add_aquifer(designation.bits.water_table); - NetBlock->add_light(designation.bits.light); - NetBlock->add_outside(designation.bits.outside); - NetBlock->add_subterranean(designation.bits.subterranean); - NetBlock->add_water_salt(designation.bits.water_salt); - NetBlock->add_water_stagnant(designation.bits.water_stagnant); - if (gamemode && (*gamemode == game_mode::ADVENTURE)) - { - auto fog_of_war = DfBlock->fog_of_war[xx][yy]; - NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - NetBlock->add_tile_dig_designation_marker(false); - NetBlock->add_tile_dig_designation_auto(false); - } - else - { - NetBlock->add_hidden(designation.bits.hidden); + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + df::tile_designation designation = DfBlock->designation[xx][yy]; + df::tile_occupancy occupancy = DfBlock->occupancy[xx][yy]; + int lava = 0; + int water = 0; + if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) + lava = designation.bits.flow_size; + else + water = designation.bits.flow_size; + NetBlock->add_magma(lava); + NetBlock->add_water(water); + NetBlock->add_aquifer(designation.bits.water_table); + NetBlock->add_light(designation.bits.light); + NetBlock->add_outside(designation.bits.outside); + NetBlock->add_subterranean(designation.bits.subterranean); + NetBlock->add_water_salt(designation.bits.water_salt); + NetBlock->add_water_stagnant(designation.bits.water_stagnant); + if (gamemode && (*gamemode == game_mode::ADVENTURE)) + { + auto fog_of_war = DfBlock->fog_of_war[xx][yy]; + NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + NetBlock->add_tile_dig_designation_marker(false); + NetBlock->add_tile_dig_designation_auto(false); + } + else + { + NetBlock->add_hidden(designation.bits.hidden); #if DF_VERSION_INT > 34011 - NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); - NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); + NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); + NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); #endif - switch (designation.bits.dig) - { - case df::enums::tile_dig_designation::No: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - case df::enums::tile_dig_designation::Default: - NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); - break; - case df::enums::tile_dig_designation::UpDownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::Channel: - NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); - break; - case df::enums::tile_dig_designation::Ramp: - NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); - break; - case df::enums::tile_dig_designation::DownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::UpStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); - break; - default: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - } - } - } + switch (designation.bits.dig) + { + case df::enums::tile_dig_designation::No: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + case df::enums::tile_dig_designation::Default: + NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); + break; + case df::enums::tile_dig_designation::UpDownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::Channel: + NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); + break; + case df::enums::tile_dig_designation::Ramp: + NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); + break; + case df::enums::tile_dig_designation::DownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::UpStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); + break; + default: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + } + } + } #if DF_VERSION_INT > 34011 - for (int i = 0; i < world->jobs.postings.size(); i++) - { - auto job = world->jobs.postings[i]->job; - if (job == nullptr) - continue; - if ( - job->pos.z > DfBlock->map_pos.z - || job->pos.z < DfBlock->map_pos.z - || job->pos.x >= (DfBlock->map_pos.x + 16) - || job->pos.x < (DfBlock->map_pos.x) - || job->pos.y >= (DfBlock->map_pos.y + 16) - || job->pos.y < (DfBlock->map_pos.y) - ) - continue; - - int index = (job->pos.x - DfBlock->map_pos.x) + (16 * (job->pos.y - DfBlock->map_pos.y)); - - switch (job->job_type) - { - case job_type::Dig: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); - break; - case job_type::CarveUpwardStaircase: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_STAIR_DIG); - break; - case job_type::CarveDownwardStaircase: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DOWN_STAIR_DIG); - break; - case job_type::CarveUpDownStaircase: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_DOWN_STAIR_DIG); - break; - case job_type::CarveRamp: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::RAMP_DIG); - break; - case job_type::DigChannel: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::CHANNEL_DIG); - break; - case job_type::FellTree: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); - break; - case job_type::GatherPlants: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); - break; - default: - break; - } - } + for (int i = 0; i < world->jobs.postings.size(); i++) + { + auto job = world->jobs.postings[i]->job; + if (job == nullptr) + continue; + if ( + job->pos.z > DfBlock->map_pos.z + || job->pos.z < DfBlock->map_pos.z + || job->pos.x >= (DfBlock->map_pos.x + 16) + || job->pos.x < (DfBlock->map_pos.x) + || job->pos.y >= (DfBlock->map_pos.y + 16) + || job->pos.y < (DfBlock->map_pos.y) + ) + continue; + + int index = (job->pos.x - DfBlock->map_pos.x) + (16 * (job->pos.y - DfBlock->map_pos.y)); + + switch (job->job_type) + { + case job_type::Dig: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::CarveUpwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_STAIR_DIG); + break; + case job_type::CarveDownwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DOWN_STAIR_DIG); + break; + case job_type::CarveUpDownStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case job_type::CarveRamp: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::RAMP_DIG); + break; + case job_type::DigChannel: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::CHANNEL_DIG); + break; + case job_type::FellTree: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::GatherPlants: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + default: + break; + } + } #endif } void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) { - for (int i = 0; i < df::global::world->buildings.all.size(); i++) - { - df::building * bld = df::global::world->buildings.all[i]; - if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) - { - auto out_bld = NetBlock->add_buildings(); - out_bld->set_index(bld->id); - continue; - } - - int z2 = bld->z; - - if (bld->getType() == building_type::Well) - { - df::building_wellst * well_building = virtual_cast(bld); - if (well_building) - { - z2 = well_building->bucket_z; - } - } - if (bld->z < min.z || z2 >= max.z) - { - auto out_bld = NetBlock->add_buildings(); - out_bld->set_index(bld->id); - continue; - } - auto out_bld = NetBlock->add_buildings(); - CopyBuilding(i, out_bld); - df::building_actual* actualBuilding = virtual_cast(bld); - if (actualBuilding) - { - for (int i = 0; i < actualBuilding->contained_items.size(); i++) - { - auto buildingItem = out_bld->add_items(); - buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); - CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); - } - } - } + for (int i = 0; i < df::global::world->buildings.all.size(); i++) + { + df::building * bld = df::global::world->buildings.all[i]; + if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); + continue; + } + + int z2 = bld->z; + + if (bld->getType() == building_type::Well) + { + df::building_wellst * well_building = virtual_cast(bld); + if (well_building) + { + z2 = well_building->bucket_z; + } + } + if (bld->z < min.z || z2 >= max.z) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); + continue; + } + auto out_bld = NetBlock->add_buildings(); + CopyBuilding(i, out_bld); + df::building_actual* actualBuilding = virtual_cast(bld); + if (actualBuilding) + { + for (int i = 0; i < actualBuilding->contained_items.size(); i++) + { + auto buildingItem = out_bld->add_items(); + buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); + CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); + } + } + } } void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - std::vector materials; + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + std::vector materials; #if DF_VERSION_INT > 34011 - std::vector items; - std::vector grasses; - if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, &grasses, NULL, NULL, &items)) - return; + std::vector items; + std::vector grasses; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, &grasses, NULL, NULL, &items)) + return; #else - if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) - return; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) + return; #endif - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - auto send_pile = NetBlock->add_spatterpile(); - for (int i = 0; i < materials.size(); i++) - { - auto mat = materials[i]; - if (mat->amount[xx][yy] == 0) - continue; - auto send_spat = send_pile->add_spatters(); - send_spat->set_state((MatterState)mat->mat_state); - CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); - send_spat->set_amount(mat->amount[xx][yy]); - } + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + auto send_pile = NetBlock->add_spatterpile(); + for (int i = 0; i < materials.size(); i++) + { + auto mat = materials[i]; + if (mat->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + send_spat->set_state((MatterState)mat->mat_state); + CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); + send_spat->set_amount(mat->amount[xx][yy]); + } #if DF_VERSION_INT > 34011 - for (int i = 0; i < items.size(); i++) - { - auto item = items[i]; - if (item->amount[xx][yy] == 0) - continue; - auto send_spat = send_pile->add_spatters(); - CopyMat(send_spat->mutable_material(), item->mattype, item->matindex); - send_spat->set_amount(item->amount[xx][yy]); - auto send_item = send_spat->mutable_item(); - send_item->set_mat_type(item->item_type); - send_item->set_mat_index(item->item_subtype); - } - int grassPercent = 0; - for (int i = 0; i < grasses.size(); i++) - { - auto grass = grasses[i]; - if (grass->amount[xx][yy] > grassPercent) - grassPercent = grass->amount[xx][yy]; - } - NetBlock->add_grass_percent(grassPercent); + for (int i = 0; i < items.size(); i++) + { + auto item = items[i]; + if (item->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + CopyMat(send_spat->mutable_material(), item->mattype, item->matindex); + send_spat->set_amount(item->amount[xx][yy]); + auto send_item = send_spat->mutable_item(); + send_item->set_mat_type(item->item_type); + send_item->set_mat_index(item->item_subtype); + } + int grassPercent = 0; + for (int i = 0; i < grasses.size(); i++) + { + auto grass = grasses[i]; + if (grass->amount[xx][yy] > grassPercent) + grassPercent = grass->amount[xx][yy]; + } + NetBlock->add_grass_percent(grassPercent); #endif - } + } } void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { - NetItem->set_id(DfItem->id); - NetItem->set_flags1(DfItem->flags.whole); - NetItem->set_flags2(DfItem->flags2.whole); - auto pos = NetItem->mutable_pos(); - pos->set_x(DfItem->pos.x); - pos->set_y(DfItem->pos.y); - pos->set_z(DfItem->pos.z); - auto mat = NetItem->mutable_material(); - mat->set_mat_index(DfItem->getMaterialIndex()); - mat->set_mat_type(DfItem->getMaterial()); - auto type = NetItem->mutable_type(); - type->set_mat_type(DfItem->getType()); - type->set_mat_index(DfItem->getSubtype()); - if (DfItem->getType() == item_type::BOX) - { - type->set_mat_index(DfItem->isBag()); - } - auto constructed_item = virtual_cast(DfItem); - if(constructed_item) - { - for (int i = 0; i < constructed_item->improvements.size(); i++) - { - auto improvement = constructed_item->improvements[i]; - if (!improvement || improvement->getType() != improvement_type::THREAD) - continue; - - auto improvement_thread = virtual_cast(improvement); - if (!improvement_thread || improvement_thread->dye.mat_type < 0) - continue; - - DFHack::MaterialInfo info; - if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) - continue; - - ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); - } - } - else if (DfItem->getType() == item_type::THREAD) - { - auto thread = virtual_cast(DfItem); - if (thread && thread->dye_mat_type >= 0) - { - DFHack::MaterialInfo info; - if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) - ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); - } - } + NetItem->set_id(DfItem->id); + NetItem->set_flags1(DfItem->flags.whole); + NetItem->set_flags2(DfItem->flags2.whole); + auto pos = NetItem->mutable_pos(); + pos->set_x(DfItem->pos.x); + pos->set_y(DfItem->pos.y); + pos->set_z(DfItem->pos.z); + auto mat = NetItem->mutable_material(); + mat->set_mat_index(DfItem->getMaterialIndex()); + mat->set_mat_type(DfItem->getMaterial()); + auto type = NetItem->mutable_type(); + type->set_mat_type(DfItem->getType()); + type->set_mat_index(DfItem->getSubtype()); + if (DfItem->getType() == item_type::BOX) + { + type->set_mat_index(DfItem->isBag()); + } + auto constructed_item = virtual_cast(DfItem); + if (constructed_item) + { + for (int i = 0; i < constructed_item->improvements.size(); i++) + { + auto improvement = constructed_item->improvements[i]; + if (!improvement || improvement->getType() != improvement_type::THREAD) + continue; + + auto improvement_thread = virtual_cast(improvement); + if (!improvement_thread || improvement_thread->dye.mat_type < 0) + continue; + + DFHack::MaterialInfo info; + if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) + continue; + + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + } + else if (DfItem->getType() == item_type::THREAD) + { + auto thread = virtual_cast(DfItem); + if (thread && thread->dye_mat_type >= 0) + { + DFHack::MaterialInfo info; + if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + } } void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - for (int i = 0; i < DfBlock->items.size(); i++) - { - int id = DfBlock->items[i]; - - - auto item = df::item::find(id); - if(item) - CopyItem(NetBlock->add_items(), item); - } + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + for (int i = 0; i < DfBlock->items.size(); i++) + { + int id = DfBlock->items[i]; + + + auto item = df::item::find(id); + if (item) + CopyItem(NetBlock->add_items(), item); + } } static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) { - int x, y, z; - DFHack::Maps::getPosition(x, y, z); - out->set_map_x(x); - out->set_map_y(y); - MapExtras::MapCache MC; - int center_x = (in->min_x() + in->max_x()) / 2; - int center_y = (in->min_y() + in->max_y()) / 2; - - int NUMBER_OF_POINTS = ((in->max_x() - center_x + 1) * 2) * ((in->max_y() - center_y + 1) * 2); - int blocks_needed; - if (in->has_blocks_needed()) - blocks_needed = in->blocks_needed(); - else - blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z()); - int blocks_sent = 0; - int min_x = in->min_x(); - int min_y = in->min_y(); - int max_x = in->max_x(); - int max_y = in->max_y(); - int min_z = in->min_z(); - int max_z = in->max_z(); - bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest. - //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); - for (int zz = max_z - 1; zz >= min_z; zz--) - { - // (di, dj) is a vector - direction in which we move right now - int di = 1; - int dj = 0; - // length of current segment - int segment_length = 1; - // current position (i, j) and how much of current segment we passed - int i = center_x; - int j = center_y; - int segment_passed = 0; - for (int k = 0; k < NUMBER_OF_POINTS; ++k) - { - if (blocks_sent >= blocks_needed) - break; - if (!(i < min_x || i >= max_x || j < min_y || j >= max_y)) - { - DFCoord pos = DFCoord(i, j, zz); - df::map_block * block = DFHack::Maps::getBlock(pos); - if (block != NULL) - { - int nonAir = 0; - for (int xxx = 0; xxx < 16; xxx++) - for (int yyy = 0; yyy < 16; yyy++) - { - if ((DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::None && - DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) - || block->designation[xxx][yyy].bits.flow_size > 0 - || block->occupancy[xxx][yyy].bits.building > 0) - nonAir++; - } - if (nonAir > 0 || !sentBuildings) - { - bool tileChanged = IsTiletypeChanged(pos); - bool desChanged = IsDesignationChanged(pos); - bool spatterChanged = IsspatterChanged(pos); - bool buildingChanged = !sentBuildings; - bool itemsChanged = areItemsChanged(&block->items); - //bool bldChanged = IsBuildingChanged(pos); - RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged) - net_block = out->add_map_blocks(); - if (tileChanged) - { - CopyBlock(block, net_block, &MC, pos); - blocks_sent++; - } - if (desChanged) - CopyDesignation(block, net_block, &MC, pos); - if (buildingChanged) - { - CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); - sentBuildings = true; - } - if (spatterChanged) - Copyspatters(block, net_block, &MC, pos); - if (itemsChanged) - CopyItems(block, net_block, &MC, pos); - } - } - } - - // make a step, add 'direction' vector (di, dj) to current position (i, j) - i += di; - j += dj; - ++segment_passed; - //System.out.println(i + " " + j); - - if (segment_passed == segment_length) - { - // done with current segment - segment_passed = 0; - - // 'rotate' directions - int buffer = di; - di = -dj; - dj = buffer; - - // increase segment length if necessary - if (dj == 0) { - ++segment_length; - } - } - } - } - MC.trash(); - return CR_OK; + int x, y, z; + DFHack::Maps::getPosition(x, y, z); + out->set_map_x(x); + out->set_map_y(y); + MapExtras::MapCache MC; + int center_x = (in->min_x() + in->max_x()) / 2; + int center_y = (in->min_y() + in->max_y()) / 2; + + int NUMBER_OF_POINTS = ((in->max_x() - center_x + 1) * 2) * ((in->max_y() - center_y + 1) * 2); + int blocks_needed; + if (in->has_blocks_needed()) + blocks_needed = in->blocks_needed(); + else + blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z()); + int blocks_sent = 0; + int min_x = in->min_x(); + int min_y = in->min_y(); + int max_x = in->max_x(); + int max_y = in->max_y(); + int min_z = in->min_z(); + int max_z = in->max_z(); + bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest. + //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); + for (int zz = max_z - 1; zz >= min_z; zz--) + { + // (di, dj) is a vector - direction in which we move right now + int di = 1; + int dj = 0; + // length of current segment + int segment_length = 1; + // current position (i, j) and how much of current segment we passed + int i = center_x; + int j = center_y; + int segment_passed = 0; + for (int k = 0; k < NUMBER_OF_POINTS; ++k) + { + if (blocks_sent >= blocks_needed) + break; + if (!(i < min_x || i >= max_x || j < min_y || j >= max_y)) + { + DFCoord pos = DFCoord(i, j, zz); + df::map_block * block = DFHack::Maps::getBlock(pos); + if (block != NULL) + { + int nonAir = 0; + for (int xxx = 0; xxx < 16; xxx++) + for (int yyy = 0; yyy < 16; yyy++) + { + if ((DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::None && + DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) + || block->designation[xxx][yyy].bits.flow_size > 0 + || block->occupancy[xxx][yyy].bits.building > 0) + nonAir++; + } + if (nonAir > 0 || !sentBuildings) + { + bool tileChanged = IsTiletypeChanged(pos); + bool desChanged = IsDesignationChanged(pos); + bool spatterChanged = IsspatterChanged(pos); + bool buildingChanged = !sentBuildings; + bool itemsChanged = areItemsChanged(&block->items); + //bool bldChanged = IsBuildingChanged(pos); + RemoteFortressReader::MapBlock *net_block; + if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged) + net_block = out->add_map_blocks(); + if (tileChanged) + { + CopyBlock(block, net_block, &MC, pos); + blocks_sent++; + } + if (desChanged) + CopyDesignation(block, net_block, &MC, pos); + if (buildingChanged) + { + CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); + sentBuildings = true; + } + if (spatterChanged) + Copyspatters(block, net_block, &MC, pos); + if (itemsChanged) + CopyItems(block, net_block, &MC, pos); + } + } + } + + // make a step, add 'direction' vector (di, dj) to current position (i, j) + i += di; + j += dj; + ++segment_passed; + //System.out.println(i + " " + j); + + if (segment_passed == segment_length) + { + // done with current segment + segment_passed = 0; + + // 'rotate' directions + int buffer = di; + di = -dj; + dj = buffer; + + // increase segment length if necessary + if (dj == 0) { + ++segment_length; + } + } + } + } + MC.trash(); + return CR_OK; } static command_result GetTiletypeList(color_ostream &stream, const EmptyMessage *in, TiletypeList *out) { - int count = 0; - FOR_ENUM_ITEMS(tiletype, tt) - { - Tiletype * type = out->add_tiletype_list(); - type->set_id(tt); - type->set_name(ENUM_KEY_STR(tiletype, tt)); - const char * name = tileName(tt); - if (name != NULL && name[0] != 0) - type->set_caption(name); - type->set_shape(TranslateShape(tileShape(tt))); - type->set_special(TranslateSpecial(tileSpecial(tt))); - type->set_material(TranslateMaterial(tileMaterial(tt))); - type->set_variant(TranslateVariant(tileVariant(tt))); - type->set_direction(tileDirection(tt).getStr()); - count++; - } - return CR_OK; + int count = 0; + FOR_ENUM_ITEMS(tiletype, tt) + { + Tiletype * type = out->add_tiletype_list(); + type->set_id(tt); + type->set_name(ENUM_KEY_STR(tiletype, tt)); + const char * name = tileName(tt); + if (name != NULL && name[0] != 0) + type->set_caption(name); + type->set_shape(TranslateShape(tileShape(tt))); + type->set_special(TranslateSpecial(tileSpecial(tt))); + type->set_material(TranslateMaterial(tileMaterial(tt))); + type->set_variant(TranslateVariant(tileVariant(tt))); + type->set_direction(tileDirection(tt).getStr()); + count++; + } + return CR_OK; } static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out) { - int min_x = in->min_x() / 3; - int min_y = in->min_y() / 3; - int min_z = in->min_z(); - int max_x = in->max_x() / 3; - int max_y = in->max_y() / 3; - int max_z = in->max_z(); + int min_x = in->min_x() / 3; + int min_y = in->min_y() / 3; + int min_z = in->min_z(); + int max_x = in->max_x() / 3; + int max_y = in->max_y() / 3; + int max_z = in->max_z(); #if DF_VERSION_INT < 40001 - //plants are gotten differently here + //plants are gotten differently here #else - for (int xx = min_x; xx < max_x; xx++) - for (int yy = min_y; yy < max_y; yy++) - { - if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) - continue; - df::map_block_column * column = world->map.column_index[xx][yy]; - for (int i = 0; i < column->plants.size(); i++) - { - df::plant * plant = column->plants[i]; - if (!plant->tree_info) - { - if (plant->pos.z < min_z || plant->pos.z >= max_z) - continue; - if (plant->pos.x < in->min_x() * 16 || plant->pos.x >= in->max_x() * 16) - continue; - if (plant->pos.y < in->min_y() * 16 || plant->pos.y >= in->max_y() * 16) - continue; - } - else - { - if (plant->pos.z - plant->tree_info->roots_depth < min_z || plant->pos.z + plant->tree_info->body_height > max_z) - continue; - if (plant->pos.x - plant->tree_info->dim_x / 2 < in->min_x() * 16 || plant->pos.x + plant->tree_info->dim_x / 2 >= in->max_x() * 16) - continue; - if (plant->pos.y - plant->tree_info->dim_y / 2 < in->min_y() * 16 || plant->pos.y + plant->tree_info->dim_y / 2 >= in->max_y() * 16) - continue; - } - RemoteFortressReader::PlantDef * out_plant = out->add_plant_list(); - out_plant->set_index(plant->material); - out_plant->set_pos_x(plant->pos.x); - out_plant->set_pos_y(plant->pos.y); - out_plant->set_pos_z(plant->pos.z); - } - } + for (int xx = min_x; xx < max_x; xx++) + for (int yy = min_y; yy < max_y; yy++) + { + if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) + continue; + df::map_block_column * column = world->map.column_index[xx][yy]; + for (int i = 0; i < column->plants.size(); i++) + { + df::plant * plant = column->plants[i]; + if (!plant->tree_info) + { + if (plant->pos.z < min_z || plant->pos.z >= max_z) + continue; + if (plant->pos.x < in->min_x() * 16 || plant->pos.x >= in->max_x() * 16) + continue; + if (plant->pos.y < in->min_y() * 16 || plant->pos.y >= in->max_y() * 16) + continue; + } + else + { + if (plant->pos.z - plant->tree_info->roots_depth < min_z || plant->pos.z + plant->tree_info->body_height > max_z) + continue; + if (plant->pos.x - plant->tree_info->dim_x / 2 < in->min_x() * 16 || plant->pos.x + plant->tree_info->dim_x / 2 >= in->max_x() * 16) + continue; + if (plant->pos.y - plant->tree_info->dim_y / 2 < in->min_y() * 16 || plant->pos.y + plant->tree_info->dim_y / 2 >= in->max_y() * 16) + continue; + } + RemoteFortressReader::PlantDef * out_plant = out->add_plant_list(); + out_plant->set_index(plant->material); + out_plant->set_pos_x(plant->pos.x); + out_plant->set_pos_y(plant->pos.y); + out_plant->set_pos_z(plant->pos.z); + } + } #endif - return CR_OK; + return CR_OK; } static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out) { - auto world = df::global::world; - for (int i = 0; i < world->units.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - auto send_unit = out->add_creature_list(); - send_unit->set_id(unit->id); - send_unit->set_pos_x(unit->pos.x); - send_unit->set_pos_y(unit->pos.y); - send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); - send_unit->set_flags1(unit->flags1.whole); - send_unit->set_flags2(unit->flags2.whole); - send_unit->set_flags3(unit->flags3.whole); - send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); - auto size_info = send_unit->mutable_size_info(); - size_info->set_size_cur(unit->body.size_info.size_cur); - size_info->set_size_base(unit->body.size_info.size_base); - size_info->set_area_cur(unit->body.size_info.area_cur); - size_info->set_area_base(unit->body.size_info.area_base); - size_info->set_length_cur(unit->body.size_info.length_cur); - size_info->set_length_base(unit->body.size_info.length_base); - if (unit->name.has_name) - { - send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); - } - - auto appearance = send_unit->mutable_appearance(); - for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) - appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); - for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) - appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); - for (int j = 0; j < unit->appearance.colors.size(); j++) - appearance->add_colors(unit->appearance.colors[j]); - appearance->set_size_modifier(unit->appearance.size_modifier); - - send_unit->set_profession_id(unit->profession); - - std::vector pvec; - - if (Units::getNoblePositions(&pvec, unit)) - { - for (int j = 0; j < pvec.size(); j++) - { - auto noble_positon = pvec[j]; - send_unit->add_noble_positions(noble_positon.position->code); - } - } - - send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); - - auto creatureRaw = world->raws.creatures.all[unit->race]; - auto casteRaw = creatureRaw->caste[unit->caste]; - - for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) - { - auto type = unit->appearance.tissue_style_type[j]; - if (type < 0) - continue; - int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); - auto styleRaw = casteRaw->tissue_styles[style_raw_index]; - if (styleRaw->token == "HAIR") - { - auto send_style = appearance->mutable_hair(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "BEARD") - { - auto send_style = appearance->mutable_beard(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "MOUSTACHE") - { - auto send_style = appearance->mutable_moustache(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "SIDEBURNS") - { - auto send_style = appearance->mutable_sideburns(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - } - - for (int j = 0; j < unit->inventory.size(); j++) - { - auto inventory_item = unit->inventory[j]; - auto sent_item = send_unit->add_inventory(); - sent_item->set_mode((InventoryMode)inventory_item->mode); - CopyItem(sent_item->mutable_item(), inventory_item->item); - } - } - return CR_OK; + return GetUnitListInside(stream, NULL, out); +} + +static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) +{ + auto world = df::global::world; + for (int i = 0; i < world->units.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + auto send_unit = out->add_creature_list(); + send_unit->set_id(unit->id); + send_unit->set_pos_x(unit->pos.x); + send_unit->set_pos_y(unit->pos.y); + send_unit->set_pos_z(unit->pos.z); + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + if (in != NULL) + { + if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) + continue; + if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) + continue; + if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) + continue; + } + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); + auto size_info = send_unit->mutable_size_info(); + size_info->set_size_cur(unit->body.size_info.size_cur); + size_info->set_size_base(unit->body.size_info.size_base); + size_info->set_area_cur(unit->body.size_info.area_cur); + size_info->set_area_base(unit->body.size_info.area_base); + size_info->set_length_cur(unit->body.size_info.length_cur); + size_info->set_length_base(unit->body.size_info.length_base); + if (unit->name.has_name) + { + send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); + } + + auto appearance = send_unit->mutable_appearance(); + for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) + appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); + for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) + appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); + for (int j = 0; j < unit->appearance.colors.size(); j++) + appearance->add_colors(unit->appearance.colors[j]); + appearance->set_size_modifier(unit->appearance.size_modifier); + + send_unit->set_profession_id(unit->profession); + + std::vector pvec; + + if (Units::getNoblePositions(&pvec, unit)) + { + for (int j = 0; j < pvec.size(); j++) + { + auto noble_positon = pvec[j]; + send_unit->add_noble_positions(noble_positon.position->code); + } + } + + send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); + + auto creatureRaw = world->raws.creatures.all[unit->race]; + auto casteRaw = creatureRaw->caste[unit->caste]; + + for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) + { + auto type = unit->appearance.tissue_style_type[j]; + if (type < 0) + continue; + int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); + auto styleRaw = casteRaw->tissue_styles[style_raw_index]; + if (styleRaw->token == "HAIR") + { + auto send_style = appearance->mutable_hair(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "BEARD") + { + auto send_style = appearance->mutable_beard(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "MOUSTACHE") + { + auto send_style = appearance->mutable_moustache(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "SIDEBURNS") + { + auto send_style = appearance->mutable_sideburns(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + } + + for (int j = 0; j < unit->inventory.size(); j++) + { + auto inventory_item = unit->inventory[j]; + auto sent_item = send_unit->add_inventory(); + sent_item->set_mode((InventoryMode)inventory_item->mode); + CopyItem(sent_item->mutable_item(), inventory_item->item); + } + + if (unit->flags1.bits.projectile) + { + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); + if (item == NULL) + continue; + if (item->unit != unit) + continue; + send_unit->set_subpos_x(item->pos_x / 100000.0); + send_unit->set_subpos_y(item->pos_y / 100000.0); + send_unit->set_subpos_z(item->pos_z / 140000.0); + } + } + } + return CR_OK; } static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out) { - int x, y, z, w, h, cx, cy, cz; - Gui::getWindowSize(w, h); - Gui::getViewCoords(x, y, z); - Gui::getCursorCoords(cx, cy, cz); + int x, y, z, w, h, cx, cy, cz; + Gui::getWindowSize(w, h); + Gui::getViewCoords(x, y, z); + Gui::getCursorCoords(cx, cy, cz); #if DF_VERSION_INT > 34011 - auto embark = Gui::getViewscreenByType(0); - if (embark) - { - df::embark_location location = embark->location; - df::world_data * data = df::global::world->world_data; - if (data && data->region_map) - { - z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; - } - } + auto embark = Gui::getViewscreenByType(0); + if (embark) + { + df::embark_location location = embark->location; + df::world_data * data = df::global::world->world_data; + if (data && data->region_map) + { + z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; + } + } #endif - out->set_view_pos_x(x); - out->set_view_pos_y(y); - out->set_view_pos_z(z); - out->set_view_size_x(w); - out->set_view_size_y(h); - out->set_cursor_pos_x(cx); - out->set_cursor_pos_y(cy); - out->set_cursor_pos_z(cz); - out->set_follow_unit_id(ui->follow_unit); - out->set_follow_item_id(ui->follow_item); - return CR_OK; + out->set_view_pos_x(x); + out->set_view_pos_y(y); + out->set_view_pos_z(z); + out->set_view_size_x(w); + out->set_view_size_y(h); + out->set_cursor_pos_x(cx); + out->set_cursor_pos_y(cy); + out->set_cursor_pos_z(cz); + + if (gamemode && *gamemode == GameMode::ADVENTURE) + out->set_follow_unit_id(world->units.active[0]->id); + else + out->set_follow_unit_id(ui->follow_unit); + out->set_follow_item_id(ui->follow_item); + return CR_OK; } static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) { - if (!Maps::IsValid()) - return CR_FAILURE; - uint32_t size_x, size_y, size_z; - int32_t pos_x, pos_y, pos_z; - Maps::getSize(size_x, size_y, size_z); - Maps::getPosition(pos_x, pos_y, pos_z); - out->set_block_size_x(size_x); - out->set_block_size_y(size_y); - out->set_block_size_z(size_z); - out->set_block_pos_x(pos_x); - out->set_block_pos_y(pos_y); - out->set_block_pos_z(pos_z); - out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); - out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); - out->set_save_name(df::global::world->cur_savegame.save_dir); - return CR_OK; + if (!Maps::IsValid()) + return CR_FAILURE; + uint32_t size_x, size_y, size_z; + int32_t pos_x, pos_y, pos_z; + Maps::getSize(size_x, size_y, size_z); + Maps::getPosition(pos_x, pos_y, pos_z); + out->set_block_size_x(size_x); + out->set_block_size_y(size_y); + out->set_block_size_z(size_z); + out->set_block_pos_x(pos_x); + out->set_block_pos_y(pos_y); + out->set_block_pos_z(pos_z); + out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); + out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); + out->set_save_name(df::global::world->cur_savegame.save_dir); + return CR_OK; } DFCoord GetMapCenter() { - DFCoord output; + DFCoord output; #if DF_VERSION_INT > 34011 - auto embark = Gui::getViewscreenByType(0); - if (embark) - { - df::embark_location location = embark->location; - output.x = (location.region_pos.x * 16) + 8; - output.y = (location.region_pos.y * 16) + 8; - output.z = 100; - df::world_data * data = df::global::world->world_data; - if (data && data->region_map) - { - output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; - } - } - else + auto embark = Gui::getViewscreenByType(0); + if (embark) + { + df::embark_location location = embark->location; + output.x = (location.region_pos.x * 16) + 8; + output.y = (location.region_pos.y * 16) + 8; + output.z = 100; + df::world_data * data = df::global::world->world_data; + if (data && data->region_map) + { + output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; + } + } + else #endif - if (Maps::IsValid()) - { - int x, y, z; - Maps::getPosition(x,y,z); - output = DFCoord(x, y, z); - } + if (Maps::IsValid()) + { + int x, y, z; + Maps::getPosition(x, y, z); + output = DFCoord(x, y, z); + } #if DF_VERSION_INT > 34011 - else - for (int i = 0; i < df::global::world->armies.all.size(); i++) - { - df::army * thisArmy = df::global::world->armies.all[i]; - if (thisArmy->flags.is_set(df::enums::army_flags::player)) - { - output.x = (thisArmy->pos.x / 3) - 1; - output.y = (thisArmy->pos.y / 3) - 1; - output.z = thisArmy->pos.z; - } - } + else + for (int i = 0; i < df::global::world->armies.all.size(); i++) + { + df::army * thisArmy = df::global::world->armies.all[i]; + if (thisArmy->flags.is_set(df::enums::army_flags::player)) + { + output.x = (thisArmy->pos.x / 3) - 1; + output.y = (thisArmy->pos.y / 3) - 1; + output.z = thisArmy->pos.z; + } + } #endif - return output; + return output; } static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - if (!data->region_map) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); - auto poles = data->flip_latitude; + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + auto poles = data->flip_latitude; #if DF_VERSION_INT > 34011 - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif - for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx++) - { - df::region_map_entry * map_entry = &data->region_map[xx][yy]; - df::world_region * region = data->regions[map_entry->region_id]; - out->add_elevation(map_entry->elevation); - out->add_rainfall(map_entry->rainfall); - out->add_vegetation(map_entry->vegetation); - out->add_temperature(map_entry->temperature); - out->add_evilness(map_entry->evilness); - out->add_drainage(map_entry->drainage); - out->add_volcanism(map_entry->volcanism); - out->add_savagery(map_entry->savagery); - out->add_salinity(map_entry->salinity); - auto clouds = out->add_clouds(); + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + out->add_elevation(map_entry->elevation); + out->add_rainfall(map_entry->rainfall); + out->add_vegetation(map_entry->vegetation); + out->add_temperature(map_entry->temperature); + out->add_evilness(map_entry->evilness); + out->add_drainage(map_entry->drainage); + out->add_volcanism(map_entry->volcanism); + out->add_savagery(map_entry->savagery); + out->add_salinity(map_entry->salinity); + auto clouds = out->add_clouds(); #if DF_VERSION_INT > 34011 - clouds->set_cirrus(map_entry->clouds.bits.cirrus); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); #else - clouds->set_cirrus(map_entry->clouds.bits.striped); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); #endif - if (region->type == world_region_type::Lake) - { - out->add_water_elevation(region->lake_surface); - } - else - out->add_water_elevation(99); - } - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - - - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + if (region->type == world_region_type::Lake) + { + out->add_water_elevation(region->lake_surface); + } + else + out->add_water_elevation(99); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + + + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) { - df::world_region * region = df::world_region::find(e1->region_id); - df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); - out->set_rainfall(e1->rainfall); - out->set_vegetation(e1->vegetation); - out->set_temperature(e1->temperature); - out->set_evilness(e1->evilness); - out->set_drainage(e1->drainage); - out->set_volcanism(e1->volcanism); - out->set_savagery(e1->savagery); - out->set_salinity(e1->salinity); - if (region->type == world_region_type::Lake) - out->set_water_elevation(region->lake_surface); - else - out->set_water_elevation(99); - - int topLayer = 0; - for (int i = 0; i < geoBiome->layers.size(); i++) - { - auto layer = geoBiome->layers[i]; - if (layer->top_height == 0) - { - topLayer = layer->mat_index; - } - if (layer->type != geo_layer_type::SOIL - && layer->type != geo_layer_type::SOIL_OCEAN - && layer->type != geo_layer_type::SOIL_SAND) - { - auto mat = out->add_stone_materials(); - mat->set_mat_index(layer->mat_index); - mat->set_mat_type(0); - } - } - auto surfaceMat = out->mutable_surface_material(); - surfaceMat->set_mat_index(topLayer); - surfaceMat->set_mat_type(0); - - for (int i = 0; i < region->population.size(); i++) - { - auto pop = region->population[i]; - if (pop->type == world_population_type::Grass) - { - auto plantMat = out->add_plant_materials(); - - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); - } - else if (pop->type == world_population_type::Tree) - { - auto plantMat = out->add_tree_materials(); - - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); - } - } + df::world_region * region = df::world_region::find(e1->region_id); + df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); + out->set_rainfall(e1->rainfall); + out->set_vegetation(e1->vegetation); + out->set_temperature(e1->temperature); + out->set_evilness(e1->evilness); + out->set_drainage(e1->drainage); + out->set_volcanism(e1->volcanism); + out->set_savagery(e1->savagery); + out->set_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->set_water_elevation(region->lake_surface); + else + out->set_water_elevation(99); + + int topLayer = 0; + for (int i = 0; i < geoBiome->layers.size(); i++) + { + auto layer = geoBiome->layers[i]; + if (layer->top_height == 0) + { + topLayer = layer->mat_index; + } + if (layer->type != geo_layer_type::SOIL + && layer->type != geo_layer_type::SOIL_OCEAN + && layer->type != geo_layer_type::SOIL_SAND) + { + auto mat = out->add_stone_materials(); + mat->set_mat_index(layer->mat_index); + mat->set_mat_type(0); + } + } + auto surfaceMat = out->mutable_surface_material(); + surfaceMat->set_mat_index(topLayer); + surfaceMat->set_mat_type(0); + + for (int i = 0; i < region->population.size(); i++) + { + auto pop = region->population[i]; + if (pop->type == world_population_type::Grass) + { + auto plantMat = out->add_plant_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + else if (pop->type == world_population_type::Tree) + { + auto plantMat = out->add_tree_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + } #if DF_VERSION_INT >= 43005 - out->set_snow(e1->snowfall); + out->set_snow(e1->snowfall); #endif } static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - if (!data->region_map) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); #if DF_VERSION_INT > 34011 - auto poles = data->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + auto poles = data->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif - for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx++) - { - df::region_map_entry * map_entry = &data->region_map[xx][yy]; - df::world_region * region = data->regions[map_entry->region_id]; - - auto regionTile = out->add_region_tiles(); - regionTile->set_elevation(map_entry->elevation); - SetRegionTile(regionTile, map_entry); - auto clouds = out->add_clouds(); + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + + auto regionTile = out->add_region_tiles(); + regionTile->set_elevation(map_entry->elevation); + SetRegionTile(regionTile, map_entry); + auto clouds = out->add_clouds(); #if DF_VERSION_INT > 34011 - clouds->set_cirrus(map_entry->clouds.bits.cirrus); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); #else - clouds->set_cirrus(map_entry->clouds.bits.striped); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); #endif - } - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_data * worldData) { - df::world_region * region = worldData->regions[e1->region_id]; - out->add_rainfall(e1->rainfall); - out->add_vegetation(e1->vegetation); - out->add_temperature(e1->temperature); - out->add_evilness(e1->evilness); - out->add_drainage(e1->drainage); - out->add_volcanism(e1->volcanism); - out->add_savagery(e1->savagery); - out->add_salinity(e1->salinity); - if (region->type == world_region_type::Lake) - out->add_water_elevation(region->lake_surface); - else - out->add_water_elevation(99); + df::world_region * region = worldData->regions[e1->region_id]; + out->add_rainfall(e1->rainfall); + out->add_vegetation(e1->vegetation); + out->add_temperature(e1->temperature); + out->add_evilness(e1->evilness); + out->add_drainage(e1->drainage); + out->add_volcanism(e1->volcanism); + out->add_savagery(e1->savagery); + out->add_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->add_water_elevation(region->lake_surface); + else + out->add_water_elevation(99); } static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) { - if (pos.x < 0) - pos.x = 0; - if (pos.y < 0) - pos.y = 0; - if (pos.x >= worldData->world_width) - pos.x = worldData->world_width - 1; - if (pos.y >= worldData->world_height) - pos.y = worldData->world_height - 1; - AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); } static void AddRegionTiles(RegionTile * out, df::coord2d pos, df::world_data * worldData) { - if (pos.x < 0) - pos.x = 0; - if (pos.y < 0) - pos.y = 0; - if (pos.x >= worldData->world_width) - pos.x = worldData->world_width - 1; - if (pos.y >= worldData->world_height) - pos.y = worldData->world_height - 1; - SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); } static df::coord2d ShiftCoords(df::coord2d source, int direction) { - switch (direction) - { - case 1: - return df::coord2d(source.x - 1, source.y + 1); - case 2: - return df::coord2d(source.x, source.y + 1); - case 3: - return df::coord2d(source.x + 1, source.y + 1); - case 4: - return df::coord2d(source.x - 1, source.y); - case 5: - return source; - case 6: - return df::coord2d(source.x + 1, source.y); - case 7: - return df::coord2d(source.x - 1, source.y - 1); - case 8: - return df::coord2d(source.x, source.y - 1); - case 9: - return df::coord2d(source.x + 1, source.y - 1); - default: - return source; - } + switch (direction) + { + case 1: + return df::coord2d(source.x - 1, source.y + 1); + case 2: + return df::coord2d(source.x, source.y + 1); + case 3: + return df::coord2d(source.x + 1, source.y + 1); + case 4: + return df::coord2d(source.x - 1, source.y); + case 5: + return source; + case 6: + return df::coord2d(source.x + 1, source.y); + case 7: + return df::coord2d(source.x - 1, source.y - 1); + case 8: + return df::coord2d(source.x, source.y - 1); + case 9: + return df::coord2d(source.x + 1, source.y - 1); + default: + return source; + } } static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, WorldMap * out) { - int pos_x = worldRegionDetails->pos.x; - int pos_y = worldRegionDetails->pos.y; - out->set_map_x(pos_x); - out->set_map_y(pos_y); - out->set_world_width(17); - out->set_world_height(17); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); - out->set_name_english(name); - out->set_name(name); + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + out->set_world_width(17); + out->set_world_height(17); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); #if DF_VERSION_INT > 34011 - auto poles = worldData->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + auto poles = worldData->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif - df::world_region_details * south = NULL; - df::world_region_details * east = NULL; - df::world_region_details * southEast = NULL; - - for (int i = 0; i < worldData->region_details.size(); i++) - { - auto region = worldData->region_details[i]; - if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) - southEast = region; - else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) - east = region; - else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) - south = region; - } - - for (int yy = 0; yy < 17; yy++) - for (int xx = 0; xx < 17; xx++) - { - //This is because the bottom row doesn't line up. - if (xx == 16 && yy == 16 && southEast != NULL) - { - out->add_elevation(southEast->elevation[0][0]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); - } - else if (xx == 16 && east != NULL) - { - out->add_elevation(east->elevation[0][yy]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); - } - else if (yy == 16 && south != NULL) - { - out->add_elevation(south->elevation[xx][0]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); - } - else - { - out->add_elevation(worldRegionDetails->elevation[xx][yy]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); - } - - if (xx == 16 || yy == 16) - { - out->add_river_tiles(); - } - else - { - auto riverTile = out->add_river_tiles(); - auto east = riverTile->mutable_east(); - auto north = riverTile->mutable_north(); - auto south = riverTile->mutable_south(); - auto west = riverTile->mutable_west(); - - north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); - north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); - north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); - north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); - - south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); - south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); - south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); - south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); - - west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); - west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); - west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); - west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); - - east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); - east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); - east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); - east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); - } - } + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (int i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + out->add_elevation(southEast->elevation[0][0]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + out->add_elevation(east->elevation[0][yy]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + out->add_elevation(south->elevation[xx][0]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + out->add_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + if (xx == 16 || yy == 16) + { + out->add_river_tiles(); + } + else + { + auto riverTile = out->add_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + } } static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, RegionMap * out) { - int pos_x = worldRegionDetails->pos.x; - int pos_y = worldRegionDetails->pos.y; - out->set_map_x(pos_x); - out->set_map_y(pos_y); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); - out->set_name_english(name); - out->set_name(name); - - - df::world_region_details * south = NULL; - df::world_region_details * east = NULL; - df::world_region_details * southEast = NULL; - - for (int i = 0; i < worldData->region_details.size(); i++) - { - auto region = worldData->region_details[i]; - if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) - southEast = region; - else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) - east = region; - else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) - south = region; - } - - RegionTile* outputTiles[17][17]; - - for (int yy = 0; yy < 17; yy++) - for (int xx = 0; xx < 17; xx++) - { - auto tile = out->add_tiles(); - outputTiles[xx][yy] = tile; - //This is because the bottom row doesn't line up. - if (xx == 16 && yy == 16 && southEast != NULL) - { - tile->set_elevation(southEast->elevation[0][0]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); - } - else if (xx == 16 && east != NULL) - { - tile->set_elevation(east->elevation[0][yy]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); - } - else if (yy == 16 && south != NULL) - { - tile->set_elevation(south->elevation[xx][0]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); - } - else - { - tile->set_elevation(worldRegionDetails->elevation[xx][yy]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); - } - - auto riverTile = tile->mutable_river_tiles(); - auto east = riverTile->mutable_east(); - auto north = riverTile->mutable_north(); - auto south = riverTile->mutable_south(); - auto west = riverTile->mutable_west(); - - if (xx < 16) - { - north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); - north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); - north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); - north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); - } - else - { - north->set_active(0); - north->set_elevation(100); - north->set_min_pos(-30000); - north->set_max_pos(-30000); - } - - if (yy < 16 && xx < 16) - { - south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); - south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); - south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); - south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); - } - else - { - south->set_active(0); - south->set_elevation(100); - south->set_min_pos(-30000); - south->set_max_pos(-30000); - } - - if (yy < 16) - { - west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); - west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); - west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); - west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); - } - else - { - west->set_active(0); - west->set_elevation(100); - west->set_min_pos(-30000); - west->set_max_pos(-30000); - } - - if (xx < 16 && yy < 16) - { - east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); - east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); - east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); - east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); - } - else - { - east->set_active(0); - east->set_elevation(100); - east->set_min_pos(-30000); - east->set_max_pos(-30000); - } - } - - auto regionMap = worldData->region_map[pos_x][pos_y]; - - for (int i = 0; i < worldData->sites.size(); i++) - { - df::world_site* site = worldData->sites[i]; - if (!site) - continue; - - int region_min_x = pos_x * 16; - int region_min_y = pos_y * 16; - - if ((site->global_min_x > (region_min_x + 16)) || - (site->global_min_y > (region_min_y + 16)) || - (site->global_max_x < (region_min_x)) || - (site->global_max_y < (region_min_y))) - continue; - - if (site->realization == NULL) - continue; - - auto realization = site->realization; - for (int site_x = 0; site_x < 17; site_x++) - for (int site_y = 0; site_y < 17; site_y++) - { - int region_x = site->global_min_x - region_min_x + site_x; - int region_y = site->global_min_y - region_min_y + site_y; - - if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) - continue; - - for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) - { - auto in_building = realization->building_map[site_x][site_y].buildings[j]; - auto out_building = outputTiles[region_x][region_y]->add_buildings(); - - out_building->set_id(in_building->id); + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); + + + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (int i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + RegionTile* outputTiles[17][17]; + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + auto tile = out->add_tiles(); + outputTiles[xx][yy] = tile; + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + tile->set_elevation(southEast->elevation[0][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + tile->set_elevation(east->elevation[0][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + tile->set_elevation(south->elevation[xx][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + tile->set_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + auto riverTile = tile->mutable_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + if (xx < 16) + { + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + } + else + { + north->set_active(0); + north->set_elevation(100); + north->set_min_pos(-30000); + north->set_max_pos(-30000); + } + + if (yy < 16 && xx < 16) + { + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + } + else + { + south->set_active(0); + south->set_elevation(100); + south->set_min_pos(-30000); + south->set_max_pos(-30000); + } + + if (yy < 16) + { + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + } + else + { + west->set_active(0); + west->set_elevation(100); + west->set_min_pos(-30000); + west->set_max_pos(-30000); + } + + if (xx < 16 && yy < 16) + { + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + else + { + east->set_active(0); + east->set_elevation(100); + east->set_min_pos(-30000); + east->set_max_pos(-30000); + } + } + + auto regionMap = worldData->region_map[pos_x][pos_y]; + + for (int i = 0; i < worldData->sites.size(); i++) + { + df::world_site* site = worldData->sites[i]; + if (!site) + continue; + + int region_min_x = pos_x * 16; + int region_min_y = pos_y * 16; + + if ((site->global_min_x >(region_min_x + 16)) || + (site->global_min_y > (region_min_y + 16)) || + (site->global_max_x < (region_min_x)) || + (site->global_max_y < (region_min_y))) + continue; + + if (site->realization == NULL) + continue; + + auto realization = site->realization; + for (int site_x = 0; site_x < 17; site_x++) + for (int site_y = 0; site_y < 17; site_y++) + { + int region_x = site->global_min_x - region_min_x + site_x; + int region_y = site->global_min_y - region_min_y + site_y; + + if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) + continue; + + for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) + { + auto in_building = realization->building_map[site_x][site_y].buildings[j]; + auto out_building = outputTiles[region_x][region_y]->add_buildings(); + + out_building->set_id(in_building->id); #if DF_VERSION_INT > 34011 - out_building->set_type((SiteRealizationBuildingType)in_building->type); + out_building->set_type((SiteRealizationBuildingType)in_building->type); #endif - out_building->set_min_x(in_building->min_x - (site_x * 48)); - out_building->set_min_y(in_building->min_y - (site_y * 48)); - out_building->set_max_x(in_building->max_x - (site_x * 48)); - out_building->set_max_y(in_building->max_y - (site_y * 48)); + out_building->set_min_x(in_building->min_x - (site_x * 48)); + out_building->set_min_y(in_building->min_y - (site_y * 48)); + out_building->set_max_x(in_building->max_x - (site_x * 48)); + out_building->set_max_y(in_building->max_y - (site_y * 48)); - CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); + CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); #if DF_VERSION_INT >= 43005 - STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); - if (tower_info) - { - CopyMat(out_building->mutable_material(), tower_info->wall_item.mat_type, tower_info->wall_item.mat_index); - - auto out_tower = out_building->mutable_tower_info(); - out_tower->set_roof_z(tower_info->roof_z); - out_tower->set_round(tower_info->shape.bits.round); - out_tower->set_goblin(tower_info->shape.bits.goblin); - } - STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); - if (wall_info) - { - CopyMat(out_building->mutable_material(), wall_info->wall_item.mat_type, wall_info->wall_item.mat_index); - - auto out_wall = out_building->mutable_wall_info(); - - out_wall->set_start_x(wall_info->start_x - (site_x * 48)); - out_wall->set_start_y(wall_info->start_y - (site_y * 48)); - out_wall->set_start_z(wall_info->start_z); - out_wall->set_end_x(wall_info->end_x - (site_x * 48)); - out_wall->set_end_y(wall_info->end_y - (site_y * 48)); - out_wall->set_end_z(wall_info->end_z); - } + STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); + if (tower_info) + { + CopyMat(out_building->mutable_material(), tower_info->wall_item.mat_type, tower_info->wall_item.mat_index); + + auto out_tower = out_building->mutable_tower_info(); + out_tower->set_roof_z(tower_info->roof_z); + out_tower->set_round(tower_info->shape.bits.round); + out_tower->set_goblin(tower_info->shape.bits.goblin); + } + STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); + if (wall_info) + { + CopyMat(out_building->mutable_material(), wall_info->wall_item.mat_type, wall_info->wall_item.mat_index); + + auto out_wall = out_building->mutable_wall_info(); + + out_wall->set_start_x(wall_info->start_x - (site_x * 48)); + out_wall->set_start_y(wall_info->start_y - (site_y * 48)); + out_wall->set_start_z(wall_info->start_z); + out_wall->set_end_x(wall_info->end_x - (site_x * 48)); + out_wall->set_end_y(wall_info->end_y - (site_y * 48)); + out_wall->set_end_z(wall_info->end_z); + } #endif - } + } - } - } + } + } } static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) { - if (!df::global::world->world_data) - { - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) - { - df::world_region_details * region = data->region_details[i]; - if (!region) - continue; - WorldMap * regionMap = out->add_world_maps(); - CopyLocalMap(data, region, regionMap); - } - return CR_OK; + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (int i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + WorldMap * regionMap = out->add_world_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; } static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) { - if (!df::global::world->world_data) - { - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) - { - df::world_region_details * region = data->region_details[i]; - if (!region) - continue; - RegionMap * regionMap = out->add_region_maps(); - CopyLocalMap(data, region, regionMap); - } - return CR_OK; + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (int i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + RegionMap * regionMap = out->add_region_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; } static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) { - return GetPartialCreatureRaws(stream, NULL, out); + return GetPartialCreatureRaws(stream, NULL, out); } static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out) { - if (!df::global::world) - return CR_FAILURE; + if (!df::global::world) + return CR_FAILURE; - df::world * world = df::global::world; + df::world * world = df::global::world; - int list_start = 0; - int list_end = world->raws.creatures.all.size(); + int list_start = 0; + int list_end = world->raws.creatures.all.size(); - if (in != nullptr) - { - list_start = in->list_start(); - if(in->list_end() < list_end) - list_end = in->list_end(); - } + if (in != nullptr) + { + list_start = in->list_start(); + if (in->list_end() < list_end) + list_end = in->list_end(); + } - for (int i = list_start; i < list_end; i++) - { - df::creature_raw * orig_creature = world->raws.creatures.all[i]; + for (int i = list_start; i < list_end; i++) + { + df::creature_raw * orig_creature = world->raws.creatures.all[i]; - auto send_creature = out->add_creature_raws(); + auto send_creature = out->add_creature_raws(); - send_creature->set_index(i); - send_creature->set_creature_id(orig_creature->creature_id); - send_creature->add_name(orig_creature->name[0]); - send_creature->add_name(orig_creature->name[1]); - send_creature->add_name(orig_creature->name[2]); + send_creature->set_index(i); + send_creature->set_creature_id(orig_creature->creature_id); + send_creature->add_name(orig_creature->name[0]); + send_creature->add_name(orig_creature->name[1]); + send_creature->add_name(orig_creature->name[2]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); - send_creature->add_general_child_name(orig_creature->general_child_name[0]); - send_creature->add_general_child_name(orig_creature->general_child_name[1]); + send_creature->add_general_child_name(orig_creature->general_child_name[0]); + send_creature->add_general_child_name(orig_creature->general_child_name[1]); - send_creature->set_creature_tile(orig_creature->creature_tile); - send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); + send_creature->set_creature_tile(orig_creature->creature_tile); + send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); - ConvertDfColor(orig_creature->color, send_creature->mutable_color()); + ConvertDfColor(orig_creature->color, send_creature->mutable_color()); - send_creature->set_adultsize(orig_creature->adultsize); + send_creature->set_adultsize(orig_creature->adultsize); - for (int j = 0; j < orig_creature->caste.size(); j++) - { - auto orig_caste = orig_creature->caste[j]; - if (!orig_caste) - continue; - auto send_caste = send_creature->add_caste(); + for (int j = 0; j < orig_creature->caste.size(); j++) + { + auto orig_caste = orig_creature->caste[j]; + if (!orig_caste) + continue; + auto send_caste = send_creature->add_caste(); - send_caste->set_index(j); + send_caste->set_index(j); - send_caste->set_caste_id(orig_caste->caste_id); + send_caste->set_caste_id(orig_caste->caste_id); - send_caste->add_caste_name(orig_caste->caste_name[0]); - send_caste->add_caste_name(orig_caste->caste_name[1]); - send_caste->add_caste_name(orig_caste->caste_name[2]); + send_caste->add_caste_name(orig_caste->caste_name[0]); + send_caste->add_caste_name(orig_caste->caste_name[1]); + send_caste->add_caste_name(orig_caste->caste_name[2]); - send_caste->add_baby_name(orig_caste->baby_name[0]); - send_caste->add_baby_name(orig_caste->baby_name[1]); + send_caste->add_baby_name(orig_caste->baby_name[0]); + send_caste->add_baby_name(orig_caste->baby_name[1]); - send_caste->add_child_name(orig_caste->child_name[0]); - send_caste->add_child_name(orig_caste->child_name[1]); - send_caste->set_gender(orig_caste->gender); + send_caste->add_child_name(orig_caste->child_name[0]); + send_caste->add_child_name(orig_caste->child_name[1]); + send_caste->set_gender(orig_caste->gender); - for (int partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) - { - auto orig_part = orig_caste->body_info.body_parts[partIndex]; - if (!orig_part) - continue; - auto send_part = send_caste->add_body_parts(); + for (int partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) + { + auto orig_part = orig_caste->body_info.body_parts[partIndex]; + if (!orig_part) + continue; + auto send_part = send_caste->add_body_parts(); - send_part->set_token(orig_part->token); - send_part->set_category(orig_part->category); - send_part->set_parent(orig_part->con_part_id); + send_part->set_token(orig_part->token); + send_part->set_category(orig_part->category); + send_part->set_parent(orig_part->con_part_id); - for (int partFlagIndex = 0; partFlagIndex <= ENUM_LAST_ITEM(body_part_raw_flags); partFlagIndex++) - { - send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); - } + for (int partFlagIndex = 0; partFlagIndex <= ENUM_LAST_ITEM(body_part_raw_flags); partFlagIndex++) + { + send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); + } - for (int layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) - { - auto orig_layer = orig_part->layers[layerIndex]; - if (!orig_layer) - continue; - auto send_layer = send_part->add_layers(); + for (int layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) + { + auto orig_layer = orig_part->layers[layerIndex]; + if (!orig_layer) + continue; + auto send_layer = send_part->add_layers(); - send_layer->set_layer_name(orig_layer->layer_name); - send_layer->set_tissue_id(orig_layer->tissue_id); - send_layer->set_layer_depth(orig_layer->layer_depth); - for (int layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) - { - send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); - } - } + send_layer->set_layer_name(orig_layer->layer_name); + send_layer->set_tissue_id(orig_layer->tissue_id); + send_layer->set_layer_depth(orig_layer->layer_depth); + for (int layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) + { + send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); + } + } - send_part->set_relsize(orig_part->relsize); - } + send_part->set_relsize(orig_part->relsize); + } - send_caste->set_total_relsize(orig_caste->body_info.total_relsize); + send_caste->set_total_relsize(orig_caste->body_info.total_relsize); - for (int k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) - { - auto send_mod = send_caste->add_modifiers(); - auto orig_mod = orig_caste->bp_appearance.modifiers[k]; - send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + for (int k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) + { + auto send_mod = send_caste->add_modifiers(); + auto orig_mod = orig_caste->bp_appearance.modifiers[k]; + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); #if DF_VERSION_INT > 34011 - if (orig_mod->growth_rate > 0) - { - send_mod->set_mod_min(orig_mod->growth_min); - send_mod->set_mod_max(orig_mod->growth_max); - } - else + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else #endif - { - send_mod->set_mod_min(orig_mod->ranges[0]); - send_mod->set_mod_max(orig_mod->ranges[6]); - } - - } - for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) - { - send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); - send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); - send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); - } - for (int k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) - { - auto send_mod = send_caste->add_body_appearance_modifiers(); - auto orig_mod = orig_caste->body_appearance_modifiers[k]; - - send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + + } + for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) + { + send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); + send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); + send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); + } + for (int k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) + { + auto send_mod = send_caste->add_body_appearance_modifiers(); + auto orig_mod = orig_caste->body_appearance_modifiers[k]; + + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); #if DF_VERSION_INT > 34011 - if (orig_mod->growth_rate > 0) - { - send_mod->set_mod_min(orig_mod->growth_min); - send_mod->set_mod_max(orig_mod->growth_max); - } - else + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else #endif - { - send_mod->set_mod_min(orig_mod->ranges[0]); - send_mod->set_mod_max(orig_mod->ranges[6]); - } - } - for (int k = 0; k < orig_caste->color_modifiers.size(); k++) - { - auto send_mod = send_caste->add_color_modifiers(); - auto orig_mod = orig_caste->color_modifiers[k]; - - for (int l = 0; l < orig_mod->pattern_index.size(); l++) - { - auto orig_pattern = world->raws.language.patterns[orig_mod->pattern_index[l]]; - auto send_pattern = send_mod->add_patterns(); - - for (int m = 0; m < orig_pattern->colors.size(); m++) - { - auto send_color = send_pattern->add_colors(); - auto orig_color = world->raws.language.colors[orig_pattern->colors[m]]; - send_color->set_red(orig_color->red * 255.0); - send_color->set_green(orig_color->green * 255.0); - send_color->set_blue(orig_color->blue * 255.0); - } - - send_pattern->set_id(orig_pattern->id); - send_pattern->set_pattern((PatternType)orig_pattern->pattern); - } - - for (int l = 0; l < orig_mod->body_part_id.size(); l++) - { - send_mod->add_body_part_id(orig_mod->body_part_id[l]); - send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); - send_mod->set_start_date(orig_mod->start_date); - send_mod->set_end_date(orig_mod->end_date); - send_mod->set_part(orig_mod->part); - } - } - - send_caste->set_description(orig_caste->description); - send_caste->set_adult_size(orig_caste->misc.adult_size); - } - - for (int j = 0; j < orig_creature->tissue.size(); j++) - { - auto orig_tissue = orig_creature->tissue[j]; - auto send_tissue = send_creature->add_tissues(); - - send_tissue->set_id(orig_tissue->id); - send_tissue->set_name(orig_tissue->tissue_name_singular); - send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); - - CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); - } -} - - return CR_OK; + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + } + for (int k = 0; k < orig_caste->color_modifiers.size(); k++) + { + auto send_mod = send_caste->add_color_modifiers(); + auto orig_mod = orig_caste->color_modifiers[k]; + + for (int l = 0; l < orig_mod->pattern_index.size(); l++) + { + auto orig_pattern = world->raws.language.patterns[orig_mod->pattern_index[l]]; + auto send_pattern = send_mod->add_patterns(); + + for (int m = 0; m < orig_pattern->colors.size(); m++) + { + auto send_color = send_pattern->add_colors(); + auto orig_color = world->raws.language.colors[orig_pattern->colors[m]]; + send_color->set_red(orig_color->red * 255.0); + send_color->set_green(orig_color->green * 255.0); + send_color->set_blue(orig_color->blue * 255.0); + } + + send_pattern->set_id(orig_pattern->id); + send_pattern->set_pattern((PatternType)orig_pattern->pattern); + } + + for (int l = 0; l < orig_mod->body_part_id.size(); l++) + { + send_mod->add_body_part_id(orig_mod->body_part_id[l]); + send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); + send_mod->set_start_date(orig_mod->start_date); + send_mod->set_end_date(orig_mod->end_date); + send_mod->set_part(orig_mod->part); + } + } + + send_caste->set_description(orig_caste->description); + send_caste->set_adult_size(orig_caste->misc.adult_size); + } + + for (int j = 0; j < orig_creature->tissue.size(); j++) + { + auto orig_tissue = orig_creature->tissue[j]; + auto send_tissue = send_creature->add_tissues(); + + send_tissue->set_id(orig_tissue->id); + send_tissue->set_name(orig_tissue->tissue_name_singular); + send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); + + CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); + } + } + + return CR_OK; } static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out) { - GetPartialPlantRaws(stream, nullptr, out); - return CR_OK; + GetPartialPlantRaws(stream, nullptr, out); + return CR_OK; } static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out) { - if (!df::global::world) - return CR_FAILURE; - - df::world * world = df::global::world; - - int list_start = 0; - int list_end = world->raws.plants.all.size(); - - if (in != nullptr) - { - list_start = in->list_start(); - if (in->list_end() < list_end) - list_end = in->list_end(); - } - - for (int i = 0; i < world->raws.plants.all.size(); i++) - { - df::plant_raw* plant_local = world->raws.plants.all[i]; - PlantRaw* plant_remote = out->add_plant_raws(); - - plant_remote->set_index(i); - plant_remote->set_id(plant_local->id); - plant_remote->set_name(plant_local->name); - if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) - plant_remote->set_tile(plant_local->tiles.shrub_tile); - else - plant_remote->set_tile(plant_local->tiles.tree_tile); + if (!df::global::world) + return CR_FAILURE; + + df::world * world = df::global::world; + + int list_start = 0; + int list_end = world->raws.plants.all.size(); + + if (in != nullptr) + { + list_start = in->list_start(); + if (in->list_end() < list_end) + list_end = in->list_end(); + } + + for (int i = 0; i < world->raws.plants.all.size(); i++) + { + df::plant_raw* plant_local = world->raws.plants.all[i]; + PlantRaw* plant_remote = out->add_plant_raws(); + + plant_remote->set_index(i); + plant_remote->set_id(plant_local->id); + plant_remote->set_name(plant_local->name); + if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) + plant_remote->set_tile(plant_local->tiles.shrub_tile); + else + plant_remote->set_tile(plant_local->tiles.tree_tile); #if DF_VERSION_INT > 34011 - for (int j = 0; j < plant_local->growths.size(); j++) - { - df::plant_growth* growth_local = plant_local->growths[j]; - TreeGrowth * growth_remote = plant_remote->add_growths(); - growth_remote->set_index(j); - growth_remote->set_id(growth_local->id); - growth_remote->set_name(growth_local->name); - for (int k = 0; k < growth_local->prints.size(); k++) - { - df::plant_growth_print* print_local = growth_local->prints[k]; - GrowthPrint* print_remote = growth_remote->add_prints(); - print_remote->set_priority(print_local->priority); - print_remote->set_color(print_local->color[0] | (print_local->color[2] * 8)); - print_remote->set_timing_start(print_local->timing_start); - print_remote->set_timing_end(print_local->timing_end); - print_remote->set_tile(print_local->tile_growth); - } - growth_remote->set_timing_start(growth_local->timing_1); - growth_remote->set_timing_end(growth_local->timing_2); - growth_remote->set_twigs(growth_local->locations.bits.twigs); - growth_remote->set_light_branches(growth_local->locations.bits.light_branches); - growth_remote->set_heavy_branches(growth_local->locations.bits.heavy_branches); - growth_remote->set_trunk(growth_local->locations.bits.trunk); - growth_remote->set_roots(growth_local->locations.bits.roots); - growth_remote->set_cap(growth_local->locations.bits.cap); - growth_remote->set_sapling(growth_local->locations.bits.sapling); - growth_remote->set_timing_start(growth_local->timing_1); - growth_remote->set_timing_end(growth_local->timing_2); - growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); - growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); - CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); - } + for (int j = 0; j < plant_local->growths.size(); j++) + { + df::plant_growth* growth_local = plant_local->growths[j]; + TreeGrowth * growth_remote = plant_remote->add_growths(); + growth_remote->set_index(j); + growth_remote->set_id(growth_local->id); + growth_remote->set_name(growth_local->name); + for (int k = 0; k < growth_local->prints.size(); k++) + { + df::plant_growth_print* print_local = growth_local->prints[k]; + GrowthPrint* print_remote = growth_remote->add_prints(); + print_remote->set_priority(print_local->priority); + print_remote->set_color(print_local->color[0] | (print_local->color[2] * 8)); + print_remote->set_timing_start(print_local->timing_start); + print_remote->set_timing_end(print_local->timing_end); + print_remote->set_tile(print_local->tile_growth); + } + growth_remote->set_timing_start(growth_local->timing_1); + growth_remote->set_timing_end(growth_local->timing_2); + growth_remote->set_twigs(growth_local->locations.bits.twigs); + growth_remote->set_light_branches(growth_local->locations.bits.light_branches); + growth_remote->set_heavy_branches(growth_local->locations.bits.heavy_branches); + growth_remote->set_trunk(growth_local->locations.bits.trunk); + growth_remote->set_roots(growth_local->locations.bits.roots); + growth_remote->set_cap(growth_local->locations.bits.cap); + growth_remote->set_sapling(growth_local->locations.bits.sapling); + growth_remote->set_timing_start(growth_local->timing_1); + growth_remote->set_timing_end(growth_local->timing_2); + growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); + growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); + CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); + } #endif - } - return CR_OK; + } + return CR_OK; } static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out) { - df::graphic * gps = df::global::gps; - out->set_width(gps->dimx); - out->set_height(gps->dimy); - for (int i = 0; i < (gps->dimx * gps->dimy); i++) - { - int index = i * 4; - auto tile = out->add_tiles(); - tile->set_character(gps->screen[index]); - tile->set_foreground(gps->screen[index + 1] | (gps->screen[index + 3] * 8)); - tile->set_background(gps->screen[index + 2]); - } - - return CR_OK; + df::graphic * gps = df::global::gps; + out->set_width(gps->dimx); + out->set_height(gps->dimy); + for (int i = 0; i < (gps->dimx * gps->dimy); i++) + { + int index = i * 4; + auto tile = out->add_tiles(); + tile->set_character(gps->screen[index]); + tile->set_foreground(gps->screen[index + 1] | (gps->screen[index + 3] * 8)); + tile->set_background(gps->screen[index + 2]); + } + + return CR_OK; } static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in) { #if DF_VERSION_INT > 34011 - SDL::Event e; - e.key.type = in->type(); - e.key.state = in->state(); - e.key.ksym.mod = (SDL::Mod)in->mod(); - e.key.ksym.scancode = in->scancode(); - e.key.ksym.sym = (SDL::Key)in->sym(); - e.key.ksym.unicode = in->unicode(); - SDL_PushEvent(&e); + SDL::Event e; + e.key.type = in->type(); + e.key.state = in->state(); + e.key.ksym.mod = (SDL::Mod)in->mod(); + e.key.ksym.scancode = in->scancode(); + e.key.ksym.sym = (SDL::Key)in->sym(); + e.key.ksym.unicode = in->unicode(); + SDL_PushEvent(&e); #endif - return CR_OK; + return CR_OK; } static command_result SendDigCommand(color_ostream &stream, const DigCommand *in) { - MapExtras::MapCache mc; - - for (int i = 0; i < in->locations_size(); i++) - { - auto pos = in->locations(i); - auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); - switch (in->designation()) - { - case NO_DIG: - des.bits.dig = tile_dig_designation::No; - break; - case DEFAULT_DIG: - des.bits.dig = tile_dig_designation::Default; - break; - case UP_DOWN_STAIR_DIG: - des.bits.dig = tile_dig_designation::UpDownStair; - break; - case CHANNEL_DIG: - des.bits.dig = tile_dig_designation::Channel; - break; - case RAMP_DIG: - des.bits.dig = tile_dig_designation::Ramp; - break; - case DOWN_STAIR_DIG: - des.bits.dig = tile_dig_designation::DownStair; - break; - case UP_STAIR_DIG: - des.bits.dig = tile_dig_designation::UpStair; - break; - default: - break; - } - mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + MapExtras::MapCache mc; + + for (int i = 0; i < in->locations_size(); i++) + { + auto pos = in->locations(i); + auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); + switch (in->designation()) + { + case NO_DIG: + des.bits.dig = tile_dig_designation::No; + break; + case DEFAULT_DIG: + des.bits.dig = tile_dig_designation::Default; + break; + case UP_DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpDownStair; + break; + case CHANNEL_DIG: + des.bits.dig = tile_dig_designation::Channel; + break; + case RAMP_DIG: + des.bits.dig = tile_dig_designation::Ramp; + break; + case DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::DownStair; + break; + case UP_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpStair; + break; + default: + break; + } + mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); #if DF_VERSION_INT >= 43005 - //remove and job postings related. - for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next) - { - if (listing->item == NULL) - continue; - auto type = listing->item->job_type; - switch (type) - { - case df::enums::job_type::CarveFortification: - case df::enums::job_type::DetailWall: - case df::enums::job_type::DetailFloor: - case df::enums::job_type::Dig: - case df::enums::job_type::CarveUpwardStaircase: - case df::enums::job_type::CarveDownwardStaircase: - case df::enums::job_type::CarveUpDownStaircase: - case df::enums::job_type::CarveRamp: - case df::enums::job_type::DigChannel: - case df::enums::job_type::FellTree: - case df::enums::job_type::GatherPlants: - case df::enums::job_type::RemoveConstruction: - case df::enums::job_type::CarveTrack: - { - if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z())) - { - Job::removeJob(listing->item); - goto JOB_FOUND; - } - break; - } - default: - continue; - } - } - JOB_FOUND: - continue; + //remove and job postings related. + for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next) + { + if (listing->item == NULL) + continue; + auto type = listing->item->job_type; + switch (type) + { + case df::enums::job_type::CarveFortification: + case df::enums::job_type::DetailWall: + case df::enums::job_type::DetailFloor: + case df::enums::job_type::Dig: + case df::enums::job_type::CarveUpwardStaircase: + case df::enums::job_type::CarveDownwardStaircase: + case df::enums::job_type::CarveUpDownStaircase: + case df::enums::job_type::CarveRamp: + case df::enums::job_type::DigChannel: + case df::enums::job_type::FellTree: + case df::enums::job_type::GatherPlants: + case df::enums::job_type::RemoveConstruction: + case df::enums::job_type::CarveTrack: + { + if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z())) + { + Job::removeJob(listing->item); + goto JOB_FOUND; + } + break; + } + default: + continue; + } + } + JOB_FOUND: + continue; #endif - } + } - mc.WriteAll(); - return CR_OK; + mc.WriteAll(); + return CR_OK; } static command_result SetPauseState(color_ostream &stream, const SingleBool *in) { - DFHack::World::SetPauseState(in->value()); - return CR_OK; + DFHack::World::SetPauseState(in->value()); + return CR_OK; } static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out) { - out->set_value(World::ReadPauseState()); - return CR_OK; + out->set_value(World::ReadPauseState()); + return CR_OK; } -command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) +static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) { - out->set_dfhack_version(DFHACK_VERSION); + out->set_dfhack_version(DFHACK_VERSION); #if DF_VERSION_INT == 34011 - out->set_dwarf_fortress_version("0.34.11"); + out->set_dwarf_fortress_version("0.34.11"); #else - out->set_dwarf_fortress_version(DF_VERSION); + out->set_dwarf_fortress_version(DF_VERSION); #endif - out->set_remote_fortress_reader_version(RFR_VERSION); - return command_result(); + out->set_remote_fortress_reader_version(RFR_VERSION); + return CR_OK; } + +static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out) +{ + for (int i = 0; i < world->status.reports.size(); i++) + { + auto local_rep = world->status.reports[i]; + if (!local_rep) + continue; + auto send_rep = out->add_reports(); + send_rep->set_type(local_rep->type); + send_rep->set_text(DF2UTF(local_rep->text)); + ConvertDfColor(local_rep->color | (local_rep->bright ? 8 : 0), send_rep->mutable_color()); + send_rep->set_duration(local_rep->duration); + send_rep->set_continuation(local_rep->flags.bits.continuation); + send_rep->set_unconscious(local_rep->flags.bits.unconscious); + send_rep->set_announcement(local_rep->flags.bits.announcement); + send_rep->set_repeat_count(local_rep->repeat_count); + ConvertDFCoord(local_rep->pos, send_rep->mutable_pos()); + send_rep->set_id(local_rep->id); + send_rep->set_year(local_rep->year); + send_rep->set_time(local_rep->time); + } + return CR_OK; +} \ No newline at end of file From c4f9f6edb2534ef4aa3177535a73e40833f4826e Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 17 Dec 2017 18:42:03 +0530 Subject: [PATCH 0690/1012] Add item stack size. --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/remotefortressreader.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 62ea5ee54..433a247ae 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -267,6 +267,7 @@ message Item optional MatPair type = 5; optional MatPair material = 6; optional ColorDefinition dye = 7; + optional int32 stack_size = 8; } message MapBlock diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 704c91ae1..22826ead3 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1313,7 +1313,12 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { type->set_mat_index(DfItem->isBag()); } - auto constructed_item = virtual_cast(DfItem); + VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem); + if (actual_item) + { + NetItem->set_stack_size(actual_item->stack_size); + } + VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); if (constructed_item) { for (int i = 0; i < constructed_item->improvements.size(); i++) From e478c00ce53831ffaabc812bf39d8baf36d4bc08 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 19 Dec 2017 11:20:12 +0530 Subject: [PATCH 0691/1012] Replace tab with space. --- plugins/proto/RemoteFortressReader.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 433a247ae..cb7acca07 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -267,7 +267,7 @@ message Item optional MatPair type = 5; optional MatPair material = 6; optional ColorDefinition dye = 7; - optional int32 stack_size = 8; + optional int32 stack_size = 8; } message MapBlock From b8d5aba0efce5e160519566f8290bd718ce28217 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 20 Dec 2017 00:02:26 -0500 Subject: [PATCH 0692/1012] Update xml, scripts --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e2e256066..c531a0901 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e2e256066cc4a5c427172d9d27db25b7823e4e86 +Subproject commit c531a09019e521fabb3c943bb49f831f635c309f diff --git a/scripts b/scripts index 252a5adbf..8b6507ed7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 252a5adbf1a5cae45cf7e623d3c5ce6f3aa1195d +Subproject commit 8b6507ed7e8841afe35ee8e156f08edbb7c59f45 From 04b1a8065477bc1b3ca2edd3d216c214f7f5e03c Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 20 Dec 2017 17:11:01 +0530 Subject: [PATCH 0693/1012] Don't re-send all reports all the time. --- .../remotefortressreader.cpp | 4638 +++++++++-------- 1 file changed, 2326 insertions(+), 2312 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 22826ead3..93df40dac 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -168,86 +168,86 @@ static command_result GetReports(color_ostream & stream, const EmptyMessage * in void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); const char* growth_locations[] = { - "TWIGS", - "LIGHT_BRANCHES", - "HEAVY_BRANCHES", - "TRUNK", - "ROOTS", - "CAP", - "SAPLING", - "SHRUB" + "TWIGS", + "LIGHT_BRANCHES", + "HEAVY_BRANCHES", + "TRUNK", + "ROOTS", + "CAP", + "SAPLING", + "SHRUB" }; #define GROWTH_LOCATIONS_SIZE 8 command_result dump_bp_mods(color_ostream &out, vector & parameters) { - remove("bp_appearance_mods.csv"); - ofstream output; - output.open("bp_appearance_mods.csv"); - - output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; - - for (int creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) - { - auto creatureRaw = world->raws.creatures.all[creatureIndex]; - for (int casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) - { - df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; - for (int partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) - { - output << creatureIndex << ";"; - output << creatureRaw->creature_id << ";"; - output << casteRaw->caste_id << ";"; - output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";"; - output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";"; - int layer = casteRaw->bp_appearance.layer_idx[partIndex]; - if (layer < 0) - output << "N/A;"; - else - output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; - output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; - auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; + remove("bp_appearance_mods.csv"); + ofstream output; + output.open("bp_appearance_mods.csv"); + + output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; + + for (int creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) + { + auto creatureRaw = world->raws.creatures.all[creatureIndex]; + for (int casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) + { + df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; + for (int partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) + { + output << creatureIndex << ";"; + output << creatureRaw->creature_id << ";"; + output << casteRaw->caste_id << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";"; + int layer = casteRaw->bp_appearance.layer_idx[partIndex]; + if (layer < 0) + output << "N/A;"; + else + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; + output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; + auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; #if DF_VERSION_INT > 34011 - if (appMod->growth_rate > 0) - { - output << appMod->growth_min << " - " << appMod->growth_max << "\n"; - } - else + if (appMod->growth_rate > 0) + { + output << appMod->growth_min << " - " << appMod->growth_max << "\n"; + } + else #endif - { - output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; - output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; - } - } - } - } + { + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; + } + } + } + } - output.close(); + output.close(); - return CR_OK; + return CR_OK; } command_result RemoteFortressReader_version(color_ostream &out, vector ¶meters) { - out.print(RFR_VERSION); - return CR_OK; + out.print(RFR_VERSION); + return CR_OK; } // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - //// Fill the command list with your commands. - commands.push_back(PluginCommand( - "dump_bp_mods", "Dump bodypart mods for debugging", - dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " This command does nothing at all.\n" - "Example:\n" - " isoworldremote\n" - " Does nothing.\n" - )); - commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); - return CR_OK; + //// Fill the command list with your commands. + commands.push_back(PluginCommand( + "dump_bp_mods", "Dump bodypart mods for debugging", + dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " isoworldremote\n" + " Does nothing.\n" + )); + commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); + return CR_OK; } #ifndef SF_ALLOW_REMOTE @@ -256,380 +256,380 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addFunction("GetMaterialList", GetMaterialList, SF_ALLOW_REMOTE); - svc->addFunction("GetGrowthList", GetGrowthList, SF_ALLOW_REMOTE); - svc->addFunction("GetBlockList", GetBlockList, SF_ALLOW_REMOTE); - svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); - svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); - svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); - svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); - svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); - svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); - svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); - svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); - svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); - svc->addFunction("GetBuildingDefList", GetBuildingDefList, SF_ALLOW_REMOTE); - svc->addFunction("GetWorldMap", GetWorldMap, SF_ALLOW_REMOTE); - svc->addFunction("GetWorldMapNew", GetWorldMapNew, SF_ALLOW_REMOTE); - svc->addFunction("GetRegionMaps", GetRegionMaps, SF_ALLOW_REMOTE); - svc->addFunction("GetRegionMapsNew", GetRegionMapsNew, SF_ALLOW_REMOTE); - svc->addFunction("GetCreatureRaws", GetCreatureRaws, SF_ALLOW_REMOTE); - svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws, SF_ALLOW_REMOTE); - svc->addFunction("GetWorldMapCenter", GetWorldMapCenter, SF_ALLOW_REMOTE); - svc->addFunction("GetPlantRaws", GetPlantRaws, SF_ALLOW_REMOTE); - svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws, SF_ALLOW_REMOTE); - svc->addFunction("CopyScreen", CopyScreen, SF_ALLOW_REMOTE); - svc->addFunction("PassKeyboardEvent", PassKeyboardEvent, SF_ALLOW_REMOTE); - svc->addFunction("SendDigCommand", SendDigCommand, SF_ALLOW_REMOTE); - svc->addFunction("SetPauseState", SetPauseState, SF_ALLOW_REMOTE); - svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); - svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); - svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); - return svc; + RPCService *svc = new RPCService(); + svc->addFunction("GetMaterialList", GetMaterialList, SF_ALLOW_REMOTE); + svc->addFunction("GetGrowthList", GetGrowthList, SF_ALLOW_REMOTE); + svc->addFunction("GetBlockList", GetBlockList, SF_ALLOW_REMOTE); + svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); + svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); + svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); + svc->addFunction("GetBuildingDefList", GetBuildingDefList, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMap", GetWorldMap, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapNew", GetWorldMapNew, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMaps", GetRegionMaps, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMapsNew", GetRegionMapsNew, SF_ALLOW_REMOTE); + svc->addFunction("GetCreatureRaws", GetCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapCenter", GetWorldMapCenter, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantRaws", GetPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("CopyScreen", CopyScreen, SF_ALLOW_REMOTE); + svc->addFunction("PassKeyboardEvent", PassKeyboardEvent, SF_ALLOW_REMOTE); + svc->addFunction("SendDigCommand", SendDigCommand, SF_ALLOW_REMOTE); + svc->addFunction("SetPauseState", SetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); + return svc; } // This is called right before the plugin library is removed from memory. DFhackCExport command_result plugin_shutdown(color_ostream &out) { - // You *MUST* kill all threads you created before this returns. - // If everything fails, just return CR_FAILURE. Your plugin will be - // in a zombie state, but things won't crash. - return CR_OK; + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; } uint16_t fletcher16(uint8_t const *data, size_t bytes) { - uint16_t sum1 = 0xff, sum2 = 0xff; - - while (bytes) { - size_t tlen = bytes > 20 ? 20 : bytes; - bytes -= tlen; - do { - sum2 += sum1 += *data++; - } while (--tlen); - sum1 = (sum1 & 0xff) + (sum1 >> 8); - sum2 = (sum2 & 0xff) + (sum2 >> 8); - } - /* Second reduction step to reduce sums to 8 bits */ - sum1 = (sum1 & 0xff) + (sum1 >> 8); - sum2 = (sum2 & 0xff) + (sum2 >> 8); - return sum2 << 8 | sum1; + uint16_t sum1 = 0xff, sum2 = 0xff; + + while (bytes) { + size_t tlen = bytes > 20 ? 20 : bytes; + bytes -= tlen; + do { + sum2 += sum1 += *data++; + } while (--tlen); + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + } + /* Second reduction step to reduce sums to 8 bits */ + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + return sum2 << 8 | sum1; } void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) { - if (!df::global::enabler) - return; + if (!df::global::enabler) + return; - auto enabler = df::global::enabler; + auto enabler = df::global::enabler; - out->set_red((int)(enabler->ccolor[index][0] * 255)); - out->set_green((int)(enabler->ccolor[index][1] * 255)); - out->set_blue((int)(enabler->ccolor[index][2] * 255)); + out->set_red((int)(enabler->ccolor[index][0] * 255)); + out->set_green((int)(enabler->ccolor[index][1] * 255)); + out->set_blue((int)(enabler->ccolor[index][2] * 255)); } void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) { - int index = in[0] | (8 * in[2]); - ConvertDfColor(index, out); + int index = in[0] | (8 * in[2]); + ConvertDfColor(index, out); } void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out) { - df::descriptor_color *color = world->raws.language.colors[index]; - out->set_red(color->red * 255); - out->set_green(color->green * 255); - out->set_blue(color->blue * 255); + df::descriptor_color *color = world->raws.language.colors[index]; + out->set_red(color->red * 255); + out->set_green(color->green * 255); + out->set_blue(color->blue * 255); } void ConvertDFCoord(DFCoord in, RemoteFortressReader::Coord * out) { - out->set_x(in.x); - out->set_y(in.y); - out->set_z(in.z); + out->set_x(in.x); + out->set_y(in.y); + out->set_z(in.z); } RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { - switch (material) - { - case df::enums::tiletype_material::NONE: - return RemoteFortressReader::NO_MATERIAL; - break; - case df::enums::tiletype_material::AIR: - return RemoteFortressReader::AIR; - break; - case df::enums::tiletype_material::SOIL: - return RemoteFortressReader::SOIL; - break; - case df::enums::tiletype_material::STONE: - return RemoteFortressReader::STONE; - break; - case df::enums::tiletype_material::FEATURE: - return RemoteFortressReader::FEATURE; - break; - case df::enums::tiletype_material::LAVA_STONE: - return RemoteFortressReader::LAVA_STONE; - break; - case df::enums::tiletype_material::MINERAL: - return RemoteFortressReader::MINERAL; - break; - case df::enums::tiletype_material::FROZEN_LIQUID: - return RemoteFortressReader::FROZEN_LIQUID; - break; - case df::enums::tiletype_material::CONSTRUCTION: - return RemoteFortressReader::CONSTRUCTION; - break; - case df::enums::tiletype_material::GRASS_LIGHT: - return RemoteFortressReader::GRASS_LIGHT; - break; - case df::enums::tiletype_material::GRASS_DARK: - return RemoteFortressReader::GRASS_DARK; - break; - case df::enums::tiletype_material::GRASS_DRY: - return RemoteFortressReader::GRASS_DRY; - break; - case df::enums::tiletype_material::GRASS_DEAD: - return RemoteFortressReader::GRASS_DEAD; - break; - case df::enums::tiletype_material::PLANT: - return RemoteFortressReader::PLANT; - break; - case df::enums::tiletype_material::HFS: - return RemoteFortressReader::HFS; - break; - case df::enums::tiletype_material::CAMPFIRE: - return RemoteFortressReader::CAMPFIRE; - break; - case df::enums::tiletype_material::FIRE: - return RemoteFortressReader::FIRE; - break; - case df::enums::tiletype_material::ASHES: - return RemoteFortressReader::ASHES; - break; - case df::enums::tiletype_material::MAGMA: - return RemoteFortressReader::MAGMA; - break; - case df::enums::tiletype_material::DRIFTWOOD: - return RemoteFortressReader::DRIFTWOOD; - break; - case df::enums::tiletype_material::POOL: - return RemoteFortressReader::POOL; - break; - case df::enums::tiletype_material::BROOK: - return RemoteFortressReader::BROOK; - break; - case df::enums::tiletype_material::RIVER: - return RemoteFortressReader::RIVER; - break; + switch (material) + { + case df::enums::tiletype_material::NONE: + return RemoteFortressReader::NO_MATERIAL; + break; + case df::enums::tiletype_material::AIR: + return RemoteFortressReader::AIR; + break; + case df::enums::tiletype_material::SOIL: + return RemoteFortressReader::SOIL; + break; + case df::enums::tiletype_material::STONE: + return RemoteFortressReader::STONE; + break; + case df::enums::tiletype_material::FEATURE: + return RemoteFortressReader::FEATURE; + break; + case df::enums::tiletype_material::LAVA_STONE: + return RemoteFortressReader::LAVA_STONE; + break; + case df::enums::tiletype_material::MINERAL: + return RemoteFortressReader::MINERAL; + break; + case df::enums::tiletype_material::FROZEN_LIQUID: + return RemoteFortressReader::FROZEN_LIQUID; + break; + case df::enums::tiletype_material::CONSTRUCTION: + return RemoteFortressReader::CONSTRUCTION; + break; + case df::enums::tiletype_material::GRASS_LIGHT: + return RemoteFortressReader::GRASS_LIGHT; + break; + case df::enums::tiletype_material::GRASS_DARK: + return RemoteFortressReader::GRASS_DARK; + break; + case df::enums::tiletype_material::GRASS_DRY: + return RemoteFortressReader::GRASS_DRY; + break; + case df::enums::tiletype_material::GRASS_DEAD: + return RemoteFortressReader::GRASS_DEAD; + break; + case df::enums::tiletype_material::PLANT: + return RemoteFortressReader::PLANT; + break; + case df::enums::tiletype_material::HFS: + return RemoteFortressReader::HFS; + break; + case df::enums::tiletype_material::CAMPFIRE: + return RemoteFortressReader::CAMPFIRE; + break; + case df::enums::tiletype_material::FIRE: + return RemoteFortressReader::FIRE; + break; + case df::enums::tiletype_material::ASHES: + return RemoteFortressReader::ASHES; + break; + case df::enums::tiletype_material::MAGMA: + return RemoteFortressReader::MAGMA; + break; + case df::enums::tiletype_material::DRIFTWOOD: + return RemoteFortressReader::DRIFTWOOD; + break; + case df::enums::tiletype_material::POOL: + return RemoteFortressReader::POOL; + break; + case df::enums::tiletype_material::BROOK: + return RemoteFortressReader::BROOK; + break; + case df::enums::tiletype_material::RIVER: + return RemoteFortressReader::RIVER; + break; #if DF_VERSION_INT > 40001 - case df::enums::tiletype_material::ROOT: - return RemoteFortressReader::ROOT; - break; - case df::enums::tiletype_material::TREE: - return RemoteFortressReader::TREE_MATERIAL; - break; - case df::enums::tiletype_material::MUSHROOM: - return RemoteFortressReader::MUSHROOM; - break; - case df::enums::tiletype_material::UNDERWORLD_GATE: - return RemoteFortressReader::UNDERWORLD_GATE; - break; + case df::enums::tiletype_material::ROOT: + return RemoteFortressReader::ROOT; + break; + case df::enums::tiletype_material::TREE: + return RemoteFortressReader::TREE_MATERIAL; + break; + case df::enums::tiletype_material::MUSHROOM: + return RemoteFortressReader::MUSHROOM; + break; + case df::enums::tiletype_material::UNDERWORLD_GATE: + return RemoteFortressReader::UNDERWORLD_GATE; + break; #endif - default: - return RemoteFortressReader::NO_MATERIAL; - break; - } - return RemoteFortressReader::NO_MATERIAL; + default: + return RemoteFortressReader::NO_MATERIAL; + break; + } + return RemoteFortressReader::NO_MATERIAL; } RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special special) { - switch (special) - { - case df::enums::tiletype_special::NONE: - return RemoteFortressReader::NO_SPECIAL; - break; - case df::enums::tiletype_special::NORMAL: - return RemoteFortressReader::NORMAL; - break; - case df::enums::tiletype_special::RIVER_SOURCE: - return RemoteFortressReader::RIVER_SOURCE; - break; - case df::enums::tiletype_special::WATERFALL: - return RemoteFortressReader::WATERFALL; - break; - case df::enums::tiletype_special::SMOOTH: - return RemoteFortressReader::SMOOTH; - break; - case df::enums::tiletype_special::FURROWED: - return RemoteFortressReader::FURROWED; - break; - case df::enums::tiletype_special::WET: - return RemoteFortressReader::WET; - break; - case df::enums::tiletype_special::DEAD: - return RemoteFortressReader::DEAD; - break; - case df::enums::tiletype_special::WORN_1: - return RemoteFortressReader::WORN_1; - break; - case df::enums::tiletype_special::WORN_2: - return RemoteFortressReader::WORN_2; - break; - case df::enums::tiletype_special::WORN_3: - return RemoteFortressReader::WORN_3; - break; - case df::enums::tiletype_special::TRACK: - return RemoteFortressReader::TRACK; - break; + switch (special) + { + case df::enums::tiletype_special::NONE: + return RemoteFortressReader::NO_SPECIAL; + break; + case df::enums::tiletype_special::NORMAL: + return RemoteFortressReader::NORMAL; + break; + case df::enums::tiletype_special::RIVER_SOURCE: + return RemoteFortressReader::RIVER_SOURCE; + break; + case df::enums::tiletype_special::WATERFALL: + return RemoteFortressReader::WATERFALL; + break; + case df::enums::tiletype_special::SMOOTH: + return RemoteFortressReader::SMOOTH; + break; + case df::enums::tiletype_special::FURROWED: + return RemoteFortressReader::FURROWED; + break; + case df::enums::tiletype_special::WET: + return RemoteFortressReader::WET; + break; + case df::enums::tiletype_special::DEAD: + return RemoteFortressReader::DEAD; + break; + case df::enums::tiletype_special::WORN_1: + return RemoteFortressReader::WORN_1; + break; + case df::enums::tiletype_special::WORN_2: + return RemoteFortressReader::WORN_2; + break; + case df::enums::tiletype_special::WORN_3: + return RemoteFortressReader::WORN_3; + break; + case df::enums::tiletype_special::TRACK: + return RemoteFortressReader::TRACK; + break; #if DF_VERSION_INT > 40001 - case df::enums::tiletype_special::SMOOTH_DEAD: - return RemoteFortressReader::SMOOTH_DEAD; - break; + case df::enums::tiletype_special::SMOOTH_DEAD: + return RemoteFortressReader::SMOOTH_DEAD; + break; #endif - default: - return RemoteFortressReader::NO_SPECIAL; - break; - } - return RemoteFortressReader::NO_SPECIAL; + default: + return RemoteFortressReader::NO_SPECIAL; + break; + } + return RemoteFortressReader::NO_SPECIAL; } RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) { - switch (shape) - { - case df::enums::tiletype_shape::NONE: - return RemoteFortressReader::NO_SHAPE; - break; - case df::enums::tiletype_shape::EMPTY: - return RemoteFortressReader::EMPTY; - break; - case df::enums::tiletype_shape::FLOOR: - return RemoteFortressReader::FLOOR; - break; - case df::enums::tiletype_shape::BOULDER: - return RemoteFortressReader::BOULDER; - break; - case df::enums::tiletype_shape::PEBBLES: - return RemoteFortressReader::PEBBLES; - break; - case df::enums::tiletype_shape::WALL: - return RemoteFortressReader::WALL; - break; - case df::enums::tiletype_shape::FORTIFICATION: - return RemoteFortressReader::FORTIFICATION; - break; - case df::enums::tiletype_shape::STAIR_UP: - return RemoteFortressReader::STAIR_UP; - break; - case df::enums::tiletype_shape::STAIR_DOWN: - return RemoteFortressReader::STAIR_DOWN; - break; - case df::enums::tiletype_shape::STAIR_UPDOWN: - return RemoteFortressReader::STAIR_UPDOWN; - break; - case df::enums::tiletype_shape::RAMP: - return RemoteFortressReader::RAMP; - break; - case df::enums::tiletype_shape::RAMP_TOP: - return RemoteFortressReader::RAMP_TOP; - break; - case df::enums::tiletype_shape::BROOK_BED: - return RemoteFortressReader::BROOK_BED; - break; - case df::enums::tiletype_shape::BROOK_TOP: - return RemoteFortressReader::BROOK_TOP; - break; + switch (shape) + { + case df::enums::tiletype_shape::NONE: + return RemoteFortressReader::NO_SHAPE; + break; + case df::enums::tiletype_shape::EMPTY: + return RemoteFortressReader::EMPTY; + break; + case df::enums::tiletype_shape::FLOOR: + return RemoteFortressReader::FLOOR; + break; + case df::enums::tiletype_shape::BOULDER: + return RemoteFortressReader::BOULDER; + break; + case df::enums::tiletype_shape::PEBBLES: + return RemoteFortressReader::PEBBLES; + break; + case df::enums::tiletype_shape::WALL: + return RemoteFortressReader::WALL; + break; + case df::enums::tiletype_shape::FORTIFICATION: + return RemoteFortressReader::FORTIFICATION; + break; + case df::enums::tiletype_shape::STAIR_UP: + return RemoteFortressReader::STAIR_UP; + break; + case df::enums::tiletype_shape::STAIR_DOWN: + return RemoteFortressReader::STAIR_DOWN; + break; + case df::enums::tiletype_shape::STAIR_UPDOWN: + return RemoteFortressReader::STAIR_UPDOWN; + break; + case df::enums::tiletype_shape::RAMP: + return RemoteFortressReader::RAMP; + break; + case df::enums::tiletype_shape::RAMP_TOP: + return RemoteFortressReader::RAMP_TOP; + break; + case df::enums::tiletype_shape::BROOK_BED: + return RemoteFortressReader::BROOK_BED; + break; + case df::enums::tiletype_shape::BROOK_TOP: + return RemoteFortressReader::BROOK_TOP; + break; #if DF_VERSION_INT > 40001 - case df::enums::tiletype_shape::BRANCH: - return RemoteFortressReader::BRANCH; - break; + case df::enums::tiletype_shape::BRANCH: + return RemoteFortressReader::BRANCH; + break; #endif #if DF_VERSION_INT < 40001 - case df::enums::tiletype_shape::TREE: - return RemoteFortressReader::TREE_SHAPE; - break; + case df::enums::tiletype_shape::TREE: + return RemoteFortressReader::TREE_SHAPE; + break; #endif #if DF_VERSION_INT > 40001 - case df::enums::tiletype_shape::TRUNK_BRANCH: - return RemoteFortressReader::TRUNK_BRANCH; - break; - case df::enums::tiletype_shape::TWIG: - return RemoteFortressReader::TWIG; - break; + case df::enums::tiletype_shape::TRUNK_BRANCH: + return RemoteFortressReader::TRUNK_BRANCH; + break; + case df::enums::tiletype_shape::TWIG: + return RemoteFortressReader::TWIG; + break; #endif - case df::enums::tiletype_shape::SAPLING: - return RemoteFortressReader::SAPLING; - break; - case df::enums::tiletype_shape::SHRUB: - return RemoteFortressReader::SHRUB; - break; - case df::enums::tiletype_shape::ENDLESS_PIT: - return RemoteFortressReader::EMPTY; - break; - default: - return RemoteFortressReader::NO_SHAPE; - break; - } - return RemoteFortressReader::NO_SHAPE; + case df::enums::tiletype_shape::SAPLING: + return RemoteFortressReader::SAPLING; + break; + case df::enums::tiletype_shape::SHRUB: + return RemoteFortressReader::SHRUB; + break; + case df::enums::tiletype_shape::ENDLESS_PIT: + return RemoteFortressReader::EMPTY; + break; + default: + return RemoteFortressReader::NO_SHAPE; + break; + } + return RemoteFortressReader::NO_SHAPE; } RemoteFortressReader::TiletypeVariant TranslateVariant(df::tiletype_variant variant) { - switch (variant) - { - case df::enums::tiletype_variant::NONE: - return RemoteFortressReader::NO_VARIANT; - break; - case df::enums::tiletype_variant::VAR_1: - return RemoteFortressReader::VAR_1; - break; - case df::enums::tiletype_variant::VAR_2: - return RemoteFortressReader::VAR_2; - break; - case df::enums::tiletype_variant::VAR_3: - return RemoteFortressReader::VAR_3; - break; - case df::enums::tiletype_variant::VAR_4: - return RemoteFortressReader::VAR_4; - break; - default: - return RemoteFortressReader::NO_VARIANT; - break; - } - return RemoteFortressReader::NO_VARIANT; + switch (variant) + { + case df::enums::tiletype_variant::NONE: + return RemoteFortressReader::NO_VARIANT; + break; + case df::enums::tiletype_variant::VAR_1: + return RemoteFortressReader::VAR_1; + break; + case df::enums::tiletype_variant::VAR_2: + return RemoteFortressReader::VAR_2; + break; + case df::enums::tiletype_variant::VAR_3: + return RemoteFortressReader::VAR_3; + break; + case df::enums::tiletype_variant::VAR_4: + return RemoteFortressReader::VAR_4; + break; + default: + return RemoteFortressReader::NO_VARIANT; + break; + } + return RemoteFortressReader::NO_VARIANT; } static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) { - clock_t start = clock(); - for (int i = 0; i < world->map.map_blocks.size(); i++) - { - df::map_block * block = world->map.map_blocks[i]; - fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); - } - clock_t end = clock(); - double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; - stream.print("Checking all hashes took %f seconds.", elapsed_secs); - return CR_OK; + clock_t start = clock(); + for (int i = 0; i < world->map.map_blocks.size(); i++) + { + df::map_block * block = world->map.map_blocks[i]; + fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); + } + clock_t end = clock(); + double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; + stream.print("Checking all hashes took %f seconds.", elapsed_secs); + return CR_OK; } void CopyMat(RemoteFortressReader::MatPair * mat, int type, int index) { - if (type >= MaterialInfo::FIGURE_BASE && type < MaterialInfo::PLANT_BASE) - { - df::historical_figure * figure = df::historical_figure::find(index); - if (figure) - { - type -= MaterialInfo::GROUP_SIZE; - index = figure->race; - } - } - mat->set_mat_type(type); - mat->set_mat_index(index); + if (type >= MaterialInfo::FIGURE_BASE && type < MaterialInfo::PLANT_BASE) + { + df::historical_figure * figure = df::historical_figure::find(index); + if (figure) + { + type -= MaterialInfo::GROUP_SIZE; + index = figure->race; + } + } + mat->set_mat_type(type); + mat->set_mat_index(index); } @@ -637,2249 +637,2263 @@ map hashes; bool IsTiletypeChanged(DFCoord pos) { - uint16_t hash; - df::map_block * block = Maps::getBlock(pos); - if (block) - hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); - else - hash = 0; - if (hashes[pos] != hash) - { - hashes[pos] = hash; - return true; - } - return false; + uint16_t hash; + df::map_block * block = Maps::getBlock(pos); + if (block) + hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); + else + hash = 0; + if (hashes[pos] != hash) + { + hashes[pos] = hash; + return true; + } + return false; } map waterHashes; bool IsDesignationChanged(DFCoord pos) { - uint16_t hash; - df::map_block * block = Maps::getBlock(pos); - if (block) - hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); - else - hash = 0; - if (waterHashes[pos] != hash) - { - waterHashes[pos] = hash; - return true; - } - return false; + uint16_t hash; + df::map_block * block = Maps::getBlock(pos); + if (block) + hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); + else + hash = 0; + if (waterHashes[pos] != hash) + { + waterHashes[pos] = hash; + return true; + } + return false; } map buildingHashes; bool IsBuildingChanged(DFCoord pos) { - df::map_block * block = Maps::getBlock(pos); - bool changed = false; - for (int x = 0; x < 16; x++) - for (int y = 0; y < 16; y++) - { - DFCoord localPos = DFCoord(pos.x * 16 + x, pos.y * 16 + y, pos.z); - auto bld = block->occupancy[x][y].bits.building; - if (buildingHashes[pos] != bld) - { - buildingHashes[pos] = bld; - changed = true; - } - } - return changed; + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + DFCoord localPos = DFCoord(pos.x * 16 + x, pos.y * 16 + y, pos.z); + auto bld = block->occupancy[x][y].bits.building; + if (buildingHashes[pos] != bld) + { + buildingHashes[pos] = bld; + changed = true; + } + } + return changed; } map spatterHashes; bool IsspatterChanged(DFCoord pos) { - df::map_block * block = Maps::getBlock(pos); - bool changed = false; - std::vector materials; + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + std::vector materials; #if DF_VERSION_INT > 34011 - std::vector items; - if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) - return false; + std::vector items; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return false; #else - if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL)) - return false; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL)) + return false; #endif - uint16_t hash = 0; + uint16_t hash = 0; - for (int i = 0; i < materials.size(); i++) - { - auto mat = materials[i]; - hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); - } + for (int i = 0; i < materials.size(); i++) + { + auto mat = materials[i]; + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); + } #if DF_VERSION_INT > 34011 - for (int i = 0; i < items.size(); i++) - { - auto item = items[i]; - hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); - } + for (int i = 0; i < items.size(); i++) + { + auto item = items[i]; + hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); + } #endif - if (spatterHashes[pos] != hash) - { - spatterHashes[pos] = hash; - return true; - } - return false; + if (spatterHashes[pos] != hash) + { + spatterHashes[pos] = hash; + return true; + } + return false; } map itemHashes; bool isItemChanged(int i) { - uint16_t hash = 0; - auto item = df::item::find(i); - if (item) - { - hash = fletcher16((uint8_t*)item, sizeof(df::item)); - } - if (itemHashes[i] != hash) - { - itemHashes[i] = hash; - return true; - } - return false; + uint16_t hash = 0; + auto item = df::item::find(i); + if (item) + { + hash = fletcher16((uint8_t*)item, sizeof(df::item)); + } + if (itemHashes[i] != hash) + { + itemHashes[i] = hash; + return true; + } + return false; } bool areItemsChanged(vector * items) { - bool result = false; - for (int i = 0; i < items->size(); i++) - { - if (isItemChanged(items->at(i))) - result = true; - } - return result; + bool result = false; + for (int i = 0; i < items->size(); i++) + { + if (isItemChanged(items->at(i))) + result = true; + } + return result; } static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { - hashes.clear(); - waterHashes.clear(); - buildingHashes.clear(); - spatterHashes.clear(); - itemHashes.clear(); - return CR_OK; + hashes.clear(); + waterHashes.clear(); + buildingHashes.clear(); + spatterHashes.clear(); + itemHashes.clear(); + return CR_OK; } df::matter_state GetState(df::material * mat, uint16_t temp = 10015) { - df::matter_state state = matter_state::Solid; - if (temp >= mat->heat.melting_point) - state = df::matter_state::Liquid; - if (temp >= mat->heat.boiling_point) - state = matter_state::Gas; - return state; + df::matter_state state = matter_state::Solid; + if (temp >= mat->heat.melting_point) + state = df::matter_state::Liquid; + if (temp >= mat->heat.boiling_point) + state = matter_state::Gas; + return state; } static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - - - - df::world_raws *raws = &world->raws; - df::world_history *history = &world->history; - MaterialInfo mat; - for (int i = 0; i < raws->inorganics.size(); i++) - { - mat.decode(0, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(0); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); - } - } - for (int i = 0; i < 19; i++) - { - int k = -1; - if (i == 7) - k = 1;// for coal. - for (int j = -1; j <= k; j++) - { - mat.decode(i, j); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(i); - mat_def->mutable_mat_pair()->set_mat_index(j); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); - } - } - } - for (int i = 0; i < raws->creatures.all.size(); i++) - { - df::creature_raw * creature = raws->creatures.all[i]; - for (int j = 0; j < creature->material.size(); j++) - { - mat.decode(j + MaterialInfo::CREATURE_BASE, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + 19); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); - } - } - } - //for (int i = 0; i < history->figures.size(); i++) - //{ - // df::historical_figure * figure = history->figures[i]; - // if (figure->race < 0) - // continue; - // df::creature_raw * creature = raws->creatures.all[figure->race]; - // for (int j = 0; j < creature->material.size(); j++) - // { - // mat.decode(j + MaterialInfo::FIGURE_BASE, i); - // MaterialDefinition *mat_def = out->add_material_list(); - // mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); - // mat_def->mutable_mat_pair()->set_mat_index(i); - // stringstream id; - // id << "HF" << i << mat.getToken(); - // mat_def->set_id(id.str()); - // mat_def->set_name(mat.toString()); //find the name at cave temperature; - // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) - // { - // df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; - // mat_def->mutable_state_color()->set_red(color->red * 255); - // mat_def->mutable_state_color()->set_green(color->green * 255); - // mat_def->mutable_state_color()->set_blue(color->blue * 255); - // } - // } - //} - for (int i = 0; i < raws->plants.all.size(); i++) - { - df::plant_raw * plant = raws->plants.all[i]; - for (int j = 0; j < plant->material.size(); j++) - { - mat.decode(j + 419, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + 419); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) - { - ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); - } - } - } - return CR_OK; + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + + + + df::world_raws *raws = &world->raws; + df::world_history *history = &world->history; + MaterialInfo mat; + for (int i = 0; i < raws->inorganics.size(); i++) + { + mat.decode(0, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(0); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); + } + } + for (int i = 0; i < 19; i++) + { + int k = -1; + if (i == 7) + k = 1;// for coal. + for (int j = -1; j <= k; j++) + { + mat.decode(i, j); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(i); + mat_def->mutable_mat_pair()->set_mat_index(j); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); + } + } + } + for (int i = 0; i < raws->creatures.all.size(); i++) + { + df::creature_raw * creature = raws->creatures.all[i]; + for (int j = 0; j < creature->material.size(); j++) + { + mat.decode(j + MaterialInfo::CREATURE_BASE, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + 19); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); + } + } + } + //for (int i = 0; i < history->figures.size(); i++) + //{ + // df::historical_figure * figure = history->figures[i]; + // if (figure->race < 0) + // continue; + // df::creature_raw * creature = raws->creatures.all[figure->race]; + // for (int j = 0; j < creature->material.size(); j++) + // { + // mat.decode(j + MaterialInfo::FIGURE_BASE, i); + // MaterialDefinition *mat_def = out->add_material_list(); + // mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); + // mat_def->mutable_mat_pair()->set_mat_index(i); + // stringstream id; + // id << "HF" << i << mat.getToken(); + // mat_def->set_id(id.str()); + // mat_def->set_name(mat.toString()); //find the name at cave temperature; + // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + // { + // df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; + // mat_def->mutable_state_color()->set_red(color->red * 255); + // mat_def->mutable_state_color()->set_green(color->green * 255); + // mat_def->mutable_state_color()->set_blue(color->blue * 255); + // } + // } + //} + for (int i = 0; i < raws->plants.all.size(); i++) + { + df::plant_raw * plant = raws->plants.all[i]; + for (int j = 0; j < plant->material.size(); j++) + { + mat.decode(j + 419, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + 419); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) + { + ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); + } + } + } + return CR_OK; } static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - FOR_ENUM_ITEMS(item_type, it) - { - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(-1); - mat_def->set_id(ENUM_KEY_STR(item_type, it)); - if (it == item_type::BOX) - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(0); - mat_def->set_id("BOX_CHEST"); - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(1); - mat_def->set_id("BOX_BAG"); - } - int subtypes = Items::getSubtypeCount(it); - if (subtypes >= 0) - { - for (int i = 0; i < subtypes; i++) - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(i); - df::itemdef * item = Items::getSubtypeDef(it, i); - mat_def->set_id(item->id); - } - } - } - - - return CR_OK; + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + FOR_ENUM_ITEMS(item_type, it) + { + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(-1); + mat_def->set_id(ENUM_KEY_STR(item_type, it)); + if (it == item_type::BOX) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("BOX_CHEST"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("BOX_BAG"); + } + int subtypes = Items::getSubtypeCount(it); + if (subtypes >= 0) + { + for (int i = 0; i < subtypes; i++) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + df::itemdef * item = Items::getSubtypeDef(it, i); + mat_def->set_id(item->id); + } + } + } + + + return CR_OK; } static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } - df::world_raws *raws = &world->raws; - if (!raws) - return CR_OK;//'. + df::world_raws *raws = &world->raws; + if (!raws) + return CR_OK;//'. - for (int i = 0; i < raws->plants.all.size(); i++) - { - df::plant_raw * pp = raws->plants.all[i]; - if (!pp) - continue; - MaterialDefinition * basePlant = out->add_material_list(); - basePlant->set_id(pp->id + ":BASE"); - basePlant->set_name(pp->name); - basePlant->mutable_mat_pair()->set_mat_type(-1); - basePlant->mutable_mat_pair()->set_mat_index(i); + for (int i = 0; i < raws->plants.all.size(); i++) + { + df::plant_raw * pp = raws->plants.all[i]; + if (!pp) + continue; + MaterialDefinition * basePlant = out->add_material_list(); + basePlant->set_id(pp->id + ":BASE"); + basePlant->set_name(pp->name); + basePlant->mutable_mat_pair()->set_mat_type(-1); + basePlant->mutable_mat_pair()->set_mat_index(i); #if DF_VERSION_INT > 40001 - for (int g = 0; g < pp->growths.size(); g++) - { - df::plant_growth* growth = pp->growths[g]; - if (!growth) - continue; - for (int l = 0; l < GROWTH_LOCATIONS_SIZE; l++) - { - MaterialDefinition * out_growth = out->add_material_list(); - out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); - out_growth->set_name(growth->name); - out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); - out_growth->mutable_mat_pair()->set_mat_index(i); - } - } + for (int g = 0; g < pp->growths.size(); g++) + { + df::plant_growth* growth = pp->growths[g]; + if (!growth) + continue; + for (int l = 0; l < GROWTH_LOCATIONS_SIZE; l++) + { + MaterialDefinition * out_growth = out->add_material_list(); + out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); + out_growth->set_name(growth->name); + out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); + out_growth->mutable_mat_pair()->set_mat_index(i); + } + } #endif - } - return CR_OK; + } + return CR_OK; } void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - - MapExtras::Block * block = MC->BlockAtTile(DfBlock->map_pos); - - int trunk_percent[16][16]; - int tree_x[16][16]; - int tree_y[16][16]; - int tree_z[16][16]; - for (int xx = 0; xx < 16; xx++) - for (int yy = 0; yy < 16; yy++) - { - trunk_percent[xx][yy] = 255; - tree_x[xx][yy] = -3000; - tree_y[xx][yy] = -3000; - tree_z[xx][yy] = -3000; - } + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + + MapExtras::Block * block = MC->BlockAtTile(DfBlock->map_pos); + + int trunk_percent[16][16]; + int tree_x[16][16]; + int tree_y[16][16]; + int tree_z[16][16]; + for (int xx = 0; xx < 16; xx++) + for (int yy = 0; yy < 16; yy++) + { + trunk_percent[xx][yy] = 255; + tree_x[xx][yy] = -3000; + tree_y[xx][yy] = -3000; + tree_z[xx][yy] = -3000; + } #if DF_VERSION_INT > 34011 - df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; - for (int i = 0; i < column->plants.size(); i++) - { - df::plant* plant = column->plants[i]; - if (plant->tree_info == NULL) - continue; - df::plant_tree_info * tree_info = plant->tree_info; - if ( - plant->pos.z - tree_info->roots_depth > DfBlock->map_pos.z - || (plant->pos.z + tree_info->body_height) <= DfBlock->map_pos.z - || (plant->pos.x - tree_info->dim_x / 2) > (DfBlock->map_pos.x + 16) - || (plant->pos.x + tree_info->dim_x / 2) < (DfBlock->map_pos.x) - || (plant->pos.y - tree_info->dim_y / 2) > (DfBlock->map_pos.y + 16) - || (plant->pos.y + tree_info->dim_y / 2) < (DfBlock->map_pos.y) - ) - continue; - DFCoord localPos = plant->pos - DfBlock->map_pos; - for (int xx = 0; xx < tree_info->dim_x; xx++) - for (int yy = 0; yy < tree_info->dim_y; yy++) - { - int xxx = localPos.x - (tree_info->dim_x / 2) + xx; - int yyy = localPos.y - (tree_info->dim_y / 2) + yy; - if (xxx < 0 - || yyy < 0 - || xxx >= 16 - || yyy >= 16 - ) - continue; - df::plant_tree_tile tile; - if (-localPos.z < 0) - { - tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; - } - else - { - tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; - } - if (!tile.whole || tile.bits.blocked) - continue; - if (tree_info->body_height <= 1) - trunk_percent[xxx][yyy] = 0; - else - trunk_percent[xxx][yyy] = -localPos.z * 100 / (tree_info->body_height - 1); - tree_x[xxx][yyy] = xx - tree_info->dim_x / 2; - tree_y[xxx][yyy] = yy - tree_info->dim_y / 2; - tree_z[xxx][yyy] = localPos.z; - } - } + df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; + for (int i = 0; i < column->plants.size(); i++) + { + df::plant* plant = column->plants[i]; + if (plant->tree_info == NULL) + continue; + df::plant_tree_info * tree_info = plant->tree_info; + if ( + plant->pos.z - tree_info->roots_depth > DfBlock->map_pos.z + || (plant->pos.z + tree_info->body_height) <= DfBlock->map_pos.z + || (plant->pos.x - tree_info->dim_x / 2) > (DfBlock->map_pos.x + 16) + || (plant->pos.x + tree_info->dim_x / 2) < (DfBlock->map_pos.x) + || (plant->pos.y - tree_info->dim_y / 2) > (DfBlock->map_pos.y + 16) + || (plant->pos.y + tree_info->dim_y / 2) < (DfBlock->map_pos.y) + ) + continue; + DFCoord localPos = plant->pos - DfBlock->map_pos; + for (int xx = 0; xx < tree_info->dim_x; xx++) + for (int yy = 0; yy < tree_info->dim_y; yy++) + { + int xxx = localPos.x - (tree_info->dim_x / 2) + xx; + int yyy = localPos.y - (tree_info->dim_y / 2) + yy; + if (xxx < 0 + || yyy < 0 + || xxx >= 16 + || yyy >= 16 + ) + continue; + df::plant_tree_tile tile; + if (-localPos.z < 0) + { + tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; + } + else + { + tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; + } + if (!tile.whole || tile.bits.blocked) + continue; + if (tree_info->body_height <= 1) + trunk_percent[xxx][yyy] = 0; + else + trunk_percent[xxx][yyy] = -localPos.z * 100 / (tree_info->body_height - 1); + tree_x[xxx][yyy] = xx - tree_info->dim_x / 2; + tree_y[xxx][yyy] = yy - tree_info->dim_y / 2; + tree_z[xxx][yyy] = localPos.z; + } + } #endif - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - df::tiletype tile = DfBlock->tiletype[xx][yy]; - NetBlock->add_tiles(tile); - df::coord2d p = df::coord2d(xx, yy); - t_matpair baseMat = block->baseMaterialAt(p); - t_matpair staticMat = block->staticMaterialAt(p); - switch (tileMaterial(tile)) - { - case tiletype_material::FROZEN_LIQUID: - staticMat.mat_type = builtin_mats::WATER; - staticMat.mat_index = -1; - break; - default: - break; - } - CopyMat(NetBlock->add_materials(), staticMat.mat_type, staticMat.mat_index); - CopyMat(NetBlock->add_layer_materials(), 0, block->layerMaterialAt(p)); - CopyMat(NetBlock->add_vein_materials(), 0, block->veinMaterialAt(p)); - CopyMat(NetBlock->add_base_materials(), baseMat.mat_type, baseMat.mat_index); - RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); - CopyMat(constructionItem, -1, -1); - if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) - { - df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); - if (con) - { - CopyMat(constructionItem, con->item_type, con->item_subtype); - } - } - NetBlock->add_tree_percent(trunk_percent[xx][yy]); - NetBlock->add_tree_x(tree_x[xx][yy]); - NetBlock->add_tree_y(tree_y[xx][yy]); - NetBlock->add_tree_z(tree_z[xx][yy]); - } + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + df::tiletype tile = DfBlock->tiletype[xx][yy]; + NetBlock->add_tiles(tile); + df::coord2d p = df::coord2d(xx, yy); + t_matpair baseMat = block->baseMaterialAt(p); + t_matpair staticMat = block->staticMaterialAt(p); + switch (tileMaterial(tile)) + { + case tiletype_material::FROZEN_LIQUID: + staticMat.mat_type = builtin_mats::WATER; + staticMat.mat_index = -1; + break; + default: + break; + } + CopyMat(NetBlock->add_materials(), staticMat.mat_type, staticMat.mat_index); + CopyMat(NetBlock->add_layer_materials(), 0, block->layerMaterialAt(p)); + CopyMat(NetBlock->add_vein_materials(), 0, block->veinMaterialAt(p)); + CopyMat(NetBlock->add_base_materials(), baseMat.mat_type, baseMat.mat_index); + RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); + CopyMat(constructionItem, -1, -1); + if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) + { + df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); + if (con) + { + CopyMat(constructionItem, con->item_type, con->item_subtype); + } + } + NetBlock->add_tree_percent(trunk_percent[xx][yy]); + NetBlock->add_tree_x(tree_x[xx][yy]); + NetBlock->add_tree_y(tree_y[xx][yy]); + NetBlock->add_tree_z(tree_z[xx][yy]); + } } void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - df::tile_designation designation = DfBlock->designation[xx][yy]; - df::tile_occupancy occupancy = DfBlock->occupancy[xx][yy]; - int lava = 0; - int water = 0; - if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) - lava = designation.bits.flow_size; - else - water = designation.bits.flow_size; - NetBlock->add_magma(lava); - NetBlock->add_water(water); - NetBlock->add_aquifer(designation.bits.water_table); - NetBlock->add_light(designation.bits.light); - NetBlock->add_outside(designation.bits.outside); - NetBlock->add_subterranean(designation.bits.subterranean); - NetBlock->add_water_salt(designation.bits.water_salt); - NetBlock->add_water_stagnant(designation.bits.water_stagnant); - if (gamemode && (*gamemode == game_mode::ADVENTURE)) - { - auto fog_of_war = DfBlock->fog_of_war[xx][yy]; - NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - NetBlock->add_tile_dig_designation_marker(false); - NetBlock->add_tile_dig_designation_auto(false); - } - else - { - NetBlock->add_hidden(designation.bits.hidden); + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + df::tile_designation designation = DfBlock->designation[xx][yy]; + df::tile_occupancy occupancy = DfBlock->occupancy[xx][yy]; + int lava = 0; + int water = 0; + if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) + lava = designation.bits.flow_size; + else + water = designation.bits.flow_size; + NetBlock->add_magma(lava); + NetBlock->add_water(water); + NetBlock->add_aquifer(designation.bits.water_table); + NetBlock->add_light(designation.bits.light); + NetBlock->add_outside(designation.bits.outside); + NetBlock->add_subterranean(designation.bits.subterranean); + NetBlock->add_water_salt(designation.bits.water_salt); + NetBlock->add_water_stagnant(designation.bits.water_stagnant); + if (gamemode && (*gamemode == game_mode::ADVENTURE)) + { + auto fog_of_war = DfBlock->fog_of_war[xx][yy]; + NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + NetBlock->add_tile_dig_designation_marker(false); + NetBlock->add_tile_dig_designation_auto(false); + } + else + { + NetBlock->add_hidden(designation.bits.hidden); #if DF_VERSION_INT > 34011 - NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); - NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); + NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); + NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); #endif - switch (designation.bits.dig) - { - case df::enums::tile_dig_designation::No: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - case df::enums::tile_dig_designation::Default: - NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); - break; - case df::enums::tile_dig_designation::UpDownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::Channel: - NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); - break; - case df::enums::tile_dig_designation::Ramp: - NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); - break; - case df::enums::tile_dig_designation::DownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::UpStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); - break; - default: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - } - } - } + switch (designation.bits.dig) + { + case df::enums::tile_dig_designation::No: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + case df::enums::tile_dig_designation::Default: + NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); + break; + case df::enums::tile_dig_designation::UpDownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::Channel: + NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); + break; + case df::enums::tile_dig_designation::Ramp: + NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); + break; + case df::enums::tile_dig_designation::DownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::UpStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); + break; + default: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + } + } + } #if DF_VERSION_INT > 34011 - for (int i = 0; i < world->jobs.postings.size(); i++) - { - auto job = world->jobs.postings[i]->job; - if (job == nullptr) - continue; - if ( - job->pos.z > DfBlock->map_pos.z - || job->pos.z < DfBlock->map_pos.z - || job->pos.x >= (DfBlock->map_pos.x + 16) - || job->pos.x < (DfBlock->map_pos.x) - || job->pos.y >= (DfBlock->map_pos.y + 16) - || job->pos.y < (DfBlock->map_pos.y) - ) - continue; - - int index = (job->pos.x - DfBlock->map_pos.x) + (16 * (job->pos.y - DfBlock->map_pos.y)); - - switch (job->job_type) - { - case job_type::Dig: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); - break; - case job_type::CarveUpwardStaircase: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_STAIR_DIG); - break; - case job_type::CarveDownwardStaircase: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DOWN_STAIR_DIG); - break; - case job_type::CarveUpDownStaircase: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_DOWN_STAIR_DIG); - break; - case job_type::CarveRamp: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::RAMP_DIG); - break; - case job_type::DigChannel: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::CHANNEL_DIG); - break; - case job_type::FellTree: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); - break; - case job_type::GatherPlants: - NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); - break; - default: - break; - } - } + for (int i = 0; i < world->jobs.postings.size(); i++) + { + auto job = world->jobs.postings[i]->job; + if (job == nullptr) + continue; + if ( + job->pos.z > DfBlock->map_pos.z + || job->pos.z < DfBlock->map_pos.z + || job->pos.x >= (DfBlock->map_pos.x + 16) + || job->pos.x < (DfBlock->map_pos.x) + || job->pos.y >= (DfBlock->map_pos.y + 16) + || job->pos.y < (DfBlock->map_pos.y) + ) + continue; + + int index = (job->pos.x - DfBlock->map_pos.x) + (16 * (job->pos.y - DfBlock->map_pos.y)); + + switch (job->job_type) + { + case job_type::Dig: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::CarveUpwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_STAIR_DIG); + break; + case job_type::CarveDownwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DOWN_STAIR_DIG); + break; + case job_type::CarveUpDownStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case job_type::CarveRamp: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::RAMP_DIG); + break; + case job_type::DigChannel: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::CHANNEL_DIG); + break; + case job_type::FellTree: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::GatherPlants: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + default: + break; + } + } #endif } void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) { - for (int i = 0; i < df::global::world->buildings.all.size(); i++) - { - df::building * bld = df::global::world->buildings.all[i]; - if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) - { - auto out_bld = NetBlock->add_buildings(); - out_bld->set_index(bld->id); - continue; - } - - int z2 = bld->z; - - if (bld->getType() == building_type::Well) - { - df::building_wellst * well_building = virtual_cast(bld); - if (well_building) - { - z2 = well_building->bucket_z; - } - } - if (bld->z < min.z || z2 >= max.z) - { - auto out_bld = NetBlock->add_buildings(); - out_bld->set_index(bld->id); - continue; - } - auto out_bld = NetBlock->add_buildings(); - CopyBuilding(i, out_bld); - df::building_actual* actualBuilding = virtual_cast(bld); - if (actualBuilding) - { - for (int i = 0; i < actualBuilding->contained_items.size(); i++) - { - auto buildingItem = out_bld->add_items(); - buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); - CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); - } - } - } + for (int i = 0; i < df::global::world->buildings.all.size(); i++) + { + df::building * bld = df::global::world->buildings.all[i]; + if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); + continue; + } + + int z2 = bld->z; + + if (bld->getType() == building_type::Well) + { + df::building_wellst * well_building = virtual_cast(bld); + if (well_building) + { + z2 = well_building->bucket_z; + } + } + if (bld->z < min.z || z2 >= max.z) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); + continue; + } + auto out_bld = NetBlock->add_buildings(); + CopyBuilding(i, out_bld); + df::building_actual* actualBuilding = virtual_cast(bld); + if (actualBuilding) + { + for (int i = 0; i < actualBuilding->contained_items.size(); i++) + { + auto buildingItem = out_bld->add_items(); + buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); + CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); + } + } + } } void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - std::vector materials; + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + std::vector materials; #if DF_VERSION_INT > 34011 - std::vector items; - std::vector grasses; - if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, &grasses, NULL, NULL, &items)) - return; + std::vector items; + std::vector grasses; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, &grasses, NULL, NULL, &items)) + return; #else - if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) - return; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) + return; #endif - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - auto send_pile = NetBlock->add_spatterpile(); - for (int i = 0; i < materials.size(); i++) - { - auto mat = materials[i]; - if (mat->amount[xx][yy] == 0) - continue; - auto send_spat = send_pile->add_spatters(); - send_spat->set_state((MatterState)mat->mat_state); - CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); - send_spat->set_amount(mat->amount[xx][yy]); - } + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + auto send_pile = NetBlock->add_spatterpile(); + for (int i = 0; i < materials.size(); i++) + { + auto mat = materials[i]; + if (mat->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + send_spat->set_state((MatterState)mat->mat_state); + CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); + send_spat->set_amount(mat->amount[xx][yy]); + } #if DF_VERSION_INT > 34011 - for (int i = 0; i < items.size(); i++) - { - auto item = items[i]; - if (item->amount[xx][yy] == 0) - continue; - auto send_spat = send_pile->add_spatters(); - CopyMat(send_spat->mutable_material(), item->mattype, item->matindex); - send_spat->set_amount(item->amount[xx][yy]); - auto send_item = send_spat->mutable_item(); - send_item->set_mat_type(item->item_type); - send_item->set_mat_index(item->item_subtype); - } - int grassPercent = 0; - for (int i = 0; i < grasses.size(); i++) - { - auto grass = grasses[i]; - if (grass->amount[xx][yy] > grassPercent) - grassPercent = grass->amount[xx][yy]; - } - NetBlock->add_grass_percent(grassPercent); + for (int i = 0; i < items.size(); i++) + { + auto item = items[i]; + if (item->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + CopyMat(send_spat->mutable_material(), item->mattype, item->matindex); + send_spat->set_amount(item->amount[xx][yy]); + auto send_item = send_spat->mutable_item(); + send_item->set_mat_type(item->item_type); + send_item->set_mat_index(item->item_subtype); + } + int grassPercent = 0; + for (int i = 0; i < grasses.size(); i++) + { + auto grass = grasses[i]; + if (grass->amount[xx][yy] > grassPercent) + grassPercent = grass->amount[xx][yy]; + } + NetBlock->add_grass_percent(grassPercent); #endif - } + } } void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { - NetItem->set_id(DfItem->id); - NetItem->set_flags1(DfItem->flags.whole); - NetItem->set_flags2(DfItem->flags2.whole); - auto pos = NetItem->mutable_pos(); - pos->set_x(DfItem->pos.x); - pos->set_y(DfItem->pos.y); - pos->set_z(DfItem->pos.z); - auto mat = NetItem->mutable_material(); - mat->set_mat_index(DfItem->getMaterialIndex()); - mat->set_mat_type(DfItem->getMaterial()); - auto type = NetItem->mutable_type(); - type->set_mat_type(DfItem->getType()); - type->set_mat_index(DfItem->getSubtype()); - if (DfItem->getType() == item_type::BOX) - { - type->set_mat_index(DfItem->isBag()); - } - VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem); - if (actual_item) - { - NetItem->set_stack_size(actual_item->stack_size); - } - VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); - if (constructed_item) - { - for (int i = 0; i < constructed_item->improvements.size(); i++) - { - auto improvement = constructed_item->improvements[i]; - if (!improvement || improvement->getType() != improvement_type::THREAD) - continue; - - auto improvement_thread = virtual_cast(improvement); - if (!improvement_thread || improvement_thread->dye.mat_type < 0) - continue; - - DFHack::MaterialInfo info; - if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) - continue; - - ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); - } - } - else if (DfItem->getType() == item_type::THREAD) - { - auto thread = virtual_cast(DfItem); - if (thread && thread->dye_mat_type >= 0) - { - DFHack::MaterialInfo info; - if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) - ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); - } - } + NetItem->set_id(DfItem->id); + NetItem->set_flags1(DfItem->flags.whole); + NetItem->set_flags2(DfItem->flags2.whole); + auto pos = NetItem->mutable_pos(); + pos->set_x(DfItem->pos.x); + pos->set_y(DfItem->pos.y); + pos->set_z(DfItem->pos.z); + auto mat = NetItem->mutable_material(); + mat->set_mat_index(DfItem->getMaterialIndex()); + mat->set_mat_type(DfItem->getMaterial()); + auto type = NetItem->mutable_type(); + type->set_mat_type(DfItem->getType()); + type->set_mat_index(DfItem->getSubtype()); + if (DfItem->getType() == item_type::BOX) + { + type->set_mat_index(DfItem->isBag()); + } + VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem); + if (actual_item) + { + NetItem->set_stack_size(actual_item->stack_size); + } + VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); + if (constructed_item) + { + for (int i = 0; i < constructed_item->improvements.size(); i++) + { + auto improvement = constructed_item->improvements[i]; + if (!improvement || improvement->getType() != improvement_type::THREAD) + continue; + + auto improvement_thread = virtual_cast(improvement); + if (!improvement_thread || improvement_thread->dye.mat_type < 0) + continue; + + DFHack::MaterialInfo info; + if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) + continue; + + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + } + else if (DfItem->getType() == item_type::THREAD) + { + auto thread = virtual_cast(DfItem); + if (thread && thread->dye_mat_type >= 0) + { + DFHack::MaterialInfo info; + if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + } } void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - for (int i = 0; i < DfBlock->items.size(); i++) - { - int id = DfBlock->items[i]; + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + for (int i = 0; i < DfBlock->items.size(); i++) + { + int id = DfBlock->items[i]; - auto item = df::item::find(id); - if (item) - CopyItem(NetBlock->add_items(), item); - } + auto item = df::item::find(id); + if (item) + CopyItem(NetBlock->add_items(), item); + } } static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) { - int x, y, z; - DFHack::Maps::getPosition(x, y, z); - out->set_map_x(x); - out->set_map_y(y); - MapExtras::MapCache MC; - int center_x = (in->min_x() + in->max_x()) / 2; - int center_y = (in->min_y() + in->max_y()) / 2; - - int NUMBER_OF_POINTS = ((in->max_x() - center_x + 1) * 2) * ((in->max_y() - center_y + 1) * 2); - int blocks_needed; - if (in->has_blocks_needed()) - blocks_needed = in->blocks_needed(); - else - blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z()); - int blocks_sent = 0; - int min_x = in->min_x(); - int min_y = in->min_y(); - int max_x = in->max_x(); - int max_y = in->max_y(); - int min_z = in->min_z(); - int max_z = in->max_z(); - bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest. - //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); - for (int zz = max_z - 1; zz >= min_z; zz--) - { - // (di, dj) is a vector - direction in which we move right now - int di = 1; - int dj = 0; - // length of current segment - int segment_length = 1; - // current position (i, j) and how much of current segment we passed - int i = center_x; - int j = center_y; - int segment_passed = 0; - for (int k = 0; k < NUMBER_OF_POINTS; ++k) - { - if (blocks_sent >= blocks_needed) - break; - if (!(i < min_x || i >= max_x || j < min_y || j >= max_y)) - { - DFCoord pos = DFCoord(i, j, zz); - df::map_block * block = DFHack::Maps::getBlock(pos); - if (block != NULL) - { - int nonAir = 0; - for (int xxx = 0; xxx < 16; xxx++) - for (int yyy = 0; yyy < 16; yyy++) - { - if ((DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::None && - DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) - || block->designation[xxx][yyy].bits.flow_size > 0 - || block->occupancy[xxx][yyy].bits.building > 0) - nonAir++; - } - if (nonAir > 0 || !sentBuildings) - { - bool tileChanged = IsTiletypeChanged(pos); - bool desChanged = IsDesignationChanged(pos); - bool spatterChanged = IsspatterChanged(pos); - bool buildingChanged = !sentBuildings; - bool itemsChanged = areItemsChanged(&block->items); - //bool bldChanged = IsBuildingChanged(pos); - RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged) - net_block = out->add_map_blocks(); - if (tileChanged) - { - CopyBlock(block, net_block, &MC, pos); - blocks_sent++; - } - if (desChanged) - CopyDesignation(block, net_block, &MC, pos); - if (buildingChanged) - { - CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); - sentBuildings = true; - } - if (spatterChanged) - Copyspatters(block, net_block, &MC, pos); - if (itemsChanged) - CopyItems(block, net_block, &MC, pos); - } - } - } - - // make a step, add 'direction' vector (di, dj) to current position (i, j) - i += di; - j += dj; - ++segment_passed; - //System.out.println(i + " " + j); - - if (segment_passed == segment_length) - { - // done with current segment - segment_passed = 0; - - // 'rotate' directions - int buffer = di; - di = -dj; - dj = buffer; - - // increase segment length if necessary - if (dj == 0) { - ++segment_length; - } - } - } - } - MC.trash(); - return CR_OK; + int x, y, z; + DFHack::Maps::getPosition(x, y, z); + out->set_map_x(x); + out->set_map_y(y); + MapExtras::MapCache MC; + int center_x = (in->min_x() + in->max_x()) / 2; + int center_y = (in->min_y() + in->max_y()) / 2; + + int NUMBER_OF_POINTS = ((in->max_x() - center_x + 1) * 2) * ((in->max_y() - center_y + 1) * 2); + int blocks_needed; + if (in->has_blocks_needed()) + blocks_needed = in->blocks_needed(); + else + blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z()); + int blocks_sent = 0; + int min_x = in->min_x(); + int min_y = in->min_y(); + int max_x = in->max_x(); + int max_y = in->max_y(); + int min_z = in->min_z(); + int max_z = in->max_z(); + bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest. + //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); + for (int zz = max_z - 1; zz >= min_z; zz--) + { + // (di, dj) is a vector - direction in which we move right now + int di = 1; + int dj = 0; + // length of current segment + int segment_length = 1; + // current position (i, j) and how much of current segment we passed + int i = center_x; + int j = center_y; + int segment_passed = 0; + for (int k = 0; k < NUMBER_OF_POINTS; ++k) + { + if (blocks_sent >= blocks_needed) + break; + if (!(i < min_x || i >= max_x || j < min_y || j >= max_y)) + { + DFCoord pos = DFCoord(i, j, zz); + df::map_block * block = DFHack::Maps::getBlock(pos); + if (block != NULL) + { + int nonAir = 0; + for (int xxx = 0; xxx < 16; xxx++) + for (int yyy = 0; yyy < 16; yyy++) + { + if ((DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::None && + DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) + || block->designation[xxx][yyy].bits.flow_size > 0 + || block->occupancy[xxx][yyy].bits.building > 0) + nonAir++; + } + if (nonAir > 0 || !sentBuildings) + { + bool tileChanged = IsTiletypeChanged(pos); + bool desChanged = IsDesignationChanged(pos); + bool spatterChanged = IsspatterChanged(pos); + bool buildingChanged = !sentBuildings; + bool itemsChanged = areItemsChanged(&block->items); + //bool bldChanged = IsBuildingChanged(pos); + RemoteFortressReader::MapBlock *net_block; + if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged) + net_block = out->add_map_blocks(); + if (tileChanged) + { + CopyBlock(block, net_block, &MC, pos); + blocks_sent++; + } + if (desChanged) + CopyDesignation(block, net_block, &MC, pos); + if (buildingChanged) + { + CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); + sentBuildings = true; + } + if (spatterChanged) + Copyspatters(block, net_block, &MC, pos); + if (itemsChanged) + CopyItems(block, net_block, &MC, pos); + } + } + } + + // make a step, add 'direction' vector (di, dj) to current position (i, j) + i += di; + j += dj; + ++segment_passed; + //System.out.println(i + " " + j); + + if (segment_passed == segment_length) + { + // done with current segment + segment_passed = 0; + + // 'rotate' directions + int buffer = di; + di = -dj; + dj = buffer; + + // increase segment length if necessary + if (dj == 0) { + ++segment_length; + } + } + } + } + MC.trash(); + return CR_OK; } static command_result GetTiletypeList(color_ostream &stream, const EmptyMessage *in, TiletypeList *out) { - int count = 0; - FOR_ENUM_ITEMS(tiletype, tt) - { - Tiletype * type = out->add_tiletype_list(); - type->set_id(tt); - type->set_name(ENUM_KEY_STR(tiletype, tt)); - const char * name = tileName(tt); - if (name != NULL && name[0] != 0) - type->set_caption(name); - type->set_shape(TranslateShape(tileShape(tt))); - type->set_special(TranslateSpecial(tileSpecial(tt))); - type->set_material(TranslateMaterial(tileMaterial(tt))); - type->set_variant(TranslateVariant(tileVariant(tt))); - type->set_direction(tileDirection(tt).getStr()); - count++; - } - return CR_OK; + int count = 0; + FOR_ENUM_ITEMS(tiletype, tt) + { + Tiletype * type = out->add_tiletype_list(); + type->set_id(tt); + type->set_name(ENUM_KEY_STR(tiletype, tt)); + const char * name = tileName(tt); + if (name != NULL && name[0] != 0) + type->set_caption(name); + type->set_shape(TranslateShape(tileShape(tt))); + type->set_special(TranslateSpecial(tileSpecial(tt))); + type->set_material(TranslateMaterial(tileMaterial(tt))); + type->set_variant(TranslateVariant(tileVariant(tt))); + type->set_direction(tileDirection(tt).getStr()); + count++; + } + return CR_OK; } static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out) { - int min_x = in->min_x() / 3; - int min_y = in->min_y() / 3; - int min_z = in->min_z(); - int max_x = in->max_x() / 3; - int max_y = in->max_y() / 3; - int max_z = in->max_z(); + int min_x = in->min_x() / 3; + int min_y = in->min_y() / 3; + int min_z = in->min_z(); + int max_x = in->max_x() / 3; + int max_y = in->max_y() / 3; + int max_z = in->max_z(); #if DF_VERSION_INT < 40001 - //plants are gotten differently here + //plants are gotten differently here #else - for (int xx = min_x; xx < max_x; xx++) - for (int yy = min_y; yy < max_y; yy++) - { - if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) - continue; - df::map_block_column * column = world->map.column_index[xx][yy]; - for (int i = 0; i < column->plants.size(); i++) - { - df::plant * plant = column->plants[i]; - if (!plant->tree_info) - { - if (plant->pos.z < min_z || plant->pos.z >= max_z) - continue; - if (plant->pos.x < in->min_x() * 16 || plant->pos.x >= in->max_x() * 16) - continue; - if (plant->pos.y < in->min_y() * 16 || plant->pos.y >= in->max_y() * 16) - continue; - } - else - { - if (plant->pos.z - plant->tree_info->roots_depth < min_z || plant->pos.z + plant->tree_info->body_height > max_z) - continue; - if (plant->pos.x - plant->tree_info->dim_x / 2 < in->min_x() * 16 || plant->pos.x + plant->tree_info->dim_x / 2 >= in->max_x() * 16) - continue; - if (plant->pos.y - plant->tree_info->dim_y / 2 < in->min_y() * 16 || plant->pos.y + plant->tree_info->dim_y / 2 >= in->max_y() * 16) - continue; - } - RemoteFortressReader::PlantDef * out_plant = out->add_plant_list(); - out_plant->set_index(plant->material); - out_plant->set_pos_x(plant->pos.x); - out_plant->set_pos_y(plant->pos.y); - out_plant->set_pos_z(plant->pos.z); - } - } + for (int xx = min_x; xx < max_x; xx++) + for (int yy = min_y; yy < max_y; yy++) + { + if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) + continue; + df::map_block_column * column = world->map.column_index[xx][yy]; + for (int i = 0; i < column->plants.size(); i++) + { + df::plant * plant = column->plants[i]; + if (!plant->tree_info) + { + if (plant->pos.z < min_z || plant->pos.z >= max_z) + continue; + if (plant->pos.x < in->min_x() * 16 || plant->pos.x >= in->max_x() * 16) + continue; + if (plant->pos.y < in->min_y() * 16 || plant->pos.y >= in->max_y() * 16) + continue; + } + else + { + if (plant->pos.z - plant->tree_info->roots_depth < min_z || plant->pos.z + plant->tree_info->body_height > max_z) + continue; + if (plant->pos.x - plant->tree_info->dim_x / 2 < in->min_x() * 16 || plant->pos.x + plant->tree_info->dim_x / 2 >= in->max_x() * 16) + continue; + if (plant->pos.y - plant->tree_info->dim_y / 2 < in->min_y() * 16 || plant->pos.y + plant->tree_info->dim_y / 2 >= in->max_y() * 16) + continue; + } + RemoteFortressReader::PlantDef * out_plant = out->add_plant_list(); + out_plant->set_index(plant->material); + out_plant->set_pos_x(plant->pos.x); + out_plant->set_pos_y(plant->pos.y); + out_plant->set_pos_z(plant->pos.z); + } + } #endif - return CR_OK; + return CR_OK; } static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out) { - return GetUnitListInside(stream, NULL, out); + return GetUnitListInside(stream, NULL, out); } static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) { - auto world = df::global::world; - for (int i = 0; i < world->units.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - auto send_unit = out->add_creature_list(); - send_unit->set_id(unit->id); - send_unit->set_pos_x(unit->pos.x); - send_unit->set_pos_y(unit->pos.y); - send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - if (in != NULL) - { - if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) - continue; - if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) - continue; - if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) - continue; - } - ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); - send_unit->set_flags1(unit->flags1.whole); - send_unit->set_flags2(unit->flags2.whole); - send_unit->set_flags3(unit->flags3.whole); - send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); - auto size_info = send_unit->mutable_size_info(); - size_info->set_size_cur(unit->body.size_info.size_cur); - size_info->set_size_base(unit->body.size_info.size_base); - size_info->set_area_cur(unit->body.size_info.area_cur); - size_info->set_area_base(unit->body.size_info.area_base); - size_info->set_length_cur(unit->body.size_info.length_cur); - size_info->set_length_base(unit->body.size_info.length_base); - if (unit->name.has_name) - { - send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); - } - - auto appearance = send_unit->mutable_appearance(); - for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) - appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); - for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) - appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); - for (int j = 0; j < unit->appearance.colors.size(); j++) - appearance->add_colors(unit->appearance.colors[j]); - appearance->set_size_modifier(unit->appearance.size_modifier); - - send_unit->set_profession_id(unit->profession); - - std::vector pvec; - - if (Units::getNoblePositions(&pvec, unit)) - { - for (int j = 0; j < pvec.size(); j++) - { - auto noble_positon = pvec[j]; - send_unit->add_noble_positions(noble_positon.position->code); - } - } - - send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); - - auto creatureRaw = world->raws.creatures.all[unit->race]; - auto casteRaw = creatureRaw->caste[unit->caste]; - - for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) - { - auto type = unit->appearance.tissue_style_type[j]; - if (type < 0) - continue; - int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); - auto styleRaw = casteRaw->tissue_styles[style_raw_index]; - if (styleRaw->token == "HAIR") - { - auto send_style = appearance->mutable_hair(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "BEARD") - { - auto send_style = appearance->mutable_beard(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "MOUSTACHE") - { - auto send_style = appearance->mutable_moustache(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - else if (styleRaw->token == "SIDEBURNS") - { - auto send_style = appearance->mutable_sideburns(); - send_style->set_length(unit->appearance.tissue_length[j]); - send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); - } - } - - for (int j = 0; j < unit->inventory.size(); j++) - { - auto inventory_item = unit->inventory[j]; - auto sent_item = send_unit->add_inventory(); - sent_item->set_mode((InventoryMode)inventory_item->mode); - CopyItem(sent_item->mutable_item(), inventory_item->item); - } - - if (unit->flags1.bits.projectile) - { - for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) - { - STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); - if (item == NULL) - continue; - if (item->unit != unit) - continue; - send_unit->set_subpos_x(item->pos_x / 100000.0); - send_unit->set_subpos_y(item->pos_y / 100000.0); - send_unit->set_subpos_z(item->pos_z / 140000.0); - } - } - } - return CR_OK; + auto world = df::global::world; + for (int i = 0; i < world->units.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + auto send_unit = out->add_creature_list(); + send_unit->set_id(unit->id); + send_unit->set_pos_x(unit->pos.x); + send_unit->set_pos_y(unit->pos.y); + send_unit->set_pos_z(unit->pos.z); + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + if (in != NULL) + { + if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) + continue; + if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) + continue; + if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) + continue; + } + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); + auto size_info = send_unit->mutable_size_info(); + size_info->set_size_cur(unit->body.size_info.size_cur); + size_info->set_size_base(unit->body.size_info.size_base); + size_info->set_area_cur(unit->body.size_info.area_cur); + size_info->set_area_base(unit->body.size_info.area_base); + size_info->set_length_cur(unit->body.size_info.length_cur); + size_info->set_length_base(unit->body.size_info.length_base); + if (unit->name.has_name) + { + send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); + } + + auto appearance = send_unit->mutable_appearance(); + for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) + appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); + for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) + appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); + for (int j = 0; j < unit->appearance.colors.size(); j++) + appearance->add_colors(unit->appearance.colors[j]); + appearance->set_size_modifier(unit->appearance.size_modifier); + + send_unit->set_profession_id(unit->profession); + + std::vector pvec; + + if (Units::getNoblePositions(&pvec, unit)) + { + for (int j = 0; j < pvec.size(); j++) + { + auto noble_positon = pvec[j]; + send_unit->add_noble_positions(noble_positon.position->code); + } + } + + send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); + + auto creatureRaw = world->raws.creatures.all[unit->race]; + auto casteRaw = creatureRaw->caste[unit->caste]; + + for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) + { + auto type = unit->appearance.tissue_style_type[j]; + if (type < 0) + continue; + int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); + auto styleRaw = casteRaw->tissue_styles[style_raw_index]; + if (styleRaw->token == "HAIR") + { + auto send_style = appearance->mutable_hair(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "BEARD") + { + auto send_style = appearance->mutable_beard(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "MOUSTACHE") + { + auto send_style = appearance->mutable_moustache(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "SIDEBURNS") + { + auto send_style = appearance->mutable_sideburns(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + } + + for (int j = 0; j < unit->inventory.size(); j++) + { + auto inventory_item = unit->inventory[j]; + auto sent_item = send_unit->add_inventory(); + sent_item->set_mode((InventoryMode)inventory_item->mode); + CopyItem(sent_item->mutable_item(), inventory_item->item); + } + + if (unit->flags1.bits.projectile) + { + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); + if (item == NULL) + continue; + if (item->unit != unit) + continue; + send_unit->set_subpos_x(item->pos_x / 100000.0); + send_unit->set_subpos_y(item->pos_y / 100000.0); + send_unit->set_subpos_z(item->pos_z / 140000.0); + } + } + } + return CR_OK; } static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out) { - int x, y, z, w, h, cx, cy, cz; - Gui::getWindowSize(w, h); - Gui::getViewCoords(x, y, z); - Gui::getCursorCoords(cx, cy, cz); + int x, y, z, w, h, cx, cy, cz; + Gui::getWindowSize(w, h); + Gui::getViewCoords(x, y, z); + Gui::getCursorCoords(cx, cy, cz); #if DF_VERSION_INT > 34011 - auto embark = Gui::getViewscreenByType(0); - if (embark) - { - df::embark_location location = embark->location; - df::world_data * data = df::global::world->world_data; - if (data && data->region_map) - { - z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; - } - } + auto embark = Gui::getViewscreenByType(0); + if (embark) + { + df::embark_location location = embark->location; + df::world_data * data = df::global::world->world_data; + if (data && data->region_map) + { + z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; + } + } #endif - out->set_view_pos_x(x); - out->set_view_pos_y(y); - out->set_view_pos_z(z); - out->set_view_size_x(w); - out->set_view_size_y(h); - out->set_cursor_pos_x(cx); - out->set_cursor_pos_y(cy); - out->set_cursor_pos_z(cz); - - if (gamemode && *gamemode == GameMode::ADVENTURE) - out->set_follow_unit_id(world->units.active[0]->id); - else - out->set_follow_unit_id(ui->follow_unit); - out->set_follow_item_id(ui->follow_item); - return CR_OK; + out->set_view_pos_x(x); + out->set_view_pos_y(y); + out->set_view_pos_z(z); + out->set_view_size_x(w); + out->set_view_size_y(h); + out->set_cursor_pos_x(cx); + out->set_cursor_pos_y(cy); + out->set_cursor_pos_z(cz); + + if (gamemode && *gamemode == GameMode::ADVENTURE) + out->set_follow_unit_id(world->units.active[0]->id); + else + out->set_follow_unit_id(ui->follow_unit); + out->set_follow_item_id(ui->follow_item); + return CR_OK; } static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) { - if (!Maps::IsValid()) - return CR_FAILURE; - uint32_t size_x, size_y, size_z; - int32_t pos_x, pos_y, pos_z; - Maps::getSize(size_x, size_y, size_z); - Maps::getPosition(pos_x, pos_y, pos_z); - out->set_block_size_x(size_x); - out->set_block_size_y(size_y); - out->set_block_size_z(size_z); - out->set_block_pos_x(pos_x); - out->set_block_pos_y(pos_y); - out->set_block_pos_z(pos_z); - out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); - out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); - out->set_save_name(df::global::world->cur_savegame.save_dir); - return CR_OK; + if (!Maps::IsValid()) + return CR_FAILURE; + uint32_t size_x, size_y, size_z; + int32_t pos_x, pos_y, pos_z; + Maps::getSize(size_x, size_y, size_z); + Maps::getPosition(pos_x, pos_y, pos_z); + out->set_block_size_x(size_x); + out->set_block_size_y(size_y); + out->set_block_size_z(size_z); + out->set_block_pos_x(pos_x); + out->set_block_pos_y(pos_y); + out->set_block_pos_z(pos_z); + out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); + out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); + out->set_save_name(df::global::world->cur_savegame.save_dir); + return CR_OK; } DFCoord GetMapCenter() { - DFCoord output; + DFCoord output; #if DF_VERSION_INT > 34011 - auto embark = Gui::getViewscreenByType(0); - if (embark) - { - df::embark_location location = embark->location; - output.x = (location.region_pos.x * 16) + 8; - output.y = (location.region_pos.y * 16) + 8; - output.z = 100; - df::world_data * data = df::global::world->world_data; - if (data && data->region_map) - { - output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; - } - } - else + auto embark = Gui::getViewscreenByType(0); + if (embark) + { + df::embark_location location = embark->location; + output.x = (location.region_pos.x * 16) + 8; + output.y = (location.region_pos.y * 16) + 8; + output.z = 100; + df::world_data * data = df::global::world->world_data; + if (data && data->region_map) + { + output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; + } + } + else #endif - if (Maps::IsValid()) - { - int x, y, z; - Maps::getPosition(x, y, z); - output = DFCoord(x, y, z); - } + if (Maps::IsValid()) + { + int x, y, z; + Maps::getPosition(x, y, z); + output = DFCoord(x, y, z); + } #if DF_VERSION_INT > 34011 - else - for (int i = 0; i < df::global::world->armies.all.size(); i++) - { - df::army * thisArmy = df::global::world->armies.all[i]; - if (thisArmy->flags.is_set(df::enums::army_flags::player)) - { - output.x = (thisArmy->pos.x / 3) - 1; - output.y = (thisArmy->pos.y / 3) - 1; - output.z = thisArmy->pos.z; - } - } + else + for (int i = 0; i < df::global::world->armies.all.size(); i++) + { + df::army * thisArmy = df::global::world->armies.all[i]; + if (thisArmy->flags.is_set(df::enums::army_flags::player)) + { + output.x = (thisArmy->pos.x / 3) - 1; + output.y = (thisArmy->pos.y / 3) - 1; + output.z = thisArmy->pos.z; + } + } #endif - return output; + return output; } static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - if (!data->region_map) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); - auto poles = data->flip_latitude; + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + auto poles = data->flip_latitude; #if DF_VERSION_INT > 34011 - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif - for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx++) - { - df::region_map_entry * map_entry = &data->region_map[xx][yy]; - df::world_region * region = data->regions[map_entry->region_id]; - out->add_elevation(map_entry->elevation); - out->add_rainfall(map_entry->rainfall); - out->add_vegetation(map_entry->vegetation); - out->add_temperature(map_entry->temperature); - out->add_evilness(map_entry->evilness); - out->add_drainage(map_entry->drainage); - out->add_volcanism(map_entry->volcanism); - out->add_savagery(map_entry->savagery); - out->add_salinity(map_entry->salinity); - auto clouds = out->add_clouds(); + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + out->add_elevation(map_entry->elevation); + out->add_rainfall(map_entry->rainfall); + out->add_vegetation(map_entry->vegetation); + out->add_temperature(map_entry->temperature); + out->add_evilness(map_entry->evilness); + out->add_drainage(map_entry->drainage); + out->add_volcanism(map_entry->volcanism); + out->add_savagery(map_entry->savagery); + out->add_salinity(map_entry->salinity); + auto clouds = out->add_clouds(); #if DF_VERSION_INT > 34011 - clouds->set_cirrus(map_entry->clouds.bits.cirrus); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); #else - clouds->set_cirrus(map_entry->clouds.bits.striped); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); #endif - if (region->type == world_region_type::Lake) - { - out->add_water_elevation(region->lake_surface); - } - else - out->add_water_elevation(99); - } - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - - - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + if (region->type == world_region_type::Lake) + { + out->add_water_elevation(region->lake_surface); + } + else + out->add_water_elevation(99); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + + + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) { - df::world_region * region = df::world_region::find(e1->region_id); - df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); - out->set_rainfall(e1->rainfall); - out->set_vegetation(e1->vegetation); - out->set_temperature(e1->temperature); - out->set_evilness(e1->evilness); - out->set_drainage(e1->drainage); - out->set_volcanism(e1->volcanism); - out->set_savagery(e1->savagery); - out->set_salinity(e1->salinity); - if (region->type == world_region_type::Lake) - out->set_water_elevation(region->lake_surface); - else - out->set_water_elevation(99); - - int topLayer = 0; - for (int i = 0; i < geoBiome->layers.size(); i++) - { - auto layer = geoBiome->layers[i]; - if (layer->top_height == 0) - { - topLayer = layer->mat_index; - } - if (layer->type != geo_layer_type::SOIL - && layer->type != geo_layer_type::SOIL_OCEAN - && layer->type != geo_layer_type::SOIL_SAND) - { - auto mat = out->add_stone_materials(); - mat->set_mat_index(layer->mat_index); - mat->set_mat_type(0); - } - } - auto surfaceMat = out->mutable_surface_material(); - surfaceMat->set_mat_index(topLayer); - surfaceMat->set_mat_type(0); - - for (int i = 0; i < region->population.size(); i++) - { - auto pop = region->population[i]; - if (pop->type == world_population_type::Grass) - { - auto plantMat = out->add_plant_materials(); - - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); - } - else if (pop->type == world_population_type::Tree) - { - auto plantMat = out->add_tree_materials(); - - plantMat->set_mat_index(pop->plant); - plantMat->set_mat_type(419); - } - } + df::world_region * region = df::world_region::find(e1->region_id); + df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); + out->set_rainfall(e1->rainfall); + out->set_vegetation(e1->vegetation); + out->set_temperature(e1->temperature); + out->set_evilness(e1->evilness); + out->set_drainage(e1->drainage); + out->set_volcanism(e1->volcanism); + out->set_savagery(e1->savagery); + out->set_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->set_water_elevation(region->lake_surface); + else + out->set_water_elevation(99); + + int topLayer = 0; + for (int i = 0; i < geoBiome->layers.size(); i++) + { + auto layer = geoBiome->layers[i]; + if (layer->top_height == 0) + { + topLayer = layer->mat_index; + } + if (layer->type != geo_layer_type::SOIL + && layer->type != geo_layer_type::SOIL_OCEAN + && layer->type != geo_layer_type::SOIL_SAND) + { + auto mat = out->add_stone_materials(); + mat->set_mat_index(layer->mat_index); + mat->set_mat_type(0); + } + } + auto surfaceMat = out->mutable_surface_material(); + surfaceMat->set_mat_index(topLayer); + surfaceMat->set_mat_type(0); + + for (int i = 0; i < region->population.size(); i++) + { + auto pop = region->population[i]; + if (pop->type == world_population_type::Grass) + { + auto plantMat = out->add_plant_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + else if (pop->type == world_population_type::Tree) + { + auto plantMat = out->add_tree_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + } #if DF_VERSION_INT >= 43005 - out->set_snow(e1->snowfall); + out->set_snow(e1->snowfall); #endif } static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) { - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - if (!data->region_map) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); #if DF_VERSION_INT > 34011 - auto poles = data->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + auto poles = data->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif - for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx++) - { - df::region_map_entry * map_entry = &data->region_map[xx][yy]; - df::world_region * region = data->regions[map_entry->region_id]; - - auto regionTile = out->add_region_tiles(); - regionTile->set_elevation(map_entry->elevation); - SetRegionTile(regionTile, map_entry); - auto clouds = out->add_clouds(); + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + + auto regionTile = out->add_region_tiles(); + regionTile->set_elevation(map_entry->elevation); + SetRegionTile(regionTile, map_entry); + auto clouds = out->add_clouds(); #if DF_VERSION_INT > 34011 - clouds->set_cirrus(map_entry->clouds.bits.cirrus); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); #else - clouds->set_cirrus(map_entry->clouds.bits.striped); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); #endif - } - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; } static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_data * worldData) { - df::world_region * region = worldData->regions[e1->region_id]; - out->add_rainfall(e1->rainfall); - out->add_vegetation(e1->vegetation); - out->add_temperature(e1->temperature); - out->add_evilness(e1->evilness); - out->add_drainage(e1->drainage); - out->add_volcanism(e1->volcanism); - out->add_savagery(e1->savagery); - out->add_salinity(e1->salinity); - if (region->type == world_region_type::Lake) - out->add_water_elevation(region->lake_surface); - else - out->add_water_elevation(99); + df::world_region * region = worldData->regions[e1->region_id]; + out->add_rainfall(e1->rainfall); + out->add_vegetation(e1->vegetation); + out->add_temperature(e1->temperature); + out->add_evilness(e1->evilness); + out->add_drainage(e1->drainage); + out->add_volcanism(e1->volcanism); + out->add_savagery(e1->savagery); + out->add_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->add_water_elevation(region->lake_surface); + else + out->add_water_elevation(99); } static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) { - if (pos.x < 0) - pos.x = 0; - if (pos.y < 0) - pos.y = 0; - if (pos.x >= worldData->world_width) - pos.x = worldData->world_width - 1; - if (pos.y >= worldData->world_height) - pos.y = worldData->world_height - 1; - AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); } static void AddRegionTiles(RegionTile * out, df::coord2d pos, df::world_data * worldData) { - if (pos.x < 0) - pos.x = 0; - if (pos.y < 0) - pos.y = 0; - if (pos.x >= worldData->world_width) - pos.x = worldData->world_width - 1; - if (pos.y >= worldData->world_height) - pos.y = worldData->world_height - 1; - SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); } static df::coord2d ShiftCoords(df::coord2d source, int direction) { - switch (direction) - { - case 1: - return df::coord2d(source.x - 1, source.y + 1); - case 2: - return df::coord2d(source.x, source.y + 1); - case 3: - return df::coord2d(source.x + 1, source.y + 1); - case 4: - return df::coord2d(source.x - 1, source.y); - case 5: - return source; - case 6: - return df::coord2d(source.x + 1, source.y); - case 7: - return df::coord2d(source.x - 1, source.y - 1); - case 8: - return df::coord2d(source.x, source.y - 1); - case 9: - return df::coord2d(source.x + 1, source.y - 1); - default: - return source; - } + switch (direction) + { + case 1: + return df::coord2d(source.x - 1, source.y + 1); + case 2: + return df::coord2d(source.x, source.y + 1); + case 3: + return df::coord2d(source.x + 1, source.y + 1); + case 4: + return df::coord2d(source.x - 1, source.y); + case 5: + return source; + case 6: + return df::coord2d(source.x + 1, source.y); + case 7: + return df::coord2d(source.x - 1, source.y - 1); + case 8: + return df::coord2d(source.x, source.y - 1); + case 9: + return df::coord2d(source.x + 1, source.y - 1); + default: + return source; + } } static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, WorldMap * out) { - int pos_x = worldRegionDetails->pos.x; - int pos_y = worldRegionDetails->pos.y; - out->set_map_x(pos_x); - out->set_map_y(pos_y); - out->set_world_width(17); - out->set_world_height(17); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); - out->set_name_english(name); - out->set_name(name); + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + out->set_world_width(17); + out->set_world_height(17); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); #if DF_VERSION_INT > 34011 - auto poles = worldData->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } + auto poles = worldData->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } #else - out->set_world_poles(WorldPoles::NO_POLES); + out->set_world_poles(WorldPoles::NO_POLES); #endif - df::world_region_details * south = NULL; - df::world_region_details * east = NULL; - df::world_region_details * southEast = NULL; - - for (int i = 0; i < worldData->region_details.size(); i++) - { - auto region = worldData->region_details[i]; - if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) - southEast = region; - else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) - east = region; - else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) - south = region; - } - - for (int yy = 0; yy < 17; yy++) - for (int xx = 0; xx < 17; xx++) - { - //This is because the bottom row doesn't line up. - if (xx == 16 && yy == 16 && southEast != NULL) - { - out->add_elevation(southEast->elevation[0][0]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); - } - else if (xx == 16 && east != NULL) - { - out->add_elevation(east->elevation[0][yy]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); - } - else if (yy == 16 && south != NULL) - { - out->add_elevation(south->elevation[xx][0]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); - } - else - { - out->add_elevation(worldRegionDetails->elevation[xx][yy]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); - } - - if (xx == 16 || yy == 16) - { - out->add_river_tiles(); - } - else - { - auto riverTile = out->add_river_tiles(); - auto east = riverTile->mutable_east(); - auto north = riverTile->mutable_north(); - auto south = riverTile->mutable_south(); - auto west = riverTile->mutable_west(); - - north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); - north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); - north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); - north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); - - south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); - south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); - south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); - south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); - - west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); - west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); - west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); - west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); - - east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); - east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); - east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); - east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); - } - } + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (int i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + out->add_elevation(southEast->elevation[0][0]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + out->add_elevation(east->elevation[0][yy]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + out->add_elevation(south->elevation[xx][0]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + out->add_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + if (xx == 16 || yy == 16) + { + out->add_river_tiles(); + } + else + { + auto riverTile = out->add_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + } } static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, RegionMap * out) { - int pos_x = worldRegionDetails->pos.x; - int pos_y = worldRegionDetails->pos.y; - out->set_map_x(pos_x); - out->set_map_y(pos_y); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); - out->set_name_english(name); - out->set_name(name); - - - df::world_region_details * south = NULL; - df::world_region_details * east = NULL; - df::world_region_details * southEast = NULL; - - for (int i = 0; i < worldData->region_details.size(); i++) - { - auto region = worldData->region_details[i]; - if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) - southEast = region; - else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) - east = region; - else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) - south = region; - } - - RegionTile* outputTiles[17][17]; - - for (int yy = 0; yy < 17; yy++) - for (int xx = 0; xx < 17; xx++) - { - auto tile = out->add_tiles(); - outputTiles[xx][yy] = tile; - //This is because the bottom row doesn't line up. - if (xx == 16 && yy == 16 && southEast != NULL) - { - tile->set_elevation(southEast->elevation[0][0]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); - } - else if (xx == 16 && east != NULL) - { - tile->set_elevation(east->elevation[0][yy]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); - } - else if (yy == 16 && south != NULL) - { - tile->set_elevation(south->elevation[xx][0]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); - } - else - { - tile->set_elevation(worldRegionDetails->elevation[xx][yy]); - AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); - } - - auto riverTile = tile->mutable_river_tiles(); - auto east = riverTile->mutable_east(); - auto north = riverTile->mutable_north(); - auto south = riverTile->mutable_south(); - auto west = riverTile->mutable_west(); - - if (xx < 16) - { - north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); - north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); - north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); - north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); - } - else - { - north->set_active(0); - north->set_elevation(100); - north->set_min_pos(-30000); - north->set_max_pos(-30000); - } - - if (yy < 16 && xx < 16) - { - south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); - south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); - south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); - south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); - } - else - { - south->set_active(0); - south->set_elevation(100); - south->set_min_pos(-30000); - south->set_max_pos(-30000); - } - - if (yy < 16) - { - west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); - west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); - west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); - west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); - } - else - { - west->set_active(0); - west->set_elevation(100); - west->set_min_pos(-30000); - west->set_max_pos(-30000); - } - - if (xx < 16 && yy < 16) - { - east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); - east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); - east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); - east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); - } - else - { - east->set_active(0); - east->set_elevation(100); - east->set_min_pos(-30000); - east->set_max_pos(-30000); - } - } - - auto regionMap = worldData->region_map[pos_x][pos_y]; - - for (int i = 0; i < worldData->sites.size(); i++) - { - df::world_site* site = worldData->sites[i]; - if (!site) - continue; - - int region_min_x = pos_x * 16; - int region_min_y = pos_y * 16; - - if ((site->global_min_x >(region_min_x + 16)) || - (site->global_min_y > (region_min_y + 16)) || - (site->global_max_x < (region_min_x)) || - (site->global_max_y < (region_min_y))) - continue; - - if (site->realization == NULL) - continue; - - auto realization = site->realization; - for (int site_x = 0; site_x < 17; site_x++) - for (int site_y = 0; site_y < 17; site_y++) - { - int region_x = site->global_min_x - region_min_x + site_x; - int region_y = site->global_min_y - region_min_y + site_y; - - if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) - continue; - - for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) - { - auto in_building = realization->building_map[site_x][site_y].buildings[j]; - auto out_building = outputTiles[region_x][region_y]->add_buildings(); - - out_building->set_id(in_building->id); + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); + + + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (int i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + RegionTile* outputTiles[17][17]; + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + auto tile = out->add_tiles(); + outputTiles[xx][yy] = tile; + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + tile->set_elevation(southEast->elevation[0][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + tile->set_elevation(east->elevation[0][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + tile->set_elevation(south->elevation[xx][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + tile->set_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + auto riverTile = tile->mutable_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + if (xx < 16) + { + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + } + else + { + north->set_active(0); + north->set_elevation(100); + north->set_min_pos(-30000); + north->set_max_pos(-30000); + } + + if (yy < 16 && xx < 16) + { + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + } + else + { + south->set_active(0); + south->set_elevation(100); + south->set_min_pos(-30000); + south->set_max_pos(-30000); + } + + if (yy < 16) + { + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + } + else + { + west->set_active(0); + west->set_elevation(100); + west->set_min_pos(-30000); + west->set_max_pos(-30000); + } + + if (xx < 16 && yy < 16) + { + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + else + { + east->set_active(0); + east->set_elevation(100); + east->set_min_pos(-30000); + east->set_max_pos(-30000); + } + } + + auto regionMap = worldData->region_map[pos_x][pos_y]; + + for (int i = 0; i < worldData->sites.size(); i++) + { + df::world_site* site = worldData->sites[i]; + if (!site) + continue; + + int region_min_x = pos_x * 16; + int region_min_y = pos_y * 16; + + if ((site->global_min_x >(region_min_x + 16)) || + (site->global_min_y > (region_min_y + 16)) || + (site->global_max_x < (region_min_x)) || + (site->global_max_y < (region_min_y))) + continue; + + if (site->realization == NULL) + continue; + + auto realization = site->realization; + for (int site_x = 0; site_x < 17; site_x++) + for (int site_y = 0; site_y < 17; site_y++) + { + int region_x = site->global_min_x - region_min_x + site_x; + int region_y = site->global_min_y - region_min_y + site_y; + + if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) + continue; + + for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) + { + auto in_building = realization->building_map[site_x][site_y].buildings[j]; + auto out_building = outputTiles[region_x][region_y]->add_buildings(); + + out_building->set_id(in_building->id); #if DF_VERSION_INT > 34011 - out_building->set_type((SiteRealizationBuildingType)in_building->type); + out_building->set_type((SiteRealizationBuildingType)in_building->type); #endif - out_building->set_min_x(in_building->min_x - (site_x * 48)); - out_building->set_min_y(in_building->min_y - (site_y * 48)); - out_building->set_max_x(in_building->max_x - (site_x * 48)); - out_building->set_max_y(in_building->max_y - (site_y * 48)); + out_building->set_min_x(in_building->min_x - (site_x * 48)); + out_building->set_min_y(in_building->min_y - (site_y * 48)); + out_building->set_max_x(in_building->max_x - (site_x * 48)); + out_building->set_max_y(in_building->max_y - (site_y * 48)); - CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); + CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); #if DF_VERSION_INT >= 43005 - STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); - if (tower_info) - { - CopyMat(out_building->mutable_material(), tower_info->wall_item.mat_type, tower_info->wall_item.mat_index); - - auto out_tower = out_building->mutable_tower_info(); - out_tower->set_roof_z(tower_info->roof_z); - out_tower->set_round(tower_info->shape.bits.round); - out_tower->set_goblin(tower_info->shape.bits.goblin); - } - STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); - if (wall_info) - { - CopyMat(out_building->mutable_material(), wall_info->wall_item.mat_type, wall_info->wall_item.mat_index); - - auto out_wall = out_building->mutable_wall_info(); - - out_wall->set_start_x(wall_info->start_x - (site_x * 48)); - out_wall->set_start_y(wall_info->start_y - (site_y * 48)); - out_wall->set_start_z(wall_info->start_z); - out_wall->set_end_x(wall_info->end_x - (site_x * 48)); - out_wall->set_end_y(wall_info->end_y - (site_y * 48)); - out_wall->set_end_z(wall_info->end_z); - } + STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); + if (tower_info) + { + CopyMat(out_building->mutable_material(), tower_info->wall_item.mat_type, tower_info->wall_item.mat_index); + + auto out_tower = out_building->mutable_tower_info(); + out_tower->set_roof_z(tower_info->roof_z); + out_tower->set_round(tower_info->shape.bits.round); + out_tower->set_goblin(tower_info->shape.bits.goblin); + } + STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); + if (wall_info) + { + CopyMat(out_building->mutable_material(), wall_info->wall_item.mat_type, wall_info->wall_item.mat_index); + + auto out_wall = out_building->mutable_wall_info(); + + out_wall->set_start_x(wall_info->start_x - (site_x * 48)); + out_wall->set_start_y(wall_info->start_y - (site_y * 48)); + out_wall->set_start_z(wall_info->start_z); + out_wall->set_end_x(wall_info->end_x - (site_x * 48)); + out_wall->set_end_y(wall_info->end_y - (site_y * 48)); + out_wall->set_end_z(wall_info->end_z); + } #endif - } + } - } - } + } + } } static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) { - if (!df::global::world->world_data) - { - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) - { - df::world_region_details * region = data->region_details[i]; - if (!region) - continue; - WorldMap * regionMap = out->add_world_maps(); - CopyLocalMap(data, region, regionMap); - } - return CR_OK; + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (int i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + WorldMap * regionMap = out->add_world_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; } static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) { - if (!df::global::world->world_data) - { - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) - { - df::world_region_details * region = data->region_details[i]; - if (!region) - continue; - RegionMap * regionMap = out->add_region_maps(); - CopyLocalMap(data, region, regionMap); - } - return CR_OK; + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (int i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + RegionMap * regionMap = out->add_region_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; } static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) { - return GetPartialCreatureRaws(stream, NULL, out); + return GetPartialCreatureRaws(stream, NULL, out); } static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out) { - if (!df::global::world) - return CR_FAILURE; + if (!df::global::world) + return CR_FAILURE; - df::world * world = df::global::world; + df::world * world = df::global::world; - int list_start = 0; - int list_end = world->raws.creatures.all.size(); + int list_start = 0; + int list_end = world->raws.creatures.all.size(); - if (in != nullptr) - { - list_start = in->list_start(); - if (in->list_end() < list_end) - list_end = in->list_end(); - } + if (in != nullptr) + { + list_start = in->list_start(); + if (in->list_end() < list_end) + list_end = in->list_end(); + } - for (int i = list_start; i < list_end; i++) - { - df::creature_raw * orig_creature = world->raws.creatures.all[i]; + for (int i = list_start; i < list_end; i++) + { + df::creature_raw * orig_creature = world->raws.creatures.all[i]; - auto send_creature = out->add_creature_raws(); + auto send_creature = out->add_creature_raws(); - send_creature->set_index(i); - send_creature->set_creature_id(orig_creature->creature_id); - send_creature->add_name(orig_creature->name[0]); - send_creature->add_name(orig_creature->name[1]); - send_creature->add_name(orig_creature->name[2]); + send_creature->set_index(i); + send_creature->set_creature_id(orig_creature->creature_id); + send_creature->add_name(orig_creature->name[0]); + send_creature->add_name(orig_creature->name[1]); + send_creature->add_name(orig_creature->name[2]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); - send_creature->add_general_child_name(orig_creature->general_child_name[0]); - send_creature->add_general_child_name(orig_creature->general_child_name[1]); + send_creature->add_general_child_name(orig_creature->general_child_name[0]); + send_creature->add_general_child_name(orig_creature->general_child_name[1]); - send_creature->set_creature_tile(orig_creature->creature_tile); - send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); + send_creature->set_creature_tile(orig_creature->creature_tile); + send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); - ConvertDfColor(orig_creature->color, send_creature->mutable_color()); + ConvertDfColor(orig_creature->color, send_creature->mutable_color()); - send_creature->set_adultsize(orig_creature->adultsize); + send_creature->set_adultsize(orig_creature->adultsize); - for (int j = 0; j < orig_creature->caste.size(); j++) - { - auto orig_caste = orig_creature->caste[j]; - if (!orig_caste) - continue; - auto send_caste = send_creature->add_caste(); + for (int j = 0; j < orig_creature->caste.size(); j++) + { + auto orig_caste = orig_creature->caste[j]; + if (!orig_caste) + continue; + auto send_caste = send_creature->add_caste(); - send_caste->set_index(j); + send_caste->set_index(j); - send_caste->set_caste_id(orig_caste->caste_id); + send_caste->set_caste_id(orig_caste->caste_id); - send_caste->add_caste_name(orig_caste->caste_name[0]); - send_caste->add_caste_name(orig_caste->caste_name[1]); - send_caste->add_caste_name(orig_caste->caste_name[2]); + send_caste->add_caste_name(orig_caste->caste_name[0]); + send_caste->add_caste_name(orig_caste->caste_name[1]); + send_caste->add_caste_name(orig_caste->caste_name[2]); - send_caste->add_baby_name(orig_caste->baby_name[0]); - send_caste->add_baby_name(orig_caste->baby_name[1]); + send_caste->add_baby_name(orig_caste->baby_name[0]); + send_caste->add_baby_name(orig_caste->baby_name[1]); - send_caste->add_child_name(orig_caste->child_name[0]); - send_caste->add_child_name(orig_caste->child_name[1]); - send_caste->set_gender(orig_caste->gender); + send_caste->add_child_name(orig_caste->child_name[0]); + send_caste->add_child_name(orig_caste->child_name[1]); + send_caste->set_gender(orig_caste->gender); - for (int partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) - { - auto orig_part = orig_caste->body_info.body_parts[partIndex]; - if (!orig_part) - continue; - auto send_part = send_caste->add_body_parts(); + for (int partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) + { + auto orig_part = orig_caste->body_info.body_parts[partIndex]; + if (!orig_part) + continue; + auto send_part = send_caste->add_body_parts(); - send_part->set_token(orig_part->token); - send_part->set_category(orig_part->category); - send_part->set_parent(orig_part->con_part_id); + send_part->set_token(orig_part->token); + send_part->set_category(orig_part->category); + send_part->set_parent(orig_part->con_part_id); - for (int partFlagIndex = 0; partFlagIndex <= ENUM_LAST_ITEM(body_part_raw_flags); partFlagIndex++) - { - send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); - } + for (int partFlagIndex = 0; partFlagIndex <= ENUM_LAST_ITEM(body_part_raw_flags); partFlagIndex++) + { + send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); + } - for (int layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) - { - auto orig_layer = orig_part->layers[layerIndex]; - if (!orig_layer) - continue; - auto send_layer = send_part->add_layers(); + for (int layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) + { + auto orig_layer = orig_part->layers[layerIndex]; + if (!orig_layer) + continue; + auto send_layer = send_part->add_layers(); - send_layer->set_layer_name(orig_layer->layer_name); - send_layer->set_tissue_id(orig_layer->tissue_id); - send_layer->set_layer_depth(orig_layer->layer_depth); - for (int layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) - { - send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); - } - } + send_layer->set_layer_name(orig_layer->layer_name); + send_layer->set_tissue_id(orig_layer->tissue_id); + send_layer->set_layer_depth(orig_layer->layer_depth); + for (int layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) + { + send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); + } + } - send_part->set_relsize(orig_part->relsize); - } + send_part->set_relsize(orig_part->relsize); + } - send_caste->set_total_relsize(orig_caste->body_info.total_relsize); + send_caste->set_total_relsize(orig_caste->body_info.total_relsize); - for (int k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) - { - auto send_mod = send_caste->add_modifiers(); - auto orig_mod = orig_caste->bp_appearance.modifiers[k]; - send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + for (int k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) + { + auto send_mod = send_caste->add_modifiers(); + auto orig_mod = orig_caste->bp_appearance.modifiers[k]; + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); #if DF_VERSION_INT > 34011 - if (orig_mod->growth_rate > 0) - { - send_mod->set_mod_min(orig_mod->growth_min); - send_mod->set_mod_max(orig_mod->growth_max); - } - else + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else #endif - { - send_mod->set_mod_min(orig_mod->ranges[0]); - send_mod->set_mod_max(orig_mod->ranges[6]); - } - - } - for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) - { - send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); - send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); - send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); - } - for (int k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) - { - auto send_mod = send_caste->add_body_appearance_modifiers(); - auto orig_mod = orig_caste->body_appearance_modifiers[k]; - - send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + + } + for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) + { + send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); + send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); + send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); + } + for (int k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) + { + auto send_mod = send_caste->add_body_appearance_modifiers(); + auto orig_mod = orig_caste->body_appearance_modifiers[k]; + + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); #if DF_VERSION_INT > 34011 - if (orig_mod->growth_rate > 0) - { - send_mod->set_mod_min(orig_mod->growth_min); - send_mod->set_mod_max(orig_mod->growth_max); - } - else + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else #endif - { - send_mod->set_mod_min(orig_mod->ranges[0]); - send_mod->set_mod_max(orig_mod->ranges[6]); - } - } - for (int k = 0; k < orig_caste->color_modifiers.size(); k++) - { - auto send_mod = send_caste->add_color_modifiers(); - auto orig_mod = orig_caste->color_modifiers[k]; - - for (int l = 0; l < orig_mod->pattern_index.size(); l++) - { - auto orig_pattern = world->raws.language.patterns[orig_mod->pattern_index[l]]; - auto send_pattern = send_mod->add_patterns(); - - for (int m = 0; m < orig_pattern->colors.size(); m++) - { - auto send_color = send_pattern->add_colors(); - auto orig_color = world->raws.language.colors[orig_pattern->colors[m]]; - send_color->set_red(orig_color->red * 255.0); - send_color->set_green(orig_color->green * 255.0); - send_color->set_blue(orig_color->blue * 255.0); - } - - send_pattern->set_id(orig_pattern->id); - send_pattern->set_pattern((PatternType)orig_pattern->pattern); - } - - for (int l = 0; l < orig_mod->body_part_id.size(); l++) - { - send_mod->add_body_part_id(orig_mod->body_part_id[l]); - send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); - send_mod->set_start_date(orig_mod->start_date); - send_mod->set_end_date(orig_mod->end_date); - send_mod->set_part(orig_mod->part); - } - } - - send_caste->set_description(orig_caste->description); - send_caste->set_adult_size(orig_caste->misc.adult_size); - } - - for (int j = 0; j < orig_creature->tissue.size(); j++) - { - auto orig_tissue = orig_creature->tissue[j]; - auto send_tissue = send_creature->add_tissues(); - - send_tissue->set_id(orig_tissue->id); - send_tissue->set_name(orig_tissue->tissue_name_singular); - send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); - - CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); - } - } - - return CR_OK; + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + } + for (int k = 0; k < orig_caste->color_modifiers.size(); k++) + { + auto send_mod = send_caste->add_color_modifiers(); + auto orig_mod = orig_caste->color_modifiers[k]; + + for (int l = 0; l < orig_mod->pattern_index.size(); l++) + { + auto orig_pattern = world->raws.language.patterns[orig_mod->pattern_index[l]]; + auto send_pattern = send_mod->add_patterns(); + + for (int m = 0; m < orig_pattern->colors.size(); m++) + { + auto send_color = send_pattern->add_colors(); + auto orig_color = world->raws.language.colors[orig_pattern->colors[m]]; + send_color->set_red(orig_color->red * 255.0); + send_color->set_green(orig_color->green * 255.0); + send_color->set_blue(orig_color->blue * 255.0); + } + + send_pattern->set_id(orig_pattern->id); + send_pattern->set_pattern((PatternType)orig_pattern->pattern); + } + + for (int l = 0; l < orig_mod->body_part_id.size(); l++) + { + send_mod->add_body_part_id(orig_mod->body_part_id[l]); + send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); + send_mod->set_start_date(orig_mod->start_date); + send_mod->set_end_date(orig_mod->end_date); + send_mod->set_part(orig_mod->part); + } + } + + send_caste->set_description(orig_caste->description); + send_caste->set_adult_size(orig_caste->misc.adult_size); + } + + for (int j = 0; j < orig_creature->tissue.size(); j++) + { + auto orig_tissue = orig_creature->tissue[j]; + auto send_tissue = send_creature->add_tissues(); + + send_tissue->set_id(orig_tissue->id); + send_tissue->set_name(orig_tissue->tissue_name_singular); + send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); + + CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); + } + } + + return CR_OK; } static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out) { - GetPartialPlantRaws(stream, nullptr, out); - return CR_OK; + GetPartialPlantRaws(stream, nullptr, out); + return CR_OK; } static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out) { - if (!df::global::world) - return CR_FAILURE; - - df::world * world = df::global::world; - - int list_start = 0; - int list_end = world->raws.plants.all.size(); - - if (in != nullptr) - { - list_start = in->list_start(); - if (in->list_end() < list_end) - list_end = in->list_end(); - } - - for (int i = 0; i < world->raws.plants.all.size(); i++) - { - df::plant_raw* plant_local = world->raws.plants.all[i]; - PlantRaw* plant_remote = out->add_plant_raws(); - - plant_remote->set_index(i); - plant_remote->set_id(plant_local->id); - plant_remote->set_name(plant_local->name); - if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) - plant_remote->set_tile(plant_local->tiles.shrub_tile); - else - plant_remote->set_tile(plant_local->tiles.tree_tile); + if (!df::global::world) + return CR_FAILURE; + + df::world * world = df::global::world; + + int list_start = 0; + int list_end = world->raws.plants.all.size(); + + if (in != nullptr) + { + list_start = in->list_start(); + if (in->list_end() < list_end) + list_end = in->list_end(); + } + + for (int i = 0; i < world->raws.plants.all.size(); i++) + { + df::plant_raw* plant_local = world->raws.plants.all[i]; + PlantRaw* plant_remote = out->add_plant_raws(); + + plant_remote->set_index(i); + plant_remote->set_id(plant_local->id); + plant_remote->set_name(plant_local->name); + if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) + plant_remote->set_tile(plant_local->tiles.shrub_tile); + else + plant_remote->set_tile(plant_local->tiles.tree_tile); #if DF_VERSION_INT > 34011 - for (int j = 0; j < plant_local->growths.size(); j++) - { - df::plant_growth* growth_local = plant_local->growths[j]; - TreeGrowth * growth_remote = plant_remote->add_growths(); - growth_remote->set_index(j); - growth_remote->set_id(growth_local->id); - growth_remote->set_name(growth_local->name); - for (int k = 0; k < growth_local->prints.size(); k++) - { - df::plant_growth_print* print_local = growth_local->prints[k]; - GrowthPrint* print_remote = growth_remote->add_prints(); - print_remote->set_priority(print_local->priority); - print_remote->set_color(print_local->color[0] | (print_local->color[2] * 8)); - print_remote->set_timing_start(print_local->timing_start); - print_remote->set_timing_end(print_local->timing_end); - print_remote->set_tile(print_local->tile_growth); - } - growth_remote->set_timing_start(growth_local->timing_1); - growth_remote->set_timing_end(growth_local->timing_2); - growth_remote->set_twigs(growth_local->locations.bits.twigs); - growth_remote->set_light_branches(growth_local->locations.bits.light_branches); - growth_remote->set_heavy_branches(growth_local->locations.bits.heavy_branches); - growth_remote->set_trunk(growth_local->locations.bits.trunk); - growth_remote->set_roots(growth_local->locations.bits.roots); - growth_remote->set_cap(growth_local->locations.bits.cap); - growth_remote->set_sapling(growth_local->locations.bits.sapling); - growth_remote->set_timing_start(growth_local->timing_1); - growth_remote->set_timing_end(growth_local->timing_2); - growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); - growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); - CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); - } + for (int j = 0; j < plant_local->growths.size(); j++) + { + df::plant_growth* growth_local = plant_local->growths[j]; + TreeGrowth * growth_remote = plant_remote->add_growths(); + growth_remote->set_index(j); + growth_remote->set_id(growth_local->id); + growth_remote->set_name(growth_local->name); + for (int k = 0; k < growth_local->prints.size(); k++) + { + df::plant_growth_print* print_local = growth_local->prints[k]; + GrowthPrint* print_remote = growth_remote->add_prints(); + print_remote->set_priority(print_local->priority); + print_remote->set_color(print_local->color[0] | (print_local->color[2] * 8)); + print_remote->set_timing_start(print_local->timing_start); + print_remote->set_timing_end(print_local->timing_end); + print_remote->set_tile(print_local->tile_growth); + } + growth_remote->set_timing_start(growth_local->timing_1); + growth_remote->set_timing_end(growth_local->timing_2); + growth_remote->set_twigs(growth_local->locations.bits.twigs); + growth_remote->set_light_branches(growth_local->locations.bits.light_branches); + growth_remote->set_heavy_branches(growth_local->locations.bits.heavy_branches); + growth_remote->set_trunk(growth_local->locations.bits.trunk); + growth_remote->set_roots(growth_local->locations.bits.roots); + growth_remote->set_cap(growth_local->locations.bits.cap); + growth_remote->set_sapling(growth_local->locations.bits.sapling); + growth_remote->set_timing_start(growth_local->timing_1); + growth_remote->set_timing_end(growth_local->timing_2); + growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); + growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); + CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); + } #endif - } - return CR_OK; + } + return CR_OK; } static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out) { - df::graphic * gps = df::global::gps; - out->set_width(gps->dimx); - out->set_height(gps->dimy); - for (int i = 0; i < (gps->dimx * gps->dimy); i++) - { - int index = i * 4; - auto tile = out->add_tiles(); - tile->set_character(gps->screen[index]); - tile->set_foreground(gps->screen[index + 1] | (gps->screen[index + 3] * 8)); - tile->set_background(gps->screen[index + 2]); - } - - return CR_OK; + df::graphic * gps = df::global::gps; + out->set_width(gps->dimx); + out->set_height(gps->dimy); + for (int i = 0; i < (gps->dimx * gps->dimy); i++) + { + int index = i * 4; + auto tile = out->add_tiles(); + tile->set_character(gps->screen[index]); + tile->set_foreground(gps->screen[index + 1] | (gps->screen[index + 3] * 8)); + tile->set_background(gps->screen[index + 2]); + } + + return CR_OK; } static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in) { #if DF_VERSION_INT > 34011 - SDL::Event e; - e.key.type = in->type(); - e.key.state = in->state(); - e.key.ksym.mod = (SDL::Mod)in->mod(); - e.key.ksym.scancode = in->scancode(); - e.key.ksym.sym = (SDL::Key)in->sym(); - e.key.ksym.unicode = in->unicode(); - SDL_PushEvent(&e); + SDL::Event e; + e.key.type = in->type(); + e.key.state = in->state(); + e.key.ksym.mod = (SDL::Mod)in->mod(); + e.key.ksym.scancode = in->scancode(); + e.key.ksym.sym = (SDL::Key)in->sym(); + e.key.ksym.unicode = in->unicode(); + SDL_PushEvent(&e); #endif - return CR_OK; + return CR_OK; } static command_result SendDigCommand(color_ostream &stream, const DigCommand *in) { - MapExtras::MapCache mc; - - for (int i = 0; i < in->locations_size(); i++) - { - auto pos = in->locations(i); - auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); - switch (in->designation()) - { - case NO_DIG: - des.bits.dig = tile_dig_designation::No; - break; - case DEFAULT_DIG: - des.bits.dig = tile_dig_designation::Default; - break; - case UP_DOWN_STAIR_DIG: - des.bits.dig = tile_dig_designation::UpDownStair; - break; - case CHANNEL_DIG: - des.bits.dig = tile_dig_designation::Channel; - break; - case RAMP_DIG: - des.bits.dig = tile_dig_designation::Ramp; - break; - case DOWN_STAIR_DIG: - des.bits.dig = tile_dig_designation::DownStair; - break; - case UP_STAIR_DIG: - des.bits.dig = tile_dig_designation::UpStair; - break; - default: - break; - } - mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + MapExtras::MapCache mc; + + for (int i = 0; i < in->locations_size(); i++) + { + auto pos = in->locations(i); + auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); + switch (in->designation()) + { + case NO_DIG: + des.bits.dig = tile_dig_designation::No; + break; + case DEFAULT_DIG: + des.bits.dig = tile_dig_designation::Default; + break; + case UP_DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpDownStair; + break; + case CHANNEL_DIG: + des.bits.dig = tile_dig_designation::Channel; + break; + case RAMP_DIG: + des.bits.dig = tile_dig_designation::Ramp; + break; + case DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::DownStair; + break; + case UP_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpStair; + break; + default: + break; + } + mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); #if DF_VERSION_INT >= 43005 - //remove and job postings related. - for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next) - { - if (listing->item == NULL) - continue; - auto type = listing->item->job_type; - switch (type) - { - case df::enums::job_type::CarveFortification: - case df::enums::job_type::DetailWall: - case df::enums::job_type::DetailFloor: - case df::enums::job_type::Dig: - case df::enums::job_type::CarveUpwardStaircase: - case df::enums::job_type::CarveDownwardStaircase: - case df::enums::job_type::CarveUpDownStaircase: - case df::enums::job_type::CarveRamp: - case df::enums::job_type::DigChannel: - case df::enums::job_type::FellTree: - case df::enums::job_type::GatherPlants: - case df::enums::job_type::RemoveConstruction: - case df::enums::job_type::CarveTrack: - { - if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z())) - { - Job::removeJob(listing->item); - goto JOB_FOUND; - } - break; - } - default: - continue; - } - } - JOB_FOUND: - continue; + //remove and job postings related. + for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next) + { + if (listing->item == NULL) + continue; + auto type = listing->item->job_type; + switch (type) + { + case df::enums::job_type::CarveFortification: + case df::enums::job_type::DetailWall: + case df::enums::job_type::DetailFloor: + case df::enums::job_type::Dig: + case df::enums::job_type::CarveUpwardStaircase: + case df::enums::job_type::CarveDownwardStaircase: + case df::enums::job_type::CarveUpDownStaircase: + case df::enums::job_type::CarveRamp: + case df::enums::job_type::DigChannel: + case df::enums::job_type::FellTree: + case df::enums::job_type::GatherPlants: + case df::enums::job_type::RemoveConstruction: + case df::enums::job_type::CarveTrack: + { + if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z())) + { + Job::removeJob(listing->item); + goto JOB_FOUND; + } + break; + } + default: + continue; + } + } + JOB_FOUND: + continue; #endif - } + } - mc.WriteAll(); - return CR_OK; + mc.WriteAll(); + return CR_OK; } static command_result SetPauseState(color_ostream &stream, const SingleBool *in) { - DFHack::World::SetPauseState(in->value()); - return CR_OK; + DFHack::World::SetPauseState(in->value()); + return CR_OK; } static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out) { - out->set_value(World::ReadPauseState()); - return CR_OK; + out->set_value(World::ReadPauseState()); + return CR_OK; } static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) { - out->set_dfhack_version(DFHACK_VERSION); + out->set_dfhack_version(DFHACK_VERSION); #if DF_VERSION_INT == 34011 - out->set_dwarf_fortress_version("0.34.11"); + out->set_dwarf_fortress_version("0.34.11"); #else - out->set_dwarf_fortress_version(DF_VERSION); + out->set_dwarf_fortress_version(DF_VERSION); #endif - out->set_remote_fortress_reader_version(RFR_VERSION); - return CR_OK; + out->set_remote_fortress_reader_version(RFR_VERSION); + return CR_OK; } +int lastSentReportID = -1; + static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out) { - for (int i = 0; i < world->status.reports.size(); i++) - { - auto local_rep = world->status.reports[i]; - if (!local_rep) - continue; - auto send_rep = out->add_reports(); - send_rep->set_type(local_rep->type); - send_rep->set_text(DF2UTF(local_rep->text)); - ConvertDfColor(local_rep->color | (local_rep->bright ? 8 : 0), send_rep->mutable_color()); - send_rep->set_duration(local_rep->duration); - send_rep->set_continuation(local_rep->flags.bits.continuation); - send_rep->set_unconscious(local_rep->flags.bits.unconscious); - send_rep->set_announcement(local_rep->flags.bits.announcement); - send_rep->set_repeat_count(local_rep->repeat_count); - ConvertDFCoord(local_rep->pos, send_rep->mutable_pos()); - send_rep->set_id(local_rep->id); - send_rep->set_year(local_rep->year); - send_rep->set_time(local_rep->time); - } - return CR_OK; + //First find the last report we sent, so it doesn't get resent. + int lastSentIndex = -1; + for (int i = world->status.reports.size() - 1; i >= 0; i--) + { + auto local_rep = world->status.reports[i]; + if (local_rep->id <= lastSentReportID) + { + lastSentIndex = i; + break; + } + } + for (int i = lastSentIndex + 1; i < world->status.reports.size(); i++) + { + auto local_rep = world->status.reports[i]; + if (!local_rep) + continue; + auto send_rep = out->add_reports(); + send_rep->set_type(local_rep->type); + send_rep->set_text(DF2UTF(local_rep->text)); + ConvertDfColor(local_rep->color | (local_rep->bright ? 8 : 0), send_rep->mutable_color()); + send_rep->set_duration(local_rep->duration); + send_rep->set_continuation(local_rep->flags.bits.continuation); + send_rep->set_unconscious(local_rep->flags.bits.unconscious); + send_rep->set_announcement(local_rep->flags.bits.announcement); + send_rep->set_repeat_count(local_rep->repeat_count); + ConvertDFCoord(local_rep->pos, send_rep->mutable_pos()); + send_rep->set_id(local_rep->id); + send_rep->set_year(local_rep->year); + send_rep->set_time(local_rep->time); + lastSentReportID = local_rep->id; + } + return CR_OK; } \ No newline at end of file From 9cc392e45781e2ee8bccf002203d0f5f24f11ad7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 20 Dec 2017 23:44:11 -0500 Subject: [PATCH 0694/1012] Authors.rst: Add Kromtec (dfhack/scripts#36) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 2537abfd1..fc4b71dd1 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -55,6 +55,7 @@ Jonas Ask kane-t kane-t Kelly Kinkade ab9rf Kris Parker kaypy +Kromtec Kromtec Kurik Amudnil Lethosor lethosor Mason11987 Mason11987 From d6a51f65eb4ad9f281ec01f9eb08b61996e434e0 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Fri, 22 Dec 2017 13:42:59 +0100 Subject: [PATCH 0695/1012] Blocked errant line feed at full buffer --- library/Console-windows.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 3dc687642..3caacd924 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -62,7 +62,7 @@ using namespace DFHack; using namespace tthread; // FIXME: maybe make configurable with an ini option? -#define MAX_CONSOLE_LINES 999; +#define MAX_CONSOLE_LINES 999 namespace DFHack { @@ -165,7 +165,7 @@ namespace DFHack // Blank to EOL char* tmp = (char*)malloc(inf.dwSize.X); memset(tmp, ' ', inf.dwSize.X); - output(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y); + blankout(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y); free(tmp); COORD coord = {0, inf.dwCursorPosition.Y}; // Windows uses 0-based coordinates SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); @@ -210,6 +210,13 @@ namespace DFHack } } + void blankout(const char* str, size_t len, int x, int y) + { + COORD pos = { (SHORT)x, (SHORT)y }; + DWORD count = 0; + WriteConsoleOutputCharacterA(console_out, str, len, pos, &count); + } + void output(const char* str, size_t len, int x, int y) { COORD pos = { (SHORT)x, (SHORT)y }; @@ -248,7 +255,7 @@ namespace DFHack // Blank to EOL char* tmp = (char*)malloc(inf.dwSize.X - (plen + len)); memset(tmp, ' ', inf.dwSize.X - (plen + len)); - output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y); + blankout(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y); free(tmp); } inf.dwCursorPosition.X = (SHORT)(cooked_cursor + plen); From e7a95155ab3b17bdc93ef5221cc7dbd5a0c1d23b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 20:18:55 -0500 Subject: [PATCH 0696/1012] Lua API: add get_vector() and support for renaming class methods See dfhack/df-structures@5bdb0e8 --- library/DataStaticsFields.cpp | 1 + library/xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index d6a622a9e..d6f0414bb 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -60,4 +60,5 @@ namespace df { #define FLD(mode, name) struct_field_info::mode, #name, offsetof(CUR_STRUCT, name) #define GFLD(mode, name) struct_field_info::mode, #name, (size_t)&df::global::name #define METHOD(mode, name) struct_field_info::mode, #name, 0, wrap_function(&CUR_STRUCT::name) +#define METHOD_N(mode, func, name) struct_field_info::mode, #name, 0, wrap_function(&CUR_STRUCT::func) #define FLD_END struct_field_info::END diff --git a/library/xml b/library/xml index c531a0901..31ea71c77 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c531a09019e521fabb3c943bb49f831f635c309f +Subproject commit 31ea71c778beba46f941296a89fa579d5261dab4 From 2b534bef5f7f596e2501a0236d78fff07f7e4496 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 20:20:38 -0500 Subject: [PATCH 0697/1012] Update scripts (exportlegends, create-unit) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 8b6507ed7..922aa7926 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8b6507ed7e8841afe35ee8e156f08edbb7c59f45 +Subproject commit 922aa79268cbad01834bd742cdf184c625cd80ad From 1c9748553e7ee112c395c8a1c0707f17df64deb7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 20:49:24 -0500 Subject: [PATCH 0698/1012] Remove warn-stuck-trees from onLoad.init-example --- onLoad.init-example | 1 - 1 file changed, 1 deletion(-) diff --git a/onLoad.init-example b/onLoad.init-example index 08dd536a7..1d32f07c9 100644 --- a/onLoad.init-example +++ b/onLoad.init-example @@ -1,2 +1 @@ repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] -repeat -name warn-stuck-trees -time 10 -timeUnits days -command [ warn-stuck-trees ] From 98e1842427786aca9d04c56dbbbefb21809c4ef5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 21:15:16 -0500 Subject: [PATCH 0699/1012] List removed scripts to silence docs-related build failure --- docs/Scripts-removed.rst | 14 ++++++++++++++ index.rst | 1 + 2 files changed, 15 insertions(+) create mode 100644 docs/Scripts-removed.rst diff --git a/docs/Scripts-removed.rst b/docs/Scripts-removed.rst new file mode 100644 index 000000000..4490110ba --- /dev/null +++ b/docs/Scripts-removed.rst @@ -0,0 +1,14 @@ +############### +Removed scripts +############### + +The following scripts were removed for various reasons. + +.. contents:: + :depth: 2 + +.. _warn-stuck-trees: + +warn-stuck-trees +================ +The corresponding DF bug, :bug:`9252` was fixed in DF 0.44.01. diff --git a/index.rst b/index.rst index 1f647907b..5cba99e2c 100644 --- a/index.rst +++ b/index.rst @@ -50,6 +50,7 @@ Other Contents /docs/Authors /LICENSE /NEWS + /docs/Scripts-removed For Developers ============== From d90ee0edf883bf9e53b00140cd238cf3415fe8a4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 21:15:30 -0500 Subject: [PATCH 0700/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 31ea71c77..bfeb74b74 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 31ea71c778beba46f941296a89fa579d5261dab4 +Subproject commit bfeb74b748a2af231352a11df3123af7d8ccc199 diff --git a/scripts b/scripts index 922aa7926..6934b9d47 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 922aa79268cbad01834bd742cdf184c625cd80ad +Subproject commit 6934b9d47ccce5908395998133c14c91497f0935 From c10f7ee77a6532a8d2bfa2eccc37d4a03b9b1908 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 21:44:18 -0500 Subject: [PATCH 0701/1012] Authors: add @ZechyW (dfhack/scripts#40) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index fc4b71dd1..4223fb5ee 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -133,6 +133,7 @@ Vjek Warmist warmist Wes Malone wesQ3 Will Rogers wjrogers +ZechyW ZechyW Zhentar Zhentar zilpin zilpin ======================= ======================= =========================== From 86dc58f22e1d532b6ff6404ba1d8f7f2253ca92d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 22:08:04 -0500 Subject: [PATCH 0702/1012] Update changelogs --- NEWS.rst | 33 +++++++++++++++++++++++++++++++++ docs/NEWS-dev.rst | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 673ae31f9..851df0f56 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -42,6 +42,39 @@ DFHack future Lua --- - Added a new ``dfhack.console`` API +- Exposed ``get_vector()`` (from C++) for all types that support ``find()``, + e.g. ``df.unit.get_vector() == df.global.world.units.all`` + +New Scripts +----------- +- `devel/check-other-ids`: Checks the validity of "other" vectors in the + ``world`` global +- `gui/cp437-table`: An in-game CP437 table + +Fixes +----- +- Fixed issues with the console output color affecting the prompt on Windows +- `createitem`: stopped items from teleporting away in some forts +- `gui/gm-unit`: can now edit mining skill +- `gui/quickcmd`: stopped error from adding too many commands + +Misc Improvements +----------------- +- The console now provides suggestions for built-in commands +- `devel/export-dt-ini`: avoid hardcoding flags +- `exportlegends`: + + - reordered some tags to match DF's order + - added progress indicators for exporting long lists + +- `gui/gm-editor`: added enum names to enum edit dialogs +- `gui/gm-unit`: made skill search case-insensitive +- `gui/rename`: added "clear" and "special characters" options +- `remotefortressreader`: includes item stack sizes and some performance improvements + +Removed +------- +- `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01 DFHack 0.43.05-r3 ================= diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 8dfe2866b..e44bdf96c 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,48 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.02-beta1 +==================== + +Fixes +----- +- Fixed issues with the console output color affecting the prompt on Windows +- `createitem`: stopped items from teleporting away in some forts +- `gui/gm-unit`: can now edit mining skill +- `gui/quickcmd`: stopped error from adding too many commands +- `modtools/create-unit`: fixed error when domesticating units + +Structures +---------- +- Located ``start_dwarf_count`` offset for all builds except 64-bit Linux; + `startdwarf` should work now +- Added ``buildings_other_id.DISPLAY_CASE`` +- Fixed ``viewscreen_titlest.start_savegames`` alignment +- Fixed ``unit`` alignment +- Identified ``historical_entity.unknown1b.deities`` (deity IDs) + +API Changes +----------- +- Lua; Exposed ``get_vector()`` (from C++) for all types that support + ``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all`` + +Additions/Removals +------------------ +- Added `devel/check-other-ids`: Checks the validity of "other" vectors in the + ``world`` global +- Added `gui/cp437-table`: An in-game CP437 table +- Removed `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01 + +Other Changes +------------- +- The console now provides suggestions for built-in commands +- `devel/export-dt-ini`: avoid hardcoding flags +- `gui/gm-editor`: added enum names to enum edit dialogs +- `gui/gm-unit`: made skill search case-insensitive +- `gui/rename`: added "clear" and "special characters" options +- `remotefortressreader`: includes item stack sizes and some performance improvements + + DFHack 0.44.02-alpha1 ===================== From 2e505fae289028da5211cd1a663814c672d36e41 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 22:09:09 -0500 Subject: [PATCH 0703/1012] Reorder sections in NEWS (future only) --- NEWS.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 851df0f56..66963fff2 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -12,13 +12,13 @@ Sections for each release are added as required, and consist solely of the following in order as subheadings:: - Internals - Lua - Ruby New [Internal Commands | Plugins | Scripts | Tweaks | Features] Fixes Misc Improvements Removed + Internals + Lua + Ruby When referring to a script, plugin, or command, use backticks (```) to create a link to the relevant documentation - and check that the docs are @@ -39,12 +39,6 @@ Changelog DFHack future ============= -Lua ---- -- Added a new ``dfhack.console`` API -- Exposed ``get_vector()`` (from C++) for all types that support ``find()``, - e.g. ``df.unit.get_vector() == df.global.world.units.all`` - New Scripts ----------- - `devel/check-other-ids`: Checks the validity of "other" vectors in the @@ -76,6 +70,13 @@ Removed ------- - `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01 +Lua +--- +- Added a new ``dfhack.console`` API +- Exposed ``get_vector()`` (from C++) for all types that support ``find()``, + e.g. ``df.unit.get_vector() == df.global.world.units.all`` + + DFHack 0.43.05-r3 ================= From 6175d7c151b1297298a566aad50d0bcb1460469b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Dec 2017 22:09:35 -0500 Subject: [PATCH 0704/1012] Bump to 0.44.02-beta1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d55cd9e43..4e47a12d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.02") -SET(DFHACK_RELEASE "alpha1") +SET(DFHACK_RELEASE "beta1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 22bdc6227cc08e67cb1767f44594566fd56de3d0 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 24 Dec 2017 10:50:30 +0530 Subject: [PATCH 0705/1012] add more world mode options to world info --- library/RemoteTools.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 77374ea0c..8b62d7ab7 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -395,15 +395,21 @@ static command_result GetWorldInfo(color_ostream &stream, { case game_type::DWARF_MAIN: case game_type::DWARF_RECLAIM: - out->set_mode(GetWorldInfoOut::MODE_DWARF); + case game_type::DWARF_ARENA: + case game_type::DWARF_TUTORIAL: + case game_type::DWARF_UNRETIRE: + out->set_mode(GetWorldInfoOut::MODE_DWARF); out->set_civ_id(ui->civ_id); out->set_site_id(ui->site_id); out->set_group_id(ui->group_id); out->set_race_id(ui->race_id); break; - case game_type::ADVENTURE_MAIN: - out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); + case game_type::ADVENTURE_MAIN: + case game_type::ADVENTURE_ARENA: + case game_type::ADVENTURE_DUNGEON: + case game_type::ADVENTURE_WORLD_DEBUG: + out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); if (auto unit = vector_get(world->units.active, 0)) out->set_player_unit_id(unit->id); From 73c6209f875c4032c7f0cf6a5c67f607bce393a1 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 24 Dec 2017 17:22:50 +0530 Subject: [PATCH 0706/1012] Remove extra These don't actually act the same. --- library/RemoteTools.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 8b62d7ab7..90f4104ca 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -395,8 +395,6 @@ static command_result GetWorldInfo(color_ostream &stream, { case game_type::DWARF_MAIN: case game_type::DWARF_RECLAIM: - case game_type::DWARF_ARENA: - case game_type::DWARF_TUTORIAL: case game_type::DWARF_UNRETIRE: out->set_mode(GetWorldInfoOut::MODE_DWARF); out->set_civ_id(ui->civ_id); @@ -407,8 +405,6 @@ static command_result GetWorldInfo(color_ostream &stream, case game_type::ADVENTURE_MAIN: case game_type::ADVENTURE_ARENA: - case game_type::ADVENTURE_DUNGEON: - case game_type::ADVENTURE_WORLD_DEBUG: out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); if (auto unit = vector_get(world->units.active, 0)) From 1408f7bfb3e863ec24f7375c397ba50dc01cccac Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 24 Dec 2017 18:33:25 +0530 Subject: [PATCH 0707/1012] Add a command to remotely send simple movement commands to an adventurer. --- plugins/proto/AdventureControl.proto | 11 ++ plugins/remotefortressreader/CMakeLists.txt | 3 + .../adventure_control.cpp | 162 ++++++++++++++++++ .../remotefortressreader/adventure_control.h | 9 + .../remotefortressreader.cpp | 2 + 5 files changed, 187 insertions(+) create mode 100644 plugins/proto/AdventureControl.proto create mode 100644 plugins/remotefortressreader/adventure_control.cpp create mode 100644 plugins/remotefortressreader/adventure_control.h diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto new file mode 100644 index 000000000..4db1afdf7 --- /dev/null +++ b/plugins/proto/AdventureControl.proto @@ -0,0 +1,11 @@ +package AdventureControl; + +//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization +option optimize_for = LITE_RUNTIME; + +import "RemoteFortressReader.proto"; + +message MoveCommandParams +{ + optional RemoteFortressReader.Coord direction = 1; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index ee6383a3c..72b7c94cc 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -2,16 +2,19 @@ PROJECT (remotefortressreader) # A list of source files SET(PROJECT_SRCS remotefortressreader.cpp + adventure_control.cpp building_reader.cpp ) # A list of headers SET(PROJECT_HDRS + adventure_control.h building_reader.h df_version_int.h ) #proto files to include. SET(PROJECT_PROTO ../../proto/RemoteFortressReader + ../../proto/AdventureControl ) SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp new file mode 100644 index 000000000..d5e713080 --- /dev/null +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -0,0 +1,162 @@ +#include "adventure_control.h" +#include "DataDefs.h" + +#include "df/viewscreen.h" + +#include "modules/Gui.h" + +using namespace AdventureControl; +using namespace df::enums; +using namespace DFHack; +using namespace Gui; + + +command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) +{ + auto viewScreen = getCurViewscreen(); + if (!in->has_direction()) + return CR_WRONG_USAGE; + auto dir = in->direction(); + switch (dir.x()) + { + case -1: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_NW_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_NW); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_NW_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_W_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_W); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_W_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_SW_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_SW); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_SW_UP); + break; + } + break; + } + break; + case 0: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_N_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_N); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_N_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_DOWN); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_S_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_S); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_S_UP); + break; + } + break; + } + break; + case 1: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_NE_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_NE); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_NE_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_E_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_E); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_E_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_SE_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_SE); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_SE_UP); + break; + } + break; + } + break; + } + return CR_OK; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h new file mode 100644 index 000000000..f4d912019 --- /dev/null +++ b/plugins/remotefortressreader/adventure_control.h @@ -0,0 +1,9 @@ +#ifndef ADVENTURE_CONTROL_H +#define ADVENTURE_CONTROL_H + +#include "RemoteClient.h" +#include "AdventureControl.pb.h" + +DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); + +#endif // !ADVENTURE_CONTROL_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 93df40dac..8eae24adb 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -113,6 +113,7 @@ #include "df/unit_relationship_type.h" +#include "adventure_control.h" #include "building_reader.h" using namespace DFHack; @@ -286,6 +287,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); + svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); return svc; } From 692bce8507614028e8d854f2cb6a3791df2f61ed Mon Sep 17 00:00:00 2001 From: Lethosor Date: Sun, 24 Dec 2017 13:50:54 -0500 Subject: [PATCH 0708/1012] Tabs to spaces --- library/RemoteTools.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 90f4104ca..e29220fb7 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -395,17 +395,17 @@ static command_result GetWorldInfo(color_ostream &stream, { case game_type::DWARF_MAIN: case game_type::DWARF_RECLAIM: - case game_type::DWARF_UNRETIRE: - out->set_mode(GetWorldInfoOut::MODE_DWARF); + case game_type::DWARF_UNRETIRE: + out->set_mode(GetWorldInfoOut::MODE_DWARF); out->set_civ_id(ui->civ_id); out->set_site_id(ui->site_id); out->set_group_id(ui->group_id); out->set_race_id(ui->race_id); break; - case game_type::ADVENTURE_MAIN: - case game_type::ADVENTURE_ARENA: - out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); + case game_type::ADVENTURE_MAIN: + case game_type::ADVENTURE_ARENA: + out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); if (auto unit = vector_get(world->units.active, 0)) out->set_player_unit_id(unit->id); From d6a6bf008e2b51d8a38360e4e5b6933b7f1856e0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 24 Dec 2017 23:17:39 -0500 Subject: [PATCH 0709/1012] Update xml, stonesense Ref #1206 --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index bfeb74b74..f04f55fea 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit bfeb74b748a2af231352a11df3123af7d8ccc199 +Subproject commit f04f55feaae9e234d88373cdf4491bde5fa346ef diff --git a/plugins/stonesense b/plugins/stonesense index be793a080..4c55e1439 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit be793a080e66db1ff79ac284070632ec1a896708 +Subproject commit 4c55e1439e4e9fa55f6b17147a346c9cde11f7b3 From 5d32253b6e028afb73fd8bdf62d2e5eae1f57225 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 25 Dec 2017 22:00:45 +0530 Subject: [PATCH 0710/1012] added jumping ability to adventure control. --- .../adventure_control.cpp | 51 +++++++++++++++++++ .../remotefortressreader/adventure_control.h | 3 ++ .../remotefortressreader.cpp | 16 +++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp index d5e713080..6e717ebd1 100644 --- a/plugins/remotefortressreader/adventure_control.cpp +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -5,11 +5,23 @@ #include "modules/Gui.h" +#include + using namespace AdventureControl; using namespace df::enums; using namespace DFHack; using namespace Gui; +std::queue keyQueue; + +void KeyUpdate() +{ + if (!keyQueue.empty()) + { + getCurViewscreen()->feed_key(keyQueue.front()); + keyQueue.pop(); + } +} command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) { @@ -159,4 +171,43 @@ command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParam break; } return CR_OK; +} +command_result JumpCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) +{ + if (!in->has_direction()) + return CR_WRONG_USAGE; + auto dir = in->direction(); + keyQueue.push(interface_key::A_JUMP); + int x = dir.x(); + int y = dir.y(); + if (x > 0) + { + for (int i = 0; i < x; i++) + { + keyQueue.push(interface_key::CURSOR_RIGHT); + } + } + if (x < 0) + { + for (int i = 0; i > x; i--) + { + keyQueue.push(interface_key::CURSOR_LEFT); + } + } + if (y > 0) + { + for (int i = 0; i < y; i++) + { + keyQueue.push(interface_key::CURSOR_DOWN); + } + } + if (y < 0) + { + for (int i = 0; i > y; i--) + { + keyQueue.push(interface_key::CURSOR_UP); + } + } + keyQueue.push(interface_key::SELECT); + return CR_OK; } \ No newline at end of file diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h index f4d912019..c84fe9722 100644 --- a/plugins/remotefortressreader/adventure_control.h +++ b/plugins/remotefortressreader/adventure_control.h @@ -5,5 +5,8 @@ #include "AdventureControl.pb.h" DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); +DFHack::command_result JumpCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); +void KeyUpdate(); + #endif // !ADVENTURE_CONTROL_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 8eae24adb..8ebb75d79 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -234,6 +234,8 @@ command_result RemoteFortressReader_version(color_ostream &out, vector & return CR_OK; } +DFHACK_PLUGIN_IS_ENABLED(enableUpdates); + // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -248,7 +250,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); - return svc; + svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); + return svc; } // This is called right before the plugin library is removed from memory. @@ -300,6 +304,14 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) return CR_OK; } +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!enableUpdates) + return CR_OK; + KeyUpdate(); + return CR_OK; +} + uint16_t fletcher16(uint8_t const *data, size_t bytes) { uint16_t sum1 = 0xff, sum2 = 0xff; From 9adeeaeba1ef55a33931c710a5f098803973b387 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 25 Dec 2017 22:13:49 +0530 Subject: [PATCH 0711/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index f04f55fea..9c3466336 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f04f55feaae9e234d88373cdf4491bde5fa346ef +Subproject commit 9c3466336acb74fa5bc55efaa02fbddb09a1541f From f5fc7fe1a1cf231b70252e6b364a3d9c4fe57794 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 26 Dec 2017 00:08:05 +0530 Subject: [PATCH 0712/1012] Report the current adventure mode menu back to Armok Vision, currently including careful movement options. --- plugins/proto/AdventureControl.proto | 64 +++ .../adventure_control.cpp | 427 ++++++++++-------- .../remotefortressreader/adventure_control.h | 1 + .../remotefortressreader.cpp | 6 +- 4 files changed, 308 insertions(+), 190 deletions(-) diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto index 4db1afdf7..3bef8d939 100644 --- a/plugins/proto/AdventureControl.proto +++ b/plugins/proto/AdventureControl.proto @@ -5,7 +5,71 @@ option optimize_for = LITE_RUNTIME; import "RemoteFortressReader.proto"; +enum AdvmodeMenu +{ + Default = 0; + Look = 1; + ConversationAddress = 2; + ConversationSelect = 3; + ConversationSpeak = 4; + Inventory = 5; + Drop = 6; + ThrowItem = 7; + Wear = 8; + Remove = 9; + Interact = 10; + Put = 11; + PutContainer = 12; + Eat = 13; + ThrowAim = 14; + Fire = 15; + Get = 16; + Unk17 = 17; + CombatPrefs = 18; + Companions = 19; + MovementPrefs = 20; + SpeedPrefs = 21; + InteractAction = 22; + MoveCarefully = 23; + Announcements = 24; + UseBuilding = 25; + Travel = 26; + Unk27 = 27; + Unk28 = 28; + SleepConfirm = 29; + SelectInteractionTarget = 30; + Unk31 = 31; + Unk32 = 32; + FallAction = 33; + ViewTracks = 34; + Jump = 35; + Unk36 = 36; + AttackConfirm = 37; + AttackType = 38; + AttackBodypart = 39; + AttackStrike = 40; + Unk41 = 41; + Unk42 = 42; + DodgeDirection = 43; + Unk44 = 44; + Unk45 = 45; + Build = 46; +} + message MoveCommandParams { optional RemoteFortressReader.Coord direction = 1; +} + +message MovementOption +{ + optional RemoteFortressReader.Coord dest = 1; + optional RemoteFortressReader.Coord source = 2; + optional RemoteFortressReader.Coord grab = 3; +} + +message MenuContents +{ + optional AdvmodeMenu current_menu = 1; + repeated MovementOption movements = 2; } \ No newline at end of file diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp index 6e717ebd1..c3c2933ce 100644 --- a/plugins/remotefortressreader/adventure_control.cpp +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -1,6 +1,13 @@ #include "adventure_control.h" #include "DataDefs.h" +#include "df/adventure_movement_attack_creaturest.h" +#include "df/adventure_movement_building_interactst.h" +#include "df/adventure_movement_climbst.h" +#include "df/adventure_movement_hold_itemst.h" +#include "df/adventure_movement_hold_tilest.h" +#include "df/adventure_movement_optionst.h" +#include "df/ui_advmode.h" #include "df/viewscreen.h" #include "modules/Gui.h" @@ -16,198 +23,242 @@ std::queue keyQueue; void KeyUpdate() { - if (!keyQueue.empty()) - { - getCurViewscreen()->feed_key(keyQueue.front()); - keyQueue.pop(); - } + if (!keyQueue.empty()) + { + getCurViewscreen()->feed_key(keyQueue.front()); + keyQueue.pop(); + } +} + +void SetCoord(df::coord in, RemoteFortressReader::Coord *out) +{ + out->set_x(in.x); + out->set_y(in.y); + out->set_z(in.z); } command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) { - auto viewScreen = getCurViewscreen(); - if (!in->has_direction()) - return CR_WRONG_USAGE; - auto dir = in->direction(); - switch (dir.x()) - { - case -1: - switch (dir.y()) - { - case -1: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_NW_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_NW); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_NW_UP); - break; - } - break; - case 0: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_W_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_W); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_W_UP); - break; - } - break; - case 1: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_SW_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_SW); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_SW_UP); - break; - } - break; - } - break; - case 0: - switch (dir.y()) - { - case -1: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_N_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_N); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_N_UP); - break; - } - break; - case 0: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_DOWN); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_UP); - break; - } - break; - case 1: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_S_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_S); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_S_UP); - break; - } - break; - } - break; - case 1: - switch (dir.y()) - { - case -1: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_NE_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_NE); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_NE_UP); - break; - } - break; - case 0: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_E_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_E); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_E_UP); - break; - } - break; - case 1: - switch (dir.z()) - { - case -1: - viewScreen->feed_key(interface_key::A_MOVE_SE_DOWN); - break; - case 0: - viewScreen->feed_key(interface_key::A_MOVE_SE); - break; - case 1: - viewScreen->feed_key(interface_key::A_MOVE_SE_UP); - break; - } - break; - } - break; - } - return CR_OK; + auto viewScreen = getCurViewscreen(); + if (!in->has_direction()) + return CR_WRONG_USAGE; + if (!df::global::ui_advmode->menu == ui_advmode_menu::Default) + return CR_OK; + auto dir = in->direction(); + switch (dir.x()) + { + case -1: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_NW_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_NW); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_NW_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_W_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_W); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_W_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_SW_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_SW); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_SW_UP); + break; + } + break; + } + break; + case 0: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_N_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_N); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_N_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_DOWN); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_S_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_S); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_S_UP); + break; + } + break; + } + break; + case 1: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_NE_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_NE); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_NE_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_E_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_E); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_E_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_SE_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_SE); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_SE_UP); + break; + } + break; + } + break; + } + return CR_OK; } command_result JumpCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) { - if (!in->has_direction()) - return CR_WRONG_USAGE; - auto dir = in->direction(); - keyQueue.push(interface_key::A_JUMP); - int x = dir.x(); - int y = dir.y(); - if (x > 0) - { - for (int i = 0; i < x; i++) - { - keyQueue.push(interface_key::CURSOR_RIGHT); - } - } - if (x < 0) - { - for (int i = 0; i > x; i--) - { - keyQueue.push(interface_key::CURSOR_LEFT); - } - } - if (y > 0) - { - for (int i = 0; i < y; i++) - { - keyQueue.push(interface_key::CURSOR_DOWN); - } - } - if (y < 0) - { - for (int i = 0; i > y; i--) - { - keyQueue.push(interface_key::CURSOR_UP); - } - } - keyQueue.push(interface_key::SELECT); - return CR_OK; -} \ No newline at end of file + if (!in->has_direction()) + return CR_WRONG_USAGE; + if (!df::global::ui_advmode->menu == ui_advmode_menu::Default) + return CR_OK; + auto dir = in->direction(); + keyQueue.push(interface_key::A_JUMP); + int x = dir.x(); + int y = dir.y(); + if (x > 0) + { + for (int i = 0; i < x; i++) + { + keyQueue.push(interface_key::CURSOR_RIGHT); + } + } + if (x < 0) + { + for (int i = 0; i > x; i--) + { + keyQueue.push(interface_key::CURSOR_LEFT); + } + } + if (y > 0) + { + for (int i = 0; i < y; i++) + { + keyQueue.push(interface_key::CURSOR_DOWN); + } + } + if (y < 0) + { + for (int i = 0; i > y; i--) + { + keyQueue.push(interface_key::CURSOR_UP); + } + } + keyQueue.push(interface_key::SELECT); + return CR_OK; +} + +command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, MenuContents *out) +{ + auto advUi = df::global::ui_advmode; + + if (advUi == NULL) + return CR_FAILURE; + + out->set_current_menu((AdvmodeMenu)advUi->menu); + + switch (advUi->menu) + { + case ui_advmode_menu::MoveCarefully: + for (size_t i = 0; i < advUi->movements.size(); i++) + { + auto movement = advUi->movements[i]; + auto send_movement = out->add_movements(); + SetCoord(movement->source, send_movement->mutable_source()); + SetCoord(movement->dest, send_movement->mutable_dest()); + + STRICT_VIRTUAL_CAST_VAR(climbMovement, df::adventure_movement_climbst, movement); + if (climbMovement) + SetCoord(climbMovement->grab, send_movement->mutable_grab()); + STRICT_VIRTUAL_CAST_VAR(holdTileMovement, df::adventure_movement_hold_tilest, movement); + if (holdTileMovement) + SetCoord(holdTileMovement->grab, send_movement->mutable_grab()); + } + default: + break; + } + + return CR_OK; +} diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h index c84fe9722..16d30aeee 100644 --- a/plugins/remotefortressreader/adventure_control.h +++ b/plugins/remotefortressreader/adventure_control.h @@ -6,6 +6,7 @@ DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); DFHack::command_result JumpCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); +DFHack::command_result MenuQuery(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, AdventureControl::MenuContents *out); void KeyUpdate(); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 8ebb75d79..ae392c7c1 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -130,6 +130,7 @@ REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(gamemode); +REQUIRE_GLOBAL(ui_advmode); #endif // Here go all the command declarations... @@ -291,8 +292,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); - svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); - return svc; + svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); + svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); + return svc; } // This is called right before the plugin library is removed from memory. From e6651171bfb19404376cbf2ed6d4a0afd046cccb Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 25 Dec 2017 14:40:06 -0500 Subject: [PATCH 0713/1012] json: Improve IO-related error messages --- library/lua/json.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/lua/json.lua b/library/lua/json.lua index ac7aa6327..2b4b3a66a 100644 --- a/library/lua/json.lua +++ b/library/lua/json.lua @@ -21,6 +21,9 @@ function encode_file(data, path, ...) end local contents = encode(data, ...) local f = io.open(path, 'w') + if not f then + error('Could not write to ' .. tostring(path)) + end f:write(contents) f:close() end @@ -32,7 +35,7 @@ end function decode_file(path, ...) local f = io.open(path) if not f then - error('Could not open ' .. path) + error('Could not read from ' .. tostring(path)) end local contents = f:read('*all') f:close() From 0332a5c25aec0614d08d520923ec63132a583dba Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 25 Dec 2017 20:13:15 -0500 Subject: [PATCH 0714/1012] Replace a call to abort() with a VTableMissing exception This makes some scripts crash less when the viewscreen vtable is unavailable, for example. --- library/DataDefs.cpp | 3 ++- library/LuaTypes.cpp | 9 +++++++++ library/MiscUtils.cpp | 4 ++++ library/include/Error.h | 8 ++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index c9586fcc0..ab50eb942 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -36,6 +36,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "VTableInterpose.h" +#include "Error.h" #include "MiscUtils.h" @@ -310,7 +311,7 @@ void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) return; std::cerr << "Attempt to create class '" << getName() << "' without known vtable." << std::endl; - abort(); + throw DFHack::Error::VTableMissing(getName()); } virtual_ptr virtual_identity::clone(virtual_ptr obj) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 6e3c5d5cf..f30a1878e 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1086,6 +1086,11 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?"); field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); } + catch (Error::VTableMissing &e) { + const char *cn = e.name(); + std::string tmp = stl_sprintf("Missing vtable address: %s", cn ? cn : "?"); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } catch (std::exception &e) { std::string tmp = stl_sprintf("C++ exception: %s", e.what()); field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); @@ -1110,6 +1115,10 @@ int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *cont const char *vn = e.expr(); return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?"); } + catch (Error::VTableMissing &e) { + const char *cn = e.name(); + return luaL_error(state, "%s: Missing vtable address: %s", context, cn ? cn : "?"); + } catch (std::exception &e) { return luaL_error(state, "%s: C++ exception: %s", context, e.what()); } diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index a6bb4b412..bff7f07e1 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -49,6 +49,10 @@ const char *DFHack::Error::InvalidArgument::what() const throw() { return "DFHack::Error::InvalidArgument"; } +const char *DFHack::Error::VTableMissing::what() const throw() { + return "DFHack::Error::VTableMissing"; +} + std::string stl_sprintf(const char *fmt, ...) { va_list lst; va_start(lst, fmt); diff --git a/library/include/Error.h b/library/include/Error.h index 6b4781028..2d0baadfc 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -75,6 +75,14 @@ namespace DFHack #define CHECK_INVALID_ARGUMENT(expr) \ { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); } + class DFHACK_EXPORT VTableMissing : public All { + const char *name_; + public: + VTableMissing(const char *name_ = NULL) : name_(name_) {} + const char *name() const { return name_; } + virtual const char *what() const throw(); + }; + class DFHACK_EXPORT AllSymbols : public All{}; // Syntax errors and whatnot, the xml can't be read From 4d5c7c59273638d3593cadab2e7167cfbbfcd708 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 25 Dec 2017 20:14:11 -0500 Subject: [PATCH 0715/1012] getKeyDisplay: use enabler::GetKeyDisplay() --- library/modules/Screen.cpp | 28 ---------------------------- library/xml | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 06faf610f..96f385d8d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -372,12 +372,6 @@ bool Screen::hasActiveScreens(Plugin *plugin) } #ifdef _LINUX -// Link to the libgraphics class directly: -class DFHACK_EXPORT enabler_inputst { - public: - std::string GetKeyDisplay(int binding); -}; - class DFHACK_EXPORT renderer { unsigned char *screen; long *screentexpos; @@ -418,15 +412,6 @@ public: virtual bool get_mouse_coords(int &x, int &y) { return false; } virtual bool uses_opengl(); }; -#else -struct less_sz { - bool operator() (const string &a, const string &b) const { - if (a.size() < b.size()) return true; - if (a.size() > b.size()) return false; - return a < b; - } -}; -static std::map > *keydisplay = NULL; #endif void init_screen_module(Core *core) @@ -435,26 +420,13 @@ void init_screen_module(Core *core) renderer tmp; if (!strict_virtual_cast((virtual_ptr)&tmp)) cerr << "Could not fetch the renderer vtable." << std::endl; -#else - if (!core->vinfo->getAddress("keydisplay", keydisplay)) - keydisplay = NULL; #endif } string Screen::getKeyDisplay(df::interface_key key) { -#ifdef _LINUX - auto enabler = (enabler_inputst*)df::global::enabler; if (enabler) return enabler->GetKeyDisplay(key); -#else - if (keydisplay) - { - auto it = keydisplay->find(key); - if (it != keydisplay->end() && !it->second.empty()) - return *it->second.begin(); - } -#endif return "?"; } diff --git a/library/xml b/library/xml index f04f55fea..559c23baa 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f04f55feaae9e234d88373cdf4491bde5fa346ef +Subproject commit 559c23baa06f68d62a0ba674669389273bfcfb97 From b9f6f3adc614e8fc58e725bf138983ac0cfd956d Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 25 Dec 2017 20:14:18 -0500 Subject: [PATCH 0716/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6934b9d47..a12daa56c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6934b9d47ccce5908395998133c14c91497f0935 +Subproject commit a12daa56cba2219153f4e974aa9217c888f6279c From 0eff9fa084da7602ed5dc3623f125f85f3140742 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 25 Dec 2017 22:48:54 -0500 Subject: [PATCH 0717/1012] Bump version to 0.44.03-alpha1 and update submodules --- CMakeLists.txt | 4 ++-- library/xml | 2 +- scripts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e47a12d6..5672eaab7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,8 +140,8 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.02") -SET(DFHACK_RELEASE "beta1") +set(DF_VERSION "0.44.03") +SET(DFHACK_RELEASE "alpha1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/library/xml b/library/xml index 559c23baa..71ac8d257 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 559c23baa06f68d62a0ba674669389273bfcfb97 +Subproject commit 71ac8d257fe05a6b161adf933796a9721b7890fd diff --git a/scripts b/scripts index a12daa56c..33169f442 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a12daa56cba2219153f4e974aa9217c888f6279c +Subproject commit 33169f44258188dddfae3775e8cee0d06b4e209e From 11c60424166074409f64bb28b398b8aa3cfa9d16 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 25 Dec 2017 23:01:42 -0500 Subject: [PATCH 0718/1012] Update NEWS-dev --- docs/NEWS-dev.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index e44bdf96c..6bed2e344 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,15 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.03-alpha1 +===================== + +Other Changes +------------- +- Lua: Improved ``json`` I/O error messages +- Lua: Stopped a crash when trying to create instances of classes whose vtable + addresses are not available + DFHack 0.44.02-beta1 ==================== From e9e5113ff7a29500301879628cbc6816c956b1b3 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Tue, 26 Dec 2017 12:34:46 +0530 Subject: [PATCH 0719/1012] Add careful movement type to proto. Not used yet. --- plugins/proto/AdventureControl.proto | 112 ++++++++++-------- .../adventure_control.cpp | 16 +++ .../remotefortressreader/adventure_control.h | 2 + .../remotefortressreader.cpp | 5 +- 4 files changed, 86 insertions(+), 49 deletions(-) diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto index 3bef8d939..9c41fdfad 100644 --- a/plugins/proto/AdventureControl.proto +++ b/plugins/proto/AdventureControl.proto @@ -7,53 +7,70 @@ import "RemoteFortressReader.proto"; enum AdvmodeMenu { - Default = 0; - Look = 1; - ConversationAddress = 2; - ConversationSelect = 3; - ConversationSpeak = 4; - Inventory = 5; - Drop = 6; - ThrowItem = 7; - Wear = 8; - Remove = 9; - Interact = 10; - Put = 11; - PutContainer = 12; - Eat = 13; - ThrowAim = 14; - Fire = 15; - Get = 16; - Unk17 = 17; - CombatPrefs = 18; - Companions = 19; - MovementPrefs = 20; - SpeedPrefs = 21; - InteractAction = 22; - MoveCarefully = 23; - Announcements = 24; - UseBuilding = 25; - Travel = 26; - Unk27 = 27; - Unk28 = 28; - SleepConfirm = 29; - SelectInteractionTarget = 30; - Unk31 = 31; - Unk32 = 32; - FallAction = 33; - ViewTracks = 34; - Jump = 35; - Unk36 = 36; - AttackConfirm = 37; - AttackType = 38; - AttackBodypart = 39; - AttackStrike = 40; - Unk41 = 41; - Unk42 = 42; - DodgeDirection = 43; - Unk44 = 44; - Unk45 = 45; - Build = 46; + Default = 0; + Look = 1; + ConversationAddress = 2; + ConversationSelect = 3; + ConversationSpeak = 4; + Inventory = 5; + Drop = 6; + ThrowItem = 7; + Wear = 8; + Remove = 9; + Interact = 10; + Put = 11; + PutContainer = 12; + Eat = 13; + ThrowAim = 14; + Fire = 15; + Get = 16; + Unk17 = 17; + CombatPrefs = 18; + Companions = 19; + MovementPrefs = 20; + SpeedPrefs = 21; + InteractAction = 22; + MoveCarefully = 23; + Announcements = 24; + UseBuilding = 25; + Travel = 26; + Unk27 = 27; + Unk28 = 28; + SleepConfirm = 29; + SelectInteractionTarget = 30; + Unk31 = 31; + Unk32 = 32; + FallAction = 33; + ViewTracks = 34; + Jump = 35; + Unk36 = 36; + AttackConfirm = 37; + AttackType = 38; + AttackBodypart = 39; + AttackStrike = 40; + Unk41 = 41; + Unk42 = 42; + DodgeDirection = 43; + Unk44 = 44; + Unk45 = 45; + Build = 46; +} + +enum CarefulMovementType +{ + DEFAULT_MOVEMENT = 0; + RELEASE_ITEM_HOLD = 1; + RELEASE_TILE_HOLD = 2; + ATTACK_CREATURE = 3; + HOLD_TILE = 4; + MOVE = 5; + CLIMB = 6; + HOLD_ITEM = 7; + BUILDING_INTERACT = 8; + ITEM_INTERACT = 9; + ITEM_INTERACT_GUIDE = 10; + ITEM_INTERACT_RIDE = 11; + ITEM_INTERACT_PUSH = 12; } message MoveCommandParams @@ -66,6 +83,7 @@ message MovementOption optional RemoteFortressReader.Coord dest = 1; optional RemoteFortressReader.Coord source = 2; optional RemoteFortressReader.Coord grab = 3; + optional CarefulMovementType movement_type = 4; } message MenuContents diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp index c3c2933ce..971869ac4 100644 --- a/plugins/remotefortressreader/adventure_control.cpp +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -188,6 +188,7 @@ command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParam } return CR_OK; } + command_result JumpCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) { if (!in->has_direction()) @@ -262,3 +263,18 @@ command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, return CR_OK; } + +command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in) +{ + if (!(df::global::ui_advmode->menu == ui_advmode_menu::MoveCarefully)) + return CR_OK; + int choice = in->value(); + int page = choice / 5; + int select = choice % 5; + for (int i = 0; i < page; i++) + { + keyQueue.push(interface_key::SECONDSCROLL_PAGEDOWN); + } + keyQueue.push((interface_key::interface_key)(interface_key::OPTION1 + select)); + return CR_OK; +} diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h index 16d30aeee..b01558047 100644 --- a/plugins/remotefortressreader/adventure_control.h +++ b/plugins/remotefortressreader/adventure_control.h @@ -7,6 +7,8 @@ DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); DFHack::command_result JumpCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); DFHack::command_result MenuQuery(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, AdventureControl::MenuContents *out); +DFHack::command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in); + void KeyUpdate(); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index ae392c7c1..71f519680 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -293,8 +293,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); - svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); - return svc; + svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); + svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); + return svc; } // This is called right before the plugin library is removed from memory. From cdfe1cf1c7b088ab71304b5980c9dfe00af290a7 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 26 Dec 2017 22:21:36 +0530 Subject: [PATCH 0720/1012] add a few more hotkeys to adventure control --- plugins/proto/AdventureControl.proto | 12 ++++ .../adventure_control.cpp | 58 +++++++++++++++++++ .../remotefortressreader/adventure_control.h | 1 + .../remotefortressreader.cpp | 5 +- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto index 9c41fdfad..a5785d7a3 100644 --- a/plugins/proto/AdventureControl.proto +++ b/plugins/proto/AdventureControl.proto @@ -73,6 +73,13 @@ enum CarefulMovementType ITEM_INTERACT_PUSH = 12; } +enum MiscMoveType +{ + SET_CLIMB = 0; + SET_STAND = 1; + SET_CANCEL = 2; +} + message MoveCommandParams { optional RemoteFortressReader.Coord direction = 1; @@ -90,4 +97,9 @@ message MenuContents { optional AdvmodeMenu current_menu = 1; repeated MovementOption movements = 2; +} + +message MiscMoveParams +{ + optional MiscMoveType type = 1; } \ No newline at end of file diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp index 971869ac4..1e1f26452 100644 --- a/plugins/remotefortressreader/adventure_control.cpp +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -240,6 +240,33 @@ command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, out->set_current_menu((AdvmodeMenu)advUi->menu); +// /$$$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$ /$$$$$$$$ +//| $$_____/|_ $$_/| $$ / $$| $$$ /$$$| $$_____/ +//| $$ | $$ | $$/ $$/| $$$$ /$$$$| $$ +//| $$$$$ | $$ \ $$$$/ | $$ $$/$$ $$| $$$$$ +//| $$__/ | $$ >$$ $$ | $$ $$$| $$| $$__/ +//| $$ | $$ /$$/\ $$| $$\ $ | $$| $$ +//| $$ /$$$$$$| $$ \ $$| $$ \/ | $$| $$$$$$$$ +//|__/ |______/|__/ |__/|__/ |__/|________/ +// +// +// +// /$$$$$$$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ +//|__ $$__/| $$ |__/ |__/ | $$ |__/ | $$ | $$ | $$ +// | $$ | $$$$$$$ /$$ /$$$$$$$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$ /$$ /$$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$$| $$ /$$ +// | $$ | $$__ $$| $$ /$$_____/ | $$ /$$_____/ |____ $$ /$$_____/|_ $$_/ | $$ | $$ /$$__ $$| $$ /$$__ $$ | $$__ $$ |____ $$ /$$_____/| $$ /$$/ +// | $$ | $$ \ $$| $$| $$$$$$ | $$| $$$$$$ /$$$$$$$ | $$$$$$ | $$ | $$ | $$| $$ \ $$| $$| $$ | $$ | $$ \ $$ /$$$$$$$| $$ | $$$$$$/ +// | $$ | $$ | $$| $$ \____ $$ | $$ \____ $$ /$$__ $$ \____ $$ | $$ /$$| $$ | $$| $$ | $$| $$| $$ | $$ | $$ | $$ /$$__ $$| $$ | $$_ $$ +// | $$ | $$ | $$| $$ /$$$$$$$/ | $$ /$$$$$$$/ | $$$$$$$ /$$$$$$$/ | $$$$/| $$$$$$/| $$$$$$$/| $$| $$$$$$$ | $$ | $$| $$$$$$$| $$$$$$$| $$ \ $$ +// |__/ |__/ |__/|__/|_______/ |__/|_______/ \_______/ |_______/ \___/ \______/ | $$____/ |__/ \_______/ |__/ |__/ \_______/ \_______/|__/ \__/ +// | $$ +// | $$ +// |__/ + if (advUi->menu == ui_advmode_menu::FallAction) + { + getCurViewscreen()->feed_key(interface_key::OPTION1); + } + switch (advUi->menu) { case ui_advmode_menu::MoveCarefully: @@ -252,10 +279,16 @@ command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, STRICT_VIRTUAL_CAST_VAR(climbMovement, df::adventure_movement_climbst, movement); if (climbMovement) + { SetCoord(climbMovement->grab, send_movement->mutable_grab()); + send_movement->set_movement_type(CarefulMovementType::CLIMB); + } STRICT_VIRTUAL_CAST_VAR(holdTileMovement, df::adventure_movement_hold_tilest, movement); if (holdTileMovement) + { SetCoord(holdTileMovement->grab, send_movement->mutable_grab()); + send_movement->set_movement_type(CarefulMovementType::HOLD_TILE); + } } default: break; @@ -278,3 +311,28 @@ command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfprot keyQueue.push((interface_key::interface_key)(interface_key::OPTION1 + select)); return CR_OK; } + +command_result MiscMoveCommand(DFHack::color_ostream &stream, const MiscMoveParams *in) +{ + if (!df::global::ui_advmode->menu == ui_advmode_menu::Default) + return CR_OK; + + auto type = in->type(); + + switch (type) + { + case AdventureControl::SET_CLIMB: + getCurViewscreen()->feed_key(interface_key::A_HOLD); + break; + case AdventureControl::SET_STAND: + getCurViewscreen()->feed_key(interface_key::A_STANCE); + break; + case AdventureControl::SET_CANCEL: + getCurViewscreen()->feed_key(interface_key::LEAVESCREEN); + break; + default: + break; + } + + return CR_OK; +} diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h index b01558047..bc674e427 100644 --- a/plugins/remotefortressreader/adventure_control.h +++ b/plugins/remotefortressreader/adventure_control.h @@ -8,6 +8,7 @@ DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const Adventur DFHack::command_result JumpCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); DFHack::command_result MenuQuery(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, AdventureControl::MenuContents *out); DFHack::command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in); +DFHack::command_result MiscMoveCommand(DFHack::color_ostream &stream, const AdventureControl::MiscMoveParams *in); void KeyUpdate(); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 71f519680..118e0704e 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -294,8 +294,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); - svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); - return svc; + svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); + svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE); + return svc; } // This is called right before the plugin library is removed from memory. From 7288f95c51f3e1841e875f7a13165bd386050fc0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 26 Dec 2017 12:52:18 -0500 Subject: [PATCH 0721/1012] Update NEWS.rst too --- NEWS.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 66963fff2..07267a8ab 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -75,6 +75,9 @@ Lua - Added a new ``dfhack.console`` API - Exposed ``get_vector()`` (from C++) for all types that support ``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all`` +- Improved ``json`` I/O error messages +- Stopped a crash when trying to create instances of classes whose vtable + addresses are not available DFHack 0.43.05-r3 From 4007c394384ebce6bdfeadb5dc48568ff41dbcdc Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 26 Dec 2017 12:54:08 -0500 Subject: [PATCH 0722/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 71ac8d257..f713075c4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 71ac8d257fe05a6b161adf933796a9721b7890fd +Subproject commit f713075c4dab5e3d8af475a8cf0f3be026f94d53 From 8123736de7bc3c87583ea0367696c2e477d3e6d7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 26 Dec 2017 20:36:27 -0500 Subject: [PATCH 0723/1012] Update Authors (dfhack/scripts#27) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 4223fb5ee..0f7e06811 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -17,6 +17,7 @@ AndreasPK AndreasPK Angus Mezick amezick Antalia tamarakorr Anuradha Dissanayake falconne +Atkana Atkana AtomicChicken AtomicChicken belal jimhester Ben Lubar BenLubar From 94e6e89b3154380ec71d4cf8350f57e5ea2ce710 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 26 Dec 2017 20:36:37 -0500 Subject: [PATCH 0724/1012] Update xml, scripts --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index f713075c4..24bd450fd 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f713075c4dab5e3d8af475a8cf0f3be026f94d53 +Subproject commit 24bd450fdabe8be6483ad73f1355c0fd30571b05 diff --git a/scripts b/scripts index 33169f442..78904a56d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 33169f44258188dddfae3775e8cee0d06b4e209e +Subproject commit 78904a56d41082e433b4c2e9f4983702f2a9bced From d7ea397ab27698c44d93db3433556ac3b200ae4c Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Wed, 27 Dec 2017 11:15:40 +0530 Subject: [PATCH 0725/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9c3466336..24bd450fd 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9c3466336acb74fa5bc55efaa02fbddb09a1541f +Subproject commit 24bd450fdabe8be6483ad73f1355c0fd30571b05 From e72474613ffa5f59e612594046d569a7c46feedb Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Dec 2017 13:48:53 -0500 Subject: [PATCH 0726/1012] Add new PutItemOnDisplay job to labor management plugins Also add building_type::DisplayFurniture to labormanager Fixes #1208 --- library/xml | 2 +- plugins/autohauler.cpp | 3 ++- plugins/autolabor.cpp | 3 ++- plugins/labormanager/joblabormapper.cpp | 5 ++++- plugins/labormanager/labormanager.cpp | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/library/xml b/library/xml index 24bd450fd..301807985 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 24bd450fdabe8be6483ad73f1355c0fd30571b05 +Subproject commit 301807985eefb4db88416a0f5a7f7b94aadfb667 diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index 7a3f8a3d8..62914c784 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -405,7 +405,8 @@ static const dwarf_state dwarf_states[] = { BUSY /* MakeRing */, BUSY /* MakeEarring */, BUSY /* MakeBracelet */, - BUSY /* MakeGem */ + BUSY /* MakeGem */, + BUSY /* PutItemOnDisplay */, }; // Mode assigned to labors. Either it's a hauling job, or it's not. diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 95863ffb5..a894a2891 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -373,7 +373,8 @@ static const dwarf_state dwarf_states[] = { BUSY /* MakeRing */, BUSY /* MakeEarring */, BUSY /* MakeBracelet */, - BUSY /* MakeGem */ + BUSY /* MakeGem */, + BUSY /* PutItemOnDisplay */, }; struct labor_info diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index 051331137..d16bfafcf 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -358,6 +358,7 @@ public: case df::building_type::GrateWall: case df::building_type::Bookcase: case df::building_type::Instrument: + case df::building_type::DisplayFurniture: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -463,6 +464,7 @@ public: case df::building_type::GrateWall: case df::building_type::Bookcase: case df::building_type::Instrument: + case df::building_type::DisplayFurniture: return df::unit_labor::HAUL_FURNITURE; case df::building_type::AnimalTrap: return df::unit_labor::TRAPPER; @@ -882,6 +884,7 @@ JobLaborMapper::JobLaborMapper() job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; + job_to_labor_table[df::job_type::PutItemOnDisplay] = jlf_const(df::unit_labor::HAUL_ITEM); job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation }; @@ -917,4 +920,4 @@ df::unit_labor JobLaborMapper::find_job_labor(df::job* j) return labor; } -/* End of labor deducer */ \ No newline at end of file +/* End of labor deducer */ diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 3a026d1ea..42e718488 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -377,7 +377,8 @@ static const dwarf_state dwarf_states[] = { BUSY /* MakeRing */, BUSY /* MakeEarring */, BUSY /* MakeBracelet */, - BUSY /* MakeGem */ + BUSY /* MakeGem */, + BUSY /* PutItemOnDisplay */, }; struct labor_info From a3a0ed03a384fc311bc5031c1d7e7e786a60ff38 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Dec 2017 13:50:02 -0500 Subject: [PATCH 0727/1012] Update scripts (gui/gm-editor) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 78904a56d..92ea8f43e 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 78904a56d41082e433b4c2e9f4983702f2a9bced +Subproject commit 92ea8f43ef7ace7e609a83e061d18ddbcf1f56e2 From fde1e284f4822491b3d07202585c59ac487a6e79 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Dec 2017 16:19:45 -0500 Subject: [PATCH 0728/1012] Gui::getAnyItem: support viewscreen_assign_display_itemst --- library/modules/Gui.cpp | 10 ++++++++++ library/xml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 0631e0ed7..d6eedcef7 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -74,6 +74,7 @@ using namespace DFHack; #include "df/ui_unit_view_mode.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/viewscreen_assign_display_itemst.h" #include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_dungeonmodest.h" @@ -1042,6 +1043,15 @@ df::item *Gui::getAnyItem(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_assign_display_itemst, top)) + { + if (screen->sel_column == df::viewscreen_assign_display_itemst::T_sel_column::Items) + return vector_get(screen->items[screen->item_type[screen->sel_type]], + screen->sel_item); + + return NULL; + } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedItem(); diff --git a/library/xml b/library/xml index 301807985..1297be014 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 301807985eefb4db88416a0f5a7f7b94aadfb667 +Subproject commit 1297be0141c7116e677e19a9ddcad296ae31de7d From 3f45ba4e5dbb169b3a8e176bbe5d066c7bbec92a Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Dec 2017 23:36:43 -0500 Subject: [PATCH 0729/1012] Update changelogs --- NEWS.rst | 6 ++++++ docs/NEWS-dev.rst | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 07267a8ab..abecc0900 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -63,7 +63,13 @@ Misc Improvements - `gui/gm-editor`: added enum names to enum edit dialogs - `gui/gm-unit`: made skill search case-insensitive +- `gui/pathable`: added tile types to sidebar - `gui/rename`: added "clear" and "special characters" options +- `modtools/skill-change`: + + - now updates skill levels appropriately + - only prints output if ``-loud`` is passed + - `remotefortressreader`: includes item stack sizes and some performance improvements Removed diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 6bed2e344..1d687f127 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,40 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.03-beta1 +==================== + +Fixes +----- +- `autolabor`, `autohauler`, `labormanager`: added support for "put item on + display" jobs and building/destroying display furniture +- `gui/gm-editor`: fixed an error when editing primitives in Lua tables + +Structures +---------- +- Added 7 new globals from DF: ``version``, ``min_load_version``, + ``movie_version``, ``basic_seed``, ``title``, ``title_spaced``, + ``ui_building_resize_radius`` +- Fixed an issue preventing ``enabler`` from being allocated by DFHack +- Added ``job_type.PutItemOnDisplay`` +- Found ``renderer`` vtable on osx64 +- ``adventure_movement_optionst``, ``adventure_movement_hold_tilest``, + ``adventure_movement_climbst``: named coordinate fields +- ``mission``: added type +- ``unit``: added 3 new vmethods: ``getCreatureTile``, ``getCorpseTile``, ``getGlowTile`` +- ``viewscreen_assign_display_itemst``: fixed layout on x64 and identified many fields +- ``viewscreen_reportlistst``: fixed layout, added ``mission_id`` vector +- ``world.status``: named ``missions`` vector + +Other Changes +------------- +- `devel/dump-offsets`: now ignores ``index`` globals +- `gui/pathable`: added tile types to sidebar +- `modtools/skill-change`: + + - now updates skill levels appropriately + - only prints output if ``-loud`` is passed + DFHack 0.44.03-alpha1 ===================== From 04523f9e5d57ddcf510a464e4152b098e525286f Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Dec 2017 23:40:55 -0500 Subject: [PATCH 0730/1012] dfhack/df-structures#231: twbt_render_map offset for x64 --- docs/NEWS-dev.rst | 1 + library/xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 1d687f127..771684884 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -51,6 +51,7 @@ Structures - Added 7 new globals from DF: ``version``, ``min_load_version``, ``movie_version``, ``basic_seed``, ``title``, ``title_spaced``, ``ui_building_resize_radius`` +- Added ``twbt_render_map`` code offset on x64 - Fixed an issue preventing ``enabler`` from being allocated by DFHack - Added ``job_type.PutItemOnDisplay`` - Found ``renderer`` vtable on osx64 diff --git a/library/xml b/library/xml index 1297be014..7e23a328f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1297be0141c7116e677e19a9ddcad296ae31de7d +Subproject commit 7e23a328fd81e3d6db794c0c18b8b2e7bd235649 From 677833eeaa784b8bfc9ddd9fae6b04176e4c35f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 29 Dec 2017 23:41:47 -0500 Subject: [PATCH 0731/1012] Bump to 0.44.03-beta1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5672eaab7..9a1177549 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.03") -SET(DFHACK_RELEASE "alpha1") +SET(DFHACK_RELEASE "beta1") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From fa39220777e01b365b8cada43cdf775dcf855f3c Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 31 Dec 2017 14:31:58 +0530 Subject: [PATCH 0732/1012] add projectiles, complete with velocity. --- library/xml | 2 +- plugins/proto/RemoteFortressReader.proto | 7 ++ .../remotefortressreader.cpp | 69 ++++++++++++++++--- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/library/xml b/library/xml index 24bd450fd..7e23a328f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 24bd450fdabe8be6483ad73f1355c0fd30571b05 +Subproject commit 7e23a328fd81e3d6db794c0c18b8b2e7bd235649 diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index cb7acca07..623ab7f53 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -268,6 +268,13 @@ message Item optional MatPair material = 6; optional ColorDefinition dye = 7; optional int32 stack_size = 8; + optional float subpos_x = 9; + optional float subpos_y = 10; + optional float subpos_z = 11; + optional bool projectile = 12; + optional float velocity_x = 13; + optional float velocity_y = 14; + optional float velocity_z = 15; } message MapBlock diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 118e0704e..9c7263880 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -62,6 +62,7 @@ #include "df/item.h" #include "df/item_constructed.h" #include "df/item_threadst.h" +#include "df/item_toolst.h" #include "df/itemimprovement.h" #include "df/itemimprovement_threadst.h" #include "df/itemdef.h" @@ -77,6 +78,7 @@ #include "df/plant.h" #include "df/plant_raw_flags.h" #include "df/projectile.h" +#include "df/proj_itemst.h" #include "df/proj_unitst.h" #include "df/region_map_entry.h" #include "df/report.h" @@ -91,6 +93,7 @@ #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/vehicle.h" #include "df/viewscreen_choose_start_sitest.h" #include "df/world.h" #include "df/world_data.h" @@ -1214,6 +1217,39 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N #endif } +void CopyProjectiles(RemoteFortressReader::MapBlock * NetBlock) +{ + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(projectile, df::proj_itemst, proj->item); + if (projectile == NULL) + continue; + auto NetItem = NetBlock->add_items(); + CopyItem(NetItem, projectile->item); + NetItem->set_projectile(true); + if (projectile->flags.bits.parabolic) + { + NetItem->set_subpos_x(projectile->pos_x / 100000.0); + NetItem->set_subpos_y(projectile->pos_y / 100000.0); + NetItem->set_subpos_z(projectile->pos_z / 140000.0); + NetItem->set_velocity_x(projectile->speed_x / 100000.0); + NetItem->set_velocity_y(projectile->speed_y / 100000.0); + NetItem->set_velocity_z(projectile->speed_z / 140000.0); + } + else + { + DFCoord diff = projectile->target_pos - projectile->origin_pos; + float max_dist = max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); + NetItem->set_subpos_x(projectile->origin_pos.x + (diff.x / max_dist * projectile->distance_flown) - projectile->cur_pos.x); + NetItem->set_subpos_y(projectile->origin_pos.y + (diff.y / max_dist * projectile->distance_flown) - projectile->cur_pos.y); + NetItem->set_subpos_z(projectile->origin_pos.z + (diff.z / max_dist * projectile->distance_flown) - projectile->cur_pos.z); + NetItem->set_velocity_x(diff.x / max_dist); + NetItem->set_velocity_y(diff.y / max_dist); + NetItem->set_velocity_z(diff.z / max_dist); + } + } +} + void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) { @@ -1327,6 +1363,24 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) auto type = NetItem->mutable_type(); type->set_mat_type(DfItem->getType()); type->set_mat_index(DfItem->getSubtype()); + + bool isProjectile = false; + + if (!isProjectile && DfItem->getType() == item_type::TOOL) + { + VIRTUAL_CAST_VAR(tool, df::item_toolst, DfItem); + if (tool) + { + auto vehicle = binsearch_in_vector(world->vehicles.active, tool->vehicle_id); + if (vehicle) + { + NetItem->set_subpos_x(vehicle->offset_x / 100000.0); + NetItem->set_subpos_y(vehicle->offset_y / 100000.0); + NetItem->set_subpos_z(vehicle->offset_z / 140000.0); + } + } + } + if (DfItem->getType() == item_type::BOX) { type->set_mat_index(DfItem->isBag()); @@ -1407,7 +1461,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in int max_y = in->max_y(); int min_z = in->min_z(); int max_z = in->max_z(); - bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest. + bool firstBlock = true; //Always send all the buildings needed on the first block, and none on the rest. //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); for (int zz = max_z - 1; zz >= min_z; zz--) { @@ -1440,16 +1494,14 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in || block->occupancy[xxx][yyy].bits.building > 0) nonAir++; } - if (nonAir > 0 || !sentBuildings) + if (nonAir > 0 || firstBlock) { bool tileChanged = IsTiletypeChanged(pos); bool desChanged = IsDesignationChanged(pos); bool spatterChanged = IsspatterChanged(pos); - bool buildingChanged = !sentBuildings; - bool itemsChanged = areItemsChanged(&block->items); - //bool bldChanged = IsBuildingChanged(pos); + bool itemsChanged = true; //simpler just to send the items every frame. RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged) + if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged) net_block = out->add_map_blocks(); if (tileChanged) { @@ -1458,10 +1510,11 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in } if (desChanged) CopyDesignation(block, net_block, &MC, pos); - if (buildingChanged) + if (firstBlock) { CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); - sentBuildings = true; + CopyProjectiles(net_block); + firstBlock = false; } if (spatterChanged) Copyspatters(block, net_block, &MC, pos); From bd8a6c31bac933b21b889ea18d80823670ed927a Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 6 Jan 2018 22:07:34 +0530 Subject: [PATCH 0733/1012] Send vehicles over remoteFortressReader, and don't send every block like a retard. --- .../remotefortressreader.cpp | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 9c7263880..4e4b5f14b 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1248,6 +1248,37 @@ void CopyProjectiles(RemoteFortressReader::MapBlock * NetBlock) NetItem->set_velocity_z(diff.z / max_dist); } } + for (int i = 0; i < world->vehicles.active.size(); i++) + { + bool isProj = false; + auto vehicle = world->vehicles.active[i]; + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(projectile, df::proj_itemst, proj->item); + if (!projectile) + continue; + if (projectile->item->id == vehicle->item_id) + { + isProj = true; + break; + } + } + if (isProj) + continue; + + auto item = Items::findItemByID(vehicle->item_id); + if (!item) + continue; + auto NetItem = NetBlock->add_items(); + CopyItem(NetItem, item); + NetItem->set_subpos_x(vehicle->offset_x / 100000.0); + NetItem->set_subpos_y(vehicle->offset_y / 100000.0); + NetItem->set_subpos_z(vehicle->offset_z / 140000.0); + NetItem->set_velocity_x(vehicle->speed_x / 100000.0); + NetItem->set_velocity_y(vehicle->speed_y / 100000.0); + NetItem->set_velocity_z(vehicle->speed_z / 140000.0); + + } } void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) @@ -1499,7 +1530,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in bool tileChanged = IsTiletypeChanged(pos); bool desChanged = IsDesignationChanged(pos); bool spatterChanged = IsspatterChanged(pos); - bool itemsChanged = true; //simpler just to send the items every frame. + bool itemsChanged = block->items.size() > 0; RemoteFortressReader::MapBlock *net_block; if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged) net_block = out->add_map_blocks(); From d0a924a2073da307630c7aa60c8b364908beffb8 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 6 Jan 2018 23:48:06 +0530 Subject: [PATCH 0734/1012] Send shape ids through RFR --- plugins/proto/RemoteFortressReader.proto | 11 +++++++++ .../remotefortressreader.cpp | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 623ab7f53..92c3cee42 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -275,6 +275,7 @@ message Item optional float velocity_x = 13; optional float velocity_y = 14; optional float velocity_z = 15; + optional int32 shape = 16; } message MapBlock @@ -875,3 +876,13 @@ message Status { repeated Report reports = 1; } + +message ShapeDescriptior +{ + optional string id = 1; +} + +message Language +{ + repeated ShapeDescriptior shapes = 1; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 4e4b5f14b..cafd4a4e5 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -37,6 +37,7 @@ #include "df/block_square_event_item_spatterst.h" #include "df/block_square_event_grassst.h" #endif +#include "df/art_image_element_shapest.h" #include "df/block_square_event_material_spatterst.h" #include "df/body_appearance_modifier.h" #include "df/body_part_layer_raw.h" @@ -61,6 +62,7 @@ #include "df/historical_figure.h" #include "df/item.h" #include "df/item_constructed.h" +#include "df/item_gemst.h" #include "df/item_threadst.h" #include "df/item_toolst.h" #include "df/itemimprovement.h" @@ -168,6 +170,7 @@ static command_result GetPauseState(color_ostream & stream, const EmptyMessage * static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out); +static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -299,6 +302,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE); + svc->addFunction("GetLanguage", GetLanguage, SF_ALLOW_REMOTE); return svc; } @@ -1420,6 +1424,11 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) if (actual_item) { NetItem->set_stack_size(actual_item->stack_size); + } + VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem); + if (gem_item) + { + } VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); if (constructed_item) @@ -2998,4 +3007,18 @@ static command_result GetReports(color_ostream & stream, const EmptyMessage * in lastSentReportID = local_rep->id; } return CR_OK; +} + +static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out) +{ + if (!world) + return CR_FAILURE; + + for (int i = 0; i < world->raws.language.shapes.size(); i++) + { + auto shape = world->raws.language.shapes[i]; + auto netShape = out->add_shapes(); + netShape->set_id(shape->id); + } + return CR_OK; } \ No newline at end of file From d80d16ddc385826ec9c4a897ca73bbb813423aff Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 7 Jan 2018 00:50:32 +0530 Subject: [PATCH 0735/1012] Actually send gem shape. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index cafd4a4e5..9c548066a 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1428,7 +1428,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem); if (gem_item) { - + NetItem->set_shape(gem_item->shape); } VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); if (constructed_item) From 29426111f7e6831168e73e12639aea37593ba153 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 7 Jan 2018 01:14:15 +0530 Subject: [PATCH 0736/1012] Send shapes for small gems too. --- plugins/remotefortressreader/remotefortressreader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 9c548066a..d5138639e 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -65,6 +65,7 @@ #include "df/item_gemst.h" #include "df/item_threadst.h" #include "df/item_toolst.h" +#include "df/item_smallgemst.h" #include "df/itemimprovement.h" #include "df/itemimprovement_threadst.h" #include "df/itemdef.h" @@ -1430,6 +1431,11 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { NetItem->set_shape(gem_item->shape); } + VIRTUAL_CAST_VAR(smallgem_item, df::item_smallgemst, DfItem); + if (smallgem_item) + { + NetItem->set_shape(smallgem_item->shape); + } VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); if (constructed_item) { From 724fb00b73e4e61db6c4a1a4e3591d67682e60ae Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Mon, 8 Jan 2018 09:58:37 +0530 Subject: [PATCH 0737/1012] Use an inteligenter method of setting the gem shapes. --- plugins/proto/RemoteFortressReader.proto | 1 - .../remotefortressreader.cpp | 207 +++++++++++++++++- 2 files changed, 203 insertions(+), 5 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 92c3cee42..08d804262 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -275,7 +275,6 @@ message Item optional float velocity_x = 13; optional float velocity_y = 14; optional float velocity_z = 15; - optional int32 shape = 16; } message MapBlock diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index d5138639e..da4121057 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -920,7 +920,84 @@ static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(-1); mat_def->set_id(ENUM_KEY_STR(item_type, it)); - if (it == item_type::BOX) + switch (it) + { + case df::enums::item_type::NONE: + break; + case df::enums::item_type::BAR: + break; + case df::enums::item_type::GEM: + case df::enums::item_type::SMALLGEM: + { + for (int i = 0; i < world->raws.language.shapes.size(); i++) + { + auto shape = world->raws.language.shapes[i]; + if (shape->gems_use.whole == 0) + continue; + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id); + } + break; + } + case df::enums::item_type::BLOCKS: + break; + case df::enums::item_type::ROUGH: + break; + case df::enums::item_type::BOULDER: + break; + case df::enums::item_type::WOOD: + break; + case df::enums::item_type::DOOR: + break; + case df::enums::item_type::FLOODGATE: + break; + case df::enums::item_type::BED: + break; + case df::enums::item_type::CHAIR: + break; + case df::enums::item_type::CHAIN: + break; + case df::enums::item_type::FLASK: + break; + case df::enums::item_type::GOBLET: + break; + case df::enums::item_type::INSTRUMENT: + break; + case df::enums::item_type::TOY: + break; + case df::enums::item_type::WINDOW: + break; + case df::enums::item_type::CAGE: + break; + case df::enums::item_type::BARREL: + break; + case df::enums::item_type::BUCKET: + break; + case df::enums::item_type::ANIMALTRAP: + break; + case df::enums::item_type::TABLE: + break; + case df::enums::item_type::COFFIN: + break; + case df::enums::item_type::STATUE: + break; + case df::enums::item_type::CORPSE: + break; + case df::enums::item_type::WEAPON: + break; + case df::enums::item_type::ARMOR: + break; + case df::enums::item_type::SHOES: + break; + case df::enums::item_type::SHIELD: + break; + case df::enums::item_type::HELM: + break; + case df::enums::item_type::GLOVES: + break; + case df::enums::item_type::BOX: { mat_def = out->add_material_list(); mat_def->mutable_mat_pair()->set_mat_type((int)it); @@ -930,6 +1007,128 @@ static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(1); mat_def->set_id("BOX_BAG"); + break; + } + case df::enums::item_type::BIN: + break; + case df::enums::item_type::ARMORSTAND: + break; + case df::enums::item_type::WEAPONRACK: + break; + case df::enums::item_type::CABINET: + break; + case df::enums::item_type::FIGURINE: + break; + case df::enums::item_type::AMULET: + break; + case df::enums::item_type::SCEPTER: + break; + case df::enums::item_type::AMMO: + break; + case df::enums::item_type::CROWN: + break; + case df::enums::item_type::RING: + break; + case df::enums::item_type::EARRING: + break; + case df::enums::item_type::BRACELET: + break; + case df::enums::item_type::ANVIL: + break; + case df::enums::item_type::CORPSEPIECE: + break; + case df::enums::item_type::REMAINS: + break; + case df::enums::item_type::MEAT: + break; + case df::enums::item_type::FISH: + break; + case df::enums::item_type::FISH_RAW: + break; + case df::enums::item_type::VERMIN: + break; + case df::enums::item_type::PET: + break; + case df::enums::item_type::SEEDS: + break; + case df::enums::item_type::PLANT: + break; + case df::enums::item_type::SKIN_TANNED: + break; + case df::enums::item_type::PLANT_GROWTH: + break; + case df::enums::item_type::THREAD: + break; + case df::enums::item_type::CLOTH: + break; + case df::enums::item_type::TOTEM: + break; + case df::enums::item_type::PANTS: + break; + case df::enums::item_type::BACKPACK: + break; + case df::enums::item_type::QUIVER: + break; + case df::enums::item_type::CATAPULTPARTS: + break; + case df::enums::item_type::BALLISTAPARTS: + break; + case df::enums::item_type::SIEGEAMMO: + break; + case df::enums::item_type::BALLISTAARROWHEAD: + break; + case df::enums::item_type::TRAPPARTS: + break; + case df::enums::item_type::TRAPCOMP: + break; + case df::enums::item_type::DRINK: + break; + case df::enums::item_type::POWDER_MISC: + break; + case df::enums::item_type::CHEESE: + break; + case df::enums::item_type::FOOD: + break; + case df::enums::item_type::LIQUID_MISC: + break; + case df::enums::item_type::COIN: + break; + case df::enums::item_type::GLOB: + break; + case df::enums::item_type::ROCK: + break; + case df::enums::item_type::PIPE_SECTION: + break; + case df::enums::item_type::HATCH_COVER: + break; + case df::enums::item_type::GRATE: + break; + case df::enums::item_type::QUERN: + break; + case df::enums::item_type::MILLSTONE: + break; + case df::enums::item_type::SPLINT: + break; + case df::enums::item_type::CRUTCH: + break; + case df::enums::item_type::TRACTION_BENCH: + break; + case df::enums::item_type::ORTHOPEDIC_CAST: + break; + case df::enums::item_type::TOOL: + break; + case df::enums::item_type::SLAB: + break; + case df::enums::item_type::EGG: + break; + case df::enums::item_type::BOOK: + break; + case df::enums::item_type::SHEET: + break; + case df::enums::item_type::BRANCH: + break; + default: + break; } int subtypes = Items::getSubtypeCount(it); if (subtypes >= 0) @@ -940,7 +1139,7 @@ static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(i); df::itemdef * item = Items::getSubtypeDef(it, i); - mat_def->set_id(item->id); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + item->id); } } } @@ -1429,12 +1628,12 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem); if (gem_item) { - NetItem->set_shape(gem_item->shape); + type->set_mat_index(gem_item->shape); } VIRTUAL_CAST_VAR(smallgem_item, df::item_smallgemst, DfItem); if (smallgem_item) { - NetItem->set_shape(smallgem_item->shape); + type->set_mat_index(smallgem_item->shape); } VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); if (constructed_item) From 2782008b4229be6cc87902f6512cc85f3438ae4a Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 10 Jan 2018 19:52:35 -0500 Subject: [PATCH 0738/1012] Update tweak condition-material with new field names dfhack/df-structures#236 --- library/xml | 2 +- plugins/tweak/tweaks/condition-material.h | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/library/xml b/library/xml index 7e23a328f..15a89230c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7e23a328fd81e3d6db794c0c18b8b2e7bd235649 +Subproject commit 15a89230cace8f7cc1b90de63c7f2b44a9c77481 diff --git a/plugins/tweak/tweaks/condition-material.h b/plugins/tweak/tweaks/condition-material.h index 5e5afd7f4..c9aec73c9 100644 --- a/plugins/tweak/tweaks/condition-material.h +++ b/plugins/tweak/tweaks/condition-material.h @@ -8,8 +8,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst { struct T_order_mat_data { std::vector list_entries; - std::vector list_unk1; - std::vector list_unk2; + std::vector mat_types; + std::vector mat_indices; std::vector list_unk3; std::vector list_visible; }; @@ -24,8 +24,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst { } auto data = new T_order_mat_data; data->list_entries = scr->list_entries; - data->list_unk1 = scr->list_unk1; - data->list_unk2 = scr->list_unk2; + data->mat_types = scr->mat_types; + data->mat_indices = scr->mat_indices; data->list_unk3 = scr->list_unk3; data->list_visible = scr->list_visible; order_mat_data[scr] = data; @@ -37,8 +37,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst { { T_order_mat_data *data = order_mat_data[scr]; scr->list_entries = data->list_entries; - scr->list_unk1 = data->list_unk1; - scr->list_unk2 = data->list_unk2; + scr->mat_types = data->mat_types; + scr->mat_indices = data->mat_indices; scr->list_unk3 = data->list_unk3; scr->list_visible = data->list_visible; delete data; @@ -55,8 +55,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst { // keep the first item ("no material") around, because attempts to delete it // result in it still being displayed first, regardless of list_entries[0] list_entries.resize(1); - list_unk1.resize(1); - list_unk2.resize(1); + mat_types.resize(1); + mat_indices.resize(1); list_unk3.resize(1); list_visible.resize(1); // skip "no material" here @@ -71,8 +71,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst { if (s->find(filter) != std::string::npos) { list_entries.push_back(data->list_entries[i]); - list_unk1.push_back(data->list_unk1[i]); - list_unk2.push_back(data->list_unk2[i]); + mat_types.push_back(data->mat_types[i]); + mat_indices.push_back(data->mat_indices[i]); list_unk3.push_back(data->list_unk3[i]); // this should be small enough to fit in an int16_t list_visible.push_back(int16_t(list_entries.size() - 1)); From ddad64224ad252310f790ae539931be17eb87c64 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Jan 2018 12:10:03 -0500 Subject: [PATCH 0739/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 92ea8f43e..ca151dfae 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 92ea8f43ef7ace7e609a83e061d18ddbcf1f56e2 +Subproject commit ca151dfaea8837dfce4ae332661a70edddacfde9 From 16c4cde2141319b388418bb624640aae587ffd41 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 11 Jan 2018 12:10:17 -0500 Subject: [PATCH 0740/1012] Change version to 0.44.04 and update xml --- CMakeLists.txt | 6 +++--- library/xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a1177549..d84e8dfd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,9 +140,9 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.03") -SET(DFHACK_RELEASE "beta1") -SET(DFHACK_PRERELEASE TRUE) +set(DF_VERSION "0.44.04") +set(DFHACK_RELEASE "alpha1") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/library/xml b/library/xml index 15a89230c..155c385a1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 15a89230cace8f7cc1b90de63c7f2b44a9c77481 +Subproject commit 155c385a159996928fda8dfa5e273819fa61783e From 4aa84568783644f6699cfc3d21f6d1e8f413664a Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 12 Jan 2018 00:28:15 -0500 Subject: [PATCH 0741/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 155c385a1..72dd9b146 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 155c385a159996928fda8dfa5e273819fa61783e +Subproject commit 72dd9b14633e448f5e882e27a8d9af22ea9fdd5a diff --git a/scripts b/scripts index ca151dfae..e4c6851a9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ca151dfaea8837dfce4ae332661a70edddacfde9 +Subproject commit e4c6851a9e0b763bdf12442828c3c58ff0aec65d From 14466d2dfc32d0453f4d2d70989319b044eead13 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 12 Jan 2018 00:29:35 -0500 Subject: [PATCH 0742/1012] Update NEWS-dev --- docs/NEWS-dev.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 771684884..33ff4237a 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,14 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.04-alpha1 +===================== + +Structures +---------- +- ``artifact_record``: fixed layout (changed in 0.44.04) +- ``incident``: fixed layout (changed in 0.44.01) - note that many fields have moved + DFHack 0.44.03-beta1 ==================== From 8d6e30ef0efbe8411fc05db34fb15ffb360397e1 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Fri, 12 Jan 2018 11:50:40 +0530 Subject: [PATCH 0743/1012] Fix whitespace issues --- plugins/proto/AdventureControl.proto | 6 +- .../adventure_control.cpp | 68 +++++++++---------- .../remotefortressreader.cpp | 20 +++--- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto index a5785d7a3..7d3d128d6 100644 --- a/plugins/proto/AdventureControl.proto +++ b/plugins/proto/AdventureControl.proto @@ -58,7 +58,7 @@ enum AdvmodeMenu enum CarefulMovementType { - DEFAULT_MOVEMENT = 0; + DEFAULT_MOVEMENT = 0; RELEASE_ITEM_HOLD = 1; RELEASE_TILE_HOLD = 2; ATTACK_CREATURE = 3; @@ -95,8 +95,8 @@ message MovementOption message MenuContents { - optional AdvmodeMenu current_menu = 1; - repeated MovementOption movements = 2; + optional AdvmodeMenu current_menu = 1; + repeated MovementOption movements = 2; } message MiscMoveParams diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp index 1e1f26452..acb4becac 100644 --- a/plugins/remotefortressreader/adventure_control.cpp +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -90,7 +90,7 @@ command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParam case 1: viewScreen->feed_key(interface_key::A_MOVE_SW_UP); break; - } + } break; } break; @@ -240,28 +240,28 @@ command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, out->set_current_menu((AdvmodeMenu)advUi->menu); -// /$$$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$ /$$$$$$$$ -//| $$_____/|_ $$_/| $$ / $$| $$$ /$$$| $$_____/ -//| $$ | $$ | $$/ $$/| $$$$ /$$$$| $$ -//| $$$$$ | $$ \ $$$$/ | $$ $$/$$ $$| $$$$$ -//| $$__/ | $$ >$$ $$ | $$ $$$| $$| $$__/ -//| $$ | $$ /$$/\ $$| $$\ $ | $$| $$ -//| $$ /$$$$$$| $$ \ $$| $$ \/ | $$| $$$$$$$$ -//|__/ |______/|__/ |__/|__/ |__/|________/ -// -// -// -// /$$$$$$$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ -//|__ $$__/| $$ |__/ |__/ | $$ |__/ | $$ | $$ | $$ -// | $$ | $$$$$$$ /$$ /$$$$$$$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$ /$$ /$$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$$| $$ /$$ -// | $$ | $$__ $$| $$ /$$_____/ | $$ /$$_____/ |____ $$ /$$_____/|_ $$_/ | $$ | $$ /$$__ $$| $$ /$$__ $$ | $$__ $$ |____ $$ /$$_____/| $$ /$$/ -// | $$ | $$ \ $$| $$| $$$$$$ | $$| $$$$$$ /$$$$$$$ | $$$$$$ | $$ | $$ | $$| $$ \ $$| $$| $$ | $$ | $$ \ $$ /$$$$$$$| $$ | $$$$$$/ -// | $$ | $$ | $$| $$ \____ $$ | $$ \____ $$ /$$__ $$ \____ $$ | $$ /$$| $$ | $$| $$ | $$| $$| $$ | $$ | $$ | $$ /$$__ $$| $$ | $$_ $$ -// | $$ | $$ | $$| $$ /$$$$$$$/ | $$ /$$$$$$$/ | $$$$$$$ /$$$$$$$/ | $$$$/| $$$$$$/| $$$$$$$/| $$| $$$$$$$ | $$ | $$| $$$$$$$| $$$$$$$| $$ \ $$ -// |__/ |__/ |__/|__/|_______/ |__/|_______/ \_______/ |_______/ \___/ \______/ | $$____/ |__/ \_______/ |__/ |__/ \_______/ \_______/|__/ \__/ -// | $$ -// | $$ -// |__/ + // /$$$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$ /$$$$$$$$ + //| $$_____/|_ $$_/| $$ / $$| $$$ /$$$| $$_____/ + //| $$ | $$ | $$/ $$/| $$$$ /$$$$| $$ + //| $$$$$ | $$ \ $$$$/ | $$ $$/$$ $$| $$$$$ + //| $$__/ | $$ >$$ $$ | $$ $$$| $$| $$__/ + //| $$ | $$ /$$/\ $$| $$\ $ | $$| $$ + //| $$ /$$$$$$| $$ \ $$| $$ \/ | $$| $$$$$$$$ + //|__/ |______/|__/ |__/|__/ |__/|________/ + // + // + // + // /$$$$$$$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ + //|__ $$__/| $$ |__/ |__/ | $$ |__/ | $$ | $$ | $$ + // | $$ | $$$$$$$ /$$ /$$$$$$$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$ /$$ /$$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$$| $$ /$$ + // | $$ | $$__ $$| $$ /$$_____/ | $$ /$$_____/ |____ $$ /$$_____/|_ $$_/ | $$ | $$ /$$__ $$| $$ /$$__ $$ | $$__ $$ |____ $$ /$$_____/| $$ /$$/ + // | $$ | $$ \ $$| $$| $$$$$$ | $$| $$$$$$ /$$$$$$$ | $$$$$$ | $$ | $$ | $$| $$ \ $$| $$| $$ | $$ | $$ \ $$ /$$$$$$$| $$ | $$$$$$/ + // | $$ | $$ | $$| $$ \____ $$ | $$ \____ $$ /$$__ $$ \____ $$ | $$ /$$| $$ | $$| $$ | $$| $$| $$ | $$ | $$ | $$ /$$__ $$| $$ | $$_ $$ + // | $$ | $$ | $$| $$ /$$$$$$$/ | $$ /$$$$$$$/ | $$$$$$$ /$$$$$$$/ | $$$$/| $$$$$$/| $$$$$$$/| $$| $$$$$$$ | $$ | $$| $$$$$$$| $$$$$$$| $$ \ $$ + // |__/ |__/ |__/|__/|_______/ |__/|_______/ \_______/ |_______/ \___/ \______/ | $$____/ |__/ \_______/ |__/ |__/ \_______/ \_______/|__/ \__/ + // | $$ + // | $$ + // |__/ if (advUi->menu == ui_advmode_menu::FallAction) { getCurViewscreen()->feed_key(interface_key::OPTION1); @@ -299,17 +299,17 @@ command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in) { - if (!(df::global::ui_advmode->menu == ui_advmode_menu::MoveCarefully)) - return CR_OK; - int choice = in->value(); - int page = choice / 5; - int select = choice % 5; - for (int i = 0; i < page; i++) - { - keyQueue.push(interface_key::SECONDSCROLL_PAGEDOWN); - } - keyQueue.push((interface_key::interface_key)(interface_key::OPTION1 + select)); - return CR_OK; + if (!(df::global::ui_advmode->menu == ui_advmode_menu::MoveCarefully)) + return CR_OK; + int choice = in->value(); + int page = choice / 5; + int select = choice % 5; + for (int i = 0; i < page; i++) + { + keyQueue.push(interface_key::SECONDSCROLL_PAGEDOWN); + } + keyQueue.push((interface_key::interface_key)(interface_key::OPTION1 + select)); + return CR_OK; } command_result MiscMoveCommand(DFHack::color_ostream &stream, const MiscMoveParams *in) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index da4121057..b13e56289 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -258,8 +258,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); - svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); + svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); - svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); + svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE); svc->addFunction("GetLanguage", GetLanguage, SF_ALLOW_REMOTE); @@ -318,10 +318,10 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (!enableUpdates) - return CR_OK; - KeyUpdate(); - return CR_OK; + if (!enableUpdates) + return CR_OK; + KeyUpdate(); + return CR_OK; } uint16_t fletcher16(uint8_t const *data, size_t bytes) @@ -1698,7 +1698,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in if (in->has_blocks_needed()) blocks_needed = in->blocks_needed(); else - blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z()); + blocks_needed = NUMBER_OF_POINTS * (in->max_z() - in->min_z()); int blocks_sent = 0; int min_x = in->min_x(); int min_y = in->min_y(); @@ -2660,7 +2660,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w int region_min_x = pos_x * 16; int region_min_y = pos_y * 16; - if ((site->global_min_x >(region_min_x + 16)) || + if ((site->global_min_x > (region_min_x + 16)) || (site->global_min_y > (region_min_y + 16)) || (site->global_max_x < (region_min_x)) || (site->global_max_y < (region_min_y))) From 9ba0b00580677f85246af21739190459f890f004 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Fri, 12 Jan 2018 11:59:02 +0530 Subject: [PATCH 0744/1012] Remove the stupid big warning comment. --- .../adventure_control.cpp | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp index acb4becac..6fb592d62 100644 --- a/plugins/remotefortressreader/adventure_control.cpp +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -240,28 +240,7 @@ command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, out->set_current_menu((AdvmodeMenu)advUi->menu); - // /$$$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$ /$$$$$$$$ - //| $$_____/|_ $$_/| $$ / $$| $$$ /$$$| $$_____/ - //| $$ | $$ | $$/ $$/| $$$$ /$$$$| $$ - //| $$$$$ | $$ \ $$$$/ | $$ $$/$$ $$| $$$$$ - //| $$__/ | $$ >$$ $$ | $$ $$$| $$| $$__/ - //| $$ | $$ /$$/\ $$| $$\ $ | $$| $$ - //| $$ /$$$$$$| $$ \ $$| $$ \/ | $$| $$$$$$$$ - //|__/ |______/|__/ |__/|__/ |__/|________/ - // - // - // - // /$$$$$$$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ /$$ - //|__ $$__/| $$ |__/ |__/ | $$ |__/ | $$ | $$ | $$ - // | $$ | $$$$$$$ /$$ /$$$$$$$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$ /$$ /$$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$$| $$ /$$ - // | $$ | $$__ $$| $$ /$$_____/ | $$ /$$_____/ |____ $$ /$$_____/|_ $$_/ | $$ | $$ /$$__ $$| $$ /$$__ $$ | $$__ $$ |____ $$ /$$_____/| $$ /$$/ - // | $$ | $$ \ $$| $$| $$$$$$ | $$| $$$$$$ /$$$$$$$ | $$$$$$ | $$ | $$ | $$| $$ \ $$| $$| $$ | $$ | $$ \ $$ /$$$$$$$| $$ | $$$$$$/ - // | $$ | $$ | $$| $$ \____ $$ | $$ \____ $$ /$$__ $$ \____ $$ | $$ /$$| $$ | $$| $$ | $$| $$| $$ | $$ | $$ | $$ /$$__ $$| $$ | $$_ $$ - // | $$ | $$ | $$| $$ /$$$$$$$/ | $$ /$$$$$$$/ | $$$$$$$ /$$$$$$$/ | $$$$/| $$$$$$/| $$$$$$$/| $$| $$$$$$$ | $$ | $$| $$$$$$$| $$$$$$$| $$ \ $$ - // |__/ |__/ |__/|__/|_______/ |__/|_______/ \_______/ |_______/ \___/ \______/ | $$____/ |__/ \_______/ |__/ |__/ \_______/ \_______/|__/ \__/ - // | $$ - // | $$ - // |__/ + //Fixme: Needs a proper way to control it, but for now, this is the only way to allow Armok Vision to keep going without the user needing to switch to DF. if (advUi->menu == ui_advmode_menu::FallAction) { getCurViewscreen()->feed_key(interface_key::OPTION1); From e1bf878059b6248d926d0ab50f1918930ca3195d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 Jan 2018 16:50:53 -0500 Subject: [PATCH 0745/1012] Add some missing changelog entries --- NEWS.rst | 1 + docs/NEWS-dev.rst | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index abecc0900..ca9234d63 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -49,6 +49,7 @@ Fixes ----- - Fixed issues with the console output color affecting the prompt on Windows - `createitem`: stopped items from teleporting away in some forts +- `devel/inject-raws`: now recognizes spaces in reaction names - `gui/gm-unit`: can now edit mining skill - `gui/quickcmd`: stopped error from adding too many commands diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 33ff4237a..4dd04ce43 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -40,6 +40,11 @@ Development Changelog DFHack 0.44.04-alpha1 ===================== +Fixes +----- +- `devel/inject-raws`: now recognizes spaces in reaction names +- `exportlegends`: fixed an error that could occur when exporting empty lists + Structures ---------- - ``artifact_record``: fixed layout (changed in 0.44.04) @@ -125,6 +130,11 @@ Other Changes ------------- - The console now provides suggestions for built-in commands - `devel/export-dt-ini`: avoid hardcoding flags +- `exportlegends`: + + - reordered some tags to match DF's order + - added progress indicators for exporting long lists + - `gui/gm-editor`: added enum names to enum edit dialogs - `gui/gm-unit`: made skill search case-insensitive - `gui/rename`: added "clear" and "special characters" options From dbe950d92df9c8246d1765a78dead8fb38415179 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 13 Jan 2018 16:51:03 -0500 Subject: [PATCH 0746/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 72dd9b146..f38c8d325 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 72dd9b14633e448f5e882e27a8d9af22ea9fdd5a +Subproject commit f38c8d325297d0dc354ca3b9881989980f74013d diff --git a/scripts b/scripts index e4c6851a9..ba92c1604 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e4c6851a9e0b763bdf12442828c3c58ff0aec65d +Subproject commit ba92c16046dae453af22c1ccf31a30c09787b357 From ce1644d6552e7d3cdf0abfa6dc7a1583ff60c393 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 14 Jan 2018 18:09:18 -0500 Subject: [PATCH 0747/1012] Bump to 0.44.05-alpha1 and update submodules --- CMakeLists.txt | 2 +- docs/NEWS-dev.rst | 7 +++++++ library/xml | 2 +- scripts | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d84e8dfd1..6e92fc967 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,7 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.04") +set(DF_VERSION "0.44.05") set(DFHACK_RELEASE "alpha1") set(DFHACK_PRERELEASE TRUE) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 4dd04ce43..38fbe658d 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,13 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.05-alpha1 +===================== + +Structures +---------- +- ``incident``: re-aligned again to match disassembly + DFHack 0.44.04-alpha1 ===================== diff --git a/library/xml b/library/xml index f38c8d325..3a9f401d1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f38c8d325297d0dc354ca3b9881989980f74013d +Subproject commit 3a9f401d196ee8ebc53edb9e15a13bfcb0879b4e diff --git a/scripts b/scripts index ba92c1604..2d4093e61 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ba92c16046dae453af22c1ccf31a30c09787b357 +Subproject commit 2d4093e61ea2a02742a6bfbfd80e49eb6dcdd549 From c3bf14a300d86455fc087d9dbe0031bffac79e93 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 14 Jan 2018 22:56:35 -0500 Subject: [PATCH 0748/1012] Correct bad os-type attributes in symbols.xml --- library/Core.cpp | 14 +++++++++++++- library/VersionInfoFactory.cpp | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 47bddbab8..5173a3ad9 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -640,7 +640,7 @@ static std::string sc_event_name (state_change_event id) { string getBuiltinCommand(std::string cmd) { std::string builtin = ""; - + // Check our list of builtin commands from the header if (built_in_commands.count(cmd)) builtin = cmd; @@ -1619,6 +1619,18 @@ bool Core::Init() } cerr << "Version: " << vinfo->getVersion() << endl; +#if defined(_WIN32) + const OSType expected = OS_WINDOWS; +#elif defined(_DARWIN) + const OSType expected = OS_APPLE; +#else + const OSType expected = OS_LINUX; +#endif + if (expected != vinfo->getOS()) { + cerr << "OS mismatch; resetting to " << int(expected) << endl; + vinfo->setOS(expected); + } + // Init global object pointers df::global::InitGlobals(); alias_mutex = new recursive_mutex(); diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index cdfdbf774..7033fd598 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -153,7 +153,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) else if (type == "md5-hash") { const char *cstr_value = pMemEntry->Attribute("value"); - fprintf(stderr, "%s: MD5: %s\n", cstr_name, cstr_value); + fprintf(stderr, "%s (%s): MD5: %s\n", cstr_name, cstr_os, cstr_value); if(!cstr_value) throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); mem->addMD5(cstr_value); @@ -161,7 +161,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) else if (type == "binary-timestamp") { const char *cstr_value = pMemEntry->Attribute("value"); - fprintf(stderr, "%s: PE: %s\n", cstr_name, cstr_value); + fprintf(stderr, "%s (%s): PE: %s\n", cstr_name, cstr_os, cstr_value); if(!cstr_value) throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); mem->addPE(strtol(cstr_value, 0, 16)); From 46bc26dfe2b4343adcd39034acb8447232d33973 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 14 Jan 2018 23:25:28 -0500 Subject: [PATCH 0749/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 3a9f401d1..352e23310 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3a9f401d196ee8ebc53edb9e15a13bfcb0879b4e +Subproject commit 352e233105512a702875f48d82f922f0c9ca3905 From 167fcd757819211748fd130c1ac7871076e03202 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 15 Jan 2018 00:11:03 -0500 Subject: [PATCH 0750/1012] Add gui/liquids changes to changelog --- NEWS.rst | 1 + docs/NEWS-dev.rst | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index ca9234d63..a14bafa9f 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -64,6 +64,7 @@ Misc Improvements - `gui/gm-editor`: added enum names to enum edit dialogs - `gui/gm-unit`: made skill search case-insensitive +- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards - `gui/pathable`: added tile types to sidebar - `gui/rename`: added "clear" and "special characters" options - `modtools/skill-change`: diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 38fbe658d..834282608 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -44,6 +44,10 @@ Structures ---------- - ``incident``: re-aligned again to match disassembly +Other Changes +------------- +- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards + DFHack 0.44.04-alpha1 ===================== From e2ad956227de06cff240d8d4dadbda177203c6ef Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 16 Jan 2018 15:54:41 +0530 Subject: [PATCH 0751/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 72dd9b146..6b6dda838 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 72dd9b14633e448f5e882e27a8d9af22ea9fdd5a +Subproject commit 6b6dda838fc1b9c4e1c47fbdfbf19838749bad14 From ea6757377eadf3ebe6058746f2d92e5ee2ea05d4 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 16 Jan 2018 17:04:26 +0530 Subject: [PATCH 0752/1012] add item volume to RFR --- plugins/proto/RemoteFortressReader.proto | 1 + .../remotefortressreader.cpp | 228 ++---------------- 2 files changed, 26 insertions(+), 203 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 08d804262..786ee6bc9 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -275,6 +275,7 @@ message Item optional float velocity_x = 13; optional float velocity_y = 14; optional float velocity_z = 15; + optional int32 volume = 16; } message MapBlock diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index b13e56289..3e850d20b 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -922,213 +922,33 @@ static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, mat_def->set_id(ENUM_KEY_STR(item_type, it)); switch (it) { - case df::enums::item_type::NONE: - break; - case df::enums::item_type::BAR: - break; - case df::enums::item_type::GEM: - case df::enums::item_type::SMALLGEM: - { - for (int i = 0; i < world->raws.language.shapes.size(); i++) + case df::enums::item_type::GEM: + case df::enums::item_type::SMALLGEM: + { + for (int i = 0; i < world->raws.language.shapes.size(); i++) + { + auto shape = world->raws.language.shapes[i]; + if (shape->gems_use.whole == 0) + continue; + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id); + } + break; + } + case df::enums::item_type::BOX: { - auto shape = world->raws.language.shapes[i]; - if (shape->gems_use.whole == 0) - continue; mat_def = out->add_material_list(); mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("BOX_CHEST"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("BOX_BAG"); + break; } - break; - } - case df::enums::item_type::BLOCKS: - break; - case df::enums::item_type::ROUGH: - break; - case df::enums::item_type::BOULDER: - break; - case df::enums::item_type::WOOD: - break; - case df::enums::item_type::DOOR: - break; - case df::enums::item_type::FLOODGATE: - break; - case df::enums::item_type::BED: - break; - case df::enums::item_type::CHAIR: - break; - case df::enums::item_type::CHAIN: - break; - case df::enums::item_type::FLASK: - break; - case df::enums::item_type::GOBLET: - break; - case df::enums::item_type::INSTRUMENT: - break; - case df::enums::item_type::TOY: - break; - case df::enums::item_type::WINDOW: - break; - case df::enums::item_type::CAGE: - break; - case df::enums::item_type::BARREL: - break; - case df::enums::item_type::BUCKET: - break; - case df::enums::item_type::ANIMALTRAP: - break; - case df::enums::item_type::TABLE: - break; - case df::enums::item_type::COFFIN: - break; - case df::enums::item_type::STATUE: - break; - case df::enums::item_type::CORPSE: - break; - case df::enums::item_type::WEAPON: - break; - case df::enums::item_type::ARMOR: - break; - case df::enums::item_type::SHOES: - break; - case df::enums::item_type::SHIELD: - break; - case df::enums::item_type::HELM: - break; - case df::enums::item_type::GLOVES: - break; - case df::enums::item_type::BOX: - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(0); - mat_def->set_id("BOX_CHEST"); - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(1); - mat_def->set_id("BOX_BAG"); - break; - } - case df::enums::item_type::BIN: - break; - case df::enums::item_type::ARMORSTAND: - break; - case df::enums::item_type::WEAPONRACK: - break; - case df::enums::item_type::CABINET: - break; - case df::enums::item_type::FIGURINE: - break; - case df::enums::item_type::AMULET: - break; - case df::enums::item_type::SCEPTER: - break; - case df::enums::item_type::AMMO: - break; - case df::enums::item_type::CROWN: - break; - case df::enums::item_type::RING: - break; - case df::enums::item_type::EARRING: - break; - case df::enums::item_type::BRACELET: - break; - case df::enums::item_type::ANVIL: - break; - case df::enums::item_type::CORPSEPIECE: - break; - case df::enums::item_type::REMAINS: - break; - case df::enums::item_type::MEAT: - break; - case df::enums::item_type::FISH: - break; - case df::enums::item_type::FISH_RAW: - break; - case df::enums::item_type::VERMIN: - break; - case df::enums::item_type::PET: - break; - case df::enums::item_type::SEEDS: - break; - case df::enums::item_type::PLANT: - break; - case df::enums::item_type::SKIN_TANNED: - break; - case df::enums::item_type::PLANT_GROWTH: - break; - case df::enums::item_type::THREAD: - break; - case df::enums::item_type::CLOTH: - break; - case df::enums::item_type::TOTEM: - break; - case df::enums::item_type::PANTS: - break; - case df::enums::item_type::BACKPACK: - break; - case df::enums::item_type::QUIVER: - break; - case df::enums::item_type::CATAPULTPARTS: - break; - case df::enums::item_type::BALLISTAPARTS: - break; - case df::enums::item_type::SIEGEAMMO: - break; - case df::enums::item_type::BALLISTAARROWHEAD: - break; - case df::enums::item_type::TRAPPARTS: - break; - case df::enums::item_type::TRAPCOMP: - break; - case df::enums::item_type::DRINK: - break; - case df::enums::item_type::POWDER_MISC: - break; - case df::enums::item_type::CHEESE: - break; - case df::enums::item_type::FOOD: - break; - case df::enums::item_type::LIQUID_MISC: - break; - case df::enums::item_type::COIN: - break; - case df::enums::item_type::GLOB: - break; - case df::enums::item_type::ROCK: - break; - case df::enums::item_type::PIPE_SECTION: - break; - case df::enums::item_type::HATCH_COVER: - break; - case df::enums::item_type::GRATE: - break; - case df::enums::item_type::QUERN: - break; - case df::enums::item_type::MILLSTONE: - break; - case df::enums::item_type::SPLINT: - break; - case df::enums::item_type::CRUTCH: - break; - case df::enums::item_type::TRACTION_BENCH: - break; - case df::enums::item_type::ORTHOPEDIC_CAST: - break; - case df::enums::item_type::TOOL: - break; - case df::enums::item_type::SLAB: - break; - case df::enums::item_type::EGG: - break; - case df::enums::item_type::BOOK: - break; - case df::enums::item_type::SHEET: - break; - case df::enums::item_type::BRANCH: - break; - default: - break; } int subtypes = Items::getSubtypeCount(it); if (subtypes >= 0) @@ -1665,6 +1485,8 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); } } + + NetItem->set_volume(DfItem->getVolume()); } void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) From a63347cf7a689b20260bedba33c87e2d068d3d93 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 17 Jan 2018 19:10:19 +0530 Subject: [PATCH 0753/1012] Move item reader to a separate file. --- plugins/proto/RemoteFortressReader.proto | 28 +- plugins/remotefortressreader/CMakeLists.txt | 2 + plugins/remotefortressreader/item_reader.cpp | 395 ++++++++++++++++++ plugins/remotefortressreader/item_reader.h | 25 ++ .../remotefortressreader.cpp | 167 +------- 5 files changed, 455 insertions(+), 162 deletions(-) create mode 100644 plugins/remotefortressreader/item_reader.cpp create mode 100644 plugins/remotefortressreader/item_reader.h diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 786ee6bc9..463e368fc 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -276,6 +276,7 @@ message Item optional float velocity_y = 14; optional float velocity_z = 15; optional int32 volume = 16; + repeated ItemImprovement improvements = 17; } message MapBlock @@ -885,4 +886,29 @@ message ShapeDescriptior message Language { repeated ShapeDescriptior shapes = 1; -} \ No newline at end of file +} + +enum ImprovementType +{ + ART_IMAGE = 0; + COVERED = 1; + RINGS_HANGING = 2; + BANDS = 3; + SPIKES = 4; + ITEMSPECIFIC = 5; + THREAD = 6; + CLOTH = 7; + SEWN_IMAGE = 8; + PAGES = 9; + ILLUSTRATION = 10; + INSTRUMENT_PIECE = 11; + WRITING = 12; +} + +message ItemImprovement +{ + optional MatPair mat_type = 1; + optional ImprovementType type = 2; + optional int32 shape = 3; + +} diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 72b7c94cc..10cc21047 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -4,11 +4,13 @@ SET(PROJECT_SRCS remotefortressreader.cpp adventure_control.cpp building_reader.cpp + item_reader.cpp ) # A list of headers SET(PROJECT_HDRS adventure_control.h building_reader.h + item_reader.h df_version_int.h ) #proto files to include. diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp new file mode 100644 index 000000000..256d78fd9 --- /dev/null +++ b/plugins/remotefortressreader/item_reader.cpp @@ -0,0 +1,395 @@ +#include "item_reader.h" + +#include "df/descriptor_shape.h" +#include "df/item_type.h" +#include "df/item_constructed.h" +#include "df/item_gemst.h" +#include "df/item_threadst.h" +#include "df/item_toolst.h" +#include "df/item_smallgemst.h" +#include "df/itemimprovement.h" +#include "df/itemimprovement_threadst.h" +#include "df/itemdef.h" +#include "df/map_block.h" +#include "df/vehicle.h" +#include "df/world.h" + +#include "modules/Items.h" +#include "modules/MapCache.h" +#include "modules/Materials.h" +#include "MiscUtils.h" + + +using namespace DFHack; +using namespace df::enums; +using namespace RemoteFortressReader; +using namespace std; +using namespace df::global; + + +void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) +{ + NetItem->set_id(DfItem->id); + NetItem->set_flags1(DfItem->flags.whole); + NetItem->set_flags2(DfItem->flags2.whole); + auto pos = NetItem->mutable_pos(); + pos->set_x(DfItem->pos.x); + pos->set_y(DfItem->pos.y); + pos->set_z(DfItem->pos.z); + auto mat = NetItem->mutable_material(); + mat->set_mat_index(DfItem->getMaterialIndex()); + mat->set_mat_type(DfItem->getMaterial()); + auto type = NetItem->mutable_type(); + type->set_mat_type(DfItem->getType()); + type->set_mat_index(DfItem->getSubtype()); + + bool isProjectile = false; + + item_type::item_type itemType = DfItem->getType(); + + switch (itemType) + { + case df::enums::item_type::NONE: + break; + case df::enums::item_type::BAR: + break; + case df::enums::item_type::SMALLGEM: + { + VIRTUAL_CAST_VAR(smallgem_item, df::item_smallgemst, DfItem); + type->set_mat_index(smallgem_item->shape); + break; + } + case df::enums::item_type::BLOCKS: + break; + case df::enums::item_type::ROUGH: + break; + case df::enums::item_type::BOULDER: + break; + case df::enums::item_type::WOOD: + break; + case df::enums::item_type::DOOR: + break; + case df::enums::item_type::FLOODGATE: + break; + case df::enums::item_type::BED: + break; + case df::enums::item_type::CHAIR: + break; + case df::enums::item_type::CHAIN: + break; + case df::enums::item_type::FLASK: + break; + case df::enums::item_type::GOBLET: + break; + case df::enums::item_type::INSTRUMENT: + break; + case df::enums::item_type::TOY: + break; + case df::enums::item_type::WINDOW: + break; + case df::enums::item_type::CAGE: + break; + case df::enums::item_type::BARREL: + break; + case df::enums::item_type::BUCKET: + break; + case df::enums::item_type::ANIMALTRAP: + break; + case df::enums::item_type::TABLE: + break; + case df::enums::item_type::COFFIN: + break; + case df::enums::item_type::STATUE: + break; + case df::enums::item_type::CORPSE: + break; + case df::enums::item_type::WEAPON: + break; + case df::enums::item_type::ARMOR: + break; + case df::enums::item_type::SHOES: + break; + case df::enums::item_type::SHIELD: + break; + case df::enums::item_type::HELM: + break; + case df::enums::item_type::GLOVES: + break; + case df::enums::item_type::BOX: + type->set_mat_index(DfItem->isBag()); + break; + case df::enums::item_type::BIN: + break; + case df::enums::item_type::ARMORSTAND: + break; + case df::enums::item_type::WEAPONRACK: + break; + case df::enums::item_type::CABINET: + break; + case df::enums::item_type::FIGURINE: + break; + case df::enums::item_type::AMULET: + break; + case df::enums::item_type::SCEPTER: + break; + case df::enums::item_type::AMMO: + break; + case df::enums::item_type::CROWN: + break; + case df::enums::item_type::RING: + break; + case df::enums::item_type::EARRING: + break; + case df::enums::item_type::BRACELET: + break; + case df::enums::item_type::GEM: + { + VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem); + type->set_mat_index(gem_item->shape); + break; + } + case df::enums::item_type::ANVIL: + break; + case df::enums::item_type::CORPSEPIECE: + break; + case df::enums::item_type::REMAINS: + break; + case df::enums::item_type::MEAT: + break; + case df::enums::item_type::FISH: + break; + case df::enums::item_type::FISH_RAW: + break; + case df::enums::item_type::VERMIN: + break; + case df::enums::item_type::PET: + break; + case df::enums::item_type::SEEDS: + break; + case df::enums::item_type::PLANT: + break; + case df::enums::item_type::SKIN_TANNED: + break; + case df::enums::item_type::PLANT_GROWTH: + break; + case df::enums::item_type::THREAD: + { + VIRTUAL_CAST_VAR(thread, df::item_threadst, DfItem); + if (thread && thread->dye_mat_type >= 0) + { + DFHack::MaterialInfo info; + if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + break; + } + case df::enums::item_type::CLOTH: + break; + case df::enums::item_type::TOTEM: + break; + case df::enums::item_type::PANTS: + break; + case df::enums::item_type::BACKPACK: + break; + case df::enums::item_type::QUIVER: + break; + case df::enums::item_type::CATAPULTPARTS: + break; + case df::enums::item_type::BALLISTAPARTS: + break; + case df::enums::item_type::SIEGEAMMO: + break; + case df::enums::item_type::BALLISTAARROWHEAD: + break; + case df::enums::item_type::TRAPPARTS: + break; + case df::enums::item_type::TRAPCOMP: + break; + case df::enums::item_type::DRINK: + break; + case df::enums::item_type::POWDER_MISC: + break; + case df::enums::item_type::CHEESE: + break; + case df::enums::item_type::FOOD: + break; + case df::enums::item_type::LIQUID_MISC: + break; + case df::enums::item_type::COIN: + break; + case df::enums::item_type::GLOB: + break; + case df::enums::item_type::ROCK: + break; + case df::enums::item_type::PIPE_SECTION: + break; + case df::enums::item_type::HATCH_COVER: + break; + case df::enums::item_type::GRATE: + break; + case df::enums::item_type::QUERN: + break; + case df::enums::item_type::MILLSTONE: + break; + case df::enums::item_type::SPLINT: + break; + case df::enums::item_type::CRUTCH: + break; + case df::enums::item_type::TRACTION_BENCH: + break; + case df::enums::item_type::ORTHOPEDIC_CAST: + break; + case df::enums::item_type::TOOL: + if (!isProjectile) + { + VIRTUAL_CAST_VAR(tool, df::item_toolst, DfItem); + if (tool) + { + auto vehicle = binsearch_in_vector(world->vehicles.active, tool->vehicle_id); + if (vehicle) + { + NetItem->set_subpos_x(vehicle->offset_x / 100000.0); + NetItem->set_subpos_y(vehicle->offset_y / 100000.0); + NetItem->set_subpos_z(vehicle->offset_z / 140000.0); + } + } + } + break; + case df::enums::item_type::SLAB: + break; + case df::enums::item_type::EGG: + break; + case df::enums::item_type::BOOK: + break; + case df::enums::item_type::SHEET: + break; + case df::enums::item_type::BRANCH: + break; + default: + break; + } + + VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem); + if (actual_item) + { + NetItem->set_stack_size(actual_item->stack_size); + } + + VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); + if (constructed_item) + { + for (int i = 0; i < constructed_item->improvements.size(); i++) + { + auto improvement = constructed_item->improvements[i]; + + if (!improvement) + continue; + + improvement_type::improvement_type impType = improvement->getType(); + + switch (impType) + { + case df::enums::improvement_type::ART_IMAGE: + break; + case df::enums::improvement_type::COVERED: + { + break; + } + case df::enums::improvement_type::RINGS_HANGING: + break; + case df::enums::improvement_type::BANDS: + break; + case df::enums::improvement_type::SPIKES: + break; + case df::enums::improvement_type::ITEMSPECIFIC: + break; + case df::enums::improvement_type::THREAD: + { + VIRTUAL_CAST_VAR(improvement_thread, df::itemimprovement_threadst, improvement); + if (improvement_thread->dye.mat_type >= 0) + { + DFHack::MaterialInfo info; + if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) + continue; + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + break; + } + case df::enums::improvement_type::CLOTH: + break; + case df::enums::improvement_type::SEWN_IMAGE: + break; + case df::enums::improvement_type::PAGES: + break; + case df::enums::improvement_type::ILLUSTRATION: + break; + case df::enums::improvement_type::INSTRUMENT_PIECE: + break; + case df::enums::improvement_type::WRITING: + break; + default: + break; + } + } + } + + NetItem->set_volume(DfItem->getVolume()); +} + +DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out) +{ + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + FOR_ENUM_ITEMS(item_type, it) + { + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(-1); + mat_def->set_id(ENUM_KEY_STR(item_type, it)); + switch (it) + { + case df::enums::item_type::GEM: + case df::enums::item_type::SMALLGEM: + { + for (int i = 0; i < world->raws.language.shapes.size(); i++) + { + auto shape = world->raws.language.shapes[i]; + if (shape->gems_use.whole == 0) + continue; + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id); + } + break; + } + case df::enums::item_type::BOX: + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("BOX_CHEST"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("BOX_BAG"); + break; + } + } + int subtypes = Items::getSubtypeCount(it); + if (subtypes >= 0) + { + for (int i = 0; i < subtypes; i++) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + df::itemdef * item = Items::getSubtypeDef(it, i); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + item->id); + } + } + } + return CR_OK; +} diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h new file mode 100644 index 000000000..45f8c1a95 --- /dev/null +++ b/plugins/remotefortressreader/item_reader.h @@ -0,0 +1,25 @@ +#ifndef ITEM_READER_H +#define ITEM_READER_H + +#include +#include "RemoteClient.h" +#include "RemoteFortressReader.pb.h" + +#include "DataDefs.h" + +namespace df +{ + struct item; + struct map_block; +} + +namespace MapExtras +{ + struct MapCache; +} + +DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out); +void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); +void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out); + +#endif // !ITEM_READER_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 3e850d20b..450333088 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -60,15 +60,7 @@ #include "df/enabler.h" #include "df/graphic.h" #include "df/historical_figure.h" -#include "df/item.h" -#include "df/item_constructed.h" -#include "df/item_gemst.h" -#include "df/item_threadst.h" -#include "df/item_toolst.h" -#include "df/item_smallgemst.h" -#include "df/itemimprovement.h" -#include "df/itemimprovement_threadst.h" -#include "df/itemdef.h" + #include "df/job.h" #include "df/job_type.h" #include "df/job_item.h" @@ -96,8 +88,8 @@ #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" -#include "df/vehicle.h" #include "df/viewscreen_choose_start_sitest.h" +#include "df/vehicle.h" #include "df/world.h" #include "df/world_data.h" #include "df/world_geo_biome.h" @@ -121,6 +113,7 @@ #include "adventure_control.h" #include "building_reader.h" +#include "item_reader.h" using namespace DFHack; using namespace df::enums; @@ -153,7 +146,6 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out); static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out); static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); -static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out); @@ -169,7 +161,6 @@ static command_result SendDigCommand(color_ostream &stream, const DigCommand *in static command_result SetPauseState(color_ostream & stream, const SingleBool * in); static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); -void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out); static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out); @@ -908,66 +899,6 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage return CR_OK; } -static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) -{ - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - FOR_ENUM_ITEMS(item_type, it) - { - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(-1); - mat_def->set_id(ENUM_KEY_STR(item_type, it)); - switch (it) - { - case df::enums::item_type::GEM: - case df::enums::item_type::SMALLGEM: - { - for (int i = 0; i < world->raws.language.shapes.size(); i++) - { - auto shape = world->raws.language.shapes[i]; - if (shape->gems_use.whole == 0) - continue; - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id); - } - break; - } - case df::enums::item_type::BOX: - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(0); - mat_def->set_id("BOX_CHEST"); - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(1); - mat_def->set_id("BOX_BAG"); - break; - } - } - int subtypes = Items::getSubtypeCount(it); - if (subtypes >= 0) - { - for (int i = 0; i < subtypes; i++) - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(i); - df::itemdef * item = Items::getSubtypeDef(it, i); - mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + item->id); - } - } - } - - - return CR_OK; -} - static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) { if (!Core::getInstance().isWorldLoaded()) { @@ -1403,92 +1334,6 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB } } -void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) -{ - NetItem->set_id(DfItem->id); - NetItem->set_flags1(DfItem->flags.whole); - NetItem->set_flags2(DfItem->flags2.whole); - auto pos = NetItem->mutable_pos(); - pos->set_x(DfItem->pos.x); - pos->set_y(DfItem->pos.y); - pos->set_z(DfItem->pos.z); - auto mat = NetItem->mutable_material(); - mat->set_mat_index(DfItem->getMaterialIndex()); - mat->set_mat_type(DfItem->getMaterial()); - auto type = NetItem->mutable_type(); - type->set_mat_type(DfItem->getType()); - type->set_mat_index(DfItem->getSubtype()); - - bool isProjectile = false; - - if (!isProjectile && DfItem->getType() == item_type::TOOL) - { - VIRTUAL_CAST_VAR(tool, df::item_toolst, DfItem); - if (tool) - { - auto vehicle = binsearch_in_vector(world->vehicles.active, tool->vehicle_id); - if (vehicle) - { - NetItem->set_subpos_x(vehicle->offset_x / 100000.0); - NetItem->set_subpos_y(vehicle->offset_y / 100000.0); - NetItem->set_subpos_z(vehicle->offset_z / 140000.0); - } - } - } - - if (DfItem->getType() == item_type::BOX) - { - type->set_mat_index(DfItem->isBag()); - } - VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem); - if (actual_item) - { - NetItem->set_stack_size(actual_item->stack_size); - } - VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem); - if (gem_item) - { - type->set_mat_index(gem_item->shape); - } - VIRTUAL_CAST_VAR(smallgem_item, df::item_smallgemst, DfItem); - if (smallgem_item) - { - type->set_mat_index(smallgem_item->shape); - } - VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); - if (constructed_item) - { - for (int i = 0; i < constructed_item->improvements.size(); i++) - { - auto improvement = constructed_item->improvements[i]; - if (!improvement || improvement->getType() != improvement_type::THREAD) - continue; - - auto improvement_thread = virtual_cast(improvement); - if (!improvement_thread || improvement_thread->dye.mat_type < 0) - continue; - - DFHack::MaterialInfo info; - if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) - continue; - - ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); - } - } - else if (DfItem->getType() == item_type::THREAD) - { - auto thread = virtual_cast(DfItem); - if (thread && thread->dye_mat_type >= 0) - { - DFHack::MaterialInfo info; - if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) - ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); - } - } - - NetItem->set_volume(DfItem->getVolume()); -} - void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) { NetBlock->set_map_x(DfBlock->map_pos.x); @@ -1498,13 +1343,13 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc { int id = DfBlock->items[i]; - auto item = df::item::find(id); if (item) CopyItem(NetBlock->add_items(), item); } } + static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) { int x, y, z; @@ -1684,7 +1529,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in out_plant->set_pos_y(plant->pos.y); out_plant->set_pos_z(plant->pos.z); } - } +} #endif return CR_OK; } @@ -2017,7 +1862,7 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, } else out->add_water_elevation(99); - } +} DFCoord pos = GetMapCenter(); out->set_center_x(pos.x); out->set_center_y(pos.y); From f2890620d1a0b9c3bd9616195f7e260d46ca5324 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 01:05:33 -0500 Subject: [PATCH 0754/1012] Remove tweak kitchen-keys - DF bug 614 was fixed --- dfhack.init-example | 1 - docs/Plugins.rst | 1 - plugins/tweak/tweak.cpp | 6 --- plugins/tweak/tweaks/kitchen-keys.h | 68 ----------------------------- 4 files changed, 76 deletions(-) delete mode 100644 plugins/tweak/tweaks/kitchen-keys.h diff --git a/dfhack.init-example b/dfhack.init-example index 3542956ce..75552006e 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -207,7 +207,6 @@ tweak civ-view-agreement tweak eggs-fertile tweak fps-min tweak hide-priority -tweak kitchen-keys tweak kitchen-prefs-empty tweak max-wheelbarrow tweak shift-8-scroll diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 0ed65c4a3..04f5df321 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -311,7 +311,6 @@ Subcommands that persist until disabled or DF quits: :import-priority-category: Allows changing the priority of all goods in a category when discussing an import agreement with the liaison -:kitchen-keys: Fixes DF kitchen meal keybindings (:bug:`614`) :kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences :kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) :max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 417784efb..252f4c738 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -94,7 +94,6 @@ #include "tweaks/hide-priority.h" #include "tweaks/hotkey-clear.h" #include "tweaks/import-priority-category.h" -#include "tweaks/kitchen-keys.h" #include "tweaks/kitchen-prefs-color.h" #include "tweaks/kitchen-prefs-empty.h" #include "tweaks/max-wheelbarrow.h" @@ -218,8 +217,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector (world->selected_building); - if (!ws) - return false; - if (ws->type != workshop_type::Kitchen) - return false; - return true; - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) - { - if (kitchen_in_add()) - { - for (int i = 0; i < 4; i++) - { - if (input->count(kitchen_bindings[i])) - { - ui_sidebar_menus->workshop_job.cursor = i; - input->clear(); - input->insert(df::interface_key::SELECT); - } - } - } - INTERPOSE_NEXT(feed)(input); - } - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - if (kitchen_in_add()) - for (int i = 0; i < 3; i++) - draw_binding(i, kitchen_bindings[i]); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(kitchen_keys_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(kitchen_keys_hook, render); From 909776571e1f2730db61a4312f036cd8baad2c1c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 01:06:03 -0500 Subject: [PATCH 0755/1012] dwarfmonitor: support getSelectedUnit --- plugins/dwarfmonitor.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 2ea811f38..1ee1c8db8 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1543,6 +1543,11 @@ public: dwarf_column.setHighlight(0); } + df::unit *getSelectedUnit() override + { + return (selected_column == 1) ? dwarf_column.getFirstSelectedElem() : nullptr; + } + void feed(set *input) { bool key_processed = false; @@ -1574,7 +1579,7 @@ public: } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { - df::unit *selected_unit = (selected_column == 1) ? dwarf_column.getFirstSelectedElem() : nullptr; + df::unit *selected_unit = getSelectedUnit(); if (selected_unit) { input->clear(); From 4e2c6194ca33252a64dfb69c43d7cec28e654338 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 01:06:55 -0500 Subject: [PATCH 0756/1012] dwarfmonitor: support poetic/musical/dance forms --- plugins/dwarfmonitor.cpp | 75 +++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 1ee1c8db8..18c7ac9b2 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -18,31 +18,34 @@ #include "modules/Translation.h" #include "modules/World.h" #include "modules/Maps.h" -#include "df/activity_event.h" + #include "df/activity_entry.h" -#include "df/unit_preference.h" -#include "df/unit_soul.h" +#include "df/activity_event.h" +#include "df/creature_raw.h" +#include "df/dance_form.h" +#include "df/descriptor_color.h" +#include "df/descriptor_shape.h" #include "df/item_type.h" - -#include "df/itemdef_weaponst.h" -#include "df/itemdef_trapcompst.h" -#include "df/itemdef_toyst.h" -#include "df/itemdef_toolst.h" -#include "df/itemdef_instrumentst.h" -#include "df/itemdef_armorst.h" #include "df/itemdef_ammost.h" -#include "df/itemdef_siegeammost.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_foodst.h" #include "df/itemdef_glovesst.h" -#include "df/itemdef_shoesst.h" -#include "df/itemdef_shieldst.h" #include "df/itemdef_helmst.h" +#include "df/itemdef_instrumentst.h" #include "df/itemdef_pantsst.h" -#include "df/itemdef_foodst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_siegeammost.h" +#include "df/itemdef_toolst.h" +#include "df/itemdef_toyst.h" +#include "df/itemdef_trapcompst.h" +#include "df/itemdef_weaponst.h" +#include "df/musical_form.h" +#include "df/poetic_form.h" #include "df/trapcomp_flags.h" -#include "df/creature_raw.h" +#include "df/unit_preference.h" +#include "df/unit_soul.h" #include "df/world_raws.h" -#include "df/descriptor_shape.h" -#include "df/descriptor_color.h" using std::deque; @@ -137,6 +140,14 @@ static string getUnitName(df::unit * unit) return label; } +template +static string getFormName(int32_t id, const string &default_ = "?") { + T *form = T::find(id); + if (form) + return Translation::TranslateName(&form->name); + return default_; +} + static void send_key(const df::interface_key &key) { set< df::interface_key > keys; @@ -1290,6 +1301,19 @@ struct preference_map case (T_type::LikeColor): label += "Color :" + raws.language.colors[pref.color_id]->name; break; + + case (T_type::LikePoeticForm): + label += "Poetry :" + getFormName(pref.poetic_form_id); + break; + + case (T_type::LikeMusicalForm): + label += "Music :" + getFormName(pref.musical_form_id); + break; + + case (T_type::LikeDanceForm): + label += "Dance :" + getFormName(pref.dance_form_id); + break; + } } }; @@ -1444,6 +1468,18 @@ public: return false; break; + case (T_type::LikePoeticForm): + return lhs.poetic_form_id == rhs.poetic_form_id; + break; + + case (T_type::LikeMusicalForm): + return lhs.musical_form_id == rhs.musical_form_id; + break; + + case (T_type::LikeDanceForm): + return lhs.dance_form_id == rhs.dance_form_id; + break; + default: return false; } @@ -1483,6 +1519,11 @@ public: case (T_type::LikeColor): return COLOR_BLUE; + case (T_type::LikePoeticForm): + case (T_type::LikeMusicalForm): + case (T_type::LikeDanceForm): + return COLOR_LIGHTCYAN; + default: return false; } From 5f588b376aee2503879629c9763205c0570d6c21 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 01:07:20 -0500 Subject: [PATCH 0757/1012] dwarfmonitor: make handling of unrecognized preferences more obvious --- plugins/dwarfmonitor.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 18c7ac9b2..ef77d7ae3 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1212,6 +1212,7 @@ struct preference_map break; default: + label = string("UNKNOWN ") + ENUM_ATTR_STR(item_type, caption, pref.item_type); break; } @@ -1314,6 +1315,9 @@ struct preference_map label += "Dance :" + getFormName(pref.dance_form_id); break; + default: + label += string("UNKNOWN ") + ENUM_KEY_STR(unit_preference::T_type, pref.type); + break; } } }; @@ -1525,7 +1529,7 @@ public: return COLOR_LIGHTCYAN; default: - return false; + return COLOR_LIGHTMAGENTA; } return true; From 9515a9e4f5fac41589849cbbe4ddf4a449ac509b Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 01:13:08 -0500 Subject: [PATCH 0758/1012] dwarfmonitor: use interface_key overload of OutputHotkeyString, grey out unit option --- plugins/dwarfmonitor.cpp | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index ef77d7ae3..79211fabd 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -491,6 +491,8 @@ public: void render() { + using namespace df::enums::interface_key; + if (Screen::isDismissed(this)) return; @@ -504,18 +506,18 @@ public: int32_t y = gps->dimy - 4; int32_t x = 2; - OutputHotkeyString(x, y, "Leave", "Esc"); + OutputHotkeyString(x, y, "Leave", LEAVESCREEN); x += 13; string window_label = "Window Months: " + int_to_string(window_days / min_window); - OutputHotkeyString(x, y, window_label.c_str(), "*"); + OutputHotkeyString(x, y, window_label.c_str(), SECONDSCROLL_PAGEDOWN); ++y; x = 2; - OutputHotkeyString(x, y, "Fort Stats", "Shift-D"); + OutputHotkeyString(x, y, "Fort Stats", CUSTOM_SHIFT_D); x += 3; - OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z); } std::string getFocusString() { return "dwarfmonitor_dwarfstats"; } @@ -1099,6 +1101,8 @@ public: void render() { + using namespace df::enums::interface_key; + if (Screen::isDismissed(this)) return; @@ -1113,18 +1117,18 @@ public: int32_t y = gps->dimy - 4; int32_t x = 2; - OutputHotkeyString(x, y, "Leave", "Esc"); + OutputHotkeyString(x, y, "Leave", LEAVESCREEN); x += 13; string window_label = "Window Months: " + int_to_string(window_days / min_window); - OutputHotkeyString(x, y, window_label.c_str(), "*"); + OutputHotkeyString(x, y, window_label.c_str(), SECONDSCROLL_PAGEDOWN); ++y; x = 2; - OutputHotkeyString(x, y, "Dwarf Stats", "Shift-D"); + OutputHotkeyString(x, y, "Dwarf Stats", CUSTOM_SHIFT_D); x += 3; - OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z); } std::string getFocusString() { return "dwarfmonitor_fortstats"; } @@ -1660,6 +1664,8 @@ public: void render() { + using namespace df::enums::interface_key; + if (Screen::isDismissed(this)) return; @@ -1673,10 +1679,11 @@ public: int32_t y = gps->dimy - 3; int32_t x = 2; - OutputHotkeyString(x, y, "Leave", "Esc"); + OutputHotkeyString(x, y, "Leave", LEAVESCREEN); x += 2; - OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z, false, 0, + getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY); } std::string getFocusString() { return "dwarfmonitor_preferences"; } From 7b16cf16199e1f4d3cc3c2334af688e598ad554e Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 09:54:33 -0500 Subject: [PATCH 0759/1012] dwarfmonitor: actually display creature names --- plugins/dwarfmonitor.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 79211fabd..b715f6351 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1233,15 +1233,13 @@ struct preference_map { case (T_type::LikeCreature): { - label = "Creature :"; - Units::getRaceNamePluralById(pref.creature_id); + label = "Creature :" + Units::getRaceNamePluralById(pref.creature_id); break; } case (T_type::HateCreature): { - label = "Hates :"; - Units::getRaceNamePluralById(pref.creature_id); + label = "Hates :" + Units::getRaceNamePluralById(pref.creature_id); break; } From b035d9e53ad97fdf4a897a96a6c25e51d0995912 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 10:05:31 -0500 Subject: [PATCH 0760/1012] ListColumn: only change items' foreground color in selected columns --- plugins/listcolumn.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 4e4815b69..f7747d96d 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -113,7 +113,7 @@ public: for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) { ++y; - UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; + UIColor fg_color = (is_selected_column && display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; string item_label = display_list[i]->text; From 74aefddd75b3153c2ea215de82a29f2da9ba3208 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 10:17:40 -0500 Subject: [PATCH 0761/1012] dwarfmonitor: Use a reasonable default for unhandled item types --- plugins/dwarfmonitor.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index b715f6351..6f5481369 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1216,7 +1216,18 @@ struct preference_map break; default: - label = string("UNKNOWN ") + ENUM_ATTR_STR(item_type, caption, pref.item_type); + label = ENUM_ATTR_STR(item_type, caption, pref.item_type); + if (label.size()) + { + if (label[label.size() - 1] == 's') + label += "es"; + else + label += "s"; + } + else + { + label = "UNKNOWN"; + } break; } From 1ed6c70663d4ada28538d56138ecbcee58ee47fe Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 18 Jan 2018 10:17:54 -0500 Subject: [PATCH 0762/1012] dwarfmonitor: Add "view unit" option to prefs screen --- plugins/dwarfmonitor.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 6f5481369..7aed7188d 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -45,6 +45,7 @@ #include "df/trapcomp_flags.h" #include "df/unit_preference.h" #include "df/unit_soul.h" +#include "df/viewscreen_unitst.h" #include "df/world_raws.h" using std::deque; @@ -1345,14 +1346,14 @@ public: preferences_column.auto_select = true; preferences_column.setTitle("Preference"); preferences_column.bottom_margin = 3; - preferences_column.search_margin = 35; + preferences_column.search_margin = 50; dwarf_column.multiselect = false; dwarf_column.auto_select = true; dwarf_column.allow_null = true; dwarf_column.setTitle("Units with Preference"); dwarf_column.bottom_margin = 3; - dwarf_column.search_margin = 35; + dwarf_column.search_margin = 50; populatePreferencesColumn(); } @@ -1635,6 +1636,16 @@ public: Screen::dismiss(this); return; } + else if (input->count(interface_key::CUSTOM_SHIFT_V)) + { + df::unit *unit = getSelectedUnit(); + if (unit) + { + auto unitscr = df::allocate(); + unitscr->unit = unit; + Screen::show(unitscr); + } + } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { df::unit *selected_unit = getSelectedUnit(); @@ -1690,6 +1701,10 @@ public: int32_t x = 2; OutputHotkeyString(x, y, "Leave", LEAVESCREEN); + x += 2; + OutputHotkeyString(x, y, "View Unit", CUSTOM_SHIFT_V, false, 0, + getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY); + x += 2; OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z, false, 0, getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY); From 58bef276de608cc92823e6d685971edcec035540 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 19 Jan 2018 08:25:16 +0530 Subject: [PATCH 0763/1012] send item imrpovements --- plugins/proto/RemoteFortressReader.proto | 4 ++-- plugins/remotefortressreader/item_reader.cpp | 23 ++++++++++++++++++-- plugins/remotefortressreader/item_reader.h | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 463e368fc..ef32b9b81 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -907,8 +907,8 @@ enum ImprovementType message ItemImprovement { - optional MatPair mat_type = 1; + optional MatPair material = 1; optional ImprovementType type = 2; optional int32 shape = 3; - + optional int32 specific_type= 4; } diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 256d78fd9..ed9c5f204 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -8,6 +8,9 @@ #include "df/item_toolst.h" #include "df/item_smallgemst.h" #include "df/itemimprovement.h" +#include "df/itemimprovement_bandsst.h" +#include "df/itemimprovement_coveredst.h" +#include "df/itemimprovement_itemspecificst.h" #include "df/itemimprovement_threadst.h" #include "df/itemdef.h" #include "df/map_block.h" @@ -287,22 +290,38 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) improvement_type::improvement_type impType = improvement->getType(); + auto netImp = NetItem->add_improvements(); + + netImp->set_type((ImprovementType)impType); + + auto mat = netImp->mutable_material(); + mat->set_mat_type(improvement->mat_type); + mat->set_mat_index(improvement->mat_index); + switch (impType) { case df::enums::improvement_type::ART_IMAGE: break; case df::enums::improvement_type::COVERED: { + VIRTUAL_CAST_VAR(covered, df::itemimprovement_coveredst, improvement); + netImp->set_shape(covered->shape); break; } case df::enums::improvement_type::RINGS_HANGING: break; case df::enums::improvement_type::BANDS: + { + VIRTUAL_CAST_VAR(bands, df::itemimprovement_bandsst, improvement); + netImp->set_shape(bands->shape); break; - case df::enums::improvement_type::SPIKES: - break; + } case df::enums::improvement_type::ITEMSPECIFIC: + { + VIRTUAL_CAST_VAR(specific, df::itemimprovement_itemspecificst, improvement); + netImp->set_specific_type(specific->type); break; + } case df::enums::improvement_type::THREAD: { VIRTUAL_CAST_VAR(improvement_thread, df::itemimprovement_threadst, improvement); diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h index 45f8c1a95..2bd8e7c89 100644 --- a/plugins/remotefortressreader/item_reader.h +++ b/plugins/remotefortressreader/item_reader.h @@ -15,7 +15,7 @@ namespace df namespace MapExtras { - struct MapCache; + class MapCache; } DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out); From 771b88c6c4db78e0658b81c82a17d3b2da0944da Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 21 Jan 2018 17:44:31 -0500 Subject: [PATCH 0764/1012] Update scripts, authors, dfhack.init-example for dfhack/scripts#43 --- dfhack.init-example | 3 +++ docs/Authors.rst | 1 + scripts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dfhack.init-example b/dfhack.init-example index 75552006e..82210a2a6 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -55,6 +55,9 @@ keybinding add Alt-S@dwarfmode/Default gui/settings-manager # change quantity of manager orders keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity +# view combat reports for the selected unit/corpse/spatter +keybinding add Ctrl-Shift-R view-unit-reports + ############################## # Generic adv mode bindings # ############################## diff --git a/docs/Authors.rst b/docs/Authors.rst index 0f7e06811..8c1b5096e 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -28,6 +28,7 @@ Carter Bray Qartar Chris Dombroski cdombroski Clayton Hughes Clément Vuchener cvuchener +Dan Amlund danamlund David Corbett dscorbett David Seguin dseguin Deon diff --git a/scripts b/scripts index 2d4093e61..e65369e58 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 2d4093e61ea2a02742a6bfbfd80e49eb6dcdd549 +Subproject commit e65369e58f5bc2819641e4633424aa8922cf9b1b From 1ba5477b63067c5b86c46c0df05428776378557a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 21 Jan 2018 19:27:16 -0500 Subject: [PATCH 0765/1012] Add designation priority support to MapCache and dig plugin Fixes #481 --- library/include/modules/MapCache.h | 27 ++++++- library/include/modules/Maps.h | 4 +- library/modules/MapCache.cpp | 40 +++++++++- library/modules/Maps.cpp | 7 +- plugins/dig.cpp | 115 +++++++++++++++++++++-------- 5 files changed, 157 insertions(+), 36 deletions(-) diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 0048f5bd9..36625719b 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -276,6 +276,9 @@ public: return true; } + int32_t priorityAt(df::coord2d p); + bool setPriorityAt(df::coord2d p, int32_t priority); + df::tile_occupancy OccupancyAt(df::coord2d p) { return index_tile(occupancy,p); @@ -544,13 +547,31 @@ class DFHACK_EXPORT MapCache Block * b= BlockAtTile(tilecoord); return b ? b->DesignationAt(tilecoord) : df::tile_designation(); } - bool setDesignationAt (DFCoord tilecoord, df::tile_designation des) + // priority is optional, only set if >= 0 + bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = -1) { - if(Block * b= BlockAtTile(tilecoord)) - return b->setDesignationAt(tilecoord, des); + if (Block *b = BlockAtTile(tilecoord)) + { + if (!b->setDesignationAt(tilecoord, des)) + return false; + if (priority >= 0 && b->setPriorityAt(tilecoord, priority)) + return false; + return true; + } return false; } + int32_t priorityAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->priorityAt(tilecoord) : -1; + } + bool setPriorityAt (DFCoord tilecoord, int32_t priority) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->setPriorityAt(tilecoord, priority) : false; + } + df::tile_occupancy occupancyAt (DFCoord tilecoord) { Block * b= BlockAtTile(tilecoord); diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index fb5fc9860..6b6e62f0a 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -46,6 +46,7 @@ distribution. namespace df { struct block_square_event; + struct block_square_event_designation_priorityst; struct block_square_event_frozen_liquidst; struct block_square_event_grassst; struct block_square_event_item_spatterst; @@ -321,7 +322,8 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, std::vector* grass = 0, std::vector* constructions = 0, std::vector* spoors = 0, - std::vector* items = 0 + std::vector* items = 0, + std::vector* priorities = 0 ); /// remove a block event from the block by address diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index fb4aef935..ffb9fc769 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -49,8 +49,9 @@ using namespace std; #include "df/block_burrow.h" #include "df/block_burrow_link.h" -#include "df/block_square_event_grassst.h" +#include "df/block_square_event_designation_priorityst.h" #include "df/block_square_event_frozen_liquidst.h" +#include "df/block_square_event_grassst.h" #include "df/building_type.h" #include "df/builtin_mats.h" #include "df/burrow.h" @@ -271,6 +272,43 @@ bool MapExtras::Block::setTiletypeAt(df::coord2d pos, df::tiletype tt, bool forc return true; } +static df::block_square_event_designation_priorityst *getPriorityEvent(df::map_block *block, bool write) +{ + vector events; + Maps::SortBlockEvents(block, 0, 0, 0, 0, 0, 0, 0, &events); + if (events.empty()) + { + if (!write) + return NULL; + + auto event = df::allocate(); + block->block_events.push_back((df::block_square_event*)event); + return event; + } + return events[0]; +} + +int32_t MapExtras::Block::priorityAt(df::coord2d pos) +{ + if (!block) + return false; + + if (auto event = getPriorityEvent(block, false)) + return event->priority[pos.x % 16][pos.y % 16]; + + return 0; +} + +bool MapExtras::Block::setPriorityAt(df::coord2d pos, int32_t priority) +{ + if (!block || priority < 0) + return false; + + auto event = getPriorityEvent(block, true); + event->priority[pos.x % 16][pos.y % 16] = priority; + return true; +} + bool MapExtras::Block::setVeinMaterialAt(df::coord2d pos, int16_t mat, df::inclusion_type type) { using namespace df::enums::tiletype_material; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 2b2311a7c..20255dbe3 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -402,7 +402,8 @@ bool Maps::SortBlockEvents(df::map_block *block, vector *grasses, vector *constructions, vector *spoors, - vector *items) + vector *items, + vector *priorities) { if (veins) veins->clear(); @@ -456,6 +457,10 @@ bool Maps::SortBlockEvents(df::map_block *block, if (items) items->push_back((df::block_square_event_item_spatterst *)evt); break; + case block_square_event_type::designation_priority: + if (priorities) + priorities->push_back((df::block_square_event_designation_priorityst *)evt); + break; } } return true; diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 5404f2f2d..a56688a7b 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -1,16 +1,23 @@ +#include +#include +#include +#include +#include +#include + #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Maps.h" +#include "uicommon.h" + #include "modules/Gui.h" #include "modules/MapCache.h" +#include "modules/Maps.h" #include "modules/Materials.h" -#include -#include -#include -#include -#include + +#include "df/ui_sidebar_menus.h" + using std::vector; using std::string; using std::stack; @@ -27,6 +34,7 @@ command_result digcircle (color_ostream &out, vector & parameters); command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); +REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -97,6 +105,7 @@ enum circle_what bool dig (MapExtras::MapCache & MCache, circle_what what, df::tile_dig_designation type, + int32_t priority, int32_t x, int32_t y, int32_t z, int x_max, int y_max ) @@ -175,20 +184,21 @@ bool dig (MapExtras::MapCache & MCache, break; } std::cerr << "allowing tt" << (int)tt << "\n"; - MCache.setDesignationAt(at,des); + MCache.setDesignationAt(at,des,priority); return true; }; bool lineX (MapExtras::MapCache & MCache, circle_what what, df::tile_dig_designation type, + int32_t priority, int32_t y1, int32_t y2, int32_t x, int32_t z, int x_max, int y_max ) { for(int32_t y = y1; y <= y2; y++) { - dig(MCache, what, type,x,y,z, x_max, y_max); + dig(MCache, what, type, priority, x,y,z, x_max, y_max); } return true; }; @@ -196,17 +206,51 @@ bool lineX (MapExtras::MapCache & MCache, bool lineY (MapExtras::MapCache & MCache, circle_what what, df::tile_dig_designation type, + int32_t priority, int32_t x1, int32_t x2, int32_t y, int32_t z, int x_max, int y_max ) { for(int32_t x = x1; x <= x2; x++) { - dig(MCache, what, type,x,y,z, x_max, y_max); + dig(MCache, what, type, priority, x,y,z, x_max, y_max); } return true; }; +int32_t parse_priority(color_ostream &out, vector ¶meters) +{ + int32_t default_priority = ui_sidebar_menus->designation.priority; + + for (auto it = parameters.begin(); it != parameters.end(); ++it) + { + const string &s = *it; + if (s.substr(0, 2) == "p=" || s.substr(0, 2) == "-p") + { + if (s.size() >= 3) + { + auto priority = int32_t(1000 * atof(s.c_str() + 2)); + parameters.erase(it); + return priority; + } + else if (it + 1 != parameters.end()) + { + auto priority = int32_t(1000 * atof((*(it + 1)).c_str())); + parameters.erase(it); + parameters.erase(it + 1); + return priority; + } + else + { + out.printerr("invalid priority specified; reverting to %i\n", default_priority); + break; + } + } + } + + return default_priority; +} + command_result digcircle (color_ostream &out, vector & parameters) { static bool filled = false; @@ -215,6 +259,8 @@ command_result digcircle (color_ostream &out, vector & parameters) static int diameter = 0; auto saved_d = diameter; bool force_help = false; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") @@ -326,12 +372,12 @@ command_result digcircle (color_ostream &out, vector & parameters) // paint center if(filled) { - lineY(MCache,what,type, cx - r, cx + r, cy, cz,x_max,y_max); + lineY(MCache, what, type, priority, cx - r, cx + r, cy, cz, x_max, y_max); } else { - dig(MCache, what, type,cx - r, cy, cz,x_max,y_max); - dig(MCache, what, type,cx + r, cy, cz,x_max,y_max); + dig(MCache, what, type, priority, cx - r, cy, cz, x_max, y_max); + dig(MCache, what, type, priority, cx + r, cy, cz, x_max, y_max); } adjust = false; iter = 2; @@ -363,24 +409,24 @@ command_result digcircle (color_ostream &out, vector & parameters) // paint if(filled || iter == diameter - 1) { - lineY(MCache,what,type, left, right, top , cz,x_max,y_max); - lineY(MCache,what,type, left, right, bottom , cz,x_max,y_max); + lineY(MCache, what, type, priority, left, right, top, cz, x_max, y_max); + lineY(MCache, what, type, priority, left, right, bottom, cz, x_max, y_max); } else { - dig(MCache, what, type,left, top, cz,x_max,y_max); - dig(MCache, what, type,left, bottom, cz,x_max,y_max); - dig(MCache, what, type,right, top, cz,x_max,y_max); - dig(MCache, what, type,right, bottom, cz,x_max,y_max); + dig(MCache, what, type, priority, left, top, cz, x_max, y_max); + dig(MCache, what, type, priority, left, bottom, cz, x_max, y_max); + dig(MCache, what, type, priority, right, top, cz, x_max, y_max); + dig(MCache, what, type, priority, right, bottom, cz, x_max, y_max); } if(!filled && diff > 1) { int lright = cx + lastwhole; int lleft = cx - lastwhole + adjust; - lineY(MCache,what,type, lleft + 1, left - 1, top + 1 , cz,x_max,y_max); - lineY(MCache,what,type, right + 1, lright - 1, top + 1 , cz,x_max,y_max); - lineY(MCache,what,type, lleft + 1, left - 1, bottom - 1 , cz,x_max,y_max); - lineY(MCache,what,type, right + 1, lright - 1, bottom - 1 , cz,x_max,y_max); + lineY(MCache, what, type, priority, lleft + 1, left - 1, top + 1 , cz, x_max, y_max); + lineY(MCache, what, type, priority, right + 1, lright - 1, top + 1 , cz, x_max, y_max); + lineY(MCache, what, type, priority, lleft + 1, left - 1, bottom - 1 , cz, x_max, y_max); + lineY(MCache, what, type, priority, right + 1, lright - 1, bottom - 1 , cz, x_max, y_max); } lastwhole = whole; } @@ -808,6 +854,8 @@ command_result digexp (color_ostream &out, vector & parameters) bool force_help = false; static explo_how how = EXPLO_NOTHING; static explo_what what = EXPLO_HIDDEN; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") @@ -963,7 +1011,7 @@ command_result digexp (color_ostream &out, vector & parameters) if(cross[y][x]) { des.bits.dig = tile_dig_designation::Default; - mx.setDesignationAt(pos,des); + mx.setDesignationAt(pos,des,priority); } } mx.WriteAll(); @@ -984,6 +1032,7 @@ command_result digvx (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); + lol.push_back(string("-p") + int_to_string(parse_priority(out, parameters))); return digv(out,lol); } @@ -992,6 +1041,8 @@ command_result digv (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED uint32_t x_max,y_max,z_max; bool updown = false; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters.size() && parameters[0]=="x") @@ -1118,7 +1169,7 @@ command_result digv (color_ostream &out, vector & parameters) des_minus.bits.dig = tile_dig_designation::UpDownStair; else des_minus.bits.dig = tile_dig_designation::UpStair; - MCache->setDesignationAt(current-1,des_minus); + MCache->setDesignationAt(current-1,des_minus,priority); des.bits.dig = tile_dig_designation::DownStair; } @@ -1130,7 +1181,7 @@ command_result digv (color_ostream &out, vector & parameters) des_plus.bits.dig = tile_dig_designation::UpDownStair; else des_plus.bits.dig = tile_dig_designation::DownStair; - MCache->setDesignationAt(current+1,des_plus); + MCache->setDesignationAt(current+1,des_plus,priority); if(des.bits.dig == tile_dig_designation::DownStair) des.bits.dig = tile_dig_designation::UpDownStair; @@ -1140,7 +1191,7 @@ command_result digv (color_ostream &out, vector & parameters) } if(des.bits.dig == tile_dig_designation::No) des.bits.dig = tile_dig_designation::Default; - MCache->setDesignationAt(current,des); + MCache->setDesignationAt(current,des,priority); } } MCache->WriteAll(); @@ -1153,6 +1204,7 @@ command_result diglx (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); + lol.push_back(string("-p") + int_to_string(parse_priority(out, parameters))); return digl(out,lol); } @@ -1168,6 +1220,8 @@ command_result digl (color_ostream &out, vector & parameters) uint32_t x_max,y_max,z_max; bool updown = false; bool undo = false; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters[i]=="x") @@ -1326,7 +1380,7 @@ command_result digl (color_ostream &out, vector & parameters) // undo mode: clear designation if(undo) des_minus.bits.dig = tile_dig_designation::No; - MCache->setDesignationAt(current-1,des_minus); + MCache->setDesignationAt(current-1,des_minus,priority); des.bits.dig = tile_dig_designation::DownStair; } @@ -1341,7 +1395,7 @@ command_result digl (color_ostream &out, vector & parameters) // undo mode: clear designation if(undo) des_plus.bits.dig = tile_dig_designation::No; - MCache->setDesignationAt(current+1,des_plus); + MCache->setDesignationAt(current+1,des_plus,priority); if(des.bits.dig == tile_dig_designation::DownStair) des.bits.dig = tile_dig_designation::UpDownStair; @@ -1354,7 +1408,7 @@ command_result digl (color_ostream &out, vector & parameters) // undo mode: clear designation if(undo) des.bits.dig = tile_dig_designation::No; - MCache->setDesignationAt(current,des); + MCache->setDesignationAt(current,des,priority); } } MCache->WriteAll(); @@ -1371,6 +1425,7 @@ command_result digauto (color_ostream &out, vector & parameters) command_result digtype (color_ostream &out, vector & parameters) { //mostly copy-pasted from digv + int32_t priority = parse_priority(out, parameters); CoreSuspender suspend; if ( parameters.size() > 1 ) { @@ -1474,7 +1529,7 @@ command_result digtype (color_ostream &out, vector & parameters) df::tile_designation designation = mCache->designationAt(current); designation.bits.dig = baseDes.bits.dig; - mCache->setDesignationAt(current, designation); + mCache->setDesignationAt(current, designation,priority); } } } From f75b116ae28ed06fe945141899c6db79947660c7 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 24 Jan 2018 10:22:45 +0530 Subject: [PATCH 0766/1012] Added the rest of the relavent improvement headers. --- plugins/remotefortressreader/item_reader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index ed9c5f204..e0a6ff831 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -8,9 +8,13 @@ #include "df/item_toolst.h" #include "df/item_smallgemst.h" #include "df/itemimprovement.h" +#include "df/itemimprovement_art_imagest.h" #include "df/itemimprovement_bandsst.h" #include "df/itemimprovement_coveredst.h" +#include "df/itemimprovement_illustrationst.h" #include "df/itemimprovement_itemspecificst.h" +#include "df/itemimprovement_sewn_imagest.h" +#include "df/itemimprovement_specific_type.h" #include "df/itemimprovement_threadst.h" #include "df/itemdef.h" #include "df/map_block.h" From ee7ad348f0e413d4f3a47c3484985d7e679b0cef Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 24 Jan 2018 12:09:05 +0530 Subject: [PATCH 0767/1012] add item images --- plugins/proto/RemoteFortressReader.proto | 24 ++++++ plugins/remotefortressreader/item_reader.cpp | 81 ++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index ef32b9b81..02a4474a1 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -911,4 +911,28 @@ message ItemImprovement optional ImprovementType type = 2; optional int32 shape = 3; optional int32 specific_type= 4; + optional ArtImage image = 5; } + +enum ArtImageElementType +{ + IMAGE_CREATURE = 0; + IMAGE_PLANT = 1; + IMAGE_TREE = 2; + IMAGE_SHAPE = 3; + IMAGE_ITEM = 4; +} + +message ArtImageElement +{ + optional int32 count = 1; + optional ArtImageElementType type = 2; + optional MatPair creature_item = 3; + optional MatPair material = 5; + optional int32 id = 6; +} + +message ArtImage +{ + repeated ArtImageElement elements = 1; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index e0a6ff831..99bcbd760 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -1,5 +1,15 @@ #include "item_reader.h" +#include "df/art_image.h" +#include "df/art_image_chunk.h" +#include "df/art_image_element.h" +#include "df/art_image_element_creaturest.h" +#include "df/art_image_element_itemst.h" +#include "df/art_image_element_plantst.h" +#include "df/art_image_element_shapest.h" +#include "df/art_image_element_treest.h" +#include "df/art_image_element_type.h" +#include "df/art_image_ref.h" #include "df/descriptor_shape.h" #include "df/item_type.h" #include "df/item_constructed.h" @@ -34,6 +44,75 @@ using namespace std; using namespace df::global; +void CopyImage(const df::art_image * image, ArtImage * netImage) +{ + for (int i = 0; i < image->elements.size(); i++) + { + auto element = image->elements[i]; + auto netElement = netImage->add_elements(); + auto elementType = element->getType(); + netElement->set_type((ArtImageElementType)elementType); + + netElement->set_count(element->count); + + switch (elementType) + { + case df::enums::art_image_element_type::CREATURE: + { + VIRTUAL_CAST_VAR(creature, df::art_image_element_creaturest, element); + auto cret = netElement->mutable_creature_item(); + cret->set_mat_type(creature->race); + cret->set_mat_index(creature->caste); + break; + } + case df::enums::art_image_element_type::PLANT: + { + VIRTUAL_CAST_VAR(plant, df::art_image_element_plantst, element); + netElement->set_id(plant->plant_id); + break; + } + case df::enums::art_image_element_type::TREE: + { + VIRTUAL_CAST_VAR(tree, df::art_image_element_treest, element); + netElement->set_id(tree->plant_id); + break; + } + case df::enums::art_image_element_type::SHAPE: + { + VIRTUAL_CAST_VAR(shape, df::art_image_element_shapest, element); + netElement->set_id(shape->shape_id); + break; + } + case df::enums::art_image_element_type::ITEM: + { + VIRTUAL_CAST_VAR(item, df::art_image_element_itemst, element); + auto it = netElement->mutable_creature_item(); + it->set_mat_type(item->item_type); + it->set_mat_index(item->item_subtype); + netElement->set_id(item->item_id); + auto mat = netElement->mutable_material(); + mat->set_mat_type(item->mat_type); + mat->set_mat_index(item->mat_index); + break; + } + default: + break; + } + } +} + +void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) +{ + for (int i = 0; i < world->art_image_chunks.size(); i++) + { + auto chunk = world->art_image_chunks[i]; + if (chunk->id != imageRef.id) + continue; + auto image = chunk->images[imageRef.subid]; + CopyImage(image, netImage); + } +} + void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { NetItem->set_id(DfItem->id); @@ -305,6 +384,8 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) switch (impType) { case df::enums::improvement_type::ART_IMAGE: + VIRTUAL_CAST_VAR(artImage, df::itemimprovement_art_imagest, improvement); + CopyImage(artImage->image, netImp->mutable_image()); break; case df::enums::improvement_type::COVERED: { From 68324dfe0b3ad6c11a0b177d9e87bb49f91682a1 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 25 Jan 2018 07:53:19 +0530 Subject: [PATCH 0768/1012] Send statue images. --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/item_reader.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 02a4474a1..0cdb41110 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -277,6 +277,7 @@ message Item optional float velocity_z = 15; optional int32 volume = 16; repeated ItemImprovement improvements = 17; + optional ArtImage image = 18; } message MapBlock diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 99bcbd760..36cbbb96e 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -14,9 +14,10 @@ #include "df/item_type.h" #include "df/item_constructed.h" #include "df/item_gemst.h" +#include "df/item_smallgemst.h" +#include "df/item_statuest.h" #include "df/item_threadst.h" #include "df/item_toolst.h" -#include "df/item_smallgemst.h" #include "df/itemimprovement.h" #include "df/itemimprovement_art_imagest.h" #include "df/itemimprovement_bandsst.h" @@ -186,7 +187,11 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) case df::enums::item_type::COFFIN: break; case df::enums::item_type::STATUE: + { + VIRTUAL_CAST_VAR(statue, df::item_statuest, DfItem); + CopyImage(statue->image, NetItem->mutable_image()); break; + } case df::enums::item_type::CORPSE: break; case df::enums::item_type::WEAPON: @@ -384,9 +389,11 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) switch (impType) { case df::enums::improvement_type::ART_IMAGE: + { VIRTUAL_CAST_VAR(artImage, df::itemimprovement_art_imagest, improvement); CopyImage(artImage->image, netImp->mutable_image()); break; + } case df::enums::improvement_type::COVERED: { VIRTUAL_CAST_VAR(covered, df::itemimprovement_coveredst, improvement); From f1b8fa305bedfc3b9909cc3f8b39d352a443f7e3 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 25 Jan 2018 11:55:46 +0530 Subject: [PATCH 0769/1012] try using the new function pointer. --- library/xml | 2 +- plugins/remotefortressreader/item_reader.cpp | 21 ++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/library/xml b/library/xml index f0c609211..618db6349 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f0c609211fbfd354b576cae75e9e64f8c6c80b06 +Subproject commit 618db6349ece5827d371a60b0fac1066b1d7989b diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 36cbbb96e..36d8f6ec9 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -104,13 +104,22 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) { - for (int i = 0; i < world->art_image_chunks.size(); i++) + if (df::global::getArtImage) { - auto chunk = world->art_image_chunks[i]; - if (chunk->id != imageRef.id) - continue; - auto image = chunk->images[imageRef.subid]; - CopyImage(image, netImage); + df::art_image * (*getImage)(df::art_image_ref *, int *) = (df::art_image * (*)(df::art_image_ref *, int *))df::global::getArtImage; + int subid = -1; + CopyImage(getImage(&imageRef, &subid), netImage); + } + else + { + for (int i = 0; i < world->art_image_chunks.size(); i++) + { + auto chunk = world->art_image_chunks[i]; + if (chunk->id != imageRef.id) + continue; + auto image = chunk->images[imageRef.subid]; + CopyImage(image, netImage); + } } } From ef451a2f2dff3ad659e506ee97636311d466f563 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 25 Jan 2018 20:24:12 +0530 Subject: [PATCH 0770/1012] Got the image reader function working. --- plugins/remotefortressreader/item_reader.cpp | 6 ++--- .../remotefortressreader.cpp | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 36d8f6ec9..93d6fe621 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -106,9 +106,9 @@ void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) { if (df::global::getArtImage) { - df::art_image * (*getImage)(df::art_image_ref *, int *) = (df::art_image * (*)(df::art_image_ref *, int *))df::global::getArtImage; + df::art_image * (__thiscall *getImage)(df::world*, df::art_image_ref *, int *) = (df::art_image * (__thiscall*)(df::world*, df::art_image_ref *, int *))df::global::getArtImage; int subid = -1; - CopyImage(getImage(&imageRef, &subid), netImage); + CopyImage(getImage(world, &imageRef, &subid), netImage); } else { @@ -456,7 +456,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) NetItem->set_volume(DfItem->getVolume()); } -DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out) +DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out) { if (!Core::getInstance().isWorldLoaded()) { //out->set_available(false); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 450333088..d5f55eb95 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -179,6 +179,28 @@ const char* growth_locations[] = { }; #define GROWTH_LOCATIONS_SIZE 8 + +#include "df/art_image.h" +#include "df/art_image_chunk.h" +#include "df/art_image_ref.h" +command_result generate_image(color_ostream &out, vector & parameters) +{ + df::art_image_ref imageRef; + imageRef.civ_id = -1; + imageRef.id = -1; + imageRef.site_id = -1; + imageRef.subid = -1; + + if (df::global::getArtImage) + { + df::art_image * (__thiscall *getImage)(df::world*,df::art_image_ref *, int *) = (df::art_image * (__thiscall*)(df::world*, df::art_image_ref *, int *))df::global::getArtImage; + int subid = -1; + auto image = getImage(world,&imageRef, &subid); + out.print("Id: %d, subid: %d\n", image->id, image->subid); + } + return CR_OK; +} + command_result dump_bp_mods(color_ostream &out, vector & parameters) { remove("bp_appearance_mods.csv"); @@ -249,6 +271,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Thu, 25 Jan 2018 10:55:00 -0500 Subject: [PATCH 0771/1012] Add a dfhack.script_help() function to assist scripts --- docs/Lua API.rst | 6 +++++ library/lua/dfhack.lua | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 520ac243a..d79924cc1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3777,6 +3777,12 @@ Note that this function lets errors propagate to the caller. This is intended to only allow scripts that take appropriate action when used as a module to be loaded. +* ``dfhack.script_help([name, [extension]])`` + + Returns the contents of the embedded documentation of the specified script. + ``extension`` defaults to "lua", and ``name`` defaults to the name of the + script where this function was called. + Enabling and disabling scripts ============================== diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 340b6ce64..6acbe27e8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -562,6 +562,61 @@ function dfhack.run_script_with_env(envVars, name, flags, ...) return script_code(...), env end +local function current_script_name() + local frame = 1 + while true do + local info = debug.getinfo(frame, 'f') + if not info then break end + if info.func == dfhack.run_script_with_env then + local i = 1 + while true do + local name, value = debug.getlocal(frame, i) + if not name then break end + if name == 'name' then + return value + end + i = i + 1 + end + break + end + frame = frame + 1 + end +end + +function dfhack.script_help(script_name, extension) + script_name = script_name or current_script_name() + extension = extension or 'lua' + local full_name = script_name .. '.' .. extension + local path = dfhack.internal.findScript(script_name .. '.' .. extension) + or error("Could not find script: " .. full_name) + local begin_seq, end_seq + if extension == 'rb' then + begin_seq = '=begin' + end_seq = '=end' + else + begin_seq = '[====[' + end_seq = ']====]' + end + local f = io.open(path) or error("Could not open " .. path) + local in_help = false + local help = '' + for line in f:lines() do + if line:endswith(begin_seq) then + in_help = true + elseif in_help then + if line:endswith(end_seq) then + break + end + if line ~= script_name and line ~= ('='):rep(#script_name) then + help = help .. line .. '\n' + end + end + end + f:close() + help = help:gsub('^\n+', ''):gsub('\n+$', '') + return help +end + local function _run_command(...) args = {...} if type(args[1]) == 'table' then From 5e0e674580df07fa6144545a096b2c56ec377d08 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 25 Jan 2018 10:56:08 -0500 Subject: [PATCH 0772/1012] Update submodules --- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index 352e23310..f0c609211 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 352e233105512a702875f48d82f922f0c9ca3905 +Subproject commit f0c609211fbfd354b576cae75e9e64f8c6c80b06 diff --git a/plugins/stonesense b/plugins/stonesense index 4c55e1439..455b51aab 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4c55e1439e4e9fa55f6b17147a346c9cde11f7b3 +Subproject commit 455b51aab7a93095a775285782635cfc17ac651a diff --git a/scripts b/scripts index e65369e58..cbdd03aae 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e65369e58f5bc2819641e4633424aa8922cf9b1b +Subproject commit cbdd03aae69ff6e96644e8169487eaa6b4019d52 From e3d95daf45f87eab53515975e779cf58aa80a595 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 26 Jan 2018 08:19:41 +0530 Subject: [PATCH 0773/1012] Don't use globals --- library/xml | 2 +- plugins/remotefortressreader/item_reader.cpp | 6 ++++-- plugins/remotefortressreader/item_reader.h | 5 +++++ plugins/remotefortressreader/remotefortressreader.cpp | 9 +++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/library/xml b/library/xml index 618db6349..9afe7c953 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 618db6349ece5827d371a60b0fac1066b1d7989b +Subproject commit 9afe7c9538f1bfa104c5eff54b0945eee32a76cc diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 93d6fe621..c57b90d7d 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -1,4 +1,6 @@ #include "item_reader.h" +#include "Core.h" +#include "VersionInfo.h" #include "df/art_image.h" #include "df/art_image_chunk.h" @@ -104,9 +106,9 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) { - if (df::global::getArtImage) + GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + if (getImage) { - df::art_image * (__thiscall *getImage)(df::world*, df::art_image_ref *, int *) = (df::art_image * (__thiscall*)(df::world*, df::art_image_ref *, int *))df::global::getArtImage; int subid = -1; CopyImage(getImage(world, &imageRef, &subid), netImage); } diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h index 2bd8e7c89..332280982 100644 --- a/plugins/remotefortressreader/item_reader.h +++ b/plugins/remotefortressreader/item_reader.h @@ -11,6 +11,9 @@ namespace df { struct item; struct map_block; + struct art_image; + struct art_image_ref; + struct world; } namespace MapExtras @@ -22,4 +25,6 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out); +typedef df::art_image * (__thiscall *GET_IMAGE)(df::world*, df::art_image_ref *, int *); + #endif // !ITEM_READER_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index d5f55eb95..928b7a396 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -17,6 +17,7 @@ #include "SDL_events.h" #include "SDL_keyboard.h" #include "TileTypes.h" +#include "VersionInfo.h" #if DF_VERSION_INT > 34011 #include "DFHackVersion.h" #endif @@ -191,10 +192,10 @@ command_result generate_image(color_ostream &out, vector & parameters) imageRef.site_id = -1; imageRef.subid = -1; - if (df::global::getArtImage) - { - df::art_image * (__thiscall *getImage)(df::world*,df::art_image_ref *, int *) = (df::art_image * (__thiscall*)(df::world*, df::art_image_ref *, int *))df::global::getArtImage; - int subid = -1; + GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + if (getImage) + { + int subid = -1; auto image = getImage(world,&imageRef, &subid); out.print("Id: %d, subid: %d\n", image->id, image->subid); } From aff9c22875a7856542d0624183f332a6c0687a6c Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 26 Jan 2018 08:27:03 +0530 Subject: [PATCH 0774/1012] update submodules. --- plugins/stonesense | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stonesense b/plugins/stonesense index 4c55e1439..455b51aab 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4c55e1439e4e9fa55f6b17147a346c9cde11f7b3 +Subproject commit 455b51aab7a93095a775285782635cfc17ac651a diff --git a/scripts b/scripts index 6934b9d47..fd63a1658 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6934b9d47ccce5908395998133c14c91497f0935 +Subproject commit fd63a1658f3dd7d57d81014e333d0e16e358539d From c0c80b626de32d7e2e65b4c267a6e41801514b75 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 27 Jan 2018 12:12:19 +0530 Subject: [PATCH 0775/1012] update submodules --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9afe7c953..f1566f504 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9afe7c9538f1bfa104c5eff54b0945eee32a76cc +Subproject commit f1566f504a089c95e65a9828f550050dcf1fef46 From 341c0dacf373c8f21b47d30522ee754aff4f69d8 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 27 Jan 2018 12:44:51 +0530 Subject: [PATCH 0776/1012] updated function definition for win64. --- plugins/remotefortressreader/item_reader.cpp | 2 +- plugins/remotefortressreader/item_reader.h | 7 ++++++- .../remotefortressreader.cpp | 16 ++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index c57b90d7d..2acda8aef 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -109,7 +109,7 @@ void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); if (getImage) { - int subid = -1; + int16_t subid = -1; CopyImage(getImage(world, &imageRef, &subid), netImage); } else diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h index 332280982..df25d5655 100644 --- a/plugins/remotefortressreader/item_reader.h +++ b/plugins/remotefortressreader/item_reader.h @@ -25,6 +25,11 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out); -typedef df::art_image * (__thiscall *GET_IMAGE)(df::world*, df::art_image_ref *, int *); +#if(defined(WIN32) && !defined(_WIN64)) +typedef df::art_image * (__thiscall *GET_IMAGE)(df::world*, df::art_image_ref *, int16_t *); +#else +typedef df::art_image *(__fastcall *GET_IMAGE)(df::world *, df::art_image_ref *, int16_t *); +#endif + #endif // !ITEM_READER_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 928b7a396..22b836bb7 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -192,11 +192,11 @@ command_result generate_image(color_ostream &out, vector & parameters) imageRef.site_id = -1; imageRef.subid = -1; - GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); - if (getImage) - { - int subid = -1; - auto image = getImage(world,&imageRef, &subid); + GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + if (getImage) + { + int16_t subid = -1; + auto image = getImage(world, &imageRef, &subid); out.print("Id: %d, subid: %d\n", image->id, image->subid); } return CR_OK; @@ -272,7 +272,11 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sat, 27 Jan 2018 19:26:30 +0530 Subject: [PATCH 0777/1012] fix compiling on gcc. --- plugins/remotefortressreader/item_reader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h index df25d5655..59e097ca0 100644 --- a/plugins/remotefortressreader/item_reader.h +++ b/plugins/remotefortressreader/item_reader.h @@ -26,9 +26,9 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out); #if(defined(WIN32) && !defined(_WIN64)) -typedef df::art_image * (__thiscall *GET_IMAGE)(df::world*, df::art_image_ref *, int16_t *); +typedef df::art_image * (__thiscall *GET_IMAGE)(df::world *, df::art_image_ref *, int16_t *); #else -typedef df::art_image *(__fastcall *GET_IMAGE)(df::world *, df::art_image_ref *, int16_t *); +typedef df::art_image * (*GET_IMAGE)(df::world *, df::art_image_ref *, int16_t *); #endif From db60ff3911d6179a030ce9ef1196be4d3c3820fa Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 28 Jan 2018 07:55:42 +0530 Subject: [PATCH 0778/1012] update xmls --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index f1566f504..83d4183a4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f1566f504a089c95e65a9828f550050dcf1fef46 +Subproject commit 83d4183a4c15fd3f37b53c7f911f4bfb75f21ee2 From b78b2feed26b1bf2b48b32ef7debc3dc4a11c0af Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 28 Jan 2018 10:12:50 +0530 Subject: [PATCH 0779/1012] Remove extra whitespace. --- library/xml | 2 +- plugins/remotefortressreader/remotefortressreader.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/xml b/library/xml index 83d4183a4..421c5ef8a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 83d4183a4c15fd3f37b53c7f911f4bfb75f21ee2 +Subproject commit 421c5ef8aedf0644d099ff1b779ac6fdf0624478 diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 22b836bb7..c64238a14 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -273,7 +273,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector set_pos_y(plant->pos.y); out_plant->set_pos_z(plant->pos.z); } -} + } #endif return CR_OK; } @@ -1890,7 +1890,7 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, } else out->add_water_elevation(99); -} + } DFCoord pos = GetMapCenter(); out->set_center_x(pos.x); out->set_center_y(pos.y); From 77c6ea1d33f4e5079c8abe862c6b310fcabd1886 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 28 Jan 2018 10:22:16 +0530 Subject: [PATCH 0780/1012] update xmls. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 421c5ef8a..5bfce4cd2 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 421c5ef8aedf0644d099ff1b779ac6fdf0624478 +Subproject commit 5bfce4cd2175e084b8bb006516f9b0d339feb332 From 9a1f970d2d3d2be6e745218fa9a61a257aef1538 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 28 Jan 2018 02:14:54 -0500 Subject: [PATCH 0781/1012] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index f0c609211..ef5b1c8f0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f0c609211fbfd354b576cae75e9e64f8c6c80b06 +Subproject commit ef5b1c8f037a6240acdd3957fb7b6b9de529c5e2 diff --git a/scripts b/scripts index cbdd03aae..b330e696e 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit cbdd03aae69ff6e96644e8169487eaa6b4019d52 +Subproject commit b330e696e6cb6a607d8cc77679bbe0fa5e51598c From 788a48144b3b88fb2cfb3c53f5ea9788c11c2bf2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 28 Jan 2018 02:16:57 -0500 Subject: [PATCH 0782/1012] dig: fix issues with priority parameters and digvx/diglx --- plugins/dig.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/dig.cpp b/plugins/dig.cpp index a56688a7b..148da5482 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -236,8 +236,7 @@ int32_t parse_priority(color_ostream &out, vector ¶meters) else if (it + 1 != parameters.end()) { auto priority = int32_t(1000 * atof((*(it + 1)).c_str())); - parameters.erase(it); - parameters.erase(it + 1); + parameters.erase(it, it + 2); return priority; } else @@ -251,6 +250,11 @@ int32_t parse_priority(color_ostream &out, vector ¶meters) return default_priority; } +string forward_priority(color_ostream &out, vector ¶meters) +{ + return string("-p") + int_to_string(parse_priority(out, parameters) / 1000); +} + command_result digcircle (color_ostream &out, vector & parameters) { static bool filled = false; @@ -339,6 +343,7 @@ command_result digcircle (color_ostream &out, vector & parameters) " chan = dig channel\n" "\n" " # = diameter in tiles (default = 0)\n" + " -p # = designation priority (default = 4)\n" "\n" "After you have set the options, the command called with no options\n" "repeats with the last selected parameters:\n" @@ -1032,7 +1037,7 @@ command_result digvx (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); - lol.push_back(string("-p") + int_to_string(parse_priority(out, parameters))); + lol.push_back(forward_priority(out, parameters)); return digv(out,lol); } @@ -1204,7 +1209,7 @@ command_result diglx (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); - lol.push_back(string("-p") + int_to_string(parse_priority(out, parameters))); + lol.push_back(forward_priority(out, parameters)); return digl(out,lol); } From 8d8632908efb07a7525a0aafaf81c9db3e351208 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 28 Jan 2018 13:45:21 +0530 Subject: [PATCH 0783/1012] update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 5bfce4cd2..ef5b1c8f0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5bfce4cd2175e084b8bb006516f9b0d339feb332 +Subproject commit ef5b1c8f037a6240acdd3957fb7b6b9de529c5e2 diff --git a/scripts b/scripts index fd63a1658..b330e696e 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit fd63a1658f3dd7d57d81014e333d0e16e358539d +Subproject commit b330e696e6cb6a607d8cc77679bbe0fa5e51598c From 2deeda11d23a1844fc65cd195028045e4591baae Mon Sep 17 00:00:00 2001 From: Dan Amlund Date: Sun, 28 Jan 2018 13:04:52 +0100 Subject: [PATCH 0784/1012] add many new cases for Gui::getSelectedUnit: report list, combat log list, military screen, unit health, unit custumize, assigning to cage, viewing cage, pitting, penning, burrows, look at corpse, look at corpse piece, look at named spatter --- library/modules/Gui.cpp | 121 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index d6eedcef7..380a76dee 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -49,16 +49,21 @@ using namespace DFHack; #include "df/announcement_flags.h" #include "df/assign_trade_status.h" +#include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/building_furnacest.h" #include "df/building_trapst.h" +#include "df/building_type.h" #include "df/building_workshopst.h" #include "df/d_init.h" #include "df/game_mode.h" #include "df/general_ref.h" #include "df/global_objects.h" #include "df/graphic.h" +#include "df/historical_figure.h" #include "df/interfacest.h" +#include "df/item_corpsepiecest.h" +#include "df/item_corpsest.h" #include "df/job.h" #include "df/layer_object_listst.h" #include "df/occupation.h" @@ -74,8 +79,10 @@ using namespace DFHack; #include "df/ui_unit_view_mode.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/viewscreen_announcelistst.h" #include "df/viewscreen_assign_display_itemst.h" #include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_customize_unitst.h" #include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_dungeonmodest.h" #include "df/viewscreen_dwarfmodest.h" @@ -89,6 +96,7 @@ using namespace DFHack; #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_unit_healthst.h" #include "df/viewscreen_layer_unit_relationshipst.h" #include "df/viewscreen_locationsst.h" #include "df/viewscreen_petst.h" @@ -97,6 +105,7 @@ using namespace DFHack; #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitst.h" +#include "df/viewscreen_reportlistst.h" #include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" @@ -818,6 +827,9 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) using df::global::ui_look_cursor; using df::global::ui_look_list; using df::global::ui_selected_unit; + using df::global::ui_building_in_assign; + using df::global::ui_building_assign_units; + using df::global::ui_building_item_cursor; if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top)) { @@ -934,12 +946,63 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_reportlistst, top)) + return vector_get(screen->units, screen->cursor); + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_announcelistst, top)) + { + auto *report = vector_get(screen->reports, screen->sel_idx); + for (df::unit *unit : world->units.all) + { + if (unit == screen->unit) + continue; + for (int32_t report_id : unit->reports.log[screen->report_type]) + { + if (report_id == report->id) + return unit; + } + } + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_militaryst, top)) + { + if (screen->page == df::viewscreen_layer_militaryst::T_page::Positions) { + auto positions = getLayerList(screen, 1); + if (positions->enabled && positions->active) + return vector_get(screen->positions.assigned, positions->cursor); + + auto candidates = getLayerList(screen, 2); + if (candidates->enabled && candidates->active) + return vector_get(screen->positions.candidates, candidates->cursor); + } + if (screen->page == df::viewscreen_layer_militaryst::T_page::Equip) { + auto positions = getLayerList(screen, 1); + if (positions->enabled && positions->active) + return vector_get(screen->equip.units, positions->cursor); + } + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_unit_healthst, top)) + return screen->unit; + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_customize_unitst, top)) + return screen->unit; + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedUnit(); if (!Gui::dwarfmode_hotkey(top)) return NULL; + // general assigning units in building, i.e. (q)uery cage -> (a)ssign + if (ui_building_in_assign && *ui_building_in_assign + && ui_building_assign_units && ui_building_item_cursor + && ui->main.mode != Zones) // dont show for (i) zone + return vector_get(*ui_building_assign_units, *ui_building_item_cursor); + + if (ui->follow_unit != -1) + return df::unit::find(ui->follow_unit); + switch (ui->main.mode) { case ViewUnits: { @@ -948,16 +1011,66 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) return vector_get(world->units.active, *ui_selected_unit); } + case ZonesPitInfo: // (i) zone -> (P)it + case ZonesPenInfo: // (i) zone -> pe(N) + { + if (!ui_building_assign_units || !ui_building_item_cursor) + return NULL; + + return vector_get(*ui_building_assign_units, *ui_building_item_cursor); + } + case Burrows: + { + if (ui->burrows.in_add_units_mode) + return vector_get(ui->burrows.list_units, ui->burrows.unit_cursor_pos); + + return NULL; + } + case QueryBuilding: + { + df::building *building = getAnyBuilding(top); + if (building->getType() == df::building_type::Cage) + { + auto *cage = static_cast(building); + if (*ui_building_item_cursor < cage->assigned_units.size()) + return df::unit::find(cage->assigned_units[*ui_building_item_cursor]); + } + return NULL; + } case LookAround: { if (!ui_look_list || !ui_look_cursor) return NULL; auto item = vector_get(ui_look_list->items, *ui_look_cursor); - if (item && item->type == df::ui_look_list::T_items::Unit) - return item->unit; - else - return NULL; + if (item) { + if (item->type == df::ui_look_list::T_items::Unit) + return item->unit; + else if (item->type == df::ui_look_list::T_items::Item) + { + if (item->item->getType() == df::item_type::CORPSE) + { + // loo(k) at corpse + auto *corpse = static_cast(item->item); + return df::unit::find(corpse->unit_id); + } + else if (item->item->getType() == df::item_type::CORPSEPIECE) + { + // loo(k) at corpse piece + auto *corpsepiece = static_cast(item->item); + return df::unit::find(corpsepiece->unit_id); + } + } + else if (item->type == df::ui_look_list::T_items::Spatter) + { + // loo(k) at blood/ichor/.. spatter with a name + MaterialInfo mat; + if (mat.decode(item->spatter_mat_type, item->spatter_mat_index)) + return df::unit::find(mat.figure->unit_id); + } + } + + return NULL; } default: return NULL; From e63a871363971d90852e5aaad216e27244a96934 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 31 Jan 2018 10:11:43 +0530 Subject: [PATCH 0785/1012] use the vmethod for getting improvement images. --- library/xml | 2 +- plugins/remotefortressreader/item_reader.cpp | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/library/xml b/library/xml index ef5b1c8f0..2b7ef6fde 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ef5b1c8f037a6240acdd3957fb7b6b9de529c5e2 +Subproject commit 2b7ef6fdeb87cc63eb56d57d5b6b220176840404 diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 2acda8aef..933abc501 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -106,13 +106,6 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) { - GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); - if (getImage) - { - int16_t subid = -1; - CopyImage(getImage(world, &imageRef, &subid), netImage); - } - else { for (int i = 0; i < world->art_image_chunks.size(); i++) { @@ -402,7 +395,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) case df::enums::improvement_type::ART_IMAGE: { VIRTUAL_CAST_VAR(artImage, df::itemimprovement_art_imagest, improvement); - CopyImage(artImage->image, netImp->mutable_image()); + CopyImage(artImage->getImage(DfItem), netImp->mutable_image()); break; } case df::enums::improvement_type::COVERED: From 325e0b0e33b0a6b9ca830c9d43777d0f5564d26f Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 31 Jan 2018 09:57:11 -0500 Subject: [PATCH 0786/1012] binpatch.lua: check for empty patches --- library/lua/binpatch.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/lua/binpatch.lua b/library/lua/binpatch.lua index d8e95b29f..0cae97e5b 100644 --- a/library/lua/binpatch.lua +++ b/library/lua/binpatch.lua @@ -17,6 +17,7 @@ local function load_patch(name) local old_bytes = {} local new_bytes = {} + local has_bytes = false for line in file:lines() do if string.match(line, '^%x+:') then @@ -34,10 +35,14 @@ local function load_patch(name) old_bytes[offset] = oldv new_bytes[offset] = newv + has_bytes = true end end file:close() + if not has_bytes then + return nil, 'no patch bytes found' + end return { name = name, old_bytes = old_bytes, new_bytes = new_bytes } end From 537e94d75a818b7ac704c297be41d364fee81b74 Mon Sep 17 00:00:00 2001 From: Dan Amlund Date: Sun, 28 Jan 2018 13:37:27 +0100 Subject: [PATCH 0787/1012] fix segfault when splatter did not have historical figure --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 380a76dee..e0dd46a32 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1065,7 +1065,7 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) { // loo(k) at blood/ichor/.. spatter with a name MaterialInfo mat; - if (mat.decode(item->spatter_mat_type, item->spatter_mat_index)) + if (mat.decode(item->spatter_mat_type, item->spatter_mat_index) && mat.figure) return df::unit::find(mat.figure->unit_id); } } From 38491b4be871a75692f557116d7143dc2d49712a Mon Sep 17 00:00:00 2001 From: Dan Amlund Date: Wed, 31 Jan 2018 20:01:49 +0100 Subject: [PATCH 0788/1012] add checks to avoid potential segfaults. use more dfhack idiomatic code --- library/modules/Gui.cpp | 79 ++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index e0dd46a32..e3cebd45e 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -951,16 +951,27 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_announcelistst, top)) { - auto *report = vector_get(screen->reports, screen->sel_idx); - for (df::unit *unit : world->units.all) - { - if (unit == screen->unit) - continue; - for (int32_t report_id : unit->reports.log[screen->report_type]) + if (screen->unit) { + // in (r)eports -> enter + auto *report = vector_get(screen->reports, screen->sel_idx); + if (report) { - if (report_id == report->id) - return unit; + for (df::unit *unit : world->units.all) + { + if (unit && screen->report_type >= 0 && screen->report_type < 3 + && unit != screen->unit) // find 'other' unit related to this report + { + for (int32_t report_id : unit->reports.log[screen->report_type]) + { + if (report_id == report->id) + return unit; + } + } + } } + } else { + // in (a)nnouncements + return NULL; // cannot determine unit from reports } } @@ -968,16 +979,16 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) { if (screen->page == df::viewscreen_layer_militaryst::T_page::Positions) { auto positions = getLayerList(screen, 1); - if (positions->enabled && positions->active) + if (positions && positions->enabled && positions->active) return vector_get(screen->positions.assigned, positions->cursor); auto candidates = getLayerList(screen, 2); - if (candidates->enabled && candidates->active) + if (candidates && candidates->enabled && candidates->active) return vector_get(screen->positions.candidates, candidates->cursor); } if (screen->page == df::viewscreen_layer_militaryst::T_page::Equip) { auto positions = getLayerList(screen, 1); - if (positions->enabled && positions->active) + if (positions && positions->enabled && positions->active) return vector_get(screen->equip.units, positions->cursor); } } @@ -994,10 +1005,13 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (!Gui::dwarfmode_hotkey(top)) return NULL; + if (!ui) + return NULL; + // general assigning units in building, i.e. (q)uery cage -> (a)ssign if (ui_building_in_assign && *ui_building_in_assign - && ui_building_assign_units && ui_building_item_cursor - && ui->main.mode != Zones) // dont show for (i) zone + && ui_building_assign_units && ui_building_item_cursor + && ui->main.mode != Zones) // dont show for (i) zone return vector_get(*ui_building_assign_units, *ui_building_item_cursor); if (ui->follow_unit != -1) @@ -1006,7 +1020,7 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) switch (ui->main.mode) { case ViewUnits: { - if (!ui_selected_unit) + if (!ui_selected_unit || !ui_selected_unit) return NULL; return vector_get(world->units.active, *ui_selected_unit); @@ -1014,10 +1028,10 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) case ZonesPitInfo: // (i) zone -> (P)it case ZonesPenInfo: // (i) zone -> pe(N) { - if (!ui_building_assign_units || !ui_building_item_cursor) - return NULL; + if (ui_building_assign_units || ui_building_item_cursor) + return vector_get(*ui_building_assign_units, *ui_building_item_cursor); - return vector_get(*ui_building_assign_units, *ui_building_item_cursor); + return NULL; } case Burrows: { @@ -1028,12 +1042,13 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) } case QueryBuilding: { - df::building *building = getAnyBuilding(top); - if (building->getType() == df::building_type::Cage) + if (df::building *building = getAnyBuilding(top)) { - auto *cage = static_cast(building); - if (*ui_building_item_cursor < cage->assigned_units.size()) - return df::unit::find(cage->assigned_units[*ui_building_item_cursor]); + if (VIRTUAL_CAST_VAR(cage, df::building_cagest, building)) + { + if (ui_building_item_cursor) + return df::unit::find(vector_get(cage->assigned_units, *ui_building_item_cursor)); + } } return NULL; } @@ -1042,24 +1057,16 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (!ui_look_list || !ui_look_cursor) return NULL; - auto item = vector_get(ui_look_list->items, *ui_look_cursor); - if (item) { + if (auto item = vector_get(ui_look_list->items, *ui_look_cursor)) + { if (item->type == df::ui_look_list::T_items::Unit) return item->unit; else if (item->type == df::ui_look_list::T_items::Item) { - if (item->item->getType() == df::item_type::CORPSE) - { - // loo(k) at corpse - auto *corpse = static_cast(item->item); - return df::unit::find(corpse->unit_id); - } - else if (item->item->getType() == df::item_type::CORPSEPIECE) - { - // loo(k) at corpse piece - auto *corpsepiece = static_cast(item->item); - return df::unit::find(corpsepiece->unit_id); - } + if (VIRTUAL_CAST_VAR(corpse, df::item_corpsest, item->item)) + return df::unit::find(corpse->unit_id); // loo(k) at corpse + else if (VIRTUAL_CAST_VAR(corpsepiece, df::item_corpsepiecest, item->item)) + return df::unit::find(corpsepiece->unit_id); // loo(k) at corpse piece } else if (item->type == df::ui_look_list::T_items::Spatter) { From 8665600574d959bcb3c4c7a738b8909cfbf47194 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 3 Feb 2018 10:38:17 +0530 Subject: [PATCH 0789/1012] Send tiles for shape descriptors, and art image ids. --- plugins/proto/RemoteFortressReader.proto | 2 ++ plugins/remotefortressreader/item_reader.cpp | 3 +++ plugins/remotefortressreader/remotefortressreader.cpp | 3 +-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 0cdb41110..73f75792f 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -882,6 +882,7 @@ message Status message ShapeDescriptior { optional string id = 1; + optional int32 tile = 2; } message Language @@ -936,4 +937,5 @@ message ArtImageElement message ArtImage { repeated ArtImageElement elements = 1; + optional MatPair id = 2; } \ No newline at end of file diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 933abc501..2eef1ad67 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -49,6 +49,9 @@ using namespace df::global; void CopyImage(const df::art_image * image, ArtImage * netImage) { + auto id = netImage->mutable_id(); + id->set_mat_type(image->id); + id->set_mat_index(image->subid); for (int i = 0; i < image->elements.size(); i++) { auto element = image->elements[i]; diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index c64238a14..427fc4334 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -53,8 +53,6 @@ #include "df/creature_raw.h" #include "df/creature_raw.h" #include "df/descriptor_color.h" -#include "df/descriptor_color.h" -#include "df/descriptor_pattern.h" #include "df/descriptor_pattern.h" #include "df/descriptor_shape.h" #include "df/dfhack_material_category.h" @@ -2919,6 +2917,7 @@ static command_result GetLanguage(color_ostream & stream, const EmptyMessage * i auto shape = world->raws.language.shapes[i]; auto netShape = out->add_shapes(); netShape->set_id(shape->id); + netShape->set_tile(shape->tile); } return CR_OK; } \ No newline at end of file From 0f43c07334d66406d27f0082b2cfcc90d99a1ad2 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 3 Feb 2018 11:10:26 +0530 Subject: [PATCH 0790/1012] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 2b7ef6fde..b0203a1d9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2b7ef6fdeb87cc63eb56d57d5b6b220176840404 +Subproject commit b0203a1d982212b1a82ea159d07a2dc6df31c191 From 636448f431a1298dfb00822a12812de929bc0c7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 03:28:32 -0500 Subject: [PATCH 0791/1012] Update changelog --- NEWS.rst | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index a14bafa9f..917af3fe7 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -44,26 +44,50 @@ New Scripts - `devel/check-other-ids`: Checks the validity of "other" vectors in the ``world`` global - `gui/cp437-table`: An in-game CP437 table +- `view-unit-reports`: opens the reports screen with combat reports for the selected unit Fixes ----- - Fixed issues with the console output color affecting the prompt on Windows - `createitem`: stopped items from teleporting away in some forts - `devel/inject-raws`: now recognizes spaces in reaction names +- `dig`: added support for designation priorities - fixes issues with + designations from ``digv`` and related commands having extremely high priority +- `dwarfmonitor`: + + - fixed display of creatures and poetic/music/dance forms on ``prefs`` screen + - added "view unit" option + - now exposes the selected unit to other tools + - `gui/gm-unit`: can now edit mining skill - `gui/quickcmd`: stopped error from adding too many commands +- `quicksave`: fixed an issue where the "Saving..." indicator often wouldn't appear Misc Improvements ----------------- - The console now provides suggestions for built-in commands +- `binpatch`: now reports errors for empty patch files - `devel/export-dt-ini`: avoid hardcoding flags - `exportlegends`: - reordered some tags to match DF's order - added progress indicators for exporting long lists +- `force`: now provides useful help +- `full-heal`: + + - can now select corpses to resurrect + - now resets body part temperatures upon resurrection to prevent creatures + from freezing/melting again + - now resets units' vanish countdown to reverse effects of `exterminate` + - `gui/gm-editor`: added enum names to enum edit dialogs -- `gui/gm-unit`: made skill search case-insensitive +- `gui/gm-unit`: + + - made skill search case-insensitive + - added a profession editor + - misc. layout improvements + - `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards - `gui/pathable`: added tile types to sidebar - `gui/rename`: added "clear" and "special characters" options @@ -76,7 +100,22 @@ Misc Improvements Removed ------- -- `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01 +- `warn-stuck-trees`: :bug:`9252` fixed in DF 0.44.01 +- `tweak`: ``kitchen-keys``: :bug:`614` fixed in DF 0.44.04 + +Internals +--------- +- ``Gui::getAnyUnit()`` supports many more screens/menus +- New globals available: + + - ``version`` + - ``min_load_version`` + - ``movie_version`` + - ``basic_seed`` + - ``title`` + - ``title_spaced`` + - ``ui_building_resize_radius`` + - ``soul_next_id`` Lua --- From 6181b2bce343204f7594f5c559d9b7ee09525abe Mon Sep 17 00:00:00 2001 From: Dan Amlund Date: Sat, 3 Feb 2018 16:29:30 +0100 Subject: [PATCH 0792/1012] Add tweak that replaces dwarf mode FPS counter with one that does not count when paused --- dfhack.init-example | 3 + plugins/tweak/tweak.cpp | 7 ++ plugins/tweak/tweaks/pausing-fps-counter.h | 137 +++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 plugins/tweak/tweaks/pausing-fps-counter.h diff --git a/dfhack.init-example b/dfhack.init-example index 82210a2a6..fb846a47f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -202,6 +202,9 @@ tweak hotkey-clear # Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving tweak embark-profile-name +# dwarf mode FPS counter that does not jump to FPS_CAP when paused +tweak pausing-fps-counter + # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled tweak burrow-name-cancel diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 252f4c738..6b5b1f687 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -98,6 +98,7 @@ #include "tweaks/kitchen-prefs-empty.h" #include "tweaks/max-wheelbarrow.h" #include "tweaks/military-assign.h" +#include "tweaks/pausing-fps-counter.h" #include "tweaks/nestbox-color.h" #include "tweaks/shift-8-scroll.h" #include "tweaks/stable-cursor.h" @@ -232,6 +233,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector display_frames == 1); + } + return init_have_fps_yes; + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (!df::global::pause_state || !df::global::enabler || !df::global::world + || !df::global::gps || !df::global::pause_state) + return; + + // if init.txt does not have [FPS:YES] then dont show this FPS counter + if (!dwarfmode_pausing_fps_counter_hook::init_have_fps_yes()) + return; + + static bool prev_paused = true; + static uint32_t prev_clock = 0; + static int32_t prev_frames = 0; + static uint32_t elapsed_clock = 0; + static uint32_t elapsed_frames = 0; + static double history[history_length]; + + if (prev_clock == 0) + { + // init + for (int i = 0; i < history_length; i++) + history[i] = 0.0; + } + + // disable default FPS counter because it is rendered on top of this FPS counter. + if (df::global::gps->display_frames == 1) + df::global::gps->display_frames = 0; + + if (*df::global::pause_state) + prev_paused = true; + else + { + uint32_t clock = df::global::enabler->clock; + int32_t frames = df::global::world->frame_counter; + + if (!prev_paused && prev_clock != 0 + && clock >= prev_clock && frames >= prev_frames) + { + // if we were previously paused, then dont add clock/frames, + // but wait for the next time render is called. + elapsed_clock += clock - prev_clock; + elapsed_frames += frames - prev_frames; + } + + prev_paused = false; + prev_clock = clock; + prev_frames = frames; + + // add FPS to history every second or after at least one frame. + if (elapsed_clock >= 1000 && elapsed_frames >= 1) + { + double fps = elapsed_frames / (elapsed_clock / 1000.0); + for (int i = history_length - 1; i >= 1; i--) + history[i] = history[i - 1]; + history[0] = fps; + + elapsed_clock = 0; + elapsed_frames = 0; + } + } + + // average fps over a few seconds to stabilize the counter. + double fps_sum = 0.0; + int fps_count = 0; + for (int i = 0; i < history_length; i++) + { + if (history[i] > 0.0) + { + fps_sum += history[i]; + fps_count++; + } + } + + double fps = fps_count == 0 ? 1.0 : fps_sum / fps_count; + double gfps = df::global::enabler->calculated_gfps; + + std::stringstream fps_counter; + fps_counter << "FPS:" + << setw(4) << fixed << setprecision(fps >= 1.0 ? 0 : 2) << fps + << " (" << gfps << ")"; + + // show this FPS counter same as the default counter. + int x = 10; + int y = 0; + OutputString(COLOR_WHITE, x, y, fps_counter.str(), + false, 0, COLOR_CYAN, false); + } +}; + +struct title_pausing_fps_counter_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + // if init.txt have FPS:YES then enable default FPS counter when exiting dwarf mode. + // So it is enabled if starting adventure mode without exiting dwarf fortress. + if (dwarfmode_pausing_fps_counter_hook::init_have_fps_yes() + && df::global::gps && df::global::gps->display_frames == 0) + df::global::gps->display_frames = 1; + } +}; + + +IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_pausing_fps_counter_hook, render); + +IMPLEMENT_VMETHOD_INTERPOSE(title_pausing_fps_counter_hook, render); From 426839aac290ef0a8a78648ee5ba48519c1e8f87 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 17:58:59 -0500 Subject: [PATCH 0793/1012] Update scripts, xml, NEWS for dfhack/scripts#37 --- NEWS.rst | 10 ++++++++++ library/xml | 2 +- scripts | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 917af3fe7..f704508bd 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -41,9 +41,16 @@ DFHack future New Scripts ----------- +- `break-dance`: Breaks up a stuck dance activity +- `cannibalism`: Allows consumption of sapient corpses - `devel/check-other-ids`: Checks the validity of "other" vectors in the ``world`` global +- `fillneeds`: Use with a unit selected to make them focused and unstressed +- `firestarter`: Lights things on fire: items, locations, entire inventories even! +- `flashstep`: Teleports adventurer to cursor +- `ghostly`: Turns an adventurer into a ghost or back - `gui/cp437-table`: An in-game CP437 table +- `questport`: Sends your adventurer to the location of your quest log cursor - `view-unit-reports`: opens the reports screen with combat reports for the selected unit Fixes @@ -61,6 +68,7 @@ Fixes - `gui/gm-unit`: can now edit mining skill - `gui/quickcmd`: stopped error from adding too many commands +- `names`: fixed many errors - `quicksave`: fixed an issue where the "Saving..." indicator often wouldn't appear Misc Improvements @@ -91,11 +99,13 @@ Misc Improvements - `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards - `gui/pathable`: added tile types to sidebar - `gui/rename`: added "clear" and "special characters" options +- `launch`: can now ride creatures - `modtools/skill-change`: - now updates skill levels appropriately - only prints output if ``-loud`` is passed +- `names`: can now edit names of units - `remotefortressreader`: includes item stack sizes and some performance improvements Removed diff --git a/library/xml b/library/xml index ef5b1c8f0..718788141 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ef5b1c8f037a6240acdd3957fb7b6b9de529c5e2 +Subproject commit 7187881412f8ee7518527641e0df52a3ed4a52e3 diff --git a/scripts b/scripts index b330e696e..8d079a591 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b330e696e6cb6a607d8cc77679bbe0fa5e51598c +Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7 From e88ba60ba5884261a85073b0413c24a2141e892d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 18:08:15 -0500 Subject: [PATCH 0794/1012] Document "dig" priority arguments --- docs/Plugins.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 04f5df321..c896b5fd0 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1930,6 +1930,13 @@ Basic commands: :dfhack-keybind:`digv` +.. note:: + + All commands implemented by the `dig` plugin (listed by ``ls dig``) support + specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, + where ``#`` is a number from 1 to 7. If a priority is not specified, the + priority selected in-game is used as the default. + .. _digexp: digexp From bdba95f90c1e99e98dee6bcd37031ca2631a53d7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 18:10:16 -0500 Subject: [PATCH 0795/1012] memview: check for tags from sizecheck --- plugins/devel/memview.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index b93319a07..d84a3b578 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -53,7 +53,14 @@ size_t convert(const std::string& p,bool ishex=false) conv>>ret; return ret; } -bool isAddr(uintptr_t *trg,vector & ranges) +bool isAddr(void *trg, vector &ranges) +{ + for (auto &r : ranges) + if (r.isInRange(trg)) + return true; + return false; +} +bool isAddrAt(uintptr_t *trg, vector &ranges) { if(trg[0]%4==0) for(size_t i=0;i & parameters) is_enabled = true; memdata.state=STATE_ON; } - if(parameters.size()>1) + if (vector_get(parameters, 1, string("a")).substr(0, 1) == "a") + memdata.len = detect_size(memdata.addr); + else if (parameters.size()>1) memdata.len=convert(parameters[1]); else memdata.len=20*16; From d71e252b5038f584f38d92c453b6e8ff71cac311 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 22:17:28 -0500 Subject: [PATCH 0796/1012] Bump to r1 --- CMakeLists.txt | 4 ++-- NEWS.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e92fc967..c7eb1f234 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,8 +141,8 @@ endif() # set up versioning. set(DF_VERSION "0.44.05") -set(DFHACK_RELEASE "alpha1") -set(DFHACK_PRERELEASE TRUE) +set(DFHACK_RELEASE "r1") +set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/NEWS.rst b/NEWS.rst index f704508bd..ce8635675 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -36,8 +36,8 @@ Changelog .. contents:: :depth: 2 -DFHack future -============= +DFHack 0.44.05-r1 +================= New Scripts ----------- From 39b488a103838c5ad43c69cb0f18160f27f61ed3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 23:01:15 -0500 Subject: [PATCH 0797/1012] Download/install DF --- .travis.yml | 18 +++++++++++------- travis/download-df.sh | 28 ++++++++++++++++++++++++++++ travis/get-df-version.sh | 4 ++++ 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100755 travis/download-df.sh create mode 100755 travis/get-df-version.sh diff --git a/.travis.yml b/.travis.yml index ad17693a9..ab18066f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: cpp cache: pip: true directories: + - $HOME/DF-travis - $HOME/lua53 addons: apt: @@ -22,8 +23,11 @@ matrix: - gcc-4.8-multilib - g++-4.8-multilib before_install: +- export DF_VERSION=$(sh travis/get-df-version.sh) +- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION" - pip install --user "sphinx==1.4" "requests[security]" - sh travis/build-lua.sh +- sh travis/download-df.sh script: - export PATH="$PATH:$HOME/lua53/bin" - git tag tmp-travis-build @@ -37,12 +41,12 @@ script: - python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis -- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON -- make -j3 +- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" +- make -j3 install notifications: email: false - irc: - channels: - - "chat.freenode.net#dfhack" - on_success: change - on_failure: always + # irc: + # channels: + # - "chat.freenode.net#dfhack" + # on_success: change + # on_failure: always diff --git a/travis/download-df.sh b/travis/download-df.sh new file mode 100755 index 000000000..56a2a539d --- /dev/null +++ b/travis/download-df.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +tardest="df.tar.bz2" + +cd "$(dirname "$0")" +echo "DF_VERSION: $DF_VERSION" +echo "DF_FOLDER: $DF_FOLDER" +mkdir -p "$DF_FOLDER" +cd "$DF_FOLDER" + +if [ -f receipt ]; then + echo "Already downloaded $DF_VERSION" + exit 0 +fi + +rm -rif "$tardest" df_linux + +minor=$(echo "$DF_VERSION" | cut -d. -f2) +patch=$(echo "$DF_VERSION" | cut -d. -f3) +url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" + +echo Downloading +wget "$url" -O "$tardest" +echo Extracting +tar xf "$tardest" --strip-components=1 +echo Done + +touch receipt diff --git a/travis/get-df-version.sh b/travis/get-df-version.sh new file mode 100755 index 000000000..13d317d2e --- /dev/null +++ b/travis/get-df-version.sh @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +cd .. +grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' From b54a35ce5dc36c2b375b99555072b2b7322a450f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 23:26:43 -0500 Subject: [PATCH 0798/1012] Build 64-bit DFHack, install SDL --- .travis.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab18066f3..8499aa761 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,12 @@ cache: addons: apt: packages: &default_packages + - libsdl-image1.2-dev + - libsdl-ttf2.0-dev + - libsdl1.2-dev - libxml-libxml-perl - libxml-libxslt-perl - - zlib1g-dev:i386 + - zlib1g-dev matrix: include: - env: GCC_VERSION=4.8 @@ -20,8 +23,8 @@ matrix: - ubuntu-toolchain-r-test packages: - *default_packages - - gcc-4.8-multilib - - g++-4.8-multilib + - gcc-4.8 + - g++-4.8 before_install: - export DF_VERSION=$(sh travis/get-df-version.sh) - export DF_FOLDER="$HOME/DF-travis/$DF_VERSION" @@ -41,8 +44,9 @@ script: - python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis -- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" +- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" - make -j3 install +- "$DF_FOLDER/dfhack +devel/check-release +die" notifications: email: false # irc: From 38140fb450c374ee0ff111e965773f37379bd993 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 4 Feb 2018 10:12:15 +0530 Subject: [PATCH 0799/1012] Copy engravings in RFR, and update the art image function. --- library/xml | 2 +- plugins/proto/RemoteFortressReader.proto | 21 ++++- plugins/remotefortressreader/item_reader.cpp | 36 ++++--- plugins/remotefortressreader/item_reader.h | 9 +- .../remotefortressreader.cpp | 93 ++++++++++++++++--- 5 files changed, 123 insertions(+), 38 deletions(-) diff --git a/library/xml b/library/xml index b0203a1d9..6652fd55e 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b0203a1d982212b1a82ea159d07a2dc6df31c191 +Subproject commit 6652fd55e5e489c8a5d12467d1ea85c83192ba17 diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 73f75792f..96dc37a8d 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -434,6 +434,7 @@ message BlockList repeated MapBlock map_blocks = 1; optional int32 map_x = 2; optional int32 map_y = 3; + repeated Engraving engravings = 4; } message PlantDef @@ -938,4 +939,22 @@ message ArtImage { repeated ArtImageElement elements = 1; optional MatPair id = 2; -} \ No newline at end of file +} + +message Engraving +{ + optional Coord pos = 1; + optional int32 quality = 2; + optional int32 tile = 3; + optional ArtImage image = 4; + optional bool floor = 5; + optional bool west = 6; + optional bool east = 7; + optional bool north = 8; + optional bool south = 9; + optional bool hidden = 10; + optional bool northwest = 11; + optional bool northeast = 12; + optional bool southwest = 13; + optional bool southeast = 14; + } \ No newline at end of file diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 2eef1ad67..e92420373 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -107,20 +107,6 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) } } -void CopyImage(df::art_image_ref imageRef, ArtImage * netImage) -{ - { - for (int i = 0; i < world->art_image_chunks.size(); i++) - { - auto chunk = world->art_image_chunks[i]; - if (chunk->id != imageRef.id) - continue; - auto image = chunk->images[imageRef.subid]; - CopyImage(image, netImage); - } - } -} - void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) { NetItem->set_id(DfItem->id); @@ -196,7 +182,27 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) case df::enums::item_type::STATUE: { VIRTUAL_CAST_VAR(statue, df::item_statuest, DfItem); - CopyImage(statue->image, NetItem->mutable_image()); + + df::art_image_chunk * chunk = NULL; + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + if (GetArtImageChunk) + { + chunk = GetArtImageChunk(&(world->art_image_chunks), statue->image.id); + } + else + { + for (int i = 0; i < world->art_image_chunks.size(); i++) + { + if (world->art_image_chunks[i]->id == statue->image.id) + chunk = world->art_image_chunks[i]; + } + } + if (chunk) + { + CopyImage(chunk->images[statue->image.subid], NetItem->mutable_image()); + } + + break; } case df::enums::item_type::CORPSE: diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h index 59e097ca0..045c1be51 100644 --- a/plugins/remotefortressreader/item_reader.h +++ b/plugins/remotefortressreader/item_reader.h @@ -12,7 +12,7 @@ namespace df struct item; struct map_block; struct art_image; - struct art_image_ref; + struct art_image_chunk; struct world; } @@ -25,11 +25,8 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out); -#if(defined(WIN32) && !defined(_WIN64)) -typedef df::art_image * (__thiscall *GET_IMAGE)(df::world *, df::art_image_ref *, int16_t *); -#else -typedef df::art_image * (*GET_IMAGE)(df::world *, df::art_image_ref *, int16_t *); -#endif +typedef df::art_image_chunk * (*GET_ART_IMAGE_CHUNK)(std::vector *, int); +void CopyImage(const df::art_image * image, RemoteFortressReader::ArtImage * netImage); #endif // !ITEM_READER_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 427fc4334..a724e434f 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -57,6 +57,7 @@ #include "df/descriptor_shape.h" #include "df/dfhack_material_category.h" #include "df/enabler.h" +#include "df/engraving.h" #include "df/graphic.h" #include "df/historical_figure.h" @@ -182,20 +183,18 @@ const char* growth_locations[] = { #include "df/art_image.h" #include "df/art_image_chunk.h" #include "df/art_image_ref.h" -command_result generate_image(color_ostream &out, vector & parameters) +command_result loadArtImageChunk(color_ostream &out, vector & parameters) { - df::art_image_ref imageRef; - imageRef.civ_id = -1; - imageRef.id = -1; - imageRef.site_id = -1; - imageRef.subid = -1; + if (parameters.size() != 1) + return CR_WRONG_USAGE; - GET_IMAGE getImage = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); - if (getImage) + + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + if (GetArtImageChunk) { - int16_t subid = -1; - auto image = getImage(world, &imageRef, &subid); - out.print("Id: %d, subid: %d\n", image->id, image->subid); + int index = atoi(parameters[0].c_str()); + auto chunk = GetArtImageChunk(&(world->art_image_chunks), index); + out.print("Loaded chunk id: &d", chunk->id); } return CR_OK; } @@ -271,10 +270,10 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector * items) return result; } +map engravingHashes; + +bool isEngravingNew(int index) +{ + if (engravingHashes[index]) + return false; + engravingHashes[index] = true; + return true; +} + +void engravingIsNotNew(int index) +{ + engravingHashes[index] = false; +} + static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) { hashes.clear(); @@ -806,6 +820,7 @@ static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage * buildingHashes.clear(); spatterHashes.clear(); itemHashes.clear(); + engravingHashes.clear(); return CR_OK; } @@ -1485,6 +1500,54 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in } } } + + for (int i = 0; i < world->engravings.size(); i++) + { + auto engraving = world->engravings[i]; + if (engraving->pos.x < (min_x * 16) || engraving->pos.x >(max_x * 16)) + continue; + if (engraving->pos.y < (min_y * 16) || engraving->pos.x >(max_y * 16)) + continue; + if (engraving->pos.z < (min_z * 16) || engraving->pos.x >(max_z * 16)) + continue; + if (!isEngravingNew(i)) + continue; + + df::art_image_chunk * chunk = NULL; + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + if (GetArtImageChunk) + { + chunk = GetArtImageChunk(&(world->art_image_chunks), engraving->art_id); + } + else + { + for (int i = 0; i < world->art_image_chunks.size(); i++) + { + if (world->art_image_chunks[i]->id == engraving->art_id) + chunk = world->art_image_chunks[i]; + } + } + if (!chunk) + { + engravingIsNotNew(i); + continue; + } + auto netEngraving = out->add_engravings(); + ConvertDFCoord(engraving->pos, netEngraving->mutable_pos()); + netEngraving->set_quality(engraving->quality); + netEngraving->set_tile(engraving->tile); + CopyImage(chunk->images[engraving->art_subid], netEngraving->mutable_image()); + netEngraving->set_floor(engraving->flags.bits.floor); + netEngraving->set_west(engraving->flags.bits.west); + netEngraving->set_east(engraving->flags.bits.east); + netEngraving->set_north(engraving->flags.bits.north); + netEngraving->set_south(engraving->flags.bits.south); + netEngraving->set_hidden(engraving->flags.bits.hidden); + netEngraving->set_northwest(engraving->flags.bits.northwest); + netEngraving->set_northeast(engraving->flags.bits.northeast); + netEngraving->set_southwest(engraving->flags.bits.southwest); + netEngraving->set_southeast(engraving->flags.bits.southeast); + } MC.trash(); return CR_OK; } From c621a29fe340a4b5578925877091f550343ed8ff Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 4 Feb 2018 10:21:28 +0530 Subject: [PATCH 0800/1012] Fix global name. --- plugins/remotefortressreader/item_reader.cpp | 2 +- plugins/remotefortressreader/remotefortressreader.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index e92420373..3a9fed571 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -184,7 +184,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) VIRTUAL_CAST_VAR(statue, df::item_statuest, DfItem); df::art_image_chunk * chunk = NULL; - GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); if (GetArtImageChunk) { chunk = GetArtImageChunk(&(world->art_image_chunks), statue->image.id); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index a724e434f..412d7894f 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -189,7 +189,7 @@ command_result loadArtImageChunk(color_ostream &out, vector & parameter return CR_WRONG_USAGE; - GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); if (GetArtImageChunk) { int index = atoi(parameters[0].c_str()); @@ -1514,7 +1514,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in continue; df::art_image_chunk * chunk = NULL; - GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("rfr_get_art_image")); + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); if (GetArtImageChunk) { chunk = GetArtImageChunk(&(world->art_image_chunks), engraving->art_id); From ed406e0e3d3a3a630bd227a602143f34d7759e72 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 4 Feb 2018 10:24:51 +0530 Subject: [PATCH 0801/1012] fix naming and return statement. --- plugins/remotefortressreader/remotefortressreader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 412d7894f..32bd73dad 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -194,7 +194,7 @@ command_result loadArtImageChunk(color_ostream &out, vector & parameter { int index = atoi(parameters[0].c_str()); auto chunk = GetArtImageChunk(&(world->art_image_chunks), index); - out.print("Loaded chunk id: &d", chunk->id); + out.print("Loaded chunk id: %d\n", chunk->id); } return CR_OK; } @@ -260,7 +260,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sat, 3 Feb 2018 23:56:44 -0500 Subject: [PATCH 0802/1012] Set PRINT_MODE:TEXT --- travis/download-df.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/travis/download-df.sh b/travis/download-df.sh index 56a2a539d..4430f7630 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -2,15 +2,23 @@ tardest="df.tar.bz2" +which md5sum && alias md5=md5sum +selfmd5=$(openssl md5 < "$0") +echo $selfmd5 + cd "$(dirname "$0")" echo "DF_VERSION: $DF_VERSION" echo "DF_FOLDER: $DF_FOLDER" mkdir -p "$DF_FOLDER" cd "$DF_FOLDER" -if [ -f receipt ]; then - echo "Already downloaded $DF_VERSION" - exit 0 +if [[ -f receipt ]]; then + if [[ "$selfmd5" != "$(cat receipt)" ]]; then + echo "download-df.sh changed; removing DF" + else + echo "Already downloaded $DF_VERSION" + exit 0 + fi fi rm -rif "$tardest" df_linux @@ -23,6 +31,9 @@ echo Downloading wget "$url" -O "$tardest" echo Extracting tar xf "$tardest" --strip-components=1 +echo Changing settings +echo '' >> "$DF_FOLDER/data/init/init.txt" +echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" echo Done -touch receipt +echo "$selfmd5" > receipt From 638f0ab35aecf18c02c0a3ef5430b8bb3bb0cc09 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 3 Feb 2018 23:59:01 -0500 Subject: [PATCH 0803/1012] Add "headless" mode, use in travis --- .travis.yml | 1 + library/Core.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8499aa761..d2516b967 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ before_install: - pip install --user "sphinx==1.4" "requests[security]" - sh travis/build-lua.sh - sh travis/download-df.sh +- echo "export DFHACK_HEADLESS=1" >> "$HOME/.dfhackrc" script: - export PATH="$PATH:$HOME/lua53/bin" - git tag tmp-travis-build diff --git a/library/Core.cpp b/library/Core.cpp index 5173a3ad9..58b3f85f5 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -81,6 +81,10 @@ using namespace DFHack; #include "SDL_events.h" +#ifdef LINUX_BUILD +#include +#endif + using namespace tthread; using namespace df::enums; using df::global::init; @@ -1638,7 +1642,24 @@ bool Core::Init() cerr << "Initializing Console.\n"; // init the console. bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT)); - if (is_text_mode || getenv("DFHACK_DISABLE_CONSOLE")) + bool is_headless = bool(getenv("DFHACK_HEADLESS")); + if (is_headless) + { +#ifdef LINUX_BUILD + auto endwin = (int(*)(void))dlsym(RTLD_DEFAULT, "endwin"); + if (endwin) + { + endwin(); + } + else + { + cerr << "endwin(): bind failed" << endl; + } +#else + cerr << "Headless mode not supported on Windows" << endl; +#endif + } + if ((is_text_mode && !is_headless) || getenv("DFHACK_DISABLE_CONSOLE")) { con.init(true); cerr << "Console is not available. Use dfhack-run to send commands.\n"; @@ -1718,7 +1739,7 @@ bool Core::Init() HotkeyMutex = new mutex(); HotkeyCond = new condition_variable(); - if (!is_text_mode) + if (!is_text_mode || is_headless) { cerr << "Starting IO thread.\n"; // create IO thread From 956d0ce3634289a3ef4e44030c3382a0161859c6 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 4 Feb 2018 10:30:49 +0530 Subject: [PATCH 0804/1012] update submodules. --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 6652fd55e..392922a04 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 6652fd55e5e489c8a5d12467d1ea85c83192ba17 +Subproject commit 392922a045f7965a0d75d16062452f37a7bcf7be diff --git a/scripts b/scripts index b330e696e..8d079a591 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b330e696e6cb6a607d8cc77679bbe0fa5e51598c +Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7 From a5cca757154813c5d3acb7050aed148538c2859b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 00:17:46 -0500 Subject: [PATCH 0805/1012] headless: Disable sound, wgetch --- library/Hooks-linux.cpp | 4 ++++ travis/download-df.sh | 1 + 2 files changed, 5 insertions(+) diff --git a/library/Hooks-linux.cpp b/library/Hooks-linux.cpp index b0bf5a781..8291bf899 100644 --- a/library/Hooks-linux.cpp +++ b/library/Hooks-linux.cpp @@ -88,6 +88,10 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event) struct WINDOW; DFhackCExport int wgetch(WINDOW *win) { + if (getenv("DFHACK_HEADLESS")) + { + return 0; + } static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); if(!_wgetch) { diff --git a/travis/download-df.sh b/travis/download-df.sh index 4430f7630..db5009069 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -34,6 +34,7 @@ tar xf "$tardest" --strip-components=1 echo Changing settings echo '' >> "$DF_FOLDER/data/init/init.txt" echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" +echo '[SOUND:NO]' >> "$DF_FOLDER/data/init/init.txt" echo Done echo "$selfmd5" > receipt From f8a9557562574d1b0dffc080df40e8f7dcf372bd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 00:36:19 -0500 Subject: [PATCH 0806/1012] Exit with pop-screen --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2516b967..187e9bce2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ script: - cd build-travis - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" - make -j3 install -- "$DF_FOLDER/dfhack +devel/check-release +die" +- "$DF_FOLDER/dfhack +devel/check-release +devel/pop-screen" notifications: email: false # irc: From 4aa0990dc764ad5ab328a8d6da867e772486352a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 01:48:09 -0500 Subject: [PATCH 0807/1012] Fix issue with DF not quitting --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 187e9bce2..a706d8f79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,8 @@ script: - cd build-travis - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" - make -j3 install -- "$DF_FOLDER/dfhack +devel/check-release +devel/pop-screen" +- mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init +- "$DF_FOLDER/dfhack" "+devel/check-release" "+lua" "scr.breakdown_level=1" notifications: email: false # irc: From 2de28d43576707820d2e2a3e4a76c4e4f2d6b561 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 02:13:20 -0500 Subject: [PATCH 0808/1012] Fix bash compat issues --- travis/download-df.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/travis/download-df.sh b/travis/download-df.sh index db5009069..20dc3dbd4 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -12,8 +12,8 @@ echo "DF_FOLDER: $DF_FOLDER" mkdir -p "$DF_FOLDER" cd "$DF_FOLDER" -if [[ -f receipt ]]; then - if [[ "$selfmd5" != "$(cat receipt)" ]]; then +if [ -f receipt ]; then + if [ "$selfmd5" != "$(cat receipt)" ]; then echo "download-df.sh changed; removing DF" else echo "Already downloaded $DF_VERSION" From 23efb1cbf11e8debbebeb4cbad5f355538ff46c1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 02:13:23 -0500 Subject: [PATCH 0809/1012] Run startup commands in dfhack_travis.init --- .travis.yml | 3 ++- travis/dfhack_travis.init | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 travis/dfhack_travis.init diff --git a/.travis.yml b/.travis.yml index a706d8f79..04bfcae9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,8 @@ script: - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" - make -j3 install - mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init -- "$DF_FOLDER/dfhack" "+devel/check-release" "+lua" "scr.breakdown_level=1" +- cp ../travis/dfhack_travis.init "$DF_FOLDER"/ +- "$DF_FOLDER/dfhack" notifications: email: false # irc: diff --git a/travis/dfhack_travis.init b/travis/dfhack_travis.init new file mode 100644 index 000000000..5b28cefef --- /dev/null +++ b/travis/dfhack_travis.init @@ -0,0 +1,2 @@ +devel/check-release +:lua scr.breakdown_level=df.interface_breakdown_types.QUIT From 78061085c6636e2f586d7aa92b77e2742e19918a Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 4 Feb 2018 16:02:02 +0530 Subject: [PATCH 0810/1012] Add art image properties to RFR --- library/xml | 2 +- plugins/proto/RemoteFortressReader.proto | 69 +++++++++++++++++++- plugins/remotefortressreader/item_reader.cpp | 32 +++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 392922a04..40da7231a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 392922a045f7965a0d75d16062452f37a7bcf7be +Subproject commit 40da7231a0114fe3420d475961b08826f64fbf79 diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 96dc37a8d..35f5b0abe 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -935,10 +935,25 @@ message ArtImageElement optional int32 id = 6; } +enum ArtImagePropertyType +{ + TRANSITIVE_VERB = 0; + INTRANSITIVE_VERB = 1; +} + +message ArtImageProperty +{ + optional int32 subject = 1; + optional int32 object = 2; + optional ArtImageVerb verb = 3; + optional ArtImagePropertyType type = 4; +} + message ArtImage { repeated ArtImageElement elements = 1; optional MatPair id = 2; + repeated ArtImageProperty properties = 3; } message Engraving @@ -957,4 +972,56 @@ message Engraving optional bool northeast = 12; optional bool southwest = 13; optional bool southeast = 14; - } \ No newline at end of file +} + +enum ArtImageVerb +{ + VERB_WITHERING = 0; + VERB_SURROUNDEDBY = 1; + VERB_MASSACRING = 2; + VERB_FIGHTING = 3; + VERB_LABORING = 4; + VERB_GREETING = 5; + VERB_REFUSING = 6; + VERB_SPEAKING = 7; + VERB_EMBRACING = 8; + VERB_STRIKINGDOWN = 9; + VERB_MENACINGPOSE = 10; + VERB_TRAVELING = 11; + VERB_RAISING = 12; + VERB_HIDING = 13; + VERB_LOOKINGCONFUSED = 14; + VERB_LOOKINGTERRIFIED = 15; + VERB_DEVOURING = 16; + VERB_ADMIRING = 17; + VERB_BURNING = 18; + VERB_WEEPING = 19; + VERB_LOOKINGDEJECTED = 20; + VERB_CRINGING = 21; + VERB_SCREAMING = 22; + VERB_SUBMISSIVEGESTURE = 23; + VERB_FETALPOSITION = 24; + VERB_SMEAREDINTOSPIRAL = 25; + VERB_FALLING = 26; + VERB_DEAD = 27; + VERB_LAUGHING = 28; + VERB_LOOKINGOFFENDED = 29; + VERB_BEINGSHOT = 30; + VERB_PLAINTIVEGESTURE = 31; + VERB_MELTING = 32; + VERB_SHOOTING = 33; + VERB_TORTURING = 34; + VERB_COMMITTINGDEPRAVEDACT = 35; + VERB_PRAYING = 36; + VERB_CONTEMPLATING = 37; + VERB_COOKING = 38; + VERB_ENGRAVING = 39; + VERB_PROSTRATING = 40; + VERB_SUFFERING = 41; + VERB_BEINGIMPALED = 42; + VERB_BEINGCONTORTED = 43; + VERB_BEINGFLAYED = 44; + VERB_HANGINGFROM = 45; + VERB_BEINGMUTILATED = 46; + VERB_TRIUMPHANTPOSE = 47; +} diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 3a9fed571..f96397409 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -11,6 +11,9 @@ #include "df/art_image_element_shapest.h" #include "df/art_image_element_treest.h" #include "df/art_image_element_type.h" +#include "df/art_image_property.h" +#include "df/art_image_property_intransitive_verbst.h" +#include "df/art_image_property_transitive_verbst.h" #include "df/art_image_ref.h" #include "df/descriptor_shape.h" #include "df/item_type.h" @@ -105,6 +108,35 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) break; } } + for (int i = 0; i < image->properties.size(); i++) + { + auto dfProperty = image->properties[i]; + auto netProperty = netImage->add_properties(); + auto propertyType = dfProperty->getType(); + + netProperty->set_type((ArtImagePropertyType)propertyType); + + switch (propertyType) + { + case df::enums::art_image_property_type::transitive_verb: + { + VIRTUAL_CAST_VAR(transitive, df::art_image_property_transitive_verbst, dfProperty); + netProperty->set_subject(transitive->subject); + netProperty->set_object(transitive->object); + netProperty->set_verb((ArtImageVerb)transitive->verb); + break; + } + case df::enums::art_image_property_type::intransitive_verb: + { + VIRTUAL_CAST_VAR(intransitive, df::art_image_property_intransitive_verbst, dfProperty); + netProperty->set_subject(intransitive->subject); + netProperty->set_verb((ArtImageVerb)intransitive->verb); + break; + } + default: + break; + } + } } void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) From 2af5f7ab870901edc5237e62f01e1be15a1d074f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 16:00:53 -0500 Subject: [PATCH 0811/1012] Add test runner --- .travis.yml | 5 +++-- test/main.lua | 10 ++++++++++ travis/dfhack_travis.init | 4 ++-- travis/run-tests.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 test/main.lua create mode 100644 travis/run-tests.py diff --git a/.travis.yml b/.travis.yml index 04bfcae9b..147cd13fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,8 +48,9 @@ script: - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" - make -j3 install - mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init -- cp ../travis/dfhack_travis.init "$DF_FOLDER"/ -- "$DF_FOLDER/dfhack" +- cd .. +- cp travis/dfhack_travis.init "$DF_FOLDER"/ +- python travis/run-tests.py "$DF_FOLDER" notifications: email: false # irc: diff --git a/test/main.lua b/test/main.lua new file mode 100644 index 000000000..0085a9711 --- /dev/null +++ b/test/main.lua @@ -0,0 +1,10 @@ +function set_test_stage(stage) + local f = io.open('test_stage.txt', 'w') + f:write(stage) + f:close() +end + +print('running tests') + +set_test_stage('done') +dfhack.run_command('die') diff --git a/travis/dfhack_travis.init b/travis/dfhack_travis.init index 5b28cefef..d9bc3e5ba 100644 --- a/travis/dfhack_travis.init +++ b/travis/dfhack_travis.init @@ -1,2 +1,2 @@ -devel/check-release -:lua scr.breakdown_level=df.interface_breakdown_types.QUIT +:lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR')) +test/main diff --git a/travis/run-tests.py b/travis/run-tests.py new file mode 100644 index 000000000..a8265088c --- /dev/null +++ b/travis/run-tests.py @@ -0,0 +1,30 @@ +import os, subprocess, sys + +MAX_TRIES = 5 + +dfhack = 'Dwarf Fortress.exe' if sys.platform == 'win32' else './dfhack' +test_stage = 'test_stage.txt' + +def get_test_stage(): + if os.path.isfile(test_stage): + return open(test_stage).read().strip() + return '0' + +os.chdir(sys.argv[1]) +if os.path.exists(test_stage): + os.remove(test_stage) + +tries = 0 +while True: + tries += 1 + stage = get_test_stage() + print('Run #%i: stage=%s' % (tries, get_test_stage())) + if stage == 'done': + print('Done!') + os.remove(test_stage) + sys.exit(0) + if tries > MAX_TRIES: + print('Too many tries - aborting') + sys.exit(1) + + os.system(dfhack) From b2a19dedeb589cc9db11479a6034a929e4bd109b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 22:56:24 -0500 Subject: [PATCH 0812/1012] load-art-image-chunk: safety check --- plugins/remotefortressreader/remotefortressreader.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 32bd73dad..5b84bf745 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -188,6 +188,11 @@ command_result loadArtImageChunk(color_ostream &out, vector & parameter if (parameters.size() != 1) return CR_WRONG_USAGE; + if (!Core::getInstance().isWorldLoaded()) + { + out.printerr("No world loaded\n"); + return CR_FAILURE; + } GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); if (GetArtImageChunk) @@ -2983,4 +2988,4 @@ static command_result GetLanguage(color_ostream & stream, const EmptyMessage * i netShape->set_tile(shape->tile); } return CR_OK; -} \ No newline at end of file +} From c92227f007114148f0b48059908b86645a6c36d7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Feb 2018 23:02:12 -0500 Subject: [PATCH 0813/1012] Update news (RFR, #1214) --- NEWS.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index ce8635675..478c79e01 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -106,7 +106,11 @@ Misc Improvements - only prints output if ``-loud`` is passed - `names`: can now edit names of units -- `remotefortressreader`: includes item stack sizes and some performance improvements +- `remotefortressreader`: + + - support for moving adventurers + - suport for item stack sizes, vehicles, gem shapes, item volume, art images, item improvements + - some performance improvements Removed ------- From 9a032ac447655a1e3781c73e7fefa5e6df98ff70 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 5 Feb 2018 20:11:47 +0530 Subject: [PATCH 0814/1012] I'm a moron. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 32bd73dad..9adb1fe90 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1508,7 +1508,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in continue; if (engraving->pos.y < (min_y * 16) || engraving->pos.x >(max_y * 16)) continue; - if (engraving->pos.z < (min_z * 16) || engraving->pos.x >(max_z * 16)) + if (engraving->pos.z < min_z || engraving->pos.x > max_z * 16) continue; if (!isEngravingNew(i)) continue; From 6d33069fe9679b2e320c7071f1422b85db652cb8 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Mon, 5 Feb 2018 10:41:36 -0500 Subject: [PATCH 0815/1012] Fix more copy-paste errors --- plugins/remotefortressreader/remotefortressreader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 2e9e2aacf..e321bf8d5 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1511,9 +1511,9 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in auto engraving = world->engravings[i]; if (engraving->pos.x < (min_x * 16) || engraving->pos.x >(max_x * 16)) continue; - if (engraving->pos.y < (min_y * 16) || engraving->pos.x >(max_y * 16)) + if (engraving->pos.y < (min_y * 16) || engraving->pos.y >(max_y * 16)) continue; - if (engraving->pos.z < min_z || engraving->pos.x > max_z * 16) + if (engraving->pos.z < min_z || engraving->pos.z > max_z) continue; if (!isEngravingNew(i)) continue; From c1e2633e17b7ef4a2f90c3dd3a7cccf646ec93ad Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Feb 2018 19:18:35 -0500 Subject: [PATCH 0816/1012] Refactor DFHack exception classes Move implementations out of MiscUtils.cpp to Error.cpp and make what() return a more useful description --- library/CMakeLists.txt | 1 + library/Error.cpp | 44 +++++++++++++++++ library/LuaTypes.cpp | 29 ++--------- library/MiscUtils.cpp | 12 ----- library/include/Error.h | 97 +++++++++++++------------------------ library/include/MiscUtils.h | 18 +++++++ 6 files changed, 102 insertions(+), 99 deletions(-) create mode 100644 library/Error.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 40c122de9..807a2845e 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -57,6 +57,7 @@ SET(MAIN_SOURCES Core.cpp ColorText.cpp DataDefs.cpp +Error.cpp VTableInterpose.cpp LuaWrapper.cpp LuaTypes.cpp diff --git a/library/Error.cpp b/library/Error.cpp new file mode 100644 index 000000000..773362af3 --- /dev/null +++ b/library/Error.cpp @@ -0,0 +1,44 @@ +#include "Error.h" +#include "MiscUtils.h" + +using namespace DFHack::Error; + +inline const char *safe_str(const char *s) +{ + return s ? s : "(NULL)"; +} + +NullPointer::NullPointer(const char *varname) + :All(stl_concat("NULL pointer: ", safe_str(varname))), + varname(varname) +{} + +InvalidArgument::InvalidArgument(const char *expr) + :All(stl_concat("Invalid argument; expected: ", safe_str(expr))), + expr(expr) +{} + +VTableMissing::VTableMissing(const char *name) + :All(stl_concat("Missing vtable address: ", safe_str(name))), + name(name) +{} + +SymbolsXmlParse::SymbolsXmlParse(const char* desc, int id, int row, int col) + :AllSymbols(stl_concat("error ", id, ": ", desc, ", at row ", row, " col ", col)), + desc(safe_str(desc)), id(id), row(row), col(col) +{} + +SymbolsXmlBadAttribute::SymbolsXmlBadAttribute(const char *attr) + :AllSymbols(stl_concat("attribute is either missing or invalid: ", safe_str(attr))), + attr(safe_str(attr)) +{} + +SymbolsXmlNoRoot::SymbolsXmlNoRoot() + :AllSymbols("no root element") +{} + +SymbolsXmlUnderspecifiedEntry::SymbolsXmlUnderspecifiedEntry(const char *where) + :AllSymbols(stl_concat("Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: ", + safe_str(where))), + where(safe_str(where)) +{} diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index f30a1878e..f744f3eba 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1076,20 +1076,8 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id try { id->invoke(state, 1); } - catch (Error::NullPointer &e) { - const char *vn = e.varname(); - std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?"); - field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); - } - catch (Error::InvalidArgument &e) { - const char *vn = e.expr(); - std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?"); - field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); - } - catch (Error::VTableMissing &e) { - const char *cn = e.name(); - std::string tmp = stl_sprintf("Missing vtable address: %s", cn ? cn : "?"); - field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + catch (Error::All &e) { + field_error(state, UPVAL_METHOD_NAME, e.what(), "invoke"); } catch (std::exception &e) { std::string tmp = stl_sprintf("C++ exception: %s", e.what()); @@ -1107,17 +1095,8 @@ int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *cont try { return fn(state); } - catch (Error::NullPointer &e) { - const char *vn = e.varname(); - return luaL_error(state, "%s: NULL pointer: %s", context, vn ? vn : "?"); - } - catch (Error::InvalidArgument &e) { - const char *vn = e.expr(); - return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?"); - } - catch (Error::VTableMissing &e) { - const char *cn = e.name(); - return luaL_error(state, "%s: Missing vtable address: %s", context, cn ? cn : "?"); + catch (Error::All &e) { + return luaL_error(state, "%s: %s", context, e.what()); } catch (std::exception &e) { return luaL_error(state, "%s: C++ exception: %s", context, e.what()); diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index bff7f07e1..afc3dca70 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -41,18 +41,6 @@ distribution. #include #include -const char *DFHack::Error::NullPointer::what() const throw() { - return "DFHack::Error::NullPointer"; -} - -const char *DFHack::Error::InvalidArgument::what() const throw() { - return "DFHack::Error::InvalidArgument"; -} - -const char *DFHack::Error::VTableMissing::what() const throw() { - return "DFHack::Error::VTableMissing"; -} - std::string stl_sprintf(const char *fmt, ...) { va_list lst; va_start(lst, fmt); diff --git a/library/include/Error.h b/library/include/Error.h index 2d0baadfc..d3d208b1f 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -41,118 +41,91 @@ namespace DFHack #ifdef _MSC_VER #pragma push /** - * C4275 is - The warning officially is non dll-interface class 'std::exception' used as base for + * C4275 - The warning officially is non dll-interface class 'std::exception' used as base for * dll-interface class * - * Basically, its saying that you might have an ABI problem if you mismatch compilers. We don't + * Basically, it's saying that you might have an ABI problem if you mismatch compilers. We don't * care since we build all of DFhack at once against whatever Toady is using */ #pragma warning(disable: 4275) #endif - class DFHACK_EXPORT All : public std::exception{}; + class DFHACK_EXPORT All : public std::exception + { + public: + const std::string full; + All(const std::string &full) + :full(full) + {} + virtual const char *what() const noexcept + { + return full.c_str(); + } + virtual ~All() noexcept {} + }; #ifdef _MSC_VER #pragma pop #endif class DFHACK_EXPORT NullPointer : public All { - const char *varname_; public: - NullPointer(const char *varname_ = NULL) : varname_(varname_) {} - const char *varname() const { return varname_; } - virtual const char *what() const throw(); + const char *const varname; + NullPointer(const char *varname = NULL); }; #define CHECK_NULL_POINTER(var) \ { if (var == NULL) throw DFHack::Error::NullPointer(#var); } class DFHACK_EXPORT InvalidArgument : public All { - const char *expr_; public: - InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {} - const char *expr() const { return expr_; } - virtual const char *what() const throw(); + const char *const expr; + InvalidArgument(const char *expr = NULL); }; #define CHECK_INVALID_ARGUMENT(expr) \ { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); } class DFHACK_EXPORT VTableMissing : public All { - const char *name_; public: - VTableMissing(const char *name_ = NULL) : name_(name_) {} - const char *name() const { return name_; } - virtual const char *what() const throw(); + const char *const name; + VTableMissing(const char *name = NULL); }; - class DFHACK_EXPORT AllSymbols : public All{}; + class DFHACK_EXPORT AllSymbols : public All + { + public: + AllSymbols(const std::string &full) + :All(full) + {} + }; // Syntax errors and whatnot, the xml can't be read class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols { public: - SymbolsXmlParse(const char* _desc, int _id, int _row, int _col) - :desc(_desc), id(_id), row(_row), col(_col) - { - std::stringstream s; - s << "error " << id << ": " << desc << ", at row " << row << " col " << col; - full = s.str(); - } - std::string full; + SymbolsXmlParse(const char* desc, int id, int row, int col); const std::string desc; const int id; const int row; const int col; - virtual ~SymbolsXmlParse() throw(){}; - virtual const char* what() const throw() - { - return full.c_str(); - } }; - class DFHACK_EXPORT SymbolsXmlBadAttribute : public All + class DFHACK_EXPORT SymbolsXmlBadAttribute : public AllSymbols { public: - SymbolsXmlBadAttribute(const char* _attr) : attr(_attr) - { - std::stringstream s; - s << "attribute is either missing or invalid: " << attr; - full = s.str(); - } - std::string full; + SymbolsXmlBadAttribute(const char* attr); std::string attr; - virtual ~SymbolsXmlBadAttribute() throw(){}; - virtual const char* what() const throw() - { - return full.c_str(); - } }; - class DFHACK_EXPORT SymbolsXmlNoRoot : public All + class DFHACK_EXPORT SymbolsXmlNoRoot : public AllSymbols { public: - SymbolsXmlNoRoot() {} - virtual ~SymbolsXmlNoRoot() throw(){}; - virtual const char* what() const throw() - { - return "Symbol file is missing root element."; - } + SymbolsXmlNoRoot(); }; - class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public All + class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public AllSymbols { public: - SymbolsXmlUnderspecifiedEntry(const char * _where) : where(_where) - { - std::stringstream s; - s << "Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: " << where; - full = s.str(); - } - virtual ~SymbolsXmlUnderspecifiedEntry() throw(){}; + SymbolsXmlUnderspecifiedEntry(const char *where); std::string where; - std::string full; - virtual const char* what() const throw() - { - return full.c_str(); - } }; } } diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 917b67489..ff0c842c5 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -349,6 +349,24 @@ DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); +// https://stackoverflow.com/questions/27375089/what-is-the-easiest-way-to-print-a-variadic-parameter-pack-using-stdostream +template +inline std::string stl_concat(Args... args) +{ + std::ostringstream os; + (void)(int[]){0, (void(os << std::forward(args)), 0)...}; + return os.str(); +} + +template +inline std::string stl_concat(const std::string &sep, Args... args) +{ + std::ostringstream os; + (void)(int[]){0, (void(os << sep << std::forward(args)), 0)...}; + return os.str(); +} + + // Conversion between CP437 and UTF-8 DFHACK_EXPORT std::string UTF2DF(const std::string &in); DFHACK_EXPORT std::string DF2UTF(const std::string &in); From a37ac2c349737f68d1772ba37993a0efa884b3c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Feb 2018 19:22:07 -0500 Subject: [PATCH 0817/1012] Re-enable IRC notifications --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 147cd13fe..ede4b132b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,8 +53,8 @@ script: - python travis/run-tests.py "$DF_FOLDER" notifications: email: false - # irc: - # channels: - # - "chat.freenode.net#dfhack" - # on_success: change - # on_failure: always + irc: + channels: + - "chat.freenode.net#dfhack" + on_success: change + on_failure: always From 7426f0850457102575d0e029f76f2c5849562e76 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 7 Feb 2018 10:40:52 -0600 Subject: [PATCH 0818/1012] Fix Error.cpp compile for Windows --- library/Error.cpp | 15 +++++++-------- library/include/MiscUtils.h | 18 ------------------ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/library/Error.cpp b/library/Error.cpp index 773362af3..ba1482918 100644 --- a/library/Error.cpp +++ b/library/Error.cpp @@ -3,33 +3,33 @@ using namespace DFHack::Error; -inline const char *safe_str(const char *s) +inline std::string safe_str(const char *s) { return s ? s : "(NULL)"; } NullPointer::NullPointer(const char *varname) - :All(stl_concat("NULL pointer: ", safe_str(varname))), + :All("NULL pointer: " + safe_str(varname)), varname(varname) {} InvalidArgument::InvalidArgument(const char *expr) - :All(stl_concat("Invalid argument; expected: ", safe_str(expr))), + :All("Invalid argument; expected: " + safe_str(expr)), expr(expr) {} VTableMissing::VTableMissing(const char *name) - :All(stl_concat("Missing vtable address: ", safe_str(name))), + :All("Missing vtable address: " + safe_str(name)), name(name) {} SymbolsXmlParse::SymbolsXmlParse(const char* desc, int id, int row, int col) - :AllSymbols(stl_concat("error ", id, ": ", desc, ", at row ", row, " col ", col)), + :AllSymbols(stl_sprintf("error %d: %s, at row %d col %d", id, desc, row, col)), desc(safe_str(desc)), id(id), row(row), col(col) {} SymbolsXmlBadAttribute::SymbolsXmlBadAttribute(const char *attr) - :AllSymbols(stl_concat("attribute is either missing or invalid: ", safe_str(attr))), + :AllSymbols("attribute is either missing or invalid: " + safe_str(attr)), attr(safe_str(attr)) {} @@ -38,7 +38,6 @@ SymbolsXmlNoRoot::SymbolsXmlNoRoot() {} SymbolsXmlUnderspecifiedEntry::SymbolsXmlUnderspecifiedEntry(const char *where) - :AllSymbols(stl_concat("Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: ", - safe_str(where))), + :AllSymbols("Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: " + safe_str(where)), where(safe_str(where)) {} diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index ff0c842c5..917b67489 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -349,24 +349,6 @@ DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); -// https://stackoverflow.com/questions/27375089/what-is-the-easiest-way-to-print-a-variadic-parameter-pack-using-stdostream -template -inline std::string stl_concat(Args... args) -{ - std::ostringstream os; - (void)(int[]){0, (void(os << std::forward(args)), 0)...}; - return os.str(); -} - -template -inline std::string stl_concat(const std::string &sep, Args... args) -{ - std::ostringstream os; - (void)(int[]){0, (void(os << sep << std::forward(args)), 0)...}; - return os.str(); -} - - // Conversion between CP437 and UTF-8 DFHACK_EXPORT std::string UTF2DF(const std::string &in); DFHACK_EXPORT std::string DF2UTF(const std::string &in); From 7ab8d74ac9e7cc6b7dbcf942cdc58ed7cc72458c Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 7 Feb 2018 14:32:27 -0600 Subject: [PATCH 0819/1012] Add mising Error.cpp reference to dfhack-client --- library/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 807a2845e..4d4a4e9f1 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -320,7 +320,7 @@ ADD_DEPENDENCIES(dfhack-version git-describe) ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) ADD_DEPENDENCIES(dfhack generate_headers generate_proto_core) -ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${PROJECT_PROTO_SRCS}) +ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS}) ADD_DEPENDENCIES(dfhack-client dfhack) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) From 19b65c2d1eb603e0344c1c7133f96f63cdeb03a3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 7 Feb 2018 20:14:36 -0500 Subject: [PATCH 0820/1012] Remove Error.h include (#1224) --- library/MiscUtils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index afc3dca70..e350cb9ec 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -25,7 +25,6 @@ distribution. #include "Internal.h" #include "Export.h" #include "MiscUtils.h" -#include "Error.h" #ifndef LINUX_BUILD #include From 55f5439ddf0e1688e49c51966e18e4ef4132ffdf Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Feb 2018 02:07:08 -0500 Subject: [PATCH 0821/1012] dwarfmonitor: remove unused hook, use CoreSuspendClaimer --- plugins/dwarfmonitor.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 7aed7188d..bf7c02efc 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1870,15 +1870,11 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - INTERPOSE_NEXT(feed)(input); - } - DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); + CoreSuspendClaimer suspend; if (Maps::IsValid()) { dm_lua::call("render_all"); @@ -1886,7 +1882,6 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest } }; -IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render); static bool set_monitoring_mode(const string &mode, const bool &state) @@ -1933,8 +1928,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) load_config(); if (is_enabled != enable) { - if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) || - !INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable)) + if (!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable)) return CR_FAILURE; reset(); From 342f193409f387e64ee00fba09c2220b1c98ac9d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Feb 2018 02:07:30 -0500 Subject: [PATCH 0822/1012] autochop: store skip settings in one field, fix double colon and initial load language_name fields default to -1, so the new settings could have been enabled on all worlds by default. --- plugins/autochop.cpp | 55 ++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 3b75068f6..6ba7709cc 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -49,9 +49,26 @@ static bool autochop_enabled = false; static int min_logs, max_logs; static const int LOG_CAP_MAX = 99999; static bool wait_for_threshold; -static bool skip_fruit_trees; -static bool skip_food_trees; -static bool skip_cook_trees; +struct Skip { + bool fruit_trees; + bool food_trees; + bool cook_trees; + operator int() { + return (fruit_trees ? 1 : 0) | + (food_trees ? 2 : 0) | + (cook_trees ? 4 : 0); + } + Skip &operator= (int in) { + // set all fields to false if they haven't been set in this save yet + if (in < 0) + in = 0; + fruit_trees = (in & 1); + food_trees = (in & 2); + cook_trees = (in & 4); + return *this; + } +}; +static Skip skip; static PersistentDataItem config_autochop; @@ -183,9 +200,7 @@ static void save_config() config_autochop.ival(1) = min_logs; config_autochop.ival(2) = max_logs; config_autochop.ival(3) = wait_for_threshold; - config_autochop.ival(5) = skip_fruit_trees; - config_autochop.ival(6) = skip_food_trees; - config_autochop.ival(7) = skip_cook_trees; + config_autochop.ival(4) = skip; } static void initialize() @@ -195,9 +210,7 @@ static void initialize() min_logs = 80; max_logs = 100; wait_for_threshold = false; - skip_fruit_trees = false; - skip_food_trees = false; - skip_cook_trees = false; + skip = 0; config_autochop = World::GetPersistentData("autochop/config"); if (config_autochop.isValid()) @@ -207,9 +220,7 @@ static void initialize() min_logs = config_autochop.ival(1); max_logs = config_autochop.ival(2); wait_for_threshold = config_autochop.ival(3); - skip_fruit_trees = config_autochop.ival(4); - skip_food_trees = config_autochop.ival(5); - skip_cook_trees = config_autochop.ival(6); + skip = config_autochop.ival(4); } else { @@ -240,19 +251,19 @@ static bool skip_plant(const df::plant * plant) const df::plant_raw *plant_raw = df::plant_raw::find(plant->material); // Skip fruit trees if set. - if (skip_fruit_trees && plant_raw->material_defs.type_drink != -1) + if (skip.fruit_trees && plant_raw->material_defs.type_drink != -1) return true; - if (skip_food_trees || skip_cook_trees) + if (skip.food_trees || skip.cook_trees) { df::material * mat; for (int idx = 0; idx < plant_raw->material.size(); idx++) { mat = plant_raw->material[idx]; - if (skip_food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) + if (skip.food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) return true; - if (skip_cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) + if (skip.cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) return true; } } @@ -603,15 +614,15 @@ public: } else if (input->count(interface_key::CUSTOM_F)) { - skip_fruit_trees = !skip_fruit_trees; + skip.fruit_trees = !skip.fruit_trees; } else if (input->count(interface_key::CUSTOM_E)) { - skip_food_trees = !skip_food_trees; + skip.food_trees = !skip.food_trees; } else if (input->count(interface_key::CUSTOM_C)) { - skip_cook_trees = !skip_cook_trees; + skip.cook_trees = !skip.cook_trees; } else if (enabler->tracking_on && enabler->mouse_lbut) { @@ -696,9 +707,9 @@ public: OutputString(COLOR_WHITE, x, y, "", true, left_margin); } OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); - OutputToggleString(x, y, "Skip Fruit Trees: ", CUSTOM_F, skip_fruit_trees, true, left_margin); - OutputToggleString(x, y, "Skip Edible Product Trees: ", CUSTOM_E, skip_food_trees, true, left_margin); - OutputToggleString(x, y, "Skip Cookable Product Trees: ", CUSTOM_C, skip_cook_trees, true, left_margin); + OutputToggleString(x, y, "Skip Fruit Trees", CUSTOM_F, skip.fruit_trees, true, left_margin); + OutputToggleString(x, y, "Skip Edible Product Trees", CUSTOM_E, skip.food_trees, true, left_margin); + OutputToggleString(x, y, "Skip Cookable Product Trees", CUSTOM_C, skip.cook_trees, true, left_margin); } ++y; From 9a0befa4cbb463f75c682d937134bfa8749a54b7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Feb 2018 03:03:07 -0500 Subject: [PATCH 0823/1012] Show number of trees skipped --- plugins/autochop.cpp | 61 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 6ba7709cc..a2a863540 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -230,8 +230,11 @@ static void initialize() } } -static bool skip_plant(const df::plant * plant) +static bool skip_plant(const df::plant * plant, bool *restricted) { + if (restricted) + *restricted = false; + // Skip all non-trees immediately. if (plant->flags.bits.is_shrub) return true; @@ -248,11 +251,19 @@ static bool skip_plant(const df::plant * plant) if (cur->designation[x][y].bits.hidden) return true; + df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); + if (material != tiletype_material::TREE) + return true; + const df::plant_raw *plant_raw = df::plant_raw::find(plant->material); // Skip fruit trees if set. if (skip.fruit_trees && plant_raw->material_defs.type_drink != -1) + { + if (restricted) + *restricted = true; return true; + } if (skip.food_trees || skip.cook_trees) { @@ -261,29 +272,44 @@ static bool skip_plant(const df::plant * plant) { mat = plant_raw->material[idx]; if (skip.food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) + { + if (restricted) + *restricted = true; return true; + } if (skip.cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) + { + if (restricted) + *restricted = true; return true; + } } } - df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); - if (material != tiletype_material::TREE) - return true; - return false; } -static int do_chop_designation(bool chop, bool count_only) +static int do_chop_designation(bool chop, bool count_only, int *skipped = nullptr) { int count = 0; + if (skipped) + { + *skipped = 0; + } for (size_t i = 0; i < world->plants.all.size(); i++) { const df::plant *plant = world->plants.all[i]; - if (skip_plant(plant)) + bool restricted = false; + if (skip_plant(plant, &restricted)) + { + if (restricted && skipped) + { + ++*skipped; + } continue; + } if (!count_only && !watchedBurrows.isValidPos(plant->pos)) continue; @@ -425,7 +451,11 @@ static void do_autochop() class ViewscreenAutochop : public dfhack_viewscreen { public: - ViewscreenAutochop() + ViewscreenAutochop(): + selected_column(0), + current_log_count(0), + marked_tree_count(0), + skipped_tree_count(0) { edit_mode = EDIT_NONE; burrows_column.multiselect = true; @@ -459,7 +489,7 @@ public: burrows_column.filterDisplay(); current_log_count = get_log_count(); - marked_tree_count = do_chop_designation(false, true); + marked_tree_count = do_chop_designation(false, true, &skipped_tree_count); } void change_min_logs(int delta) @@ -559,13 +589,21 @@ public: { int count = do_chop_designation(true, false); message = "Trees marked for chop: " + int_to_string(count); - marked_tree_count = do_chop_designation(false, true); + marked_tree_count = do_chop_designation(false, true, &skipped_tree_count); + if (skipped_tree_count) + { + message += ", skipped: " + int_to_string(skipped_tree_count); + } } else if (input->count(interface_key::CUSTOM_U)) { int count = do_chop_designation(false, false); message = "Trees unmarked: " + int_to_string(count); - marked_tree_count = do_chop_designation(false, true); + marked_tree_count = do_chop_designation(false, true, &skipped_tree_count); + if (skipped_tree_count) + { + message += ", skipped: " + int_to_string(skipped_tree_count); + } } else if (input->count(interface_key::CUSTOM_N)) { @@ -734,6 +772,7 @@ private: int selected_column; int current_log_count; int marked_tree_count; + int skipped_tree_count; MapExtras::MapCache mcache; string message; enum { EDIT_NONE, EDIT_MIN, EDIT_MAX } edit_mode; From 9fe9e64f09ae89fd837528b9983af6ca9743d905 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Feb 2018 03:04:40 -0500 Subject: [PATCH 0824/1012] Add dtimm to Authors.rst (#1180) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 8c1b5096e..aecfc519a 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -31,6 +31,7 @@ Clément Vuchener cvuchener Dan Amlund danamlund David Corbett dscorbett David Seguin dseguin +David Timm dtimm Deon DoctorVanGogh DoctorVanGogh Donald Ruegsegger hashaash From ca29cb8e6f8b33f10b151c66c49a0c02c59bae46 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Feb 2018 17:51:02 -0500 Subject: [PATCH 0825/1012] embark-assistant: update field names, now compiles --- plugins/embark-assistant/survey.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index ace97e5b1..6583d2479 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -198,7 +198,7 @@ namespace embark_assist { x = world_data->rivers[i]->path.x[k]; y = world_data->rivers[i]->path.y[k]; - if (world_data->rivers[i]->unk_8c[k] < 5000) { + if (world_data->rivers[i]->flow[k] < 5000) { if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; } @@ -206,10 +206,10 @@ namespace embark_assist { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; } } - else if (world_data->rivers[i]->unk_8c[k] < 10000) { + else if (world_data->rivers[i]->flow[k] < 10000) { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; } - else if (world_data->rivers[i]->unk_8c[k] < 20000) { + else if (world_data->rivers[i]->flow[k] < 20000) { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; } else { @@ -227,7 +227,7 @@ namespace embark_assist { if (x >= 0 && y >= 0 && x < world->worldgen.worldgen_parms.dim_x && y < world->worldgen.worldgen_parms.dim_y) { if (survey_results->at(x).at(y).river_size == embark_assist::defs::river_sizes::None) { if (world_data->rivers[i]->path.x.size() && - world_data->rivers[i]->unk_8c[world_data->rivers[i]->path.x.size() - 1] < 5000) { + world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 5000) { if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; } @@ -235,10 +235,10 @@ namespace embark_assist { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; } } - else if (world_data->rivers[i]->unk_8c[world_data->rivers[i]->path.x.size() - 1] < 10000) { + else if (world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 10000) { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; } - else if (world_data->rivers[i]->unk_8c[world_data->rivers[i]->path.x.size() - 1] < 20000) { + else if (world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 20000) { survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; } else { @@ -268,11 +268,11 @@ namespace embark_assist { reanimating = true; } else if (interaction->targets[k]->getType() == 2) {// Returns wrong type.. Should be df::interaction_target_type::MATERIAL - df::interaction_target_materialst* material = static_cast(interaction->targets[k]); - if (DFHack::MaterialInfo(material->anon_1, material->anon_2).isInorganic()) { - for (uint16_t l = 0; l < world->raws.inorganics[material->anon_2]->material.syndrome.size(); l++) { - for (uint16_t m = 0; m < world->raws.inorganics[material->anon_2]->material.syndrome[l]->ce.size(); m++) { - if (world->raws.inorganics[material->anon_2]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { + df::interaction_target_materialst* material = virtual_cast(interaction->targets[k]); + if (material && DFHack::MaterialInfo(material->mat_type, material->mat_index).isInorganic()) { + for (uint16_t l = 0; l < world->raws.inorganics[material->mat_index]->material.syndrome.size(); l++) { + for (uint16_t m = 0; m < world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce.size(); m++) { + if (world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { // Using this as a proxy. There seems to be a group of 4 effects for thralls: // display symbol, flash symbol, phys att change and one more. thralling = true; From b2fb6964731e711c2681c871aec8feb9b490284e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Feb 2018 02:38:50 -0500 Subject: [PATCH 0826/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 8d079a591..28a1ccc98 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7 +Subproject commit 28a1ccc9883d85106fa3b0ed59ddd57fcfc3f5c7 From c136bcbabacd5ac025810a9df6271f360830b6f4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Feb 2018 02:47:41 -0500 Subject: [PATCH 0827/1012] Update NEWS --- NEWS.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 478c79e01..ee685f8f9 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -36,6 +36,33 @@ Changelog .. contents:: :depth: 2 +DFHack 0.44.05-r2 +================= + +New Plugins +----------- +- `embark-assistant`: adds more information and features to embark screen + +New Scripts +----------- +- `adv-fix-sleepers`: fixes units in adventure mode who refuse to wake up (:bug:`6798`) +- `hermit`: blocks caravans, migrants, diplomats (for hermit challenge) + +New Features +------------ +- With ``PRINT_MODE:TEXT``, setting the ``DFHACK_HEADLESS`` environment variable + will hide DF's display and allow the console to be used normally. (Note that + this is intended for testing and is not very useful for actual gameplay.) + +Fixes +----- +- `devel/inject-raws`: fixed gloves and shoes (old typo causing errors) +- `view-item-info`: fixed an error with some shields + +Misc Improvements +----------------- +- `autochop`: can now exclude trees with fruit, + DFHack 0.44.05-r1 ================= From 269d575267d09ca85cb8d4b96e116b7674548dc4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Feb 2018 03:07:25 -0500 Subject: [PATCH 0828/1012] Document environment variables --- docs/Core.rst | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/Core.rst b/docs/Core.rst index 15adcc533..b15305fa9 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -430,6 +430,45 @@ Other init files directory, will be run when any world or that save is loaded. +Environment variables +===================== + +DFHack's behavior can be adjusted with some environment variables. For example, +on UNIX-like systems:: + + DFHACK_SOME_VAR=1 ./dfhack + +- ``DFHACK_PORT``: the port to use for the RPC server (used by ``dfhack-run`` + and `remotefortressreader` among others) instead of the default ``5000``. As + with the default, if this port cannot be used, the server is not started. + +- ``DFHACK_DISABLE_CONSOLE``: if set, the DFHack console is not set up. This is + the default behavior if ``PRINT_MODE:TEXT`` is set in ``data/init/init.txt``. + Intended for situations where DFHack cannot run in a terminal window. + +- ``DFHACK_HEADLESS``: if set, and ``PRINT_MODE:TEXT`` is set, DF's display will + be hidden, and the console will be started unless ``DFHACK_DISABLE_CONSOLE`` + is also set. Intended for non-interactive gameplay only. + +- ``DFHACK_NO_GLOBALS``, ``DFHACK_NO_VTABLES``: ignores all global or vtable + addresses in ``symbols.xml``, respectively. Intended for development use - + e.g. to make sure tools do not crash when these addresses are missing. + +- ``DFHACK_NO_DEV_PLUGINS``: if set, any plugins from the plugins/devel folder + that are built and installed will not be loaded on startup. + +- ``DFHACK_LOG_MEM_RANGES`` (macOS only): if set, logs memory ranges to + ``stderr.log``. Note that `devel/lsmem` can also do this. + +Other (non-DFHack-specific) variables that affect DFHack: + +- ``TERM``: if this is set to ``dumb`` or ``cons25`` on \*nix, the console will + not support any escape sequences (arrow keys, etc.). + +- ``LANG``, ``LC_CTYPE``: if either of these contain "UTF8" or "UTF-8" (not case + sensitive), ``DF2CONSOLE()`` will produce UTF-8-encoded text. Note that this + should be the case in most UTF-8-capable \*nix terminal emulators already. + Miscellaneous Notes =================== This section is for odd but important notes that don't fit anywhere else. From 6260062d0582a4089fd2d8e1274de6aa0db21c22 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Feb 2018 03:08:02 -0500 Subject: [PATCH 0829/1012] Add basic embark-assistant docs (closes #1226) --- docs/Plugins.rst | 12 ++++++++++++ plugins/embark-assistant/embark-assistant.cpp | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c896b5fd0..da1231184 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -526,6 +526,18 @@ nopause Disables pausing (both manual and automatic) with the exception of pause forced by `reveal` ``hell``. This is nice for digging under rivers. +.. _embark-assistant: + +embark-assistant +================ + +This plugin provides embark site selection help. It has to be run with the +``embark-assistant`` command while the pre-embark screen is displayed and shows +extended (and correct(?)) resource information for the embark rectangle as well +as normally undisplayed sites in the current embark region. It also has a site +selection tool with more options than DF's vanilla search tool. For detailed +help invoke the in game info screen. Requires 42 lines to display properly. + .. _embark-tools: embark-tools diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 283b696a0..7e0ea21cc 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -138,7 +138,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & &embark_assist::main::state->survey_results); embark_assist::main::state->match_results.resize(world->worldgen.worldgen_parms.dim_x); - + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { embark_assist::main::state->match_results[i].resize(world->worldgen.worldgen_parms.dim_y); } From 23d02a642ddef76ed37838862be549d735871a66 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 12 Feb 2018 01:45:53 -0500 Subject: [PATCH 0830/1012] Make tweak pausing-fps-counter non-default for now Ref #1219 --- dfhack.init-example | 3 --- 1 file changed, 3 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index fb846a47f..82210a2a6 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -202,9 +202,6 @@ tweak hotkey-clear # Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving tweak embark-profile-name -# dwarf mode FPS counter that does not jump to FPS_CAP when paused -tweak pausing-fps-counter - # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled tweak burrow-name-cancel From 2f8fbd7ecdb773c06695b1711d25f8f2e78fc3bd Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 12 Feb 2018 13:58:55 -0500 Subject: [PATCH 0831/1012] Show warning if all plugins fail to load (e.g. on Windows XP) --- library/PluginManager.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a4927a756..b6d1dcc92 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -797,6 +797,25 @@ PluginManager::~PluginManager() void PluginManager::init() { loadAll(); + + bool any_loaded = false; + for (auto p : all_plugins) + { + if (p.second->getState() == Plugin::PS_LOADED) + { + any_loaded = true; + break; + } + } + if (!any_loaded && !listPlugins().empty()) + { + Core::printerr("\n" +"All plugins present failed to load.\n" +"If you are using Windows XP, this is probably due to a Visual Studio 2015 bug.\n" +"Windows XP is unsupported by Microsoft as of 2014, so we do not support it.\n\n" +"If this was unexpected and you are not using Windows XP, please report this.\n\n" + ); + } } bool PluginManager::addPlugin(string name) From b3992229ad796525a52f67c91dceef9344ef4283 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 13 Feb 2018 12:26:13 +0530 Subject: [PATCH 0832/1012] update scripts. --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 8d079a591..28a1ccc98 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7 +Subproject commit 28a1ccc9883d85106fa3b0ed59ddd57fcfc3f5c7 From 8c13ce4da3d6d64760bf896caa2ee92a3946ec70 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 13 Feb 2018 12:36:00 +0530 Subject: [PATCH 0833/1012] update xmls --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 3208cff2a..40da7231a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3208cff2a041fa915260d085655a69fb15565537 +Subproject commit 40da7231a0114fe3420d475961b08826f64fbf79 From 18cf70a543636620f1c7f7d1b18f68826d2dde09 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 13 Feb 2018 12:37:45 +0530 Subject: [PATCH 0834/1012] Update Stonesense. --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index be793a080..455b51aab 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit be793a080e66db1ff79ac284070632ec1a896708 +Subproject commit 455b51aab7a93095a775285782635cfc17ac651a From 74bf06c69ebd79c10a78692098c56bbab69eb9c4 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 13 Feb 2018 20:02:42 +0530 Subject: [PATCH 0835/1012] Do some checks before trying to read json files. --- library/RemoteServer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 64be8221c..e4204014f 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -386,14 +386,17 @@ bool ServerMain::listen(int port) Json::Value configJson; std::ifstream inFile(filename, std::ios_base::in); + + bool allow_remote = false; + if (inFile.is_open()) { inFile >> configJson; inFile.close(); - } - bool allow_remote = configJson.get("allow_remote", "false").asBool(); - port = configJson.get("port", port).asInt(); + allow_remote = configJson.get("allow_remote", "false").asBool(); + port = configJson.get("port", port).asInt(); + } configJson["allow_remote"] = allow_remote; configJson["port"] = port; From 94b2ca68d8ec9bbbe09139c039b87f4c4b00ba13 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 13 Feb 2018 11:19:10 -0500 Subject: [PATCH 0836/1012] dwarfvet: fix extra % in log message Mentioned in #1227 --- plugins/dwarfvet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 920e13d34..a93de8bdc 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -779,7 +779,7 @@ command_result dwarfvet (color_ostream &out, std::vector & paramet for (size_t b =0 ; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; if (isActiveAnimalHospital(building)) { - out.print(" at x1: %d, x2%: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z); + out.print(" at x1: %d, x2: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z); } } return CR_OK; @@ -832,4 +832,4 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan break; } return CR_OK; -} \ No newline at end of file +} From 36f49541821e16c8e64404bc71234fa80083e3d7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 16 Feb 2018 02:16:06 -0500 Subject: [PATCH 0837/1012] Update Stonesense and build docs (fixed Allegro download) --- docs/Compile.rst | 13 ++++++++++--- plugins/stonesense | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index c69d1de29..74ffb4078 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -693,12 +693,19 @@ for. For example, if you are building for 32-bit Linux and 64-bit Windows, download all files starting with ``linux32`` and ``win64``. GitHub should sort files alphabetically, so all the files you need should be next to each other. +.. note:: + + * Any files containing "allegro" in their filename are only necessary for + building `stonesense`. If you are not building Stonesense, you don't have to + download these, as they are larger than any other listed files. + It is recommended that you create a build folder and run CMake to verify that you have downloaded everything at this point, assuming your download machine has CMake installed. This involves running a "generate" batch script on Windows, or -a command starting with ``cmake ..`` on Linux and OS X. CMake should -automatically locate files that you placed in ``CMake/downloads``, and use them -instead of attempting to download them. +a command starting with ``cmake ..`` on Linux and OS X, following the +instructions in the sections above. CMake should automatically locate files that +you placed in ``CMake/downloads``, and use them instead of attempting to +download them. .. _note-old-git-and-dfhack: diff --git a/plugins/stonesense b/plugins/stonesense index 455b51aab..031bf4caa 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 455b51aab7a93095a775285782635cfc17ac651a +Subproject commit 031bf4caafec321dd315202a83539d052ed49d84 From 8779168c969d95ad2a00f3161bb6809177429e4d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 16 Feb 2018 02:17:58 -0500 Subject: [PATCH 0838/1012] DFHACK_PLUGIN: qualify Plugin --- library/include/PluginManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 42bebb2d3..f7a8a7d87 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -296,7 +296,7 @@ namespace DFHack DFhackDataExport const char * plugin_name = m_plugin_name;\ DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\ DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\ - DFhackDataExport Plugin *plugin_self = NULL;\ + DFhackDataExport DFHack::Plugin *plugin_self = NULL;\ std::vector _plugin_globals;\ DFhackDataExport std::vector* plugin_globals = &_plugin_globals; \ DFhackDataExport bool plugin_dev = is_dev; From 988ce9c30d0a8cebeeded7773c5aabd6a69365ee Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 16 Feb 2018 02:18:54 -0500 Subject: [PATCH 0839/1012] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 40da7231a..a9268e6d2 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 40da7231a0114fe3420d475961b08826f64fbf79 +Subproject commit a9268e6d27fedcc97d661bbcd87c25173d570b29 From 7758fb6df5e6ad7ad9dcc6a5a72d096310f3842f Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 16 Feb 2018 02:32:53 -0500 Subject: [PATCH 0840/1012] dwarfvet: fix another log message, misc readability improvements --- plugins/dwarfvet.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index a93de8bdc..46247bb87 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -237,8 +237,7 @@ void AnimalHospital::calculateHospital(bool force, color_ostream &out) { // then walk the patient array and remark those spots as used. // If a patient is in an invalid spot, reassign it - for (size_t b =0 ; b < world->buildings.all.size(); b++) { - df::building* building = world->buildings.all[b]; + for (df::building *building : world->buildings.all) { // Check that we're not comparing ourselves; if (building->id == this->id) { @@ -507,13 +506,13 @@ void tickHandler(color_ostream& out, void* data) { // It's possible our hospital cache is empty, if so, simply copy it, and jump to the main logic if (!hospitals_cached && count_of_hospitals) { out.print("Populating hospital cache:\n"); - for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { - AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); - out.print(" Found animal hospital %d at x1: %d, y1: %d from valid hospital list\n", + for (df::building *current_hospital : hospitals_on_map) { + AnimalHospital * hospital = new AnimalHospital(current_hospital, out); + out.print(" Found animal hospital %d at x1: %d, y1: %d, z: %d from valid hospital list\n", hospital->getID(), - (*current_hospital)->x1, - (*current_hospital)->y1, - (*current_hospital)->z + current_hospital->x1, + current_hospital->y1, + current_hospital->z ); animal_hospital_zones.push_back(hospital); } From 6e3b29930da4ad927bcf9a205096f6a3198fa96c Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 16 Feb 2018 15:12:46 -0500 Subject: [PATCH 0841/1012] Avoid crash in dwarfvet due to negative width/height Partial fix for #1227 --- plugins/dwarfvet.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 46247bb87..604f399a7 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -135,8 +135,8 @@ AnimalHospital::AnimalHospital(df::building * building, color_ostream &out) { z = building->z; // Determine how many spots we have for animals - this->length = x2-x1; - this->height = y2-y1; + this->length = x2-x1+1; + this->height = y2-y1+1; // And calculate the hospital! this->calculateHospital(true, out); @@ -321,8 +321,8 @@ void AnimalHospital::calculateHospital(bool force, color_ostream &out) { spot_cur += building_offset_x; /* Start marking! */ - for (int i = 0; i != building_height; i++) { - for (int j = 0; j != building_length; j++) { + for (int i = 0; i < building_height; i++) { + for (int j = 0; j < building_length; j++) { spots_in_use[spot_cur+j] = true; } From 8c20d178b57aa4bfc754c0803d9ab165cbe0e319 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 21 Feb 2018 22:27:04 +0530 Subject: [PATCH 0842/1012] Include plant type with plant items. --- plugins/remotefortressreader/item_reader.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index f96397409..8fcb7a601 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -303,6 +303,8 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) case df::enums::item_type::SEEDS: break; case df::enums::item_type::PLANT: + //For convenience, we encode the plant type into item type, even if it's available in the material. + type->set_mat_index(DfItem->getMaterialIndex()); break; case df::enums::item_type::SKIN_TANNED: break; @@ -521,6 +523,18 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: } break; } + case df::enums::item_type::PLANT: + { + for (int i = 0; i < world->raws.plants.all.size(); i++) + { + auto plantRaw = world->raws.plants.all[i]; + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(plantRaw->index); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + plantRaw->id); + } + break; + } case df::enums::item_type::BOX: { mat_def = out->add_material_list(); From 778a1667dc9cda9b2379e50a2578e921846733ac Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 21 Feb 2018 22:31:39 +0530 Subject: [PATCH 0843/1012] ...also do it for images. --- plugins/remotefortressreader/item_reader.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 8fcb7a601..59b1f36e7 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -99,6 +99,13 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) it->set_mat_type(item->item_type); it->set_mat_index(item->item_subtype); netElement->set_id(item->item_id); + switch (item->item_type) + { + case item_type::PLANT: + it->set_mat_index(item->mat_index); + default: + break; + } auto mat = netElement->mutable_material(); mat->set_mat_type(item->mat_type); mat->set_mat_index(item->mat_index); From 9f9d90c2efdd993f431b584cadcbfa839ace1907 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 21 Feb 2018 18:46:21 -0600 Subject: [PATCH 0844/1012] Fix labormanager never assigning dwarves to fill ponds, making irrigation very difficult. --- plugins/labormanager/joblabormapper.cpp | 13 +++++++------ plugins/labormanager/labormanager.cpp | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index d16bfafcf..779839e99 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -731,7 +731,8 @@ JobLaborMapper::JobLaborMapper() job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); - job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); + job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; + job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); @@ -819,13 +820,13 @@ JobLaborMapper::JobLaborMapper() job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING); - job_to_labor_table[df::job_type::DrainAquarium] = jlf_no_labor; - job_to_labor_table[df::job_type::FillAquarium] = jlf_no_labor; - job_to_labor_table[df::job_type::FillPond] = jlf_no_labor; + job_to_labor_table[df::job_type::DrainAquarium] = jlf_const(df::unit_labor::HAUL_WATER); + job_to_labor_table[df::job_type::FillAquarium] = jlf_const(df::unit_labor::HAUL_WATER); + job_to_labor_table[df::job_type::FillPond] = jlf_const(df::unit_labor::HAUL_WATER); job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); - job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; - job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveWater2] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::GiveFood2] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 42e718488..5d6c5c971 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1941,6 +1941,10 @@ public: set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); + /* Set HAUL_WATER so we can detect ponds that need to be filled ponds. */ + + set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true); + if (print_debug) out.print("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); } @@ -1965,6 +1969,7 @@ public: set_labor(*d, l, (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || l == df::unit_labor::CLEAN || + l == df::unit_labor::HAUL_WATER || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER || l == df::unit_labor::HAUL_TRADE); From 0e7ab27850821993ebc317bed9dc0135f030a3e5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 21 Feb 2018 23:07:53 -0500 Subject: [PATCH 0845/1012] Bump to r2, update changelog, update submdules --- CMakeLists.txt | 2 +- NEWS.rst | 6 +++++- library/xml | 2 +- scripts | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7eb1f234..58be5dff1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.05") -set(DFHACK_RELEASE "r1") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/NEWS.rst b/NEWS.rst index ee685f8f9..17998f5e2 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -56,12 +56,16 @@ New Features Fixes ----- +- `devel/export-dt-ini`: fix language_name offsets for DT 39.2+ - `devel/inject-raws`: fixed gloves and shoes (old typo causing errors) +- `remotefortressreader`: fixed an issue with not all engravings being included - `view-item-info`: fixed an error with some shields Misc Improvements ----------------- -- `autochop`: can now exclude trees with fruit, +- `adv-rumors`: added more keywords, including names +- `autochop`: can now exclude trees that produce fruit, food, or cookable items +- `remotefortressreader`: added plant type support DFHack 0.44.05-r1 ================= diff --git a/library/xml b/library/xml index a9268e6d2..2794f8a6d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a9268e6d27fedcc97d661bbcd87c25173d570b29 +Subproject commit 2794f8a6d7405d4858bac486a0bb17b94740c142 diff --git a/scripts b/scripts index 28a1ccc98..9d8f2096f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 28a1ccc9883d85106fa3b0ed59ddd57fcfc3f5c7 +Subproject commit 9d8f2096fc4c247299cbc94c6a8d756f31690a4b From 2277c4ee4e3d28b6e60aeba6f87f134627be29e6 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 25 Feb 2018 10:28:38 +0100 Subject: [PATCH 0846/1012] Corrected reanimation detection --- plugins/embark-assistant/help_ui.cpp | 2 +- plugins/embark-assistant/survey.cpp | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index d75d00ce2..7be6b38d7 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -250,7 +250,7 @@ namespace embark_assist{ help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); help_text.push_back("- Right world map overlay not implemented as author has failed to"); help_text.push_back(" emulate the sizing logic exactly."); - help_text.push_back("Version 0.1 2017-08-30"); + help_text.push_back("Version 0.2 2018-02-25"); break; } diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 6583d2479..15fd3883a 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -17,6 +17,9 @@ #include "df/inorganic_flags.h" #include "df/inorganic_raw.h" #include "df/interaction.h" +#include "df/interaction_effect.h" +#include "df/interaction_effect_type.h" +#include "df/interaction_effect_animatest.h" #include "df/interaction_instance.h" #include "df/interaction_source.h" #include "df/interaction_source_regionst.h" @@ -264,10 +267,15 @@ namespace embark_assist { if (interaction->sources.size() && interaction->sources[0]->getType() == df::interaction_source_type::REGION) { for (uint16_t k = 0; k < interaction->targets.size(); k++) { - if (interaction->targets[k]->getType() == 0) { // Returns wrong type. Should be df::interaction_target_type::CORPSE - reanimating = true; + if (interaction->targets[k]->getType() == df::interaction_target_type::CORPSE) { + for (uint16_t l = 0; l < interaction->effects.size(); l++) { + if (interaction->effects[l]->getType() == df::interaction_effect_type::ANIMATE) { + reanimating = true; + break; + } + } } - else if (interaction->targets[k]->getType() == 2) {// Returns wrong type.. Should be df::interaction_target_type::MATERIAL + else if (interaction->targets[k]->getType() == df::interaction_target_type::MATERIAL) { df::interaction_target_materialst* material = virtual_cast(interaction->targets[k]); if (material && DFHack::MaterialInfo(material->mat_type, material->mat_index).isInorganic()) { for (uint16_t l = 0; l < world->raws.inorganics[material->mat_index]->material.syndrome.size(); l++) { From 7705eada5ad24410f5302ad8711c44e1464220f7 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Mon, 26 Feb 2018 18:31:33 +0100 Subject: [PATCH 0847/1012] Added magma/candy search + profile save/load. --- build/win64/install-debug.bat | 2 +- plugins/embark-assistant/defs.h | 24 ++- plugins/embark-assistant/finder_ui.cpp | 248 ++++++++++++++++++++++++- plugins/embark-assistant/help_ui.cpp | 26 ++- plugins/embark-assistant/matcher.cpp | 44 +++++ plugins/embark-assistant/survey.cpp | 28 ++- 6 files changed, 351 insertions(+), 21 deletions(-) diff --git a/build/win64/install-debug.bat b/build/win64/install-debug.bat index 0b8758461..64b5ae850 100644 --- a/build/win64/install-debug.bat +++ b/build/win64/install-debug.bat @@ -1,4 +1,4 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +call "D:\Program (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 cd VC2015 msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj cd .. \ No newline at end of file diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index 79ad5dd5b..bea91dae6 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -33,6 +33,8 @@ namespace embark_assist { int16_t elevation; bool river_present = false; int16_t river_elevation = 100; + int8_t adamantine_level; // -1 = none, 0 .. 3 = cavern 1 .. magma sea. Currently not used beyond present/absent. + int8_t magma_level; // -1 = none, 0 .. 3 = cavern 3 .. surface/volcano int8_t biome_offset; uint8_t savagery_level; // 0 - 2 uint8_t evilness_level; // 0 - 2 @@ -42,7 +44,6 @@ namespace embark_assist { }; typedef std::array, 16> mid_level_tiles; -// typedef mid_level_tile mid_level_tiles[16][16]; struct region_tile_datum { bool surveyed = false; @@ -53,7 +54,6 @@ namespace embark_assist { uint8_t min_region_soil = 10; uint8_t max_region_soil = 0; bool waterfall = false; - river_sizes river_size; int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used @@ -162,6 +162,22 @@ namespace embark_assist { Major }; +// enum class adamantine_ranges : int8_t { +// NA = -1, +// Cavern_1, +// Cavern_2, +// Cavern_3, +// Magma_Sea +// }; + + enum class magma_ranges : int8_t { + NA = -1, + Cavern_3, + Cavern_2, + Cavern_1, + Volcano + }; + enum class yes_no_ranges : int8_t { NA = -1, Yes, @@ -218,6 +234,10 @@ namespace embark_assist { yes_no_ranges evil_weather; // Will probably blow up with the magic release arcs... yes_no_ranges reanimation; yes_no_ranges thralling; + int8_t spire_count_min; // N/A(-1), 0-9 + int8_t spire_count_max; // N/A(-1), 0-9 + magma_ranges magma_min; + magma_ranges magma_max; int8_t biome_count_min; // N/A(-1), 1-9 int8_t biome_count_max; // N/A(-1), 1-9 int8_t region_type_1; // N/A(-1), df::world_region_type diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index feea89199..fdc4a57e8 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -1,3 +1,4 @@ +#include #include "Core.h" #include @@ -5,6 +6,7 @@ #include "Types.h" +#include "MemAccess.h" #include "df/biome_type.h" #include "df/inorganic_raw.h" #include "df/material_flags.h" @@ -19,6 +21,8 @@ using df::global::world; +#define profile_file_name ".\\data\\init\\embark_assistant_profile.txt" + namespace embark_assist { namespace finder_ui { @@ -45,6 +49,10 @@ namespace embark_assist { evil_weather, reanimation, thralling, + spire_count_min, + spire_count_max, + magma_min, + magma_max, biome_count_min, biome_count_max, region_type_1, @@ -136,6 +144,132 @@ namespace embark_assist { //========================================================================================================== + void save_profile() { + color_ostream_proxy out(Core::getInstance().getConsole()); + + FILE* outfile = fopen(profile_file_name, "w"); + fields i = first_fields; + + while (true) { + out.print("[%s:%i]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->current_value); + fprintf(outfile, "[%s:%i]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->current_value); + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + fclose(outfile); + } + + //========================================================================================================== + + void load_profile() { + color_ostream_proxy out(Core::getInstance().getConsole()); + FILE* infile = fopen(profile_file_name, "r"); + + if (!infile) { + out.printerr("No profile file found at %s\n", profile_file_name); + return; + } + + fields i = first_fields; + char line[80]; + int count = 80; + bool found; + int value; + + while (true) { + + fgets(line, count, infile); + if (line[0] != '[') { + out.printerr("Failed to find token start '[' at line %i\n", static_cast(i)); + return; + } + + found = false; + + for (int k = 1; k < count; k++) { + if (line[k] == ':') { + for (int l = 1; l < k; l++) { + if (state->finder_list[static_cast(i)].text.c_str()[l - 1] != line[l]) { + out.printerr("Token mismatch of %s vs %s\n", line, state->finder_list[static_cast(i)].text.c_str()); + return; + } + } + if (!sscanf(&line[k + 1], "%i]", &value)) { + out.printerr("Value extraction failure from %s\n", line); + return; + } + + for (int l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { + if (value == state->ui[static_cast(i)]->list[l].key) { + found = true; + break; + } + } + + if (!found) { + out.printerr("Value not found in plugin. Raw mismatch? %s\n", line); + return; + } + + break; + } + } + + if (!found) { + out.printerr("Value delimiter not found in %s\n", line); + return; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + fclose(infile); + + // Checking done. No do the work. + + infile = fopen(profile_file_name, "r"); + i = first_fields; + + while (true) { + fgets(line, count, infile); + + for (int k = 1; k < count; k++) { + if (line[k] == ':') { + sscanf(&line[k + 1], "%i]", &value); + + state->ui[static_cast(i)]->current_value = value; + + for (int l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { + if (value == state->ui[static_cast(i)]->list[l].key) { + state->ui[static_cast(i)]->current_display_value = l; + break; + } + } + + break; + } + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + fclose(infile); + } + + //========================================================================================================== + void ui_setup(embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { // color_ostream_proxy out(Core::getInstance().getConsole()); if (!embark_assist::finder_ui::state) { @@ -419,6 +553,56 @@ namespace embark_assist { break; + case fields::spire_count_min: + case fields::spire_count_max: + for (int16_t k = -1; k <= 9; k++) { + if (k == -1) { + element->list.push_back({ "N/A", k }); + } + else { + element->list.push_back({ std::to_string(k), k }); + } + } + + break; + + case fields::magma_min: + case fields::magma_max: + { + embark_assist::defs::magma_ranges k = embark_assist::defs::magma_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::magma_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Cavern_3: + element->list.push_back({ "3:rd Cavern", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Cavern_2: + element->list.push_back({ "2:nd Cavern", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Cavern_1: + element->list.push_back({ "1:st Cavern", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Volcano: + element->list.push_back({ "Volcano", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::magma_ranges::Volcano) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + case fields::biome_count_min: case fields::biome_count_max: for (int16_t k = 0; k < 10; k++) { @@ -659,6 +843,22 @@ namespace embark_assist { state->finder_list.push_back({ "Max Soil", static_cast(i) }); break; + case fields::spire_count_min: + state->finder_list.push_back({ "Min Adamantine", static_cast(i) }); + break; + + case fields::spire_count_max: + state->finder_list.push_back({ "Max Adamantine", static_cast(i) }); + break; + + case fields::magma_min: + state->finder_list.push_back({ "Min Magma", static_cast(i) }); + break; + + case fields::magma_max: + state->finder_list.push_back({ "Max Magma", static_cast(i) }); + break; + case fields::biome_count_min: state->finder_list.push_back({ "Min Biome Count", static_cast(i) }); break; @@ -875,6 +1075,24 @@ namespace embark_assist { static_cast(state->ui[static_cast(i)]->current_value); break; + case fields::spire_count_min: + finder.spire_count_min = state->ui[static_cast(i)]->current_value; + break; + + case fields::spire_count_max: + finder.spire_count_max = state->ui[static_cast(i)]->current_value; + break; + + case fields::magma_min: + finder.magma_min = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::magma_max: + finder.magma_max = + static_cast(state->ui[static_cast(i)]->current_value); + break; + case fields::biome_count_min: finder.biome_count_min = state->ui[static_cast(i)]->current_value; break; @@ -1015,17 +1233,25 @@ namespace embark_assist { state->ui[state->finder_list_focus]->current_index = 0; } } + } else if (input->count(df::interface_key::SELECT)) { if (!state->finder_list_active) { state->ui[state->finder_list_focus]->current_display_value = state->ui[state->finder_list_focus]->current_index; state->ui[state->finder_list_focus]->current_value = state->ui[state->finder_list_focus]->list[state->ui[state->finder_list_focus]->current_index].key; state->finder_list_active = true; } + } else if (input->count(df::interface_key::CUSTOM_F)) { input->clear(); Screen::dismiss(this); find(); return; + + } else if (input->count(df::interface_key::CUSTOM_S)) { // Save + save_profile(); + + } else if (input->count(df::interface_key::CUSTOM_L)) { // Load + load_profile(); } } @@ -1041,15 +1267,19 @@ namespace embark_assist { Screen::drawBorder("Embark Assistant Site Finder"); embark_assist::screen::paintString(lr_pen, 1, 1, "4/6"); - embark_assist::screen::paintString(white_pen, 4, 1, ":Shift list"); - embark_assist::screen::paintString(lr_pen, 16, 1, "8/2"); - embark_assist::screen::paintString(white_pen, 19, 1, ":Up/down"); - embark_assist::screen::paintString(lr_pen, 28, 1, "ENTER"); - embark_assist::screen::paintString(white_pen, 33, 1, ":Select item"); - embark_assist::screen::paintString(lr_pen, 46, 1, "f"); - embark_assist::screen::paintString(white_pen, 47, 1, ":Find"); - embark_assist::screen::paintString(lr_pen, 53, 1, "ESC"); - embark_assist::screen::paintString(white_pen, 56, 1, ":Abort"); + embark_assist::screen::paintString(white_pen, 4, 1, ":<->"); + embark_assist::screen::paintString(lr_pen, 9, 1, "8/2"); + embark_assist::screen::paintString(white_pen, 12, 1, ":Up/Down"); + embark_assist::screen::paintString(lr_pen, 21, 1, "ENTER"); + embark_assist::screen::paintString(white_pen, 26, 1, ":Select"); + embark_assist::screen::paintString(lr_pen, 34, 1, "f"); + embark_assist::screen::paintString(white_pen, 35, 1, ":Find"); + embark_assist::screen::paintString(lr_pen, 41, 1, "ESC"); + embark_assist::screen::paintString(white_pen, 44, 1, ":Abort"); + embark_assist::screen::paintString(lr_pen, 51, 1, "s"); + embark_assist::screen::paintString(white_pen, 52, 1, ":Save"); + embark_assist::screen::paintString(lr_pen, 58, 1, "l"); + embark_assist::screen::paintString(white_pen, 59, 1, ":Load"); for (uint16_t i = 0; i < state->finder_list.size(); i++) { if (i == state->finder_list_focus) { diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index 7be6b38d7..31a24e891 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -109,10 +109,10 @@ namespace embark_assist{ help_text.push_back("- Site find search. Richer set of selection criteria than the vanilla"); help_text.push_back(" DF Find that Embark Assistant suppresses (by using the same key)."); help_text.push_back(""); - help_text.push_back("The functionality requires a screen height of at least 42 lines to display"); + help_text.push_back("The functionality requires a screen height of at least 46 lines to display"); help_text.push_back("correctly (that's the height of the Finder screen), as fitting everything"); help_text.push_back("onto a standard 80*25 screen would be too challenging. The help is adjusted"); - help_text.push_back("to fit into onto an 80*42 screen."); + help_text.push_back("to fit into onto an 80*46 screen as well."); help_text.push_back("This help/info is split over several screens, and you can move between them"); help_text.push_back("using the TAB/Shift-TAB keys, and leave the help from any screen using ESC."); help_text.push_back(""); @@ -188,6 +188,9 @@ namespace embark_assist{ help_text.push_back("ENTER to select a value in the value list, entering it among the selections."); help_text.push_back("f to activate the Find functionality using the values in the middle column."); help_text.push_back("ESC to leave the screen without activating a Find operation."); + help_text.push_back("s/l is used to save/load search profile to/from embark_assistant_profile.txt"); + help_text.push_back("stored in .\\data\\init. There's some minor error detection that will refuse"); + help_text.push_back("to load a file that doesn't check out."); help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF"); help_text.push_back("itself these parameters are initiated to match the actual embark rectangle"); help_text.push_back("when a new search is initiated (prior results are cleared."); @@ -211,7 +214,7 @@ namespace embark_assist{ help_text.push_back("feature is Present in the embark, and entering the same value multiple"); help_text.push_back("times does nothing (the first match ticks all requirements off). It can be"); help_text.push_back("noted that all the Economic materials are found in the much longer Mineral"); - help_text.push_back("list. Note that Find is a fairly time consuming task (is it is in vanilla)."); + help_text.push_back("list. Note that Find is a fairly time consuming task (as it is in vanilla)."); break; case pages::Caveats: @@ -250,13 +253,18 @@ namespace embark_assist{ help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); help_text.push_back("- Right world map overlay not implemented as author has failed to"); help_text.push_back(" emulate the sizing logic exactly."); - help_text.push_back("Version 0.2 2018-02-25"); + help_text.push_back("- There's currently a DF bug that causes adamantine spires reaching"); + help_text.push_back(" that have been removed at world gen to fail to be generated. It's likely"); + help_text.push_back(" this bug also affects magma pools. This plugin does not address this."); + help_text.push_back("Version 0.3 2018-02-26"); break; } // Add control keys to first line. - embark_assist::screen::paintString(pen_lr, 1, 1, "TAB/Shift-TAB"); + embark_assist::screen::paintString(pen_lr, 1, 1, "TAB"); + embark_assist::screen::paintString(pen, 4, 1, "/"); + embark_assist::screen::paintString(pen_lr, 5, 1, "Shift-TAB"); embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page"); embark_assist::screen::paintString(pen_lr, 34, 1, "ESC"); embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help"); @@ -285,11 +293,15 @@ namespace embark_assist{ break; case pages::Finder: - embark_assist::screen::paintString(pen_lr, 1, 4, "4/6"); - embark_assist::screen::paintString(pen_lr, 1, 5, "8/2"); + embark_assist::screen::paintString(pen_lr, 1, 4, "4"); + embark_assist::screen::paintString(pen_lr, 3, 4, "6"); + embark_assist::screen::paintString(pen_lr, 1, 5, "8"); + embark_assist::screen::paintString(pen_lr, 3, 5, "2"); embark_assist::screen::paintString(pen_lr, 1, 6, "ENTER"); embark_assist::screen::paintString(pen_lr, 1, 7, "f"); embark_assist::screen::paintString(pen_lr, 1, 8, "ESC"); + embark_assist::screen::paintString(pen_lr, 1, 9, "s"); + embark_assist::screen::paintString(pen_lr, 3, 9, "l"); break; case pages::Caveats: diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 484ee3cf8..eb73eda2c 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -51,6 +51,8 @@ namespace embark_assist { bool evil_weather_found = false; bool reanimation_found = false; bool thralling_found = false; + uint8_t spire_count = 0; + int8_t magma_level = -1; bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; bool region_types[ENUM_LAST_ITEM(world_region_type) + 1]; uint8_t biome_count; @@ -199,6 +201,24 @@ namespace embark_assist { thralling_found = true; } + // Spires + if (mlt->at(i).at(k).adamantine_level != -1) { + spire_count++; + + if (finder->spire_count_max != -1 && + finder->spire_count_max < spire_count) return false; + } + + // Magma + if (mlt->at(i).at(k).magma_level != -1) { + if (mlt->at(i).at(k).magma_level > magma_level) + { + magma_level = mlt->at(i).at(k).magma_level; + if (finder->magma_max != embark_assist::defs::magma_ranges::NA && + static_cast(finder->magma_max) < magma_level) return false; + } + } + // Biomes biomes[survey_results->at(x).at(y).biome[mlt->at(i).at(k).biome_offset]] = true; @@ -292,6 +312,14 @@ namespace embark_assist { // Thralling if (finder->thralling == embark_assist::defs::yes_no_ranges::Yes && !thralling_found) return false; + // Spires + if (finder->spire_count_min != -1 && finder->spire_count_min > spire_count) return false; + if (finder->spire_count_max != -1 && finder->spire_count_max < spire_count) return false; + + // Magma + if (// finder->magma_min != embark_assist::defs::magma_ranges::NA && // This check is redundant. + finder->magma_min > static_cast(magma_level)) return false; + // Biomes if (finder->biome_count_min != -1 || finder->biome_count_max != -1) { @@ -612,6 +640,8 @@ namespace embark_assist { break; } + // Spire Count Min/Max + // Magma Min/Max // Biome Count Min (Can't do anything with Max at this level) if (finder->biome_count_min > tile->biome_count) return false; @@ -964,6 +994,8 @@ namespace embark_assist { break; } + // Spire Count Min/Max + // Magma Min/Max // Biome Count Min (Can't do anything with Max at this level) if (finder->biome_count_min > tile->biome_count) return false; @@ -1304,6 +1336,18 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter return 0; } + if (iterator->finder.spire_count_max < iterator->finder.spire_count_min && + iterator->finder.spire_count_max != -1) { + out.printerr("matcher::find: Will never find any matches with max spires < min spires\n"); + return 0; + } + + if (iterator->finder.magma_max < iterator->finder.magma_min && + iterator->finder.magma_max != embark_assist::defs::magma_ranges::NA) { + out.printerr("matcher::find: Will never find any matches with max magma < min magma\n"); + return 0; + } + if (iterator->finder.biome_count_max < iterator->finder.biome_count_min && iterator->finder.biome_count_max != -1) { out.printerr("matcher::find: Will never find any matches with max biomes < min biomes\n"); diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 15fd3883a..d11952ba9 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -14,6 +14,10 @@ #include "df/creature_interaction_effect_display_symbolst.h" #include "df/creature_interaction_effect_type.h" #include "df/feature_init.h" +#include "df/feature_init_deep_special_tubest.h" +#include "df/feature_init_magma_poolst.h" +#include "df/feature_init_volcanost.h" +#include "df/feature_type.h" #include "df/inorganic_flags.h" #include "df/inorganic_raw.h" #include "df/interaction.h" @@ -577,12 +581,33 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data base_z = elevation - 1; features = details->features[i][k]; std::map layer_bottom, layer_top; + mlt->at(i).at(k).adamantine_level = -1; + mlt->at(i).at(k).magma_level = -1; end_check_l = static_cast(features.size()); for (size_t l = 0; l < end_check_l; l++) { auto feature = features[l]; - if (feature->layer != -1 && + if (feature->feature_idx != -1) { + switch (world_data->feature_map[x / 16][y / 16].features->feature_init[x % 16][y % 16][feature->feature_idx]->getType()) + { + case df::feature_type::deep_special_tube: + mlt->at(i).at(k).adamantine_level = world_data->feature_map[x / 16][y / 16].features->feature_init[x % 16][y % 16][feature->feature_idx]->start_depth; + break; + + case df::feature_type::magma_pool: + mlt->at(i).at(k).magma_level = 2 - world_data->feature_map[x / 16][y / 16].features->feature_init[x % 16][y % 16][feature->feature_idx]->start_depth; + break; + + case df::feature_type::volcano: + mlt->at(i).at(k).magma_level = 3; + break; + + default: + break; + } + } + else if (feature->layer != -1 && feature->min_z != -30000) { auto layer = world_data->underground_regions[feature->layer]; @@ -592,7 +617,6 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data if (layer->type == df::world_underground_region::MagmaSea) { min_z = feature->min_z; // The features are individual per region tile - break; } } } From 578d6666eb9195c811af1ca6eeca02165820d1c1 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 27 Feb 2018 10:46:06 +0100 Subject: [PATCH 0848/1012] Addressed request. Improved profile saving --- build/win64/install-debug.bat | 2 +- plugins/embark-assistant/defs.h | 1 + plugins/embark-assistant/embark-assistant.cpp | 2 +- plugins/embark-assistant/finder_ui.cpp | 86 ++++++++++++------- plugins/embark-assistant/help_ui.cpp | 41 ++++----- 5 files changed, 80 insertions(+), 52 deletions(-) diff --git a/build/win64/install-debug.bat b/build/win64/install-debug.bat index 64b5ae850..0b8758461 100644 --- a/build/win64/install-debug.bat +++ b/build/win64/install-debug.bat @@ -1,4 +1,4 @@ -call "D:\Program (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 cd VC2015 msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj cd .. \ No newline at end of file diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index bea91dae6..3196bd5ee 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -162,6 +162,7 @@ namespace embark_assist { Major }; +// For possible future use. That's the level of data actually collected. // enum class adamantine_ranges : int8_t { // NA = -1, // Cavern_1, diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 7e0ea21cc..bd70df06a 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -143,7 +143,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->current_value); - fprintf(outfile, "[%s:%i]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->current_value); + for (int k = 0; k < state->ui[static_cast(i)]->list.size(); k++) { + if (state->ui[static_cast(i)]->current_value == state->ui[static_cast(i)]->list[k].key) { + fprintf(outfile, "[%s:%s]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->list[k].text.c_str()); + break; + } + } +// fprintf(outfile, "[%s:%i]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->current_value); if (i == last_fields) { break; // done } @@ -178,40 +183,46 @@ namespace embark_assist { char line[80]; int count = 80; bool found; - int value; while (true) { fgets(line, count, infile); if (line[0] != '[') { out.printerr("Failed to find token start '[' at line %i\n", static_cast(i)); + fclose(infile); return; } - found = false; - for (int k = 1; k < count; k++) { if (line[k] == ':') { for (int l = 1; l < k; l++) { if (state->finder_list[static_cast(i)].text.c_str()[l - 1] != line[l]) { out.printerr("Token mismatch of %s vs %s\n", line, state->finder_list[static_cast(i)].text.c_str()); + fclose(infile); return; } } - if (!sscanf(&line[k + 1], "%i]", &value)) { - out.printerr("Value extraction failure from %s\n", line); - return; - } + + found = false; for (int l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { - if (value == state->ui[static_cast(i)]->list[l].key) { - found = true; + for (int m = k + 1; m < count; m++) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' && + line[m] == ']') { + found = true; + } + break; + } + } + if (found) { break; } } if (!found) { - out.printerr("Value not found in plugin. Raw mismatch? %s\n", line); + out.printerr("Value extraction failure from %s\n", line); + fclose(infile); return; } @@ -221,6 +232,7 @@ namespace embark_assist { if (!found) { out.printerr("Value delimiter not found in %s\n", line); + fclose(infile); return; } @@ -243,13 +255,23 @@ namespace embark_assist { for (int k = 1; k < count; k++) { if (line[k] == ':') { - sscanf(&line[k + 1], "%i]", &value); - state->ui[static_cast(i)]->current_value = value; - + found = false; + for (int l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { - if (value == state->ui[static_cast(i)]->list[l].key) { - state->ui[static_cast(i)]->current_display_value = l; + for (int m = k + 1; m < count; m++) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' && + line[m] == ']') { + state->ui[static_cast(i)]->current_value = state->ui[static_cast(i)]->list[l].key; + state->ui[static_cast(i)]->current_display_value = l; + found = true; + } + + break; + } + } + if (found) { break; } } @@ -577,15 +599,15 @@ namespace embark_assist { break; case embark_assist::defs::magma_ranges::Cavern_3: - element->list.push_back({ "3:rd Cavern", static_cast(k) }); + element->list.push_back({ "Third Cavern", static_cast(k) }); break; case embark_assist::defs::magma_ranges::Cavern_2: - element->list.push_back({ "2:nd Cavern", static_cast(k) }); + element->list.push_back({ "Second Cavern", static_cast(k) }); break; case embark_assist::defs::magma_ranges::Cavern_1: - element->list.push_back({ "1:st Cavern", static_cast(k) }); + element->list.push_back({ "Third Cavern", static_cast(k) }); break; case embark_assist::defs::magma_ranges::Volcano: @@ -1265,20 +1287,24 @@ namespace embark_assist { Screen::clear(); Screen::drawBorder("Embark Assistant Site Finder"); - - embark_assist::screen::paintString(lr_pen, 1, 1, "4/6"); - embark_assist::screen::paintString(white_pen, 4, 1, ":<->"); - embark_assist::screen::paintString(lr_pen, 9, 1, "8/2"); + + embark_assist::screen::paintString(lr_pen, 1, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_LEFT).c_str()); + embark_assist::screen::paintString(white_pen, 2, 1, "/"); + embark_assist::screen::paintString(lr_pen, 3, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_RIGHT).c_str()); + embark_assist::screen::paintString(white_pen, 4, 1, ":\x1b/\x1a"); + embark_assist::screen::paintString(lr_pen, 9, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_UP).c_str()); + embark_assist::screen::paintString(white_pen, 10, 1, "/"); + embark_assist::screen::paintString(lr_pen, 11, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_DOWN).c_str()); embark_assist::screen::paintString(white_pen, 12, 1, ":Up/Down"); - embark_assist::screen::paintString(lr_pen, 21, 1, "ENTER"); + embark_assist::screen::paintString(lr_pen, 21, 1, DFHack::Screen::getKeyDisplay(df::interface_key::SELECT).c_str()); embark_assist::screen::paintString(white_pen, 26, 1, ":Select"); - embark_assist::screen::paintString(lr_pen, 34, 1, "f"); + embark_assist::screen::paintString(lr_pen, 34, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str()); embark_assist::screen::paintString(white_pen, 35, 1, ":Find"); - embark_assist::screen::paintString(lr_pen, 41, 1, "ESC"); + embark_assist::screen::paintString(lr_pen, 41, 1, DFHack::Screen::getKeyDisplay(df::interface_key::LEAVESCREEN).c_str()); embark_assist::screen::paintString(white_pen, 44, 1, ":Abort"); - embark_assist::screen::paintString(lr_pen, 51, 1, "s"); + embark_assist::screen::paintString(lr_pen, 51, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_S).c_str()); embark_assist::screen::paintString(white_pen, 52, 1, ":Save"); - embark_assist::screen::paintString(lr_pen, 58, 1, "l"); + embark_assist::screen::paintString(lr_pen, 58, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_L).c_str()); embark_assist::screen::paintString(white_pen, 59, 1, ":Load"); for (uint16_t i = 0; i < state->finder_list.size(); i++) { diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index 31a24e891..fb5d05969 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -189,7 +189,7 @@ namespace embark_assist{ help_text.push_back("f to activate the Find functionality using the values in the middle column."); help_text.push_back("ESC to leave the screen without activating a Find operation."); help_text.push_back("s/l is used to save/load search profile to/from embark_assistant_profile.txt"); - help_text.push_back("stored in .\\data\\init. There's some minor error detection that will refuse"); + help_text.push_back("stored in ./data/init. There's some minor error detection that will refuse"); help_text.push_back("to load a file that doesn't check out."); help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF"); help_text.push_back("itself these parameters are initiated to match the actual embark rectangle"); @@ -253,20 +253,21 @@ namespace embark_assist{ help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); help_text.push_back("- Right world map overlay not implemented as author has failed to"); help_text.push_back(" emulate the sizing logic exactly."); - help_text.push_back("- There's currently a DF bug that causes adamantine spires reaching"); - help_text.push_back(" that have been removed at world gen to fail to be generated. It's likely"); - help_text.push_back(" this bug also affects magma pools. This plugin does not address this."); + help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires"); + help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); + help_text.push_back(" generated. It's likely this bug also affects magma pools."); + help_text.push_back(" This plugin does not address this but scripts can correct it."); help_text.push_back("Version 0.3 2018-02-26"); break; } // Add control keys to first line. - embark_assist::screen::paintString(pen_lr, 1, 1, "TAB"); + embark_assist::screen::paintString(pen_lr, 1, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CHANGETAB).c_str()); embark_assist::screen::paintString(pen, 4, 1, "/"); - embark_assist::screen::paintString(pen_lr, 5, 1, "Shift-TAB"); + embark_assist::screen::paintString(pen_lr, 5, 1, DFHack::Screen::getKeyDisplay(df::interface_key::SEC_CHANGETAB).c_str()); embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page"); - embark_assist::screen::paintString(pen_lr, 34, 1, "ESC"); + embark_assist::screen::paintString(pen_lr, 34, 1, DFHack::Screen::getKeyDisplay(df::interface_key::LEAVESCREEN).c_str()); embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help"); for (uint16_t i = 0; i < help_text.size(); i++) { @@ -275,10 +276,10 @@ namespace embark_assist{ switch (current_page) { case pages::Intro: - embark_assist::screen::paintString(pen_lr, 1, 26, "i"); - embark_assist::screen::paintString(pen_lr, 1, 27, "f"); - embark_assist::screen::paintString(pen_lr, 1, 28, "c"); - embark_assist::screen::paintString(pen_lr, 1, 30, "q"); + embark_assist::screen::paintString(pen_lr, 1, 26, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 27, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 28, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_C).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 30, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_Q).c_str()); break; case pages::General: @@ -293,15 +294,15 @@ namespace embark_assist{ break; case pages::Finder: - embark_assist::screen::paintString(pen_lr, 1, 4, "4"); - embark_assist::screen::paintString(pen_lr, 3, 4, "6"); - embark_assist::screen::paintString(pen_lr, 1, 5, "8"); - embark_assist::screen::paintString(pen_lr, 3, 5, "2"); - embark_assist::screen::paintString(pen_lr, 1, 6, "ENTER"); - embark_assist::screen::paintString(pen_lr, 1, 7, "f"); - embark_assist::screen::paintString(pen_lr, 1, 8, "ESC"); - embark_assist::screen::paintString(pen_lr, 1, 9, "s"); - embark_assist::screen::paintString(pen_lr, 3, 9, "l"); + embark_assist::screen::paintString(pen_lr, 1, 4, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_LEFT).c_str()); + embark_assist::screen::paintString(pen_lr, 3, 4, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_RIGHT).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 5, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_UP).c_str()); + embark_assist::screen::paintString(pen_lr, 3, 5, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_DOWN).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 6, DFHack::Screen::getKeyDisplay(df::interface_key::SELECT).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 7, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 8, DFHack::Screen::getKeyDisplay(df::interface_key::LEAVESCREEN).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 9, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_S).c_str()); + embark_assist::screen::paintString(pen_lr, 3, 9, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_L).c_str()); break; case pages::Caveats: From 265e229d613cc9b81c27d53d12066880a702d89c Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 27 Feb 2018 19:58:07 +0530 Subject: [PATCH 0849/1012] Send all relavent generated instruement information over RFR --- library/xml | 2 +- plugins/proto/ItemdefInstrument.proto | 104 +++++++++++++++++++ plugins/proto/RemoteFortressReader.proto | 3 + plugins/remotefortressreader/CMakeLists.txt | 1 + plugins/remotefortressreader/item_reader.cpp | 79 ++++++++++++++ 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 plugins/proto/ItemdefInstrument.proto diff --git a/library/xml b/library/xml index 2794f8a6d..5fea1ef62 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2794f8a6d7405d4858bac486a0bb17b94740c142 +Subproject commit 5fea1ef62a040d24a9efa31a9a6dfd8eb1eea3f6 diff --git a/plugins/proto/ItemdefInstrument.proto b/plugins/proto/ItemdefInstrument.proto new file mode 100644 index 000000000..977e86f90 --- /dev/null +++ b/plugins/proto/ItemdefInstrument.proto @@ -0,0 +1,104 @@ +package ItemdefInstrument; + +//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization +option optimize_for = LITE_RUNTIME; + +message InstrumentFlags + { + optional bool indefinite_pitch = 1; + optional bool placed_as_building = 2; + optional bool metal_mat = 3; + optional bool stone_mat = 4; + optional bool wood_mat = 5; + optional bool glass_mat = 6; + optional bool ceramic_mat = 7; + optional bool shell_mat = 8; + optional bool bone_mat = 9; +} + +enum PitchChoiceType +{ + MEMBRANE_POSITION = 0; + SUBPART_CHOICE = 1; + KEYBOARD = 2; + STOPPING_FRET = 3; + STOPPING_AGAINST_BODY = 4; + STOPPING_HOLE = 5; + STOPPING_HOLE_KEY = 6; + SLIDE = 7; + HARMONIC_SERIES = 8; + VALVE_ROUTES_AIR = 9; + BP_IN_BELL = 10; + FOOT_PEDALS = 11; +} + +enum SoundProductionType +{ + PLUCKED_BY_BP = 0; + PLUCKED = 1; + BOWED = 2; + STRUCK_BY_BP = 3; + STRUCK = 4; + VIBRATE_BP_AGAINST_OPENING = 5; + BLOW_AGAINST_FIPPLE = 6; + BLOW_OVER_OPENING_SIDE = 7; + BLOW_OVER_OPENING_END = 8; + BLOW_OVER_SINGLE_REED = 9; + BLOW_OVER_DOUBLE_REED = 10; + BLOW_OVER_FREE_REED = 11; + STRUCK_TOGETHER = 12; + SHAKEN = 13; + SCRAPED = 14; + FRICTION = 15; + RESONATOR = 16; + BAG_OVER_REED = 17; + AIR_OVER_REED = 18; + AIR_OVER_FREE_REED = 19; + AIR_AGAINST_FIPPLE = 20; +} + +enum TuningType +{ + PEGS = 0; + ADJUSTABLE_BRIDGES = 1; + CROOKS = 2; + TIGHTENING = 4; + LEVERS = 5; +} + +message InstrumentPiece +{ + optional string type = 1; + optional string id = 2; + optional string name = 3; + optional string name_plural = 4; +} + +message InstrumentRegister +{ + optional int32 pitch_range_min = 1; + optional int32 pitch_range_max = 2; +} + +message InstrumentDef +{ + optional InstrumentFlags flags = 1; + optional int32 size = 2; + optional int32 value = 3; + optional int32 material_size = 4; + repeated InstrumentPiece pieces = 5; + optional int32 pitch_range_min = 6; + optional int32 pitch_range_max = 7; + optional int32 volume_mb_min = 8; + optional int32 volume_mb_max = 9; + repeated SoundProductionType sound_production = 10; + repeated string sound_production_parm1 = 11; + repeated string sound_production_parm2 = 12; + repeated PitchChoiceType pitch_choice = 13; + repeated string pitch_choice_parm1 = 14; + repeated string pitch_choice_parm2 = 15; + repeated TuningType tuning = 16; + repeated string tuning_parm = 17; + repeated InstrumentRegister registers = 18; + optional string description = 19; +} \ No newline at end of file diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 35f5b0abe..b2e5eae4d 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -3,6 +3,8 @@ package RemoteFortressReader; //Attempts to provide a complete framework for reading everything from a fortress needed for vizualization option optimize_for = LITE_RUNTIME; +import "ItemdefInstrument.proto"; + //We use shapes, etc, because the actual tiletypes may differ between DF versions. enum TiletypeShape { @@ -329,6 +331,7 @@ message MaterialDefinition{ optional string id = 2; optional string name = 3; optional ColorDefinition state_color = 4; //Simplifying colors to assume room temperature. + optional ItemdefInstrument.InstrumentDef instrument = 5; } message BuildingType diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 10cc21047..ad61c94ce 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -17,6 +17,7 @@ SET(PROJECT_HDRS SET(PROJECT_PROTO ../../proto/RemoteFortressReader ../../proto/AdventureControl + ../../proto/ItemdefInstrument ) SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 59b1f36e7..2adcce67f 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -1,6 +1,7 @@ #include "item_reader.h" #include "Core.h" #include "VersionInfo.h" +#include "ItemdefInstrument.pb.h" #include "df/art_image.h" #include "df/art_image_chunk.h" @@ -16,6 +17,8 @@ #include "df/art_image_property_transitive_verbst.h" #include "df/art_image_ref.h" #include "df/descriptor_shape.h" +#include "df/instrument_piece.h" +#include "df/instrument_register.h" #include "df/item_type.h" #include "df/item_constructed.h" #include "df/item_gemst.h" @@ -23,6 +26,7 @@ #include "df/item_statuest.h" #include "df/item_threadst.h" #include "df/item_toolst.h" +#include "df/itemdef_instrumentst.h" #include "df/itemimprovement.h" #include "df/itemimprovement_art_imagest.h" #include "df/itemimprovement_bandsst.h" @@ -46,6 +50,7 @@ using namespace DFHack; using namespace df::enums; using namespace RemoteFortressReader; +using namespace ItemdefInstrument; using namespace std; using namespace df::global; @@ -565,6 +570,80 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: mat_def->mutable_mat_pair()->set_mat_index(i); df::itemdef * item = Items::getSubtypeDef(it, i); mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + item->id); + switch (it) + { + case df::enums::item_type::INSTRUMENT: + { + VIRTUAL_CAST_VAR(instrument, df::itemdef_instrumentst, item); + mat_def->set_name(instrument->name); + auto send_instrument = mat_def->mutable_instrument(); + auto flags = send_instrument->mutable_flags(); + flags->set_indefinite_pitch(instrument->flags.is_set(instrument_flags::INDEFINITE_PITCH)); + flags->set_placed_as_building(instrument->flags.is_set(instrument_flags::PLACED_AS_BUILDING)); + flags->set_metal_mat(instrument->flags.is_set(instrument_flags::METAL_MAT)); + flags->set_stone_mat(instrument->flags.is_set(instrument_flags::STONE_MAT)); + flags->set_wood_mat(instrument->flags.is_set(instrument_flags::WOOD_MAT)); + flags->set_glass_mat(instrument->flags.is_set(instrument_flags::GLASS_MAT)); + flags->set_ceramic_mat(instrument->flags.is_set(instrument_flags::CERAMIC_MAT)); + flags->set_shell_mat(instrument->flags.is_set(instrument_flags::SHELL_MAT)); + flags->set_bone_mat(instrument->flags.is_set(instrument_flags::BONE_MAT)); + send_instrument->set_size(instrument->size); + send_instrument->set_value(instrument->value); + send_instrument->set_material_size(instrument->material_size); + for (int j = 0; j < instrument->pieces.size(); j++) + { + auto piece = send_instrument->add_pieces(); + piece->set_type(instrument->pieces[i]->type); + piece->set_id(instrument->pieces[i]->id); + piece->set_name(instrument->pieces[i]->name); + piece->set_name_plural(instrument->pieces[i]->name_plural); + } + send_instrument->set_pitch_range_min(instrument->pitch_range_min); + send_instrument->set_pitch_range_max(instrument->pitch_range_max); + for (int j = 0; j < instrument->sound_production.size(); j++) + { + send_instrument->add_sound_production((SoundProductionType)instrument->sound_production[j]); + } + for (int j = 0; j < instrument->sound_production_parm1.size(); j++) + { + send_instrument->add_sound_production_parm1(*(instrument->sound_production_parm1[j])); + } + for (int j = 0; j < instrument->sound_production_parm2.size(); j++) + { + send_instrument->add_sound_production_parm2(*(instrument->sound_production_parm2[j])); + } + for (int j = 0; j < instrument->pitch_choice.size(); j++) + { + send_instrument->add_pitch_choice((PitchChoiceType)instrument->pitch_choice[j]); + } + for (int j = 0; j < instrument->pitch_choice_parm1.size(); j++) + { + send_instrument->add_pitch_choice_parm1(*(instrument->pitch_choice_parm1[j])); + } + for (int j = 0; j < instrument->pitch_choice_parm2.size(); j++) + { + send_instrument->add_pitch_choice_parm2(*(instrument->pitch_choice_parm2[j])); + } + for (int j = 0; j < instrument->tuning.size(); j++) + { + send_instrument->add_tuning((TuningType)instrument->tuning[j]); + } + for (int j = 0; j < instrument->tuning_parm.size(); j++) + { + send_instrument->add_tuning_parm(*(instrument->tuning_parm[j])); + } + for (int j = 0; j < instrument->registers.size(); j++) + { + auto reg = send_instrument->add_registers(); + reg->set_pitch_range_min(instrument->registers[j]->pitch_range_min); + reg->set_pitch_range_max(instrument->registers[j]->pitch_range_max); + } + send_instrument->set_description(instrument->description); + break; + } + default: + break; + } } } } From d758104f5e5280756af82acba96898cc5728129e Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 27 Feb 2018 20:24:32 +0530 Subject: [PATCH 0850/1012] update stonesense for RFR protos --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 031bf4caa..da3065a35 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 031bf4caafec321dd315202a83539d052ed49d84 +Subproject commit da3065a35362a8ae3190234792c8cd14fffac9dd From 65e549848df2ffc174074968e9d6113e6cef6cdb Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 27 Feb 2018 21:01:38 +0530 Subject: [PATCH 0851/1012] fix some crashes in the new instrument reader code. --- plugins/proto/ItemdefInstrument.proto | 4 ++-- plugins/remotefortressreader/item_reader.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/proto/ItemdefInstrument.proto b/plugins/proto/ItemdefInstrument.proto index 977e86f90..c92491ace 100644 --- a/plugins/proto/ItemdefInstrument.proto +++ b/plugins/proto/ItemdefInstrument.proto @@ -62,8 +62,8 @@ enum TuningType PEGS = 0; ADJUSTABLE_BRIDGES = 1; CROOKS = 2; - TIGHTENING = 4; - LEVERS = 5; + TIGHTENING = 3; + LEVERS = 4; } message InstrumentPiece diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 2adcce67f..70f499598 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -575,7 +575,7 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: case df::enums::item_type::INSTRUMENT: { VIRTUAL_CAST_VAR(instrument, df::itemdef_instrumentst, item); - mat_def->set_name(instrument->name); + mat_def->set_name(DF2UTF(instrument->name)); auto send_instrument = mat_def->mutable_instrument(); auto flags = send_instrument->mutable_flags(); flags->set_indefinite_pitch(instrument->flags.is_set(instrument_flags::INDEFINITE_PITCH)); @@ -593,10 +593,10 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: for (int j = 0; j < instrument->pieces.size(); j++) { auto piece = send_instrument->add_pieces(); - piece->set_type(instrument->pieces[i]->type); - piece->set_id(instrument->pieces[i]->id); - piece->set_name(instrument->pieces[i]->name); - piece->set_name_plural(instrument->pieces[i]->name_plural); + piece->set_type(instrument->pieces[j]->type); + piece->set_id(instrument->pieces[j]->id); + piece->set_name(DF2UTF(instrument->pieces[j]->name)); + piece->set_name_plural(DF2UTF(instrument->pieces[j]->name_plural)); } send_instrument->set_pitch_range_min(instrument->pitch_range_min); send_instrument->set_pitch_range_max(instrument->pitch_range_max); From 490fbd8b266bdf8a562f8d98b33b76d44174721f Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 27 Feb 2018 21:18:15 +0530 Subject: [PATCH 0852/1012] send instrument description using UTF8 --- plugins/remotefortressreader/item_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 70f499598..9adfbced2 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -638,7 +638,7 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: reg->set_pitch_range_min(instrument->registers[j]->pitch_range_min); reg->set_pitch_range_max(instrument->registers[j]->pitch_range_max); } - send_instrument->set_description(instrument->description); + send_instrument->set_description(DF2UTF(instrument->description)); break; } default: From ef24da0cd3cd1985d6ac8dcc5be1135e33ba4d99 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 27 Feb 2018 21:06:07 +0100 Subject: [PATCH 0853/1012] Key references on overlay as well --- plugins/embark-assistant/overlay.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index 6b63fa70d..4df74b18e 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -156,13 +156,13 @@ namespace embark_assist { Screen::Pen pen_lr(' ', COLOR_LIGHTRED); Screen::Pen pen_w(' ', COLOR_WHITE); - Screen::paintString(pen_lr, width - 28, 20, "i", false); + Screen::paintString(pen_lr, width - 28, 20, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str(), false); Screen::paintString(pen_w, width - 27, 20, ":Embark Assistant Info", false); - Screen::paintString(pen_lr, width - 28, 21, "f", false); + Screen::paintString(pen_lr, width - 28, 21, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str(), false); Screen::paintString(pen_w, width - 27, 21, ":Find Embark ", false); - Screen::paintString(pen_lr, width - 28, 22, "c", false); + Screen::paintString(pen_lr, width - 28, 22, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_C).c_str(), false); Screen::paintString(pen_w, width - 27, 22, ":Cancel/Clear Find", false); - Screen::paintString(pen_lr, width - 28, 23, "q", false); + Screen::paintString(pen_lr, width - 28, 23, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_Q).c_str(), false); Screen::paintString(pen_w, width - 27, 23, ":Quit Embark Assistant", false); Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles", false); Screen::paintString(empty_pen, width - 7, 25, to_string(state->match_count), false); From 3f26d4fe09636fd2c8cc31e13621e59c8fb98831 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sat, 3 Mar 2018 11:41:58 +0530 Subject: [PATCH 0854/1012] Add flows to RFR --- plugins/proto/RemoteFortressReader.proto | 31 +++++++++++++++++++ .../remotefortressreader.cpp | 24 ++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index b2e5eae4d..bf82c164c 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -313,6 +313,7 @@ message MapBlock repeated bool tile_dig_designation_marker = 27; repeated bool tile_dig_designation_auto = 28; repeated int32 grass_percent = 29; + repeated FlowInfo flows = 30; } message MatPair { @@ -1028,3 +1029,33 @@ enum ArtImageVerb VERB_BEINGMUTILATED = 46; VERB_TRIUMPHANTPOSE = 47; } + +enum FlowType +{ + Miasma = 0; + Steam = 1; + Mist = 2; + MaterialDust = 3; + MagmaMist = 4; + Smoke = 5; + Dragonfire = 6; + Fire = 7; + Web = 8; + MaterialGas = 9; + MaterialVapor = 10; + OceanWave = 11; + SeaFoam = 12; + ItemCloud = 13; +} + +message FlowInfo +{ + optional int32 index = 1; + optional FlowType type = 2; + optional int32 density = 3; + optional Coord pos = 4; + optional Coord dest = 5; + optional bool expanding = 6; + optional bool reuse = 7; + optional int32 guide_id = 8; +} diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index e321bf8d5..5b8cf89d5 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -58,6 +58,7 @@ #include "df/dfhack_material_category.h" #include "df/enabler.h" #include "df/engraving.h" +#include "df/flow_info.h" #include "df/graphic.h" #include "df/historical_figure.h" @@ -1395,6 +1396,29 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc } } +void CopyFlow(df::flow_info * localFlow, RemoteFortressReader::FlowInfo * netFlow, int index) +{ + //There's no consistent ID to use, so we just use the pointer. + netFlow->set_index((int)localFlow); + netFlow->set_type((FlowType)localFlow->type); + netFlow->set_density(localFlow->density); + ConvertDFCoord(localFlow->pos, netFlow->mutable_pos()); + ConvertDFCoord(localFlow->dest, netFlow->mutable_dest()); + netFlow->set_expanding(localFlow->expanding); + netFlow->set_reuse(localFlow->reuse); + netFlow->set_guide_id(localFlow->guide_id); +} + +void CopyFlows(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + for (int i = 0; i < DfBlock->flows.size(); i++) + { + CopyFlow(DfBlock->flows[i], NetBlock->add_flows(), i); + } +} static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) { From 894f6c232b406048602b4cbd27ab1f5d66c6bb70 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sat, 3 Mar 2018 12:48:09 +0530 Subject: [PATCH 0855/1012] actually copy flows with the block this time. --- .../remotefortressreader.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 5b8cf89d5..6876866fc 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1466,7 +1466,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in df::map_block * block = DFHack::Maps::getBlock(pos); if (block != NULL) { - int nonAir = 0; + bool nonAir = false; for (int xxx = 0; xxx < 16; xxx++) for (int yyy = 0; yyy < 16; yyy++) { @@ -1474,16 +1474,23 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) || block->designation[xxx][yyy].bits.flow_size > 0 || block->occupancy[xxx][yyy].bits.building > 0) - nonAir++; + { + nonAir = true; + goto ItsAir; + } } - if (nonAir > 0 || firstBlock) + ItsAir: + if (block->flows.size() > 0) + nonAir = true; + if (nonAir || firstBlock) { bool tileChanged = IsTiletypeChanged(pos); bool desChanged = IsDesignationChanged(pos); bool spatterChanged = IsspatterChanged(pos); bool itemsChanged = block->items.size() > 0; + bool flows = block->flows.size() > 0; RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged) + if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged || flows) net_block = out->add_map_blocks(); if (tileChanged) { @@ -1502,6 +1509,10 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in Copyspatters(block, net_block, &MC, pos); if (itemsChanged) CopyItems(block, net_block, &MC, pos); + if (flows) + { + CopyFlows(block, net_block); + } } } } From ebf96518966496542ba381239610bd879c3a76b4 Mon Sep 17 00:00:00 2001 From: Japa Mala Illo Date: Sun, 4 Mar 2018 02:41:40 +0530 Subject: [PATCH 0856/1012] Include Materials in flows. --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/remotefortressreader.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index bf82c164c..1bd05bcc3 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -1058,4 +1058,5 @@ message FlowInfo optional bool expanding = 6; optional bool reuse = 7; optional int32 guide_id = 8; + optional MatPair material = 9; } diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 6876866fc..0f7b1e55c 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1398,8 +1398,6 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc void CopyFlow(df::flow_info * localFlow, RemoteFortressReader::FlowInfo * netFlow, int index) { - //There's no consistent ID to use, so we just use the pointer. - netFlow->set_index((int)localFlow); netFlow->set_type((FlowType)localFlow->type); netFlow->set_density(localFlow->density); ConvertDFCoord(localFlow->pos, netFlow->mutable_pos()); @@ -1407,6 +1405,9 @@ void CopyFlow(df::flow_info * localFlow, RemoteFortressReader::FlowInfo * netFlo netFlow->set_expanding(localFlow->expanding); netFlow->set_reuse(localFlow->reuse); netFlow->set_guide_id(localFlow->guide_id); + auto mat = netFlow->mutable_material(); + mat->set_mat_index(localFlow->mat_index); + mat->set_mat_type(localFlow->mat_type); } void CopyFlows(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock) From c765e12254352466e9d53ad87a7c24a058047404 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 5 Mar 2018 19:00:18 +0530 Subject: [PATCH 0857/1012] Store tool names in RFR --- plugins/remotefortressreader/item_reader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 9adfbced2..762fc9270 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -27,6 +27,7 @@ #include "df/item_threadst.h" #include "df/item_toolst.h" #include "df/itemdef_instrumentst.h" +#include "df/itemdef_toolst.h" #include "df/itemimprovement.h" #include "df/itemimprovement_art_imagest.h" #include "df/itemimprovement_bandsst.h" @@ -641,6 +642,11 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: send_instrument->set_description(DF2UTF(instrument->description)); break; } + case df::enums::item_type::TOOL: + { + VIRTUAL_CAST_VAR(tool, df::itemdef_toolst, item); + mat_def->set_name(DF2UTF(tool->name)); + } default: break; } From c3f931652d05e859f1df9d5280fb9a0d59944415 Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 6 Mar 2018 09:44:21 +0530 Subject: [PATCH 0858/1012] Send item cloud info properly. --- plugins/proto/RemoteFortressReader.proto | 1 + .../remotefortressreader.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 1bd05bcc3..694fb4057 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -1059,4 +1059,5 @@ message FlowInfo optional bool reuse = 7; optional int32 guide_id = 8; optional MatPair material = 9; + optional MatPair item = 10; } diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 0f7b1e55c..028a39241 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -59,6 +59,8 @@ #include "df/enabler.h" #include "df/engraving.h" #include "df/flow_info.h" +#include "df/flow_guide.h" +#include "df/flow_guide_item_cloudst.h" #include "df/graphic.h" #include "df/historical_figure.h" @@ -1408,6 +1410,22 @@ void CopyFlow(df::flow_info * localFlow, RemoteFortressReader::FlowInfo * netFlo auto mat = netFlow->mutable_material(); mat->set_mat_index(localFlow->mat_index); mat->set_mat_type(localFlow->mat_type); + if (localFlow->guide_id >= 0 && localFlow->type == flow_type::ItemCloud) + { + auto guide = df::flow_guide::find(localFlow->guide_id); + if (guide) + { + VIRTUAL_CAST_VAR(cloud, df::flow_guide_item_cloudst, guide); + if (cloud) + { + mat->set_mat_index(cloud->mattype); + mat->set_mat_type(cloud->mattype); + auto item = netFlow->mutable_item(); + item->set_mat_index(cloud->item_subtype); + item->set_mat_type(cloud->item_type); + } + } + } } void CopyFlows(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock) From 3e7da4d5f2132d5dcbaa5cb88f98679cba1e91da Mon Sep 17 00:00:00 2001 From: Japa Date: Tue, 6 Mar 2018 22:37:26 +0530 Subject: [PATCH 0859/1012] FIx wrong material being sent with item flows. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 028a39241..eea2902f0 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1418,7 +1418,7 @@ void CopyFlow(df::flow_info * localFlow, RemoteFortressReader::FlowInfo * netFlo VIRTUAL_CAST_VAR(cloud, df::flow_guide_item_cloudst, guide); if (cloud) { - mat->set_mat_index(cloud->mattype); + mat->set_mat_index(cloud->matindex); mat->set_mat_type(cloud->mattype); auto item = netFlow->mutable_item(); item->set_mat_index(cloud->item_subtype); From acd4adc7ccabbe86a12526a09e1df50f8396a3da Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 8 Mar 2018 02:28:28 -0600 Subject: [PATCH 0860/1012] Fix CMake warning about CMP0022 policy being set as "OLD". --- library/CMakeLists.txt | 8 +++----- plugins/stonesense | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4d4a4e9f1..8b4712c73 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -1,10 +1,8 @@ PROJECT (dfapi) -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) # prevent CMake warnings about INTERFACE_LINK_LIBRARIES vs LINK_INTERFACE_LIBRARIES -IF(CMAKE_VERSION VERSION_GREATER "2.8.12") - CMAKE_POLICY(SET CMP0022 OLD) -ENDIF() +CMAKE_POLICY(SET CMP0022 NEW) ## build options OPTION(BUILD_DEVEL "Install/package files required for development (For SDK)." OFF) @@ -365,7 +363,7 @@ IF(APPLE) ENDIF() TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp dfhack-version ${PROJECT_LIBS}) -SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") +SET_TARGET_PROPERTIES(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "") TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) diff --git a/plugins/stonesense b/plugins/stonesense index 031bf4caa..5482849c5 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 031bf4caafec321dd315202a83539d052ed49d84 +Subproject commit 5482849c52dfac9f8200af3827d16bfb3fabb899 From 4ca12621a237bb216e9d48badf4c39f9433fa4c2 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 8 Mar 2018 11:38:46 -0600 Subject: [PATCH 0861/1012] Remove remaining CMP0022 usage --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58be5dff1..17cf86a0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,5 @@ # main project file. use it from a build sub-folder, see COMPILE for details -# prevent CMake warnings about INTERFACE_LINK_LIBRARIES vs LINK_INTERFACE_LIBRARIES -IF(CMAKE_VERSION VERSION_GREATER "2.8.12") - CMAKE_POLICY(SET CMP0022 OLD) -ENDIF() - # Set up build types if(CMAKE_CONFIGURATION_TYPES) SET(CMAKE_CONFIGURATION_TYPES Release RelWithDebInfo) From 7bbfd456d33b1ad821d424d941d1ff516b407ab3 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 8 Mar 2018 11:26:14 -0600 Subject: [PATCH 0862/1012] Fix compile errors on Ubuntu 18.04 Bionic Beaver --- CMakeLists.txt | 1 + depends/jsoncpp/jsoncpp.cpp | 2 ++ library/MiscUtils.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58be5dff1..d4bba06d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ IF(UNIX) # ensure compatibility with older CPUs # enable C++11 features add_definitions(-DLINUX_BUILD) + add_definitions(-D_GLIBCXX_USE_C99) SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -Wall -Wno-unused-variable") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -mtune=generic -std=c++0x") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -mtune=generic") diff --git a/depends/jsoncpp/jsoncpp.cpp b/depends/jsoncpp/jsoncpp.cpp index eb38217ef..009d04e7a 100644 --- a/depends/jsoncpp/jsoncpp.cpp +++ b/depends/jsoncpp/jsoncpp.cpp @@ -3922,8 +3922,10 @@ Value& Path::make(Value& root) const { #define isfinite finite #else #include +#ifndef isfinite // fix isfinite on Ubuntu 18.04 #define isfinite std::isfinite #endif +#endif #if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below #define snprintf _snprintf diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index e350cb9ec..91a860991 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -36,6 +36,7 @@ distribution. #include #include #include +#include #include #include From c260996d6186b9386f6dfa820df33fcc1ca7f99d Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 8 Mar 2018 12:26:10 -0600 Subject: [PATCH 0863/1012] Update required CMake version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17cf86a0b..bb3ab92cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ endif(CMAKE_CONFIGURATION_TYPES) OPTION(BUILD_DOCS "Choose whether to build the documentation (requires python and Sphinx)." OFF) ## some generic CMake magic -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) +cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(dfhack) macro(CHECK_GCC COMPILER_PATH) From a810960bde87f04a14675a6f61a67b03f5ebebfb Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 10 Mar 2018 21:02:28 +0530 Subject: [PATCH 0864/1012] Support spiderwebs and add a fake flow type for campfires. --- plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/item_reader.cpp | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 694fb4057..0b4ce1ac7 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -1046,6 +1046,7 @@ enum FlowType OceanWave = 11; SeaFoam = 12; ItemCloud = 13; + CampFire = -1; } message FlowInfo diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 762fc9270..eb4fb549c 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -332,6 +332,10 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); } + if (DfItem->flags.bits.spider_web) + type->set_mat_index(1); + else + type->set_mat_index(0); break; } case df::enums::item_type::CLOTH: @@ -553,13 +557,24 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: mat_def = out->add_material_list(); mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(0); - mat_def->set_id("BOX_CHEST"); + mat_def->set_id("BOX/CHEST"); mat_def = out->add_material_list(); mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(1); - mat_def->set_id("BOX_BAG"); + mat_def->set_id("BOX/BAG"); break; } + case df::enums::item_type::THREAD: + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("THREAD/NORMAL"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("THREAD/WEB"); + } } int subtypes = Items::getSubtypeCount(it); if (subtypes >= 0) From f2ff609e1c04d629e79da11d98caba7d2e43ef91 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Mar 2018 16:18:15 -0500 Subject: [PATCH 0865/1012] Initial 0.44.06 support, update xml --- CMakeLists.txt | 8 +++++--- library/include/df/custom/creature_handler.methods.inc | 1 + library/xml | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 library/include/df/custom/creature_handler.methods.inc diff --git a/CMakeLists.txt b/CMakeLists.txt index 58be5dff1..d8d35f450 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,12 +140,14 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.05") -set(DFHACK_RELEASE "r2") -set(DFHACK_PRERELEASE FALSE) +set(DF_VERSION "0.44.06") +set(DFHACK_RELEASE "alpha0") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") +set(DFHACK_ABI_VERSION 1) + ## where to install things (after the build is done, classic 'make install' or package structure) # the dfhack libraries will be installed here: IF(UNIX) diff --git a/library/include/df/custom/creature_handler.methods.inc b/library/include/df/custom/creature_handler.methods.inc new file mode 100644 index 000000000..811128d05 --- /dev/null +++ b/library/include/df/custom/creature_handler.methods.inc @@ -0,0 +1 @@ +friend struct world_raws; diff --git a/library/xml b/library/xml index 2794f8a6d..a6297f128 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2794f8a6d7405d4858bac486a0bb17b94740c142 +Subproject commit a6297f128686407819fb7fe1e1fddd96606f30a0 From db95796d4c634f09e162f17f14f1b692919f5994 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Mar 2018 16:53:45 -0500 Subject: [PATCH 0866/1012] Many build fixes --- library/lua/dfhack/workshops.lua | 4 ++-- library/modules/Materials.cpp | 8 +++---- library/xml | 2 +- plugins/add-spatter.cpp | 2 +- plugins/devel/dumpmats.cpp | 10 ++++----- plugins/dwarfmonitor.cpp | 4 ++-- plugins/embark-assistant/survey.cpp | 2 +- plugins/eventful.cpp | 2 +- plugins/labormanager/joblabormapper.cpp | 12 +++++----- plugins/lua/stockflow.lua | 4 ++-- plugins/lua/workflow.lua | 2 +- plugins/remotefortressreader/item_reader.cpp | 6 ++--- .../remotefortressreader.cpp | 22 +++++++++---------- scripts | 2 +- 14 files changed, 41 insertions(+), 41 deletions(-) diff --git a/library/lua/dfhack/workshops.lua b/library/lua/dfhack/workshops.lua index 09268ff38..a09849a57 100644 --- a/library/lua/dfhack/workshops.lua +++ b/library/lua/dfhack/workshops.lua @@ -476,7 +476,7 @@ local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2) end local function scanRawsReaction(buildingId,workshopId,customId) local ret={} - for idx,reaction in ipairs(df.global.world.raws.reactions) do + for idx,reaction in ipairs(df.global.world.raws.reactions.reactions) do for k,v in pairs(reaction.building.type) do if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then table.insert(ret,reaction) @@ -575,4 +575,4 @@ function getJobs(buildingId,workshopId,customId) --get jobs, add in from raws return ret end -return _ENV \ No newline at end of file +return _ENV diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 219932657..a8f10d7d2 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -733,7 +733,7 @@ bool Materials::ReadOthers(void) bool Materials::ReadDescriptorColors (void) { - size_t size = world->raws.language.colors.size(); + size_t size = world->raws.descriptors.colors.size(); color.clear(); if(size == 0) @@ -741,7 +741,7 @@ bool Materials::ReadDescriptorColors (void) color.reserve(size); for (size_t i = 0; i < size;i++) { - df::descriptor_color *c = world->raws.language.colors[i]; + df::descriptor_color *c = world->raws.descriptors.colors[i]; t_descriptor_color col; col.id = c->id; col.name = c->name; @@ -751,13 +751,13 @@ bool Materials::ReadDescriptorColors (void) color.push_back(col); } - size = world->raws.language.patterns.size(); + size = world->raws.descriptors.patterns.size(); alldesc.clear(); alldesc.reserve(size); for (size_t i = 0; i < size;i++) { t_matgloss mat; - mat.id = world->raws.language.patterns[i]->id; + mat.id = world->raws.descriptors.patterns[i]->id; alldesc.push_back(mat); } return true; diff --git a/library/xml b/library/xml index a6297f128..2e97088b1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a6297f128686407819fb7fe1e1fddd96606f30a0 +Subproject commit 2e97088b1b573ff8f1a6e73b3478838ec8c32b41 diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 1e835b8b6..3d2d89dd1 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -356,7 +356,7 @@ static bool find_reactions(color_ostream &out) reactions.clear(); products.clear(); - auto &rlist = world->raws.reactions; + auto &rlist = df::reaction::get_vector(); for (size_t i = 0; i < rlist.size(); i++) { diff --git a/plugins/devel/dumpmats.cpp b/plugins/devel/dumpmats.cpp index e50771a9f..23c7be4e1 100644 --- a/plugins/devel/dumpmats.cpp +++ b/plugins/devel/dumpmats.cpp @@ -59,10 +59,10 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) { def_color[matter_state::Liquid] = solid_color; def_color[matter_state::Gas] = solid_color; - out.print("\t[STATE_COLOR:ALL:%s]\n", world->raws.language.colors[solid_color]->id.c_str()); + out.print("\t[STATE_COLOR:ALL:%s]\n", world->raws.descriptors.colors[solid_color]->id.c_str()); } else - out.print("\t[STATE_COLOR:ALL_SOLID:%s]\n", world->raws.language.colors[solid_color]->id.c_str()); + out.print("\t[STATE_COLOR:ALL_SOLID:%s]\n", world->raws.descriptors.colors[solid_color]->id.c_str()); } string solid_name = mat->state_name[matter_state::Solid]; @@ -141,7 +141,7 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) FOR_ENUM_ITEMS(matter_state, state) { if (mat->state_color[state] != -1 && mat->state_color[state] != def_color[state]) - out.print("\t[STATE_COLOR:%s:%s]\n", state_names[state], world->raws.language.colors[mat->state_color[state]]->id.c_str()); + out.print("\t[STATE_COLOR:%s:%s]\n", state_names[state], world->raws.descriptors.colors[mat->state_color[state]]->id.c_str()); if (mat->state_name[state] == mat->state_adj[state]) { if (mat->state_name[state].size() && mat->state_name[state] != def_name[state] || mat->state_adj[state].size() && mat->state_adj[state] != def_adj[state]) @@ -241,7 +241,7 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) if (mat->hardens_with_water.mat_type != -1) out.print("\t[HARDENS_WITH_WATER:%s:%s%s%s]\n", mat->hardens_with_water.str[0].c_str(), mat->hardens_with_water.str[1].c_str(), mat->hardens_with_water.str[2].size() ? ":" : "", mat->hardens_with_water.str[2].c_str()); if (mat->powder_dye != -1) - out.print("\t[POWDER_DYE:%s]\n", world->raws.language.colors[mat->powder_dye]->id.c_str()); + out.print("\t[POWDER_DYE:%s]\n", world->raws.descriptors.colors[mat->powder_dye]->id.c_str()); if (mat->soap_level != -0) out.print("\t[SOAP_LEVEL:%o]\n", mat->soap_level); @@ -262,4 +262,4 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector DFhackCExport command_result plugin_shutdown ( Core * c ) { return CR_OK; -} \ No newline at end of file +} diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index bf7c02efc..56ca53091 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1303,7 +1303,7 @@ struct preference_map } case (T_type::LikeShape): - label += "Shape :" + raws.language.shapes[pref.shape_id]->name_plural; + label += "Shape :" + raws.descriptors.shapes[pref.shape_id]->name_plural; break; case (T_type::LikeTree): @@ -1314,7 +1314,7 @@ struct preference_map } case (T_type::LikeColor): - label += "Color :" + raws.language.colors[pref.color_id]->name; + label += "Color :" + raws.descriptors.colors[pref.color_id]->name; break; case (T_type::LikePoeticForm): diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 6583d2479..5df070303 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -74,7 +74,7 @@ namespace embark_assist { bool geo_survey(embark_assist::defs::geo_data *geo_summary) { color_ostream_proxy out(Core::getInstance().getConsole()); df::world_data *world_data = world->world_data; - auto reactions = world->raws.reactions; + auto reactions = df::reaction::get_vector(); bool non_soil_found; uint16_t size; diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 365542314..f4056a3d5 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -397,7 +397,7 @@ static bool find_reactions(color_ostream &out) { reactions.clear(); - auto &rlist = world->raws.reactions; + auto &rlist = df::reaction::get_vector(); for (size_t i = 0; i < rlist.size(); i++) { diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index 779839e99..f47077134 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -583,11 +583,11 @@ class jlfunc_custom : public jlfunc public: df::unit_labor get_labor(df::job* j) { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + for (auto r : df::reaction::get_vector()) { - if ((*r)->code == j->reaction_name) + if (r->code == j->reaction_name) { - df::job_skill skill = (*r)->skill; + df::job_skill skill = r->skill; df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); return labor; } @@ -894,11 +894,11 @@ df::unit_labor JobLaborMapper::find_job_labor(df::job* j) { if (j->job_type == df::job_type::CustomReaction) { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + for (auto r : df::reaction::get_vector()) { - if ((*r)->code == j->reaction_name) + if (r->code == j->reaction_name) { - df::job_skill skill = (*r)->skill; + df::job_skill skill = r->skill; return ENUM_ATTR(job_skill, labor, skill); } } diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index fcaca0ecd..ca484d831 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -416,13 +416,13 @@ function collect_reactions() end for _, reaction_id in ipairs(entity.entity_raw.workshops.permitted_reaction_id) do - local reaction = df.global.world.raws.reactions[reaction_id] + local reaction = df.global.world.raws.reactions.reactions[reaction_id] local name = string.gsub(reaction.name, "^.", string.upper) reaction_entry(result, job_types.CustomReaction, {reaction_name = reaction.code}, name) end -- Reactions generated by the game. - for _, reaction in ipairs(df.global.world.raws.reactions) do + for _, reaction in ipairs(df.global.world.raws.reactions.reactions) do if reaction.source_enid == entity.id then local name = string.gsub(reaction.name, "^.", string.upper) reaction_entry(result, job_types.CustomReaction, {reaction_name = reaction.code}, name) diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index dcec1248d..6c4230a51 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -29,7 +29,7 @@ end local function get_reaction(name) if not reaction_id_cache then reaction_id_cache = {} - for i,v in ipairs(df.global.world.raws.reactions) do + for i,v in ipairs(df.global.world.raws.reactions.reactions) do reaction_id_cache[v.code] = i end end diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 59b1f36e7..5bfccbfad 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -221,7 +221,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) case df::enums::item_type::STATUE: { VIRTUAL_CAST_VAR(statue, df::item_statuest, DfItem); - + df::art_image_chunk * chunk = NULL; GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); if (GetArtImageChunk) @@ -518,9 +518,9 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: case df::enums::item_type::GEM: case df::enums::item_type::SMALLGEM: { - for (int i = 0; i < world->raws.language.shapes.size(); i++) + for (int i = 0; i < world->raws.descriptors.shapes.size(); i++) { - auto shape = world->raws.language.shapes[i]; + auto shape = world->raws.descriptors.shapes[i]; if (shape->gems_use.whole == 0) continue; mat_def = out->add_material_list(); diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index e321bf8d5..197c4bd6a 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -384,7 +384,7 @@ void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out) { - df::descriptor_color *color = world->raws.language.colors[index]; + df::descriptor_color *color = world->raws.descriptors.colors[index]; out->set_red(color->red * 255); out->set_green(color->green * 255); out->set_blue(color->blue * 255); @@ -859,7 +859,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) + if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); } @@ -877,7 +877,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(j); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) + if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); } @@ -894,7 +894,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); } @@ -916,9 +916,9 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage // id << "HF" << i << mat.getToken(); // mat_def->set_id(id.str()); // mat_def->set_name(mat.toString()); //find the name at cave temperature; - // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) + // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->descriptors.colors.size()) // { - // df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; + // df::descriptor_color *color = raws->descriptors.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; // mat_def->mutable_state_color()->set_red(color->red * 255); // mat_def->mutable_state_color()->set_green(color->green * 255); // mat_def->mutable_state_color()->set_blue(color->blue * 255); @@ -936,7 +936,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) + if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); } @@ -2683,13 +2683,13 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe for (int l = 0; l < orig_mod->pattern_index.size(); l++) { - auto orig_pattern = world->raws.language.patterns[orig_mod->pattern_index[l]]; + auto orig_pattern = world->raws.descriptors.patterns[orig_mod->pattern_index[l]]; auto send_pattern = send_mod->add_patterns(); for (int m = 0; m < orig_pattern->colors.size(); m++) { auto send_color = send_pattern->add_colors(); - auto orig_color = world->raws.language.colors[orig_pattern->colors[m]]; + auto orig_color = world->raws.descriptors.colors[orig_pattern->colors[m]]; send_color->set_red(orig_color->red * 255.0); send_color->set_green(orig_color->green * 255.0); send_color->set_blue(orig_color->blue * 255.0); @@ -2980,9 +2980,9 @@ static command_result GetLanguage(color_ostream & stream, const EmptyMessage * i if (!world) return CR_FAILURE; - for (int i = 0; i < world->raws.language.shapes.size(); i++) + for (int i = 0; i < world->raws.descriptors.shapes.size(); i++) { - auto shape = world->raws.language.shapes[i]; + auto shape = world->raws.descriptors.shapes[i]; auto netShape = out->add_shapes(); netShape->set_id(shape->id); netShape->set_tile(shape->tile); diff --git a/scripts b/scripts index 9d8f2096f..7935e1bef 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9d8f2096fc4c247299cbc94c6a8d756f31690a4b +Subproject commit 7935e1befd9d3009bf833829c4f5e0e8acb0c65b From eb22d5c38eefef4d3af5881ae8fec60f82c657d0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Mar 2018 16:55:00 -0500 Subject: [PATCH 0867/1012] Add ABI version symbol to plugins --- library/CMakeLists.txt | 1 + library/DFHackVersion.cpp | 4 ++++ library/PluginManager.cpp | 9 +++++++++ library/include/DFHackVersion.h | 2 ++ library/include/PluginManager.h | 2 ++ 5 files changed, 18 insertions(+) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4d4a4e9f1..9171c59f0 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -301,6 +301,7 @@ SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS DFHACK_VERSION="${DFHACK_VERSION}" DF_VERSION="${DF_VERSION}" DFHACK_RELEASE="${DFHACK_RELEASE}" + DFHACK_ABI_VERSION=${DFHACK_ABI_VERSION} ) IF(DFHACK_PRERELEASE) SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS diff --git a/library/DFHackVersion.cpp b/library/DFHackVersion.cpp index 9d367ae64..f4f0cf242 100644 --- a/library/DFHackVersion.cpp +++ b/library/DFHackVersion.cpp @@ -3,6 +3,10 @@ #include "git-describe.h" namespace DFHack { namespace Version { + int dfhack_abi_version() + { + return DFHACK_ABI_VERSION; + } const char *dfhack_version() { return DFHACK_VERSION; diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index b6d1dcc92..8e3f436cb 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -276,6 +276,7 @@ bool Plugin::load(color_ostream &con) plugin_check_symbol("plugin_name") plugin_check_symbol("plugin_version") + plugin_check_symbol("plugin_abi_version") plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_globals") @@ -287,11 +288,19 @@ bool Plugin::load(color_ostream &con) return false; } const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version"); + const int *plugin_abi_version = (int*) LookupPlugin(plug, "plugin_abi_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(); const char *dfhack_git_desc = Version::git_description(); const char *plug_git_desc = plug_git_desc_ptr ? *plug_git_desc_ptr : "unknown"; + if (*plugin_abi_version != Version::dfhack_abi_version()) + { + con.printerr("Plugin %s: ABI version mismatch (Plugin: %i, DFHack: %i)\n", + *plug_name, *plugin_abi_version, Version::dfhack_abi_version()); + plugin_abort_load; + return false; + } if (strcmp(dfhack_version, *plug_version) != 0) { con.printerr("Plugin %s was not built for this version of DFHack.\n" diff --git a/library/include/DFHackVersion.h b/library/include/DFHackVersion.h index afe8a03a0..c89b94ba5 100644 --- a/library/include/DFHackVersion.h +++ b/library/include/DFHackVersion.h @@ -4,6 +4,7 @@ namespace DFHack { const char *dfhack_version(); const char *df_version(); const char *dfhack_release(); + int dfhack_abi_version(); const char *git_description(); const char *git_commit(); const char *git_xml_commit(); @@ -18,6 +19,7 @@ namespace DFHack { #define DF_VERSION (DFHack::Version::df_version()) #define DFHACK_RELEASE (DFHack::Version::dfhack_release()) #define DFHACK_VERSION (DFHack::Version::dfhack_version()) + #define DFHACK_ABI_VERSION (DFHack::Version::dfhack_abi_version()) #define DFHACK_GIT_DESCRIPTION (DFHack::Version::git_description()) #define DFHACK_GIT_COMMIT (DFHack::Version::git_commit()) #define DFHACK_GIT_XML_COMMIT (DFHack::Version::git_xml_commit()) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index f7a8a7d87..c8776d8ed 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -61,6 +61,7 @@ namespace DFHack namespace Version { const char *dfhack_version(); const char *git_description(); + int dfhack_abi_version(); } // anon type, pretty much @@ -296,6 +297,7 @@ namespace DFHack DFhackDataExport const char * plugin_name = m_plugin_name;\ DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\ DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\ + DFhackDataExport int plugin_abi_version = DFHack::Version::dfhack_abi_version();\ DFhackDataExport DFHack::Plugin *plugin_self = NULL;\ std::vector _plugin_globals;\ DFhackDataExport std::vector* plugin_globals = &_plugin_globals; \ From 0ccc89a5fedeb3b0bcca76ec7eeb2e2d688600a4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Mar 2018 16:55:20 -0500 Subject: [PATCH 0868/1012] Make git-describe.cmake detect changes to itself --- library/git-describe.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/git-describe.cmake b/library/git-describe.cmake index bdde32ef9..f19b556d2 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -7,7 +7,8 @@ set(git_describe_h ${dfhack_SOURCE_DIR}/library/include/git-describe.h) if(EXISTS ${git_describe_tmp_h} AND NOT(${dfhack_SOURCE_DIR}/.git/index IS_NEWER_THAN ${git_describe_tmp_h}) AND - NOT(${dfhack_SOURCE_DIR}/.git/modules/library/xml/index IS_NEWER_THAN ${git_describe_tmp_h})) + NOT(${dfhack_SOURCE_DIR}/.git/modules/library/xml/index IS_NEWER_THAN ${git_describe_tmp_h}) AND + NOT(${dfhack_SOURCE_DIR}/library/git-describe.cmake IS_NEWER_THAN ${git_describe_tmp_h})) return() endif() From 25d29384777410deedbc2a613c1565c7957d39ac Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 11 Mar 2018 12:40:30 +0200 Subject: [PATCH 0869/1012] Fix parse_inset in gui.lua It was used in different order than the return happened. --- library/lua/gui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 3a829c529..fb125e690 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -105,7 +105,7 @@ local function parse_inset(inset) l = inset or 0 t,r,b = l,l,l end - return l,r,t,b + return l,t,r,b end function inset_frame(rect, inset, gap) From 40654954a1c9593deda3c5459a02054aa7ef0974 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 13 Mar 2018 15:49:42 -0500 Subject: [PATCH 0870/1012] Partial preparation for 0.44.07-alpha1. Still need to write release notes and add tags. --- CMakeLists.txt | 4 ++-- library/xml | 2 +- plugins/isoworld | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0a3f774a..848b3eee6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,8 +135,8 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.06") -set(DFHACK_RELEASE "alpha0") +set(DF_VERSION "0.44.07") +set(DFHACK_RELEASE "alpha1") set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/library/xml b/library/xml index 2e97088b1..dd558ca1e 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2e97088b1b573ff8f1a6e73b3478838ec8c32b41 +Subproject commit dd558ca1ea4645f14a7e5b83329d4f59d8414496 diff --git a/plugins/isoworld b/plugins/isoworld index 3ff3f05da..fbbf9e464 160000 --- a/plugins/isoworld +++ b/plugins/isoworld @@ -1 +1 @@ -Subproject commit 3ff3f05da53077dd9e67c1eef672427a2bfd95ea +Subproject commit fbbf9e46458e41707c27f2a4438452a579490db1 diff --git a/plugins/stonesense b/plugins/stonesense index 5482849c5..4c3728880 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 5482849c52dfac9f8200af3827d16bfb3fabb899 +Subproject commit 4c3728880a1b8d94046283be28d2078aac9fbe05 diff --git a/scripts b/scripts index 7935e1bef..be21a2a52 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7935e1befd9d3009bf833829c4f5e0e8acb0c65b +Subproject commit be21a2a52084e838e4b5eb1f296a03d5592389f8 From 09bc1c3169336f85155aca12454a65edc0d5b52b Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 14 Mar 2018 16:08:37 +0530 Subject: [PATCH 0871/1012] add ocean waves to RFR --- plugins/proto/RemoteFortressReader.proto | 7 +++++++ .../remotefortressreader.cpp | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 0b4ce1ac7..3298d42fb 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -439,6 +439,7 @@ message BlockList optional int32 map_x = 2; optional int32 map_y = 3; repeated Engraving engravings = 4; + repeated Wave ocean_waves = 5; } message PlantDef @@ -1062,3 +1063,9 @@ message FlowInfo optional MatPair material = 9; optional MatPair item = 10; } + +message Wave +{ + optional Coord dest = 1; + optional Coord pos = 2; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index eea2902f0..d30b0a67e 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -72,6 +72,7 @@ #include "df/material_vec_ref.h" #include "df/matter_state.h" #include "df/mental_attribute_type.h" +#include "df/ocean_wave.h" #include "df/physical_attribute_type.h" #include "df/plant.h" #include "df/plant_raw_flags.h" @@ -400,6 +401,14 @@ void ConvertDFCoord(DFCoord in, RemoteFortressReader::Coord * out) out->set_z(in.z); } +void ConvertDFCoord(int x, int y, int z, RemoteFortressReader::Coord * out) +{ + out->set_x(x); + out->set_y(y); + out->set_z(z); +} + + RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { switch (material) @@ -1607,6 +1616,13 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in netEngraving->set_southwest(engraving->flags.bits.southwest); netEngraving->set_southeast(engraving->flags.bits.southeast); } + for (int i = 0; i < world->ocean_waves.size(); i++) + { + auto wave = world->ocean_waves[i]; + auto netWave = out->add_ocean_waves(); + ConvertDFCoord(wave->x1, wave->y1, wave->z, netWave->mutable_dest()); + ConvertDFCoord(wave->x2, wave->y2, wave->z, netWave->mutable_pos()); + } MC.trash(); return CR_OK; } From 40d3b9b782bdbc07c393155bcb61222c7d53ff27 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Mar 2018 09:28:24 -0400 Subject: [PATCH 0872/1012] embark-assistant: fix copy-paste error, update docs (#1231) --- docs/Plugins.rst | 2 +- plugins/embark-assistant/finder_ui.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index da1231184..030aa9de1 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -536,7 +536,7 @@ This plugin provides embark site selection help. It has to be run with the extended (and correct(?)) resource information for the embark rectangle as well as normally undisplayed sites in the current embark region. It also has a site selection tool with more options than DF's vanilla search tool. For detailed -help invoke the in game info screen. Requires 42 lines to display properly. +help invoke the in game info screen. Requires 46 lines to display properly. .. _embark-tools: diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 2b33d8fe1..80380ca60 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -146,7 +146,7 @@ namespace embark_assist { void save_profile() { color_ostream_proxy out(Core::getInstance().getConsole()); - + FILE* outfile = fopen(profile_file_name, "w"); fields i = first_fields; @@ -607,7 +607,7 @@ namespace embark_assist { break; case embark_assist::defs::magma_ranges::Cavern_1: - element->list.push_back({ "Third Cavern", static_cast(k) }); + element->list.push_back({ "First Cavern", static_cast(k) }); break; case embark_assist::defs::magma_ranges::Volcano: @@ -1287,7 +1287,7 @@ namespace embark_assist { Screen::clear(); Screen::drawBorder("Embark Assistant Site Finder"); - + embark_assist::screen::paintString(lr_pen, 1, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_LEFT).c_str()); embark_assist::screen::paintString(white_pen, 2, 1, "/"); embark_assist::screen::paintString(lr_pen, 3, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_RIGHT).c_str()); From 3a2fc6f65fe46123d5359406349694da96cf68d8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Mar 2018 09:40:21 -0400 Subject: [PATCH 0873/1012] Update stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 4c3728880..0430344c7 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4c3728880a1b8d94046283be28d2078aac9fbe05 +Subproject commit 0430344c7bd8621b1af45bf27a8b6335dd89013e From 6f864f97b3edaf820bbbe17d68f9ec1fe1f917a7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Mar 2018 10:11:45 -0400 Subject: [PATCH 0874/1012] Update news, xml --- NEWS.rst | 28 ++++++++++++++++++++++++++++ docs/NEWS-dev.rst | 29 +++++++++++++++++++++++++++++ library/xml | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 17998f5e2..295013420 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,7 @@ Misc Improvements Removed Internals + Structures Lua Ruby @@ -36,6 +37,33 @@ Changelog .. contents:: :depth: 2 +DFHack future +============= + +Fixes +----- +- Support for building on Ubuntu 18.04 +- Fixed some CMake warnings +- `embark-assistant`: fixed detection of reanimating biomes + +Misc Improvements +----------------- +- `embark-assistant`: + + - Added search for adamantine + - Now supports saving/loading profiles + +- `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs + +Structures +---------- +- ``viewscreen_createquotast``: fixed layout +- ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors`` +- ``world.reactions``, ``world.reaction_categories``: moved to new compound, ``world.reactions``. Requires renaming: + + - ``world.reactions`` to ``world.reactions.reactions`` + - ``world.reaction_categories`` to ``world.reactions.reaction_categories`` + DFHack 0.44.05-r2 ================= diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 834282608..42f3147e9 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,35 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.07-alpha1 +===================== + +Fixes +----- +- `embark-assistant`: fixed detection of reanimating biomes + +Structures +---------- +- Several new names in instrument raw structures +- ``identity``: identified ``profession``, ``civ`` +- ``manager_order_template``: fixed last field type +- ``viewscreen_createquotast``: fixed layout +- ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors`` +- ``world.reactions``, ``world.reaction_categories``: moved to new compound, ``world.reactions``. Requires renaming: + + - ``world.reactions`` to ``world.reactions.reactions`` + - ``world.reaction_categories`` to ``world.reactions.reaction_categories`` + +Other Changes +------------- +- `embark-assistant`: + + - Added search for adamantine + - Now supports saving/loading profiles + +- `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs + + DFHack 0.44.05-alpha1 ===================== diff --git a/library/xml b/library/xml index dd558ca1e..abb25a269 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit dd558ca1ea4645f14a7e5b83329d4f59d8414496 +Subproject commit abb25a2692198d90c93a61ad70d34b12d9fcef00 From 81e3ee77225db9120fc4ccc9748adefe50072992 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Mar 2018 14:37:13 -0400 Subject: [PATCH 0875/1012] Document "fillneeds -all" in changelogs --- NEWS.rst | 1 + docs/NEWS-dev.rst | 1 + scripts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 295013420..a573ada2b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -53,6 +53,7 @@ Misc Improvements - Added search for adamantine - Now supports saving/loading profiles +- `fillneeds`: added ``-all`` option to apply to all units - `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs Structures diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 42f3147e9..f83d762e7 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -63,6 +63,7 @@ Other Changes - Added search for adamantine - Now supports saving/loading profiles +- `fillneeds`: added ``-all`` option to apply to all units - `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs diff --git a/scripts b/scripts index be21a2a52..cd8e1d747 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit be21a2a52084e838e4b5eb1f296a03d5592389f8 +Subproject commit cd8e1d747e081ba90f3c9a2335d3283289bb67b6 From 81c9c8cdfe69322c4eb35edc6853e9be89cb7ba5 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 20 Mar 2018 00:55:17 -0500 Subject: [PATCH 0876/1012] Avoid crashing labormanager if map square on lowest level is designated+hidden (#1240) --- plugins/labormanager/labormanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 5d6c5c971..3ef1808a2 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1075,8 +1075,7 @@ private: if (bl->designation[x][y].bits.hidden) { df::coord p = bl->map_pos; - df::coord c(p.x, p.y, p.z - 1); - if (Maps::getTileDesignation(c)->bits.hidden) + if (! Maps::isTileVisible(p.x, p.y, p.z-1)) continue; } From b8de7d4cf8b5378054fca850d8dcc49b61e2f1da Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 20 Mar 2018 01:44:10 -0500 Subject: [PATCH 0877/1012] check for tile material == MINERAL in digtype (#1243) --- plugins/dig.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 148da5482..d18176f5a 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -1522,6 +1522,8 @@ command_result digtype (color_ostream &out, vector & parameters) tt = mCache->tiletypeAt(current); if (!DFHack::isWallTerrain(tt)) continue; + if (tileMaterial(tt) != df::enums::tiletype_material::MINERAL) + continue; //designate it for digging df::tile_designation des = mCache->designationAt(current); From d369f8be674f72085dfc970f0c59adcd513c0b78 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 26 Mar 2018 19:07:04 +0530 Subject: [PATCH 0878/1012] Bump version number. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index e1282d600..5b7b0de7f 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.19.0" +#define RFR_VERSION "0.19.1" #include #include From 93bef3351e1553146428b3bc732d431c44ec2e2a Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 28 Mar 2018 21:55:04 -0400 Subject: [PATCH 0879/1012] Update submodules/news, bump to beta1 --- CMakeLists.txt | 2 +- NEWS.rst | 1 + docs/NEWS-dev.rst | 12 ++++++++++++ library/xml | 2 +- scripts | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 848b3eee6..17cce7e4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,7 +136,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.07") -set(DFHACK_RELEASE "alpha1") +set(DFHACK_RELEASE "beta1") set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/NEWS.rst b/NEWS.rst index a573ada2b..940dab63d 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -54,6 +54,7 @@ Misc Improvements - Now supports saving/loading profiles - `fillneeds`: added ``-all`` option to apply to all units +- `modtools/item-trigger`: added the ability to specify inventory mode(s) to trigger on - `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs Structures diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index f83d762e7..fc80e0284 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,18 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.07-beta1 +==================== + +Structures +---------- +- Added symbols for Toady's `0.44.07 Linux test build `_ to fix :bug:`10615` +- ``world_site``: fixed alignment + +Other Changes +------------- +- `modtools/item-trigger`: added the ability to specify inventory mode(s) to trigger on + DFHack 0.44.07-alpha1 ===================== diff --git a/library/xml b/library/xml index abb25a269..a7c151909 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit abb25a2692198d90c93a61ad70d34b12d9fcef00 +Subproject commit a7c151909b57c771498dda1ff7f586af7e176dcc diff --git a/scripts b/scripts index cd8e1d747..9f2231b6b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit cd8e1d747e081ba90f3c9a2335d3283289bb67b6 +Subproject commit 9f2231b6b73d6a1752588d57b3afe94876f197d2 From 2c9c935c0e2d0a3aa1428f6ebc52a1cb960b627f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 29 Mar 2018 11:38:55 -0400 Subject: [PATCH 0880/1012] Update submodules/news, bump to 0.44.08-alpha1 --- CMakeLists.txt | 4 ++-- NEWS.rst | 1 + docs/NEWS-dev.rst | 7 +++++++ library/xml | 2 +- scripts | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17cce7e4c..7ba0251a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,8 +135,8 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.07") -set(DFHACK_RELEASE "beta1") +set(DF_VERSION "0.44.08") +set(DFHACK_RELEASE "alpha1") set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/NEWS.rst b/NEWS.rst index 940dab63d..6b4dc3f12 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -45,6 +45,7 @@ Fixes - Support for building on Ubuntu 18.04 - Fixed some CMake warnings - `embark-assistant`: fixed detection of reanimating biomes +- `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units Misc Improvements ----------------- diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index fc80e0284..759badeb9 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,13 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.08-alpha1 +===================== + +Fixes +----- +- `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units + DFHack 0.44.07-beta1 ==================== diff --git a/library/xml b/library/xml index a7c151909..1d0be87a0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a7c151909b57c771498dda1ff7f586af7e176dcc +Subproject commit 1d0be87a0e3fee58c7fd2a76bf23d7d44b0076cd diff --git a/scripts b/scripts index 9f2231b6b..aee04c4a5 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9f2231b6b73d6a1752588d57b3afe94876f197d2 +Subproject commit aee04c4a55a74c49bdc1f405cc785eda9edda46d From 573fcb4bd0d44ef0baf1e03ccfb2351fb7bf7e01 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 01:00:20 -0400 Subject: [PATCH 0881/1012] Bump to 0.44.09-alpha1, update news/submodules --- CMakeLists.txt | 2 +- NEWS.rst | 2 ++ docs/NEWS-dev.rst | 8 ++++++++ library/xml | 2 +- scripts | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ba0251a3..f65a46149 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.08") +set(DF_VERSION "0.44.09") set(DFHACK_RELEASE "alpha1") set(DFHACK_PRERELEASE TRUE) diff --git a/NEWS.rst b/NEWS.rst index 6b4dc3f12..7bdcc7496 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -44,8 +44,10 @@ Fixes ----- - Support for building on Ubuntu 18.04 - Fixed some CMake warnings +- `digtype`: stopped designating non-vein tiles (open space, trees, etc.) - `embark-assistant`: fixed detection of reanimating biomes - `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units +- `labormanager`: fixed crash due to dig jobs targeting some unrevealed map blocks Misc Improvements ----------------- diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 759badeb9..a2c8abd20 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -37,6 +37,14 @@ Development Changelog .. contents:: :depth: 2 +DFHack 0.44.09-alpha1 +===================== + +Fixes +----- +- `digtype`: stopped designating non-vein tiles (open space, trees, etc.) +- `labormanager`: fixed crash due to dig jobs targeting some unrevealed map blocks + DFHack 0.44.08-alpha1 ===================== diff --git a/library/xml b/library/xml index 1d0be87a0..61a240140 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1d0be87a0e3fee58c7fd2a76bf23d7d44b0076cd +Subproject commit 61a240140332b9e3dbafbd8b3f0455c3bdd617a3 diff --git a/scripts b/scripts index aee04c4a5..4e3b9aa4e 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit aee04c4a55a74c49bdc1f405cc785eda9edda46d +Subproject commit 4e3b9aa4e32e0b552c01f9a1e4a6eaa3af0cdb58 From 476483adb3f9fbddb0427a839e8c4c5880f09687 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 13:43:32 -0400 Subject: [PATCH 0882/1012] Add a script to automatically generate both changelogs from a single file This avoids the need to manually copy entries between NEWS.rst and NEWS-dev.rst, and also helps fix some inconsistencies automatically. Pre-0.44.07 changelogs have not been converted yet. --- NEWS.rst | 1030 ----------------------------------------- conf.py | 4 + docs/NEWS-dev.rst | 398 +--------------- docs/NEWS.rst | 14 + docs/changelog.txt | 46 ++ docs/gen_changelog.py | 177 +++++++ index.rst | 2 +- 7 files changed, 245 insertions(+), 1426 deletions(-) delete mode 100644 NEWS.rst create mode 100644 docs/NEWS.rst create mode 100644 docs/changelog.txt create mode 100644 docs/gen_changelog.py diff --git a/NEWS.rst b/NEWS.rst deleted file mode 100644 index 7bdcc7496..000000000 --- a/NEWS.rst +++ /dev/null @@ -1,1030 +0,0 @@ -.. comment - This is the changelog file for DFHack. If you add or change anything, note - it here under the heading "DFHack Future", in the appropriate section. - Items within each section are listed in alphabetical order to minimise merge - conflicts. Try to match the style and level of detail of the other entries. - - This file should not contain details specific to prereleases, but it should - contain changes from previous stable releases. For example, if a bug was - introduced in one alpha version and fixed in another, do not include it - here. - - Sections for each release are added as required, and consist solely of the - following in order as subheadings:: - - New [Internal Commands | Plugins | Scripts | Tweaks | Features] - Fixes - Misc Improvements - Removed - Internals - Structures - Lua - Ruby - - When referring to a script, plugin, or command, use backticks (```) to - create a link to the relevant documentation - and check that the docs are - still up to date! - - When adding a new release, change "DFHack future" to the appropriate title - before releasing, and then add a new "DFHack future" section after releasing. - -.. _changelog: - -######### -Changelog -######### - -.. contents:: - :depth: 2 - -DFHack future -============= - -Fixes ------ -- Support for building on Ubuntu 18.04 -- Fixed some CMake warnings -- `digtype`: stopped designating non-vein tiles (open space, trees, etc.) -- `embark-assistant`: fixed detection of reanimating biomes -- `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units -- `labormanager`: fixed crash due to dig jobs targeting some unrevealed map blocks - -Misc Improvements ------------------ -- `embark-assistant`: - - - Added search for adamantine - - Now supports saving/loading profiles - -- `fillneeds`: added ``-all`` option to apply to all units -- `modtools/item-trigger`: added the ability to specify inventory mode(s) to trigger on -- `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs - -Structures ----------- -- ``viewscreen_createquotast``: fixed layout -- ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors`` -- ``world.reactions``, ``world.reaction_categories``: moved to new compound, ``world.reactions``. Requires renaming: - - - ``world.reactions`` to ``world.reactions.reactions`` - - ``world.reaction_categories`` to ``world.reactions.reaction_categories`` - -DFHack 0.44.05-r2 -================= - -New Plugins ------------ -- `embark-assistant`: adds more information and features to embark screen - -New Scripts ------------ -- `adv-fix-sleepers`: fixes units in adventure mode who refuse to wake up (:bug:`6798`) -- `hermit`: blocks caravans, migrants, diplomats (for hermit challenge) - -New Features ------------- -- With ``PRINT_MODE:TEXT``, setting the ``DFHACK_HEADLESS`` environment variable - will hide DF's display and allow the console to be used normally. (Note that - this is intended for testing and is not very useful for actual gameplay.) - -Fixes ------ -- `devel/export-dt-ini`: fix language_name offsets for DT 39.2+ -- `devel/inject-raws`: fixed gloves and shoes (old typo causing errors) -- `remotefortressreader`: fixed an issue with not all engravings being included -- `view-item-info`: fixed an error with some shields - -Misc Improvements ------------------ -- `adv-rumors`: added more keywords, including names -- `autochop`: can now exclude trees that produce fruit, food, or cookable items -- `remotefortressreader`: added plant type support - -DFHack 0.44.05-r1 -================= - -New Scripts ------------ -- `break-dance`: Breaks up a stuck dance activity -- `cannibalism`: Allows consumption of sapient corpses -- `devel/check-other-ids`: Checks the validity of "other" vectors in the - ``world`` global -- `fillneeds`: Use with a unit selected to make them focused and unstressed -- `firestarter`: Lights things on fire: items, locations, entire inventories even! -- `flashstep`: Teleports adventurer to cursor -- `ghostly`: Turns an adventurer into a ghost or back -- `gui/cp437-table`: An in-game CP437 table -- `questport`: Sends your adventurer to the location of your quest log cursor -- `view-unit-reports`: opens the reports screen with combat reports for the selected unit - -Fixes ------ -- Fixed issues with the console output color affecting the prompt on Windows -- `createitem`: stopped items from teleporting away in some forts -- `devel/inject-raws`: now recognizes spaces in reaction names -- `dig`: added support for designation priorities - fixes issues with - designations from ``digv`` and related commands having extremely high priority -- `dwarfmonitor`: - - - fixed display of creatures and poetic/music/dance forms on ``prefs`` screen - - added "view unit" option - - now exposes the selected unit to other tools - -- `gui/gm-unit`: can now edit mining skill -- `gui/quickcmd`: stopped error from adding too many commands -- `names`: fixed many errors -- `quicksave`: fixed an issue where the "Saving..." indicator often wouldn't appear - -Misc Improvements ------------------ -- The console now provides suggestions for built-in commands -- `binpatch`: now reports errors for empty patch files -- `devel/export-dt-ini`: avoid hardcoding flags -- `exportlegends`: - - - reordered some tags to match DF's order - - added progress indicators for exporting long lists - -- `force`: now provides useful help -- `full-heal`: - - - can now select corpses to resurrect - - now resets body part temperatures upon resurrection to prevent creatures - from freezing/melting again - - now resets units' vanish countdown to reverse effects of `exterminate` - -- `gui/gm-editor`: added enum names to enum edit dialogs -- `gui/gm-unit`: - - - made skill search case-insensitive - - added a profession editor - - misc. layout improvements - -- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards -- `gui/pathable`: added tile types to sidebar -- `gui/rename`: added "clear" and "special characters" options -- `launch`: can now ride creatures -- `modtools/skill-change`: - - - now updates skill levels appropriately - - only prints output if ``-loud`` is passed - -- `names`: can now edit names of units -- `remotefortressreader`: - - - support for moving adventurers - - suport for item stack sizes, vehicles, gem shapes, item volume, art images, item improvements - - some performance improvements - -Removed -------- -- `warn-stuck-trees`: :bug:`9252` fixed in DF 0.44.01 -- `tweak`: ``kitchen-keys``: :bug:`614` fixed in DF 0.44.04 - -Internals ---------- -- ``Gui::getAnyUnit()`` supports many more screens/menus -- New globals available: - - - ``version`` - - ``min_load_version`` - - ``movie_version`` - - ``basic_seed`` - - ``title`` - - ``title_spaced`` - - ``ui_building_resize_radius`` - - ``soul_next_id`` - -Lua ---- -- Added a new ``dfhack.console`` API -- Exposed ``get_vector()`` (from C++) for all types that support ``find()``, - e.g. ``df.unit.get_vector() == df.global.world.units.all`` -- Improved ``json`` I/O error messages -- Stopped a crash when trying to create instances of classes whose vtable - addresses are not available - - -DFHack 0.43.05-r3 -================= - -Internals ---------- -- Fixed an uncommon crash that could occur when printing text to the console -- Added lots of previously-missing DF classes -- More names for fields: https://github.com/DFHack/df-structures/compare/0.43.05-r2...0.43.05 - -Fixes ------ -- Linux: fixed argument to ``setarch`` in the ``dfhack`` launcher script -- Ruby: fixed an error that occurred when the DF path contained an apostrophe -- `diggingInvaders` now compiles again and is included -- `labormanager`: - - - stopped waiting for on-duty military dwarves with minor injuries to obtain care - - stopped waiting for meetings when participant(s) are dead - - fixed a crash for dwarves with no cultural identity - -- `luasocket`: fixed ``receive()`` with a byte count -- `orders`: fixed an error when importing orders with material categories -- `siren`: fixed an error -- `stockpiles`: fixed serialization of barrel and bin counts -- `view-item-info`: fixed a ``CHEESE_MAT``-related error - -Misc Improvements ------------------ -- `devel/export-dt-ini`: added more offsets for new DT versions -- `digfort`: added support for changing z-levels -- `exportlegends`: suppressed ABSTRACT_BUILDING warning -- `gui/dfstatus`: excluded logs in constructions -- `labormanager`: - - - stopped assigning woodcutting jobs to elves - - "recover wounded" jobs now weighted based on altruism - -- `remotefortressreader`: added support for buildings, grass, riders, and - hair/beard styles - - -DFHack 0.43.05-r2 -================= - -Internals ---------- -- Rebuilding DFHack can be faster if nothing Git-related has changed -- Plugins can now hook Screen::readTile() -- Improved Lua compatibility with plugins that hook into GUI functions (like TWBT) -- Expanded focus strings for jobmanagement and workquota_condition viewscreens -- ``Gui::getAnyUnit()``: added support for viewscreen_unitst, - viewscreen_textviewerst, viewscreen_layer_unit_relationshipst -- Fixed (limited) keybinding support in PRINT_MODE:TEXT on macOS -- Added a new standardized ``Gui::refreshSidebar()`` function to fix behavior of - some plugins on the lowest z-level -- New ``Buildings`` module functions: ``markedForRemoval()``, ``getCageOccupants()`` -- Limited recursive command invocations to 20 to prevent crashes -- Added an ``onLoad.init-example`` file - -Lua ---- -- Improved C++ exception handling for some native functions that aren't direct - wrappers around C++ functions (in this case, error messages could be nil and - cause the Lua interpreter to quit) -- Added support for a ``key_pen`` option in Label widgets -- Fixed ``to_first`` argument to ``dfhack.screen.dismiss()`` -- Added optional ``map`` parameters to some screen functions -- Exposed some more functions to Lua: - - - ``dfhack.gui.refreshSidebar()`` - - ``dfhack.gui.getAnyUnit()`` - - ``dfhack.gui.getAnyBuilding()`` - - ``dfhack.gui.getAnyItem()`` - - ``dfhack.gui.getAnyPlant()`` - - ``dfhack.gui.getDepthAt()`` - - ``dfhack.units.getUnitsInBox()`` - - ``dfhack.units.isVisible()`` - - ``dfhack.maps.isTileVisible()`` - - ``dfhack.buildings.markedForRemoval()`` - - ``dfhack.buildings.getCageOccupants()`` - - ``dfhack.internal.md5()`` - - ``dfhack.internal.md5File()`` - - ``dfhack.internal.threadid()`` - -- New function: ``widgets.Pages:getSelectedPage()`` -- Added a ``key`` option to EditField and FilteredList widgets -- Fixed an issue preventing ``repeatUtil.cancel()`` from working when called - from the callback - -Ruby ----- -- Fixed a crash when creating new instances of DF virtual classes (e.g. fixes a - `lever` crash) -- Ruby scripts can now be loaded from any script paths specified (from script- - paths.txt or registered through the Lua API) -- ``unit_find()`` now uses ``Gui::getSelectedUnit()`` and works in more places - (e.g. `exterminate` now works from more screens, like `command-prompt`) - -New Internal Commands ---------------------- -- `alias`: allows configuring aliases for other commands - -New Plugins ------------ -- `orders`: Manipulate manager orders -- `pathable`: Back-end for `gui/pathable` - -New Scripts ------------ -- `clear-smoke`: Removes all smoke from the map -- `empty-bin`: Empty a bin onto the floor -- `fix/retrieve-units`: Spawns stuck invaders/guests -- `fix/stuck-merchants`: Dismisses stuck merchants that haven't entered the map yet -- `gui/pathable`: View whether tiles on the map can be pathed to -- `gui/teleport`: A front-end for the `teleport` script -- `warn-stuck-trees`: Detects citizens stuck in trees - -New Tweaks ----------- -- `tweak` burrow-name-cancel: Implements the "back" option when renaming a - burrow, which currently does nothing (:bug:`1518`) -- `tweak` cage-butcher: Adds an option to butcher units when viewing cages with "q" - -Fixes ------ -- Enforced use of ``stdout.log`` and ``stderr.log`` (instead of their ``.txt`` - counterparts) on Windows -- Fixed ``getItemBaseValue()`` for cheese, sheets and instruments -- Fixed alignment in: - - - ``viewscreen_choose_start_sitest`` - - ``viewscreen_export_graphical_mapst`` - - ``viewscreen_setupadventurest`` - - ``viewscreen_setupdwarfgamest`` - -- `adv-max-skills`: fixed error due to viewscreen changes -- `autolabor`: fixed a crash when assigning haulers while traders are active -- `buildingplan`: fixed an issue that prevented certain numbers from being used - in building names -- `confirm`: - - - dialogs are now closed permanently when disabled from the settings UI - - fixed an issue that could have prevented closing dialogs opened by pressing "s" - -- `embark-tools`: stopped the sand indicator from overlapping dialogs -- `exportlegends`: fixed some crashes and site map issues -- `devel/find-offsets`: fixed ``current_weather`` scan -- `gui/extended-status`: fixed an error when no beds are available -- `gui/family-affairs`: fixed issues with assigning lovers -- `gui/gm-editor`: - - - made keybinding display order consistent - - stopped keys from performing actions in help screen - -- `gui/manager-quantity`: - - - now allows orders with a limit of 0 - - fixed screen detection - -- `gui/mechanisms`, `gui/room-list`: fixed an issue when recentering the map when exiting -- `lever`: prevented pulling non-lever buildings, which can cause crashes -- `markdown`: fixed file encoding -- `modtools/create-unit`: - - - fixed when popup announcements are present - - added checks to ensure that the current game mode is restored - -- `resume`: stopped drawing on the map border -- `show-unit-syndromes`: fixed an error when handling some syndromes -- `strangemood`: fixed some issues with material searches -- `view-item-info`: fixed a color-related error for some materials - -Misc Improvements ------------------ -- Docs: prevented automatic hyphenation in some browsers, which was producing - excessive hyphenation sometimes -- `command-prompt`: invoking ``command-prompt`` a second time now hides the prompt -- `gui/extended-status`: added an option to assign/replace the manager -- `gui/load-screen`: - - - adjusted dialog width for long folder names - - added modification times and DF versions to dialog - -- `gui/mechanisms`, `gui/room-list`, `gui/siege-engine`: add and list "exit to map" options -- `lever`: added support for pulling levers at high priority -- `markdown`: now recognizes ``-n`` in addition to ``/n`` -- `remotefortressreader`: more data exported, used by Armok Vision v0.17.0 -- `resume`, `siege-engine`: improved compatibility with GUI-hooking plugins (like TWBT) -- `sc-script`: improved help text -- `teleport`: can now be used as a module -- `tweak` embark-profile-name: now enabled in ``dfhack.init-example`` -- `tweak` hotkey-clear: fixed display on larger screens - - -DFHack 0.43.05-r1 -================= - -Internals ---------- -- 64-bit support on all platforms -- Several structure fixes to match 64-bit DF's memory layout -- Added ``DFHack::Job::removeJob()`` function -- New module: ``Designations`` - handles designation creation (currently for plants only) -- Added ``Gui::getSelectedPlant()`` -- Added ``Units::getMainSocialActivity()``, ``Units::getMainSocialEvent()`` -- Visual Studio 2015 now required to build on Windows instead of 2010 -- GCC 4.8 or newer required to build on Linux and OS X (and now supported on OS X) -- Updated TinyXML from 2.5.3 to 2.6.2 -- Added the ability to download files manually before building - -Lua ---- -- Lua has been updated to 5.3 - see http://www.lua.org/manual/5.3/readme.html for details - - - Floats are no longer implicitly converted to integers in DFHack API calls - -- ``df.new()`` supports more types: ``char``, ``intptr_t``, ``uintptr_t``, ``long``, ``unsigned long`` -- String representations of vectors and a few other containers now include their lengths -- Added a ``tile-material`` module -- Added a ``Painter:key_string()`` method -- Made ``dfhack.gui.revealInDwarfmodeMap()`` available - -Ruby ----- -- Added support for loading ruby 2.x libraries - -New Plugins ------------ -- `dwarfvet` enables animal caretaking -- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs -- `labormanager` (formerly autolabor2): a more advanced alternative to `autolabor` -- `misery`: re-added and updated for the 0.4x series -- `title-folder`: shows DF folder name in window title bar when enabled - -New Scripts ------------ -- `adv-rumors`: improves the "Bring up specific incident or rumor" menu in adventure mode -- `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. -- `install-info`: Logs basic troubleshooting information about the current DFHack installation -- `load-save`: loads a save non-interactively -- `modtools/change-build-menu`: Edit the build mode sidebar menus -- `modtools/if-entity`: Run a command if the current entity matches a given ID -- `season-palette`: Swap color palettes with the changes of the seasons -- `unforbid`: Unforbids all items - -New Tweaks ----------- -- `tweak condition-material `: fixes a crash in the work order condition material list -- `tweak hotkey-clear `: adds an option to clear bindings from DF hotkeys - -Fixes ------ -- The DF path on OS X can now contain spaces and ``:`` characters -- Buildings::setOwner() changes now persist properly when saved -- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable -- Fixed ``plug`` output alignment for plugins with long names -- `add-thought`: fixed support for emotion names -- `autochop`: - - - fixed several issues with job creation and removal - - stopped designating the center tile (unreachable) for large trees - - stopped options from moving when enabling and disabling burrows - - fixed display of unnamed burrows - -- `devel/find-offsets`: fixed a crash when vtables used by globals aren't available -- `getplants`: - - - fixed several issues with job creation and removal - - stopped designating the center tile (unreachable) for large trees - -- `gui/workflow`: added extra keybinding to work with `gui/extended-status` -- `manipulator`: - - - Fixed crash when selecting a profession from an empty list - - Custom professions are now sorted alphabetically more reliably - -- `modtools/create-item`: - - - made gloves usable by specifying handedness - - now creates pairs of boots and gloves - -- `modtools/create-unit`: - - - stopped permanently overwriting the creature creation menu in arena mode - - now uses non-English names - - added ``-setUnitToFort`` option to make a unit a civ/group member more easily - - fixed some issues where units would appear in unrevealed areas of the map - -- `modtools/item-trigger`: fixed errors with plant growths -- `remotefortressreader`: fixed a crash when serializing the local map -- `ruby`: fixed a crash when unloading the plugin on Windows -- `stonesense`: disabled overlay in STANDARD-based print modes to prevent crashes -- `title-version`: now hidden when loading an arena - -Misc Improvements ------------------ -- Documented all default keybindings (from :file:`dfhack.init-example`) in the - docs for the relevant commands; updates enforced by build system. -- `autounsuspend`: reduced update frequency to address potential performance issues -- `gui/extended-status`: added a feature to queue beds -- `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.) -- `manipulator`: added social activities to job column -- `remotefortressreader`: Added support for - - - world map snow coverage - - spatters - - wall info - - site towers, world buildings - - surface material - - building items - - DF version info - -- `title-version`: Added a prerelease indicator -- `workflow`: Re-added ``Alt-W`` keybindings - -DFHack 0.43.03-r1 -================= - -Lua ---- -- Label widgets can now easily register handlers for mouse clicks - -New Features ------------- -- `add-thought`: allow syndrome name as ``-thought`` argument -- `gui/gm-editor` - - - Added ability to insert default types into containers. For primitive types leave the type entry empty, and for references use ``*``. - - Added ``shift-esc`` binding to fully exit from editor - - Added ``gui/gm-editor toggle`` command to toggle editor visibility (saving position) - -- `modtools/create-unit`: - - - Added an option to attach units to an existing wild animal population - - Added an option to attach units to a map feature - -Fixes ------ -- `autofarm`: Can now handle crops that grow for more than a season -- `combine-plants`: Fixed recursion into sub-containers -- `createitem`: Now moves multiple created items to cursor correctly -- `exportlegends`: Improved handling of unknown enum items (fixes many errors) -- `gui/create-item`: Fixed quality when creating multiple items -- `gui/mod-manager`: Fixed error when mods folder doesn't exist -- `modtools/item-trigger`: Fixed handling of items with subtypes -- `reveal`: ``revflood`` now handles constructed stairs with floors in generated fortresses -- `stockflow`: - - - Can order metal mechanisms - - Fixed material category of thread-spinning jobs - -Misc Improvements ------------------ -- The built-in ``ls`` command now wraps the descriptions of commands -- `catsplosion`: now a lua script instead of a plugin -- `fix/diplomats`: replaces ``fixdiplomats`` -- `fix/merchants`: replaces ``fixmerchants`` -- `prefchange`: added a ``help`` option -- `probe`: now displays raw tiletype names -- Unified script documentation and in-terminal help options - -Removed -------- -- `tweak` manager-quantity: no longer needed - -DFHack 0.42.06-r1 -================= - -Internals ---------- -- Commands to run on startup can be specified on the command line with ``+`` - - Example:: - - ./dfhack +devel/print-args example - "Dwarf Fortress.exe" +devel/print-args example - -- Prevented plugins with active viewscreens from being unloaded and causing a crash -- Additional script search paths can be specified in dfhack-config/script-paths.txt - -Lua ---- -- `building-hacks` now supports ``auto_gears`` flags. It automatically finds and animates gears in building definition -- Changed how `eventful` triggers reaction complete. Now it has ``onReactionComplete`` and ``onReactionCompleting``. Second one can be canceled - -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 -- `modtools/extra-gamelog`: replaces ``log-region``, ``soundsense-season``, and ``soundsense`` - -New Features ------------- -- `buildingplan`: Support for floodgates, grates, and bars -- `colonies`: new ``place`` subcommand and supports any vermin (default honey bees) -- `confirm`: Added a confirmation for retiring locations -- `exportlegends`: Exports more information (poetic/musical/dance forms, written/artifact content, landmasses, extra histfig information, and more) -- `search-plugin`: Support for new screens: - - - location occupation assignment - - civilization animal training knowledge - - animal trainer assignment - -- `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 - -- `zone`: - - - Added ``unassign`` subcommand - - Added ``only`` option to ``assign`` subcommand - -Fixes ------ -- Fixed a crash bug caused by the historical figures DFHack uses to store persistent data. -- More plugins should recognize non-dwarf citizens -- Fixed a possible crash from cloning jobs -- moveToBuilding() now sets flags for items that aren't a structural part of the building properly -- `autotrade`, `stocks`: Made trading work when multiple caravans are present but only some can trade -- `confirm` note-delete: No longer interferes with name entry -- `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42 -- `fastdwarf`: Fixed a bug involving teleporting mothers but not the babies they're holding. -- `gaydar`: Fixed text display on OS X/Linux and failure with soul-less creatures -- `manipulator`: - - - allowed editing of non-dwarf citizens - - stopped ghosts and visitors from being editable - - fixed applying last custom profession - -- `modtools/create-unit`: Stopped making units without civs historical figures -- `modtools/force`: - - - Removed siege option - - Prevented a crash resulting from a bad civilization option - -- `showmood`: Fixed name display on OS X/Linux -- `view-item-info`: Fixed density units - -Misc Improvements ------------------ -- `autochop`: Can now edit log minimum/maximum directly and remove limit entirely -- `autolabor`, `autohauler`, `manipulator`: Added support for new jobs/labors/skills -- `colonies`: now implemented by a script -- `createitem`: Can now create items anywhere without specifying a unit, as long as a unit exists on the map -- `devel/export-dt-ini`: Updated for 0.42.06 -- `devel/find-offsets`: Automated several more scans -- `gui/gm-editor`: Now supports finding some items with a numeric ID (with ``i``) -- `lua`: Now supports some built-in variables like `gui/gm-editor`, e.g. ``unit``, ``screen`` -- `remotefortressreader`: Can now trigger keyboard events -- `stockflow`: Now offers better control over individual craft jobs -- `weather`: now implemented by a script -- `zone`: colored output - -Removed -------- -- DFusion: legacy script system, obsolete or replaced by better alternatives - - -DFHack 0.40.24-r5 -================= - -New Features ------------- -- `confirm`: - - - Added a ``uniform-delete`` option for military uniform deletion - - Added a basic in-game configuration UI - -Fixes ------ -- Fixed a rare crash that could result from running `keybinding` in onLoadWorld.init -- Script help that doesn't start with a space is now recognized correctly -- `confirm`: Fixed issues with haul-delete, route-delete, and squad-disband confirmations intercepting keys too aggressively -- `emigration` should work now -- `fix-unit-occupancy`: Significantly optimized - up to 2,000 times faster in large fortresses -- `gui/create-item`: Allow exiting quantity prompt -- `gui/family-affairs`: Fixed an issue where lack of relationships wasn't recognized and other issues -- `modtools/create-unit`: Fixed a possible issue in reclaim fortress mode -- `search-plugin`: Fixed a crash on the military screen -- `tweak` max-wheelbarrow: Fixed a minor display issue with large numbers -- `workflow`: Fixed a crash related to job postings (and added a fix for existing, broken jobs) - -Misc Improvements ------------------ -- Unrecognized command feedback now includes more information about plugins -- `fix/dry-buckets`: replaces the ``drybuckets`` plugin -- `feature`: now implemented by a script - -DFHack 0.40.24-r4 -================= - -Internals ---------- -- A method for caching screen output is now available to Lua (and C++) -- Developer plugins can be ignored on startup by setting the ``DFHACK_NO_DEV_PLUGINS`` environment variable -- The console on Linux and OS X now recognizes keyboard input between prompts -- JSON libraries available (C++ and Lua) -- More DFHack build information used in plugin version checks and available to plugins and lua scripts -- Fixed a rare overflow issue that could cause crashes on Linux and OS X -- Stopped DF window from receiving input when unfocused on OS X -- Fixed issues with keybindings involving :kbd:`Ctrl`:kbd:`A` and :kbd:`Ctrl`:kbd:`Z`, - as well as :kbd:`Alt`:kbd:`E`/:kbd:`U`/:kbd:`N` on OS X -- Multiple contexts can now be specified when adding keybindings -- Keybindings can now use :kbd:`F10`-:kbd:`F12` and :kbd:`0`-:kbd:`9` -- Plugin system is no longer restricted to plugins that exist on startup -- :file:`dfhack.init` file locations significantly generalized - -Lua ---- -- Scripts can be enabled with the built-in `enable`/`disable ` commands -- A new function, ``reqscript()``, is available as a safer alternative to ``script_environment()`` -- Lua viewscreens can choose not to intercept the OPTIONS keybinding - -New internal commands ---------------------- -- `kill-lua`: Interrupt running Lua scripts -- `type`: Show where a command is implemented - -New plugins ------------ -- `confirm`: Adds confirmation dialogs for several potentially dangerous actions -- `fix-unit-occupancy`: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (:bug:`3499`) -- `title-version` (formerly ``vshook``): Display DFHack version on title screen - -New scripts ------------ -- `armoks-blessing`: Adjust all attributes, personality, age and skills of all dwarves in play -- `brainwash`: brainwash a dwarf (modifying their personality) -- `burial`: sets all unowned coffins to allow burial ("-pets" to allow pets too) -- `deteriorateclothes`: make worn clothes on the ground wear far faster to boost FPS -- `deterioratecorpses`: make body parts wear away far faster to boost FPS -- `deterioratefood`: make food vanish after a few months if not used -- `elevate-mental`: elevate all the mental attributes of a unit -- `elevate-physical`: elevate all the physical attributes of a unit -- `emigration`: stressed dwarves may leave your fortress if they see a chance -- `fix-ster`: changes fertility/sterility of animals or dwarves -- `gui/family-affairs`: investigate and alter romantic relationships -- `make-legendary`: modify skill(s) of a single unit -- `modtools/create-unit`: create new units from nothing -- `modtools/equip-item`: a script to equip items on units -- `points`: set number of points available at embark screen -- `pref-adjust`: Adjust all preferences of all dwarves in play -- `rejuvenate`: make any "old" dwarf 20 years old -- `starvingdead`: make undead weaken after one month on the map, and crumble after six -- `view-item-info`: adds information and customisable descriptions to item viewscreens -- `warn-starving`: check for starving, thirsty, or very drowsy units and pause with warning if any are found - -New tweaks ----------- -- embark-profile-name: Allows the use of lowercase letters when saving embark profiles -- kitchen-keys: Fixes DF kitchen meal keybindings -- kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences -- kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs - -Fixes ------ -- Plugins with vmethod hooks can now be reloaded on OS X -- Lua's ``os.system()`` now works on OS X -- Fixed default arguments in Lua gametype detection functions -- Circular lua dependencies (reqscript/script_environment) fixed -- Prevented crash in ``Items::createItem()`` -- `buildingplan`: Now supports hatch covers -- `gui/create-item`: fixed assigning quality to items, made :kbd:`Esc` work properly -- `gui/gm-editor`: handles lua tables properly -- `help`: now recognizes built-in commands, like ``help`` -- `manipulator`: fixed crash when selecting custom professions when none are found -- `remotefortressreader`: fixed crash when attempting to send map info when no map was loaded -- `search-plugin`: fixed crash in unit list after cancelling a job; fixed crash when disabling stockpile category after searching in a subcategory -- `stockpiles`: now checks/sanitizes filenames when saving -- `stocks`: fixed a crash when right-clicking -- `steam-engine`: fixed a crash on arena load; number keys (e.g. 2/8) take priority over cursor keys when applicable -- tweak fps-min fixed -- tweak farm-plot-select: Stopped controls from appearing when plots weren't fully built -- `workflow`: Fixed some issues with stuck jobs. Existing stuck jobs must be cancelled and re-added -- `zone`: Fixed a crash when using ``zone set`` (and a few other potential crashes) - -Misc Improvements ------------------ -- DFHack documentation: - - - massively reorganised, into files of more readable size - - added many missing entries - - indexes, internal links, offline search all documents - - includes documentation of linked projects (df-structures, third-party scripts) - - better HTML generation with Sphinx - - documentation for scripts now located in source files - -- `autolabor`: - - - Stopped modification of labors that shouldn't be modified for brokers/diplomats - - Prioritize skilled dwarves more efficiently - - Prevent dwarves from running away with tools from previous jobs - -- `automaterial`: Fixed several issues with constructions being allowed/disallowed incorrectly when using box-select -- `dwarfmonitor`: - - - widgets' positions, formats, etc. are now customizable - - weather display now separated from the date display - - New mouse cursor widget - -- `gui/dfstatus`: Can enable/disable individual categories and customize metal bar list -- `full-heal`: ``-r`` option removes corpses -- `gui/gm-editor` - - - Pointers can now be displaced - - Added some useful aliases: "item" for the selected item, "screen" for the current screen, etc. - - Now avoids errors with unrecognized types - -- `gui/hack-wish`: renamed to `gui/create-item` -- `keybinding list ` accepts a context -- `lever`: - - - Lists lever names - - ``lever pull`` can be used to pull the currently-selected lever - -- ``memview``: Fixed display issue -- `modtools/create-item`: arguments are named more clearly, and you can specify the creator to be the unit with id ``df.global.unit_next_id-1`` (useful in conjunction with `modtools/create-unit`) -- ``nyan``: Can now be stopped with dfhack-run -- `plug`: lists all plugins; shows state and number of commands in plugins -- `prospect`: works from within command-prompt -- `quicksave`: Restricted to fortress mode -- `remotefortressreader`: Exposes more information -- `search-plugin`: - - - Supports noble suggestion screen (e.g. suggesting a baron) - - Supports fortress mode loo[k] menu - - Recognizes ? and ; keys - -- `stocks`: can now match beginning and end of item names -- `teleport`: Fixed cursor recognition -- `tidlers`, `twaterlvl`: now implemented by scripts instead of a plugin -- `tweak`: - - - debug output now logged to stderr.log instead of console - makes DFHack start faster - - farm-plot-select: Fixed issues with selecting undiscovered crops - -- `workflow`: Improved handling of plant reactions - -Removed -------- -- `embark-tools` nano: 1x1 embarks are now possible in vanilla 0.40.24 - -DFHack 0.40.24-r3 -================= - -Internals ---------- -- Ruby library now included on OS X - Ruby scripts should work on OS X 10.10 -- libstdc++ should work with older versions of OS X -- Added support for `onMapLoad.init / onMapUnload.init ` scripts -- game type detection functions are now available in the World module -- The ``DFHACK_LOG_MEM_RANGES`` environment variable can be used to log information to ``stderr.log`` on OS X -- Fixed adventure mode menu names -- Fixed command usage information for some commands - -Lua ---- -- Lua scripts will only be reloaded if necessary -- Added a ``df2console()`` wrapper, useful for printing DF (CP437-encoded) text to the console in a portable way -- Added a ``strerror()`` wrapper - -New Internal Commands ---------------------- -- `hide`, `show`: hide and show the console on Windows -- `sc-script`: Allows additional scripts to be run when certain events occur (similar to `onLoad.init` scripts) - -New Plugins ------------ -- `autohauler`: A hauling-only version of autolabor - -New Scripts ------------ -- `modtools/reaction-product-trigger`: triggers callbacks when products are produced (contrast with when reactions complete) - -New Tweaks ----------- -- `fps-min `: Fixes the in-game minimum FPS setting -- `shift-8-scroll `: Gives Shift+8 (or ``*``) priority when scrolling menus, instead of scrolling the map -- `tradereq-pet-gender `: Displays pet genders on the trade request screen - -Fixes ------ -- Fixed game type detection in `3dveins`, `gui/create-item`, `reveal`, `seedwatch` -- ``PRELOAD_LIB``: More extensible on Linux -- `add-spatter`, `eventful`: Fixed crash on world load -- `add-thought`: Now has a proper subthought arg. -- `building-hacks`: Made buildings produce/consume correct amount of power -- `fix-armory`: compiles and is available again (albeit with issues) -- `gui/gm-editor`: Added search option (accessible with "s") -- `hack-wish `: Made items stack properly. -- `modtools/skill-change`: Made level granularity work properly. -- `show-unit-syndromes`: should work -- `stockflow`: - - - Fixed error message in Arena mode - - no longer checks the DF version - - fixed ballistic arrow head orders - - convinces the bookkeeper to update records more often - -- `zone`: Stopped crash when scrolling cage owner list - -Misc Improvements ------------------ -- `autolabor`: A negative pool size can be specified to use the most unskilled dwarves -- `building-hacks`: - - - Added a way to allow building to work even if it consumes more power than is available. - - Added setPower/getPower functions. - -- `catsplosion`: Can now trigger pregnancies in (most) other creatures -- `exportlegends`: ``info`` and ``all`` options export ``legends_plus.xml`` with more data for legends utilities -- `manipulator`: - - - Added ability to edit nicknames/profession names - - added "Job" as a View Type, in addition to "Profession" and "Squad" - - added custom profession templates with masking - -- `remotefortressreader`: Exposes more information - - -DFHack 0.40.24-r2 -================= - -Internals ---------- -- Lua scripts can set environment variables of each other with ``dfhack.run_script_with_env`` -- Lua scripts can now call each others internal nonlocal functions with ``dfhack.script_environment(scriptName).functionName(arg1,arg2)`` -- `eventful`: Lua reactions no longer require LUA_HOOK as a prefix; you can register a callback for the completion of any reaction with a name -- Filesystem module now provides file access/modification times and can list directories (normally and recursively) -- Units Module: New functions:: - - isWar - isHunter - isAvailableForAdoption - isOwnCiv - isOwnRace - getRaceName - getRaceNamePlural - getRaceBabyName - getRaceChildName - isBaby - isChild - isAdult - isEggLayer - isGrazer - isMilkable - isTrainableWar - isTrainableHunting - isTamable - isMale - isFemale - isMerchant - isForest - isMarkedForSlaughter - -- Buildings Module: New Functions:: - - isActivityZone - isPenPasture - isPitPond - isActive - findPenPitAt - -Fixes ------ -- ``dfhack.run_script`` should correctly find save-specific scripts now. -- `add-thought`: updated to properly affect stress. -- `hfs-pit`: should work now -- `autobutcher`: takes gelding into account -- :file:`init.lua` existence checks should be more reliable (notably when using non-English locales) - -Misc Improvements ------------------ -Multiline commands are now possible inside dfhack.init scripts. See :file:`dfhack.init-example` for example usage. - - -DFHack 0.40.24-r1 -================= - -Internals ---------- -CMake shouldn't cache DFHACK_RELEASE anymore. People may need to manually update/delete their CMake cache files to get rid of it. - - -DFHack 0.40.24-r0 -================= - -Internals ---------- -- `EventManager`: fixed crash error with EQUIPMENT_CHANGE event. -- key modifier state exposed to Lua (ie :kbd:`Ctrl`, :kbd:`Alt`, :kbd:`Shift`) - -Fixes ------ -``dfhack.sh`` can now be run from other directories on OS X - -New Plugins ------------ -- `blueprint`: export part of your fortress to quickfort .csv files - -New Scripts ------------ -- `hotkey-notes`: print key, name, and jump position of hotkeys - -Removed -------- -- needs_porting/* - -Misc Improvements ------------------ -Added support for searching more lists - - -Older Changelogs -================ -Are kept in a seperate file: `HISTORY` - -.. that's ``docs/history.rst``, if you're reading the raw text. diff --git a/conf.py b/conf.py index ed62ad5c6..bd94e8315 100644 --- a/conf.py +++ b/conf.py @@ -21,6 +21,8 @@ import re import shlex # pylint:disable=unused-import import sys +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs')) +from gen_changelog import generate_changelog # -- Support :dfhack-keybind:`command` ------------------------------------ # this is a custom directive that pulls info from dfhack.init-example @@ -179,6 +181,7 @@ def all_keybinds_documented(): # Actually call the docs generator and run test +generate_changelog() write_script_docs() all_keybinds_documented() @@ -264,6 +267,7 @@ exclude_patterns = [ 'docs/html*', 'depends/*', 'build*', + 'docs/_auto/news*', ] # The reST default role (used for this markup: `text`) to use for all diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index a2c8abd20..68c16d272 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -1,32 +1,6 @@ .. comment - This is the development changelog file for DFHack. If you add or change - anything, note it here under the heading "DFHack Future", in the appropriate - section. Items within each section are listed in alphabetical order to - minimise merge conflicts. Try to match the style and level of detail of the - other entries. - - This file contains changes that are relevant to users of prereleases. These - changes should include changes from just the previous release, whether that - release was stable or not. For instance, a feature added in 0.43.05-alpha1 - should go under "0.43.05-alpha1" here *and* "0.43.05-r1" (or "future") in - NEWS.rst. A fix in one prerelease for an issue in the previous prerelease - should just go here in the appropriate section, not in NEWS.rst. - - Sections for each release are added as required, and consist solely of the - following in order as subheadings:: - - Fixes - Structures - API Changes - Additions/Removals - Other Changes - - When referring to a script, plugin, or command, use backticks (```) to - create a link to the relevant documentation - and check that the docs are - still up to date! - - When adding a new release, change "DFHack future" to the appropriate title - before releasing, and then add a new "DFHack future" section after releasing. + This is the changelog for development releases. Entries are included from + changelog.txt. .. _dev-changelog: @@ -37,370 +11,4 @@ Development Changelog .. contents:: :depth: 2 -DFHack 0.44.09-alpha1 -===================== - -Fixes ------ -- `digtype`: stopped designating non-vein tiles (open space, trees, etc.) -- `labormanager`: fixed crash due to dig jobs targeting some unrevealed map blocks - -DFHack 0.44.08-alpha1 -===================== - -Fixes ------ -- `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units - -DFHack 0.44.07-beta1 -==================== - -Structures ----------- -- Added symbols for Toady's `0.44.07 Linux test build `_ to fix :bug:`10615` -- ``world_site``: fixed alignment - -Other Changes -------------- -- `modtools/item-trigger`: added the ability to specify inventory mode(s) to trigger on - -DFHack 0.44.07-alpha1 -===================== - -Fixes ------ -- `embark-assistant`: fixed detection of reanimating biomes - -Structures ----------- -- Several new names in instrument raw structures -- ``identity``: identified ``profession``, ``civ`` -- ``manager_order_template``: fixed last field type -- ``viewscreen_createquotast``: fixed layout -- ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors`` -- ``world.reactions``, ``world.reaction_categories``: moved to new compound, ``world.reactions``. Requires renaming: - - - ``world.reactions`` to ``world.reactions.reactions`` - - ``world.reaction_categories`` to ``world.reactions.reaction_categories`` - -Other Changes -------------- -- `embark-assistant`: - - - Added search for adamantine - - Now supports saving/loading profiles - -- `fillneeds`: added ``-all`` option to apply to all units -- `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs - - -DFHack 0.44.05-alpha1 -===================== - -Structures ----------- -- ``incident``: re-aligned again to match disassembly - -Other Changes -------------- -- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards - -DFHack 0.44.04-alpha1 -===================== - -Fixes ------ -- `devel/inject-raws`: now recognizes spaces in reaction names -- `exportlegends`: fixed an error that could occur when exporting empty lists - -Structures ----------- -- ``artifact_record``: fixed layout (changed in 0.44.04) -- ``incident``: fixed layout (changed in 0.44.01) - note that many fields have moved - -DFHack 0.44.03-beta1 -==================== - -Fixes ------ -- `autolabor`, `autohauler`, `labormanager`: added support for "put item on - display" jobs and building/destroying display furniture -- `gui/gm-editor`: fixed an error when editing primitives in Lua tables - -Structures ----------- -- Added 7 new globals from DF: ``version``, ``min_load_version``, - ``movie_version``, ``basic_seed``, ``title``, ``title_spaced``, - ``ui_building_resize_radius`` -- Added ``twbt_render_map`` code offset on x64 -- Fixed an issue preventing ``enabler`` from being allocated by DFHack -- Added ``job_type.PutItemOnDisplay`` -- Found ``renderer`` vtable on osx64 -- ``adventure_movement_optionst``, ``adventure_movement_hold_tilest``, - ``adventure_movement_climbst``: named coordinate fields -- ``mission``: added type -- ``unit``: added 3 new vmethods: ``getCreatureTile``, ``getCorpseTile``, ``getGlowTile`` -- ``viewscreen_assign_display_itemst``: fixed layout on x64 and identified many fields -- ``viewscreen_reportlistst``: fixed layout, added ``mission_id`` vector -- ``world.status``: named ``missions`` vector - -Other Changes -------------- -- `devel/dump-offsets`: now ignores ``index`` globals -- `gui/pathable`: added tile types to sidebar -- `modtools/skill-change`: - - - now updates skill levels appropriately - - only prints output if ``-loud`` is passed - -DFHack 0.44.03-alpha1 -===================== - -Other Changes -------------- -- Lua: Improved ``json`` I/O error messages -- Lua: Stopped a crash when trying to create instances of classes whose vtable - addresses are not available - -DFHack 0.44.02-beta1 -==================== - -Fixes ------ -- Fixed issues with the console output color affecting the prompt on Windows -- `createitem`: stopped items from teleporting away in some forts -- `gui/gm-unit`: can now edit mining skill -- `gui/quickcmd`: stopped error from adding too many commands -- `modtools/create-unit`: fixed error when domesticating units - -Structures ----------- -- Located ``start_dwarf_count`` offset for all builds except 64-bit Linux; - `startdwarf` should work now -- Added ``buildings_other_id.DISPLAY_CASE`` -- Fixed ``viewscreen_titlest.start_savegames`` alignment -- Fixed ``unit`` alignment -- Identified ``historical_entity.unknown1b.deities`` (deity IDs) - -API Changes ------------ -- Lua; Exposed ``get_vector()`` (from C++) for all types that support - ``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all`` - -Additions/Removals ------------------- -- Added `devel/check-other-ids`: Checks the validity of "other" vectors in the - ``world`` global -- Added `gui/cp437-table`: An in-game CP437 table -- Removed `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01 - -Other Changes -------------- -- The console now provides suggestions for built-in commands -- `devel/export-dt-ini`: avoid hardcoding flags -- `exportlegends`: - - - reordered some tags to match DF's order - - added progress indicators for exporting long lists - -- `gui/gm-editor`: added enum names to enum edit dialogs -- `gui/gm-unit`: made skill search case-insensitive -- `gui/rename`: added "clear" and "special characters" options -- `remotefortressreader`: includes item stack sizes and some performance improvements - - -DFHack 0.44.02-alpha1 -===================== - -Fixes ------ -- Fixed a crash that could occur if a symbol table in symbols.xml had no content -- The Lua API can now wrap functions with 12 or 13 parameters - -Structures ----------- -- The ``ui_menu_width`` global is now a 2-byte array; the second item is the - former ``ui_area_map_width`` global, which is now removed -- The former ``announcements`` global is now a field in ``d_init`` -- ``world`` fields formerly beginning with ``job_`` are now fields of - ``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list`` - -API Changes ------------ -- Lua: Added a new ``dfhack.console`` API - -DFHack 0.43.05-beta2 -==================== - -Fixes ------ -- Fixed Buildings::updateBuildings(), along with building creation/deletion events -- Fixed ``plug`` output alignment for plugins with long names -- Fixed a crash that happened when a ``LUA_PATH`` environment variable was set -- `add-thought`: fixed number conversion -- `gui/workflow`: fixed range editing producing the wrong results for certain numbers -- `modtools/create-unit`: now uses non-English names -- `modtools/item-trigger`: fixed errors with plant growths -- `remotefortressreader`: fixed a crash when serializing the local map -- `stockflow`: fixed an issue with non-integer manager order limits -- `title-folder`: fixed compatibility issues with certain SDL libraries on macOS - -Structures ----------- -- Added some missing renderer VTable addresses on macOS -- ``entity.resources.organic``: identified ``parchment`` -- ``entity_sell_category``: added ``Parchment`` and ``CupsMugsGoblets`` -- ``ui_advmode_menu``: added ``Build`` -- ``ui_unit_view_mode``: added ``PrefOccupation`` -- ``unit_skill``: identified ``natural_skill_lvl`` (was ``unk_1c``) -- ``viewscreen_jobmanagementst``: identified ``max_workshops`` -- ``viewscreen_overallstatusst``: made ``visible_pages`` an enum -- ``viewscreen_pricest``: identified fields -- ``viewscreen_workquota_conditionst``: gave some fields ``unk`` names - -API Changes ------------ -- Allowed the Lua API to accept integer-like floats and strings when expecting an integer -- Lua: New ``Painter:key_string()`` method -- Lua: Added ``dfhack.getArchitecture()`` and ``dfhack.getArchitectureName()`` - -Additions/Removals: -------------------- -- Added `adv-rumors` script: improves the "Bring up specific incident or rumor" menu in adventure mode -- Added `install-info` script for basic troubleshooting -- Added `tweak condition-material `: fixes a crash in the work order condition material list -- Added `tweak hotkey-clear `: adds an option to clear bindings from DF hotkeys -- `autofarm`: reverted local biome detection (from 0.43.05-alpha3) - -Other Changes -------------- -- Added a DOWNLOAD_RUBY CMake option, to allow use of a system/external ruby library -- Added the ability to download files manually before building -- `gui/extended-status`: added a feature to queue beds -- `remotefortressreader`: added building items, DF version info -- `stonesense`: Added support for 64-bit macOS and Linux - -DFHack 0.43.05-beta1 -==================== - -Fixes ------ -- Fixed various crashes on 64-bit Windows related to DFHack screens, notably `manipulator` -- Fixed addresses of next_id globals on 64-bit Linux (fixes an `automaterial`/box-select crash) -- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable -- `modtools/create-unit`: stopped permanently overwriting the creature creation - menu in arena mode -- `season-palette`: fixed an issue where only part of the screen was redrawn - after changing the color scheme -- `title-version`: now hidden when loading an arena - -Structures ----------- -- ``file_compressorst``: fixed field sizes on x64 -- ``historical_entity``: fixed alignment on x64 -- ``ui_sidebar_menus.command_line``: fixed field sizes on x64 -- ``viewscreen_choose_start_sitest``: added 3 missing fields, renamed ``in_embark_only_warning`` -- ``viewscreen_layer_arena_creaturest``: identified more fields -- ``world.math``: identified -- ``world.murky_pools``: identified - -Additions/Removals ------------------- -- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs - -Other Changes -------------- -- `title-version`: Added a prerelease indicator - -DFHack 0.43.05-alpha4 -===================== - -Fixes ------ -- Fixed an issue with uninitialized bitfields that was causing several issues - (disappearing buildings in `buildingplan`'s planning mode, strange behavior in - the extended `stocks` screen, and likely other problems). This issue was - introduced in 0.43.05-alpha3. -- `stockflow`: Fixed an "integer expected" error - -Structures ----------- -- Located several globals on 64-bit Linux: flows, timed_events, ui_advmode, - ui_building_assign_type, ui_building_assign_is_marked, - ui_building_assign_units, ui_building_assign_items, and ui_look_list. This - fixes `search-plugin`, `zone`, and `force`, among others. -- ``ui_sidebar_menus``: Fixed some x64 alignment issues - -Additions/Removals ------------------- -- Added `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. - Useful for fixing blocked tiles introduced by the above buildingplan issue. -- Added a Lua ``tile-material`` module - -Other Changes -------------- -- `labormanager`: Add support for shell crafts -- `manipulator`: Custom professions are now sorted alphabetically more reliably - -DFHack 0.43.05-alpha3 -===================== - -Fixes ------ -- `add-thought`: fixed support for emotion names -- `autofarm`: Made surface farms detect local biome -- `devel/export-dt-ini`: fixed squad_schedule_entry size -- `labormanager`: - - - Now accounts for unit attributes - - Made instrument-building jobs work (constructed instruments) - - Fixed deconstructing constructed instruments - - Fixed jobs in bowyer's shops - - Fixed trap component jobs - - Fixed multi-material construction jobs - - Fixed deconstruction of buildings containing items - - Fixed interference caused by "store item in vehicle" jobs - -- `manipulator`: Fixed crash when selecting a profession from an empty list -- `ruby`: - - - Fixed crash on Win64 due to truncated global addresses - - Fixed compilation on Win64 - - Use correct raw string length with encodings - -Structures ----------- -- Changed many ``comment`` XML attributes with version numbers to use new - ``since`` attribute instead -- ``activity_event_conflictst.sides``: named many fields -- ``building_def.build_key``: fixed size on 64-bit Linux and OS X -- ``historical_kills``: - - - ``unk_30`` -> ``killed_underground_region`` - - ``unk_40`` -> ``killed_region`` - -- ``historical_kills.killed_undead``: removed ``skeletal`` flag -- ``ui_advmode``: aligned enough so that it doesn't crash (64-bit OS X/Linux) -- ``ui_advmode.show_menu``: changed from bool to enum -- ``unit_personality.emotions.flags``: now a bitfield - -API Changes ------------ -- Added ``DFHack::Job::removeJob()`` function -- C++: Removed bitfield constructors that take an initial value. These kept - bitfields from being used in unions. Set ``bitfield.whole`` directly instead. -- Lua: ``bitfield.whole`` now returns an integer, not a decimal - -Additions/Removals ------------------- -- Removed source for treefarm plugin (wasn't built) -- Added `modtools/change-build-menu`: Edit the build mode sidebar menus -- Added `modtools/if-entity`: Run a command if the current entity matches a - given ID -- Added `season-palette`: Swap color palettes with the changes of the seasons - -Other changes -------------- -- Changed minimum GCC version to 4.8 on OS X and Linux (earlier versions - wouldn't have worked on Linux anyway) -- Updated TinyXML from 2.5.3 to 2.6.2 +.. include:: /docs/_auto/news-dev.rst diff --git a/docs/NEWS.rst b/docs/NEWS.rst new file mode 100644 index 000000000..60d180eaa --- /dev/null +++ b/docs/NEWS.rst @@ -0,0 +1,14 @@ +.. comment + This is the changelog for stable releases. Entries are included from + changelog.txt. + +.. _changelog: + +######### +Changelog +######### + +.. contents:: + :depth: 2 + +.. include:: /docs/_auto/news.rst diff --git a/docs/changelog.txt b/docs/changelog.txt new file mode 100644 index 000000000..80fb8e4e7 --- /dev/null +++ b/docs/changelog.txt @@ -0,0 +1,46 @@ +================================================================================ +# Future + + +================================================================================ +# 0.44.09-alpha1 + +## Fixes +- `digtype`: stopped designating non-vein tiles (open space, trees, etc.) +- `labormanager`: fixed crash due to dig jobs targeting some unrevealed map blocks + + +================================================================================ +# 0.44.08-alpha1 + +## Fixes +- `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units + + +================================================================================ +# 0.44.07-beta1 + +## Structures +-@ Added symbols for Toady's `0.44.07 Linux test build `_ to fix :bug:`10615` +-@ ``world_site``: fixed alignment + +## Misc improvements +- `modtools/item-trigger`: added the ability to specify inventory mode(s) to trigger on + + +================================================================================ +# 0.44.07-alpha1 + +## Fixes +- `embark-assistant`: fixed detection of reanimating biomes + +## Structures +- Several new names in instrument raw structures +- ``identity``: identified ``profession``, ``civ`` +- ``manager_order_template``: fixed last field type +- ``viewscreen_createquotast``: fixed layout +- ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors`` +- ``world.reactions``, ``world.reaction_categories``:\ moved to new compound, ``world.reactions``. Requires renaming + + - ``world.reactions`` to ``world.reactions.reactions`` + - ``world.reaction_categories`` to ``world.reactions.reaction_categories`` diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py new file mode 100644 index 000000000..2336e9962 --- /dev/null +++ b/docs/gen_changelog.py @@ -0,0 +1,177 @@ +import collections +import copy +import itertools +import os + +CHANGELOG_SECTIONS = [ + 'New Plugins', + 'New Scripts', + 'New Tweaks', + 'New Features', + 'New Internal Commands', + 'Fixes', + 'Misc Improvements', + 'Removed', + 'Internals', + 'Structures', + 'Lua', + 'Ruby', +] + +class ChangelogEntry(object): + def __init__(self, text, section, stable_version, dev_version): + text = text.lstrip('- ') + # normalize section to title case + self.section = ' '.join(word[0].upper() + word[1:].lower() + for word in section.strip().split()) + self.stable_version = stable_version + self.dev_version = dev_version + self.dev_only = text.startswith('@') + text = text.lstrip('@ ') + self.children = [] + + split_index = text.find(': ') + if split_index != -1: + self.feature, description = text[:split_index], text[split_index+1:] + if description.strip(): + self.children.insert(0, description.strip()) + else: + self.feature = text + self.feature = self.feature.replace(':\\', ':') + + self.sort_key = self.feature.upper() + + def __repr__(self): + return 'ChangelogEntry(%r, %r)' % (self.feature, self.children) + +def parse_changelog(): + cur_stable = None + cur_dev = None + cur_section = None + last_entry = None + entries = [] + + with open('docs/changelog.txt') as f: + for line_id, line in enumerate(f.readlines()): + line_id += 1 + if not line.strip() or line.startswith('==='): + continue + + if line.startswith('##'): + cur_section = line.lstrip('#').strip() + elif line.startswith('#'): + cur_dev = line.lstrip('#').strip().lower() + if ('alpha' not in cur_dev and 'beta' not in cur_dev and + 'rc' not in cur_dev): + cur_stable = cur_dev + elif line.startswith('-'): + if not cur_stable or not cur_dev or not cur_section: + raise ValueError( + 'changelog.txt:%i: Entry without section' % line_id) + last_entry = ChangelogEntry(line.strip(), cur_section, + cur_stable, cur_dev) + entries.append(last_entry) + # entries.setdefault(cur_stable, []).append(last_entry) + # entries.setdefault(cur_dev, []).append(last_entry) + elif line.lstrip().startswith('-'): + if not cur_stable or not cur_dev: + raise ValueError( + 'changelog.txt:%i: Sub-entry without section' % line_id) + if not last_entry: + raise ValueError( + 'changelog.txt:%i: Sub-entry without parent' % line_id) + last_entry.children.append(line.strip('- \n')) + else: + raise ValueError('Invalid line: ' + line) + + return entries + +def consolidate_changelog(all_entries): + for sections in all_entries.values(): + for section, entries in sections.items(): + # sort() is stable, so reverse entries so that older entries for the + # same feature are on top + entries.reverse() + entries.sort(key=lambda entry: entry.sort_key) + new_entries = [] + for feature, group in itertools.groupby(entries, + lambda e: e.feature): + old_entries = list(group) + children = list(itertools.chain(*[entry.children + for entry in old_entries])) + new_entry = copy.deepcopy(old_entries[0]) + new_entry.children = children + new_entries.append(new_entry) + entries[:] = new_entries + + + +def print_changelog(versions, all_entries, path): + # all_entries: version -> section -> entry + with open(path, 'w') as f: + write = lambda s: f.write(s + '\n') + for version in versions: + sections = all_entries[version] + if not sections: + continue + version = 'DFHack ' + version + write(version) + write('=' * len(version)) + write('') + for section in CHANGELOG_SECTIONS: + entries = sections[section] + if not entries: + continue + write(section) + write('-' * len(section)) + for entry in entries: + if len(entry.children) == 1: + write('- ' + entry.feature + ': ' + + entry.children[0].strip('- ')) + continue + elif entry.children: + write('- ' + entry.feature + ':') + write('') + for child in entry.children: + write(' - ' + child) + write('') + else: + write('- ' + entry.feature) + write('') + write('') + + +def generate_changelog(): + entries = parse_changelog() + + # scan for unrecognized sections + for entry in entries: + if entry.section not in CHANGELOG_SECTIONS: + raise RuntimeWarning('Unknown section: ' + entry.section) + + # ordered versions + versions = ['future'] + # map versions to stable versions + stable_version_map = {} + # version -> section -> entry + stable_entries = collections.defaultdict(lambda: + collections.defaultdict(list)) + dev_entries = collections.defaultdict(lambda: + collections.defaultdict(list)) + for entry in entries: + if entry.dev_version not in versions: + versions.append(entry.dev_version) + stable_version_map.setdefault(entry.dev_version, entry.stable_version) + if not entry.dev_only: + stable_entries[entry.stable_version][entry.section].append(entry) + dev_entries[entry.dev_version][entry.section].append(entry) + + consolidate_changelog(stable_entries) + + print_changelog(versions, stable_entries, 'docs/_auto/news.rst') + print_changelog(versions, dev_entries, 'docs/_auto/news-dev.rst') + +if __name__ == '__main__': + os.chdir(os.path.abspath(os.path.dirname(__file__))) + os.chdir('..') + generate_changelog() diff --git a/index.rst b/index.rst index 5cba99e2c..d4bd223c5 100644 --- a/index.rst +++ b/index.rst @@ -49,7 +49,7 @@ Other Contents /docs/Authors /LICENSE - /NEWS + /docs/NEWS /docs/Scripts-removed For Developers From 4158267edc18fa2fece56b3edc090c568d849b78 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 14:14:03 -0400 Subject: [PATCH 0883/1012] Ensure that docs/_auto/ exists --- conf.py | 2 -- docs/_auto/.gitignore | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 docs/_auto/.gitignore diff --git a/conf.py b/conf.py index bd94e8315..8ab449b01 100644 --- a/conf.py +++ b/conf.py @@ -145,8 +145,6 @@ def write_script_docs(): magic strings. """ kinds = document_scripts() - if not os.path.isdir('docs/_auto'): - os.mkdir('docs/_auto') head = { 'base': 'Basic Scripts', 'devel': 'Development Scripts', diff --git a/docs/_auto/.gitignore b/docs/_auto/.gitignore new file mode 100644 index 000000000..30d85567b --- /dev/null +++ b/docs/_auto/.gitignore @@ -0,0 +1 @@ +*.rst From b3c3480c95c2fdc1995c6bd635854e8442dde98b Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 14:26:47 -0400 Subject: [PATCH 0884/1012] Fix doc build dependencies --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f65a46149..0ca927010 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,6 +354,8 @@ if (BUILD_DOCS) file(GLOB SPHINX_DEPS "${CMAKE_CURRENT_SOURCE_DIR}/docs/*.rst" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/changelog.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_changelog.py" "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*" "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" @@ -366,7 +368,6 @@ if (BUILD_DOCS) ) set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" - "${CMAKE_CURRENT_SOURCE_DIR}/NEWS.rst" "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" ) From d9d4b3deba976793a38ae900e59097aa8dea39cf Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 14:49:02 -0400 Subject: [PATCH 0885/1012] Fix NEWS installation --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ca927010..66418270a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,7 +331,7 @@ endif() # build the lib itself IF(BUILD_LIBRARY) add_subdirectory (library) - install(FILES LICENSE.rst NEWS.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES LICENSE.rst docs/NEWS.rst docs/NEWS-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH}) From c993417489a0dec4f86cb8ca284c57ef0c2c468e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 20:55:08 -0400 Subject: [PATCH 0886/1012] Migrate 0.43 and earlier changelogs to History.rst --- docs/History.rst | 994 +++++++++++++++++++++++++++++++++++++++++++++++ docs/NEWS.rst | 7 + 2 files changed, 1001 insertions(+) diff --git a/docs/History.rst b/docs/History.rst index e789d48c6..8386c4cab 100644 --- a/docs/History.rst +++ b/docs/History.rst @@ -12,11 +12,1005 @@ in ``NEWS.rst`` doesn't get too long. .. contents:: :depth: 2 +DFHack 0.43.05-r3 +================= + +Internals +--------- +- Fixed an uncommon crash that could occur when printing text to the console +- Added lots of previously-missing DF classes +- More names for fields: https://github.com/DFHack/df-structures/compare/0.43.05-r2...0.43.05 + +Fixes +----- +- Linux: fixed argument to ``setarch`` in the ``dfhack`` launcher script +- Ruby: fixed an error that occurred when the DF path contained an apostrophe +- `diggingInvaders` now compiles again and is included +- `labormanager`: + + - stopped waiting for on-duty military dwarves with minor injuries to obtain care + - stopped waiting for meetings when participant(s) are dead + - fixed a crash for dwarves with no cultural identity + +- `luasocket`: fixed ``receive()`` with a byte count +- `orders`: fixed an error when importing orders with material categories +- `siren`: fixed an error +- `stockpiles`: fixed serialization of barrel and bin counts +- `view-item-info`: fixed a ``CHEESE_MAT``-related error + +Misc Improvements +----------------- +- `devel/export-dt-ini`: added more offsets for new DT versions +- `digfort`: added support for changing z-levels +- `exportlegends`: suppressed ABSTRACT_BUILDING warning +- `gui/dfstatus`: excluded logs in constructions +- `labormanager`: + + - stopped assigning woodcutting jobs to elves + - "recover wounded" jobs now weighted based on altruism + +- `remotefortressreader`: added support for buildings, grass, riders, and + hair/beard styles + + +DFHack 0.43.05-r2 +================= + +Internals +--------- +- Rebuilding DFHack can be faster if nothing Git-related has changed +- Plugins can now hook Screen::readTile() +- Improved Lua compatibility with plugins that hook into GUI functions (like TWBT) +- Expanded focus strings for jobmanagement and workquota_condition viewscreens +- ``Gui::getAnyUnit()``: added support for viewscreen_unitst, + viewscreen_textviewerst, viewscreen_layer_unit_relationshipst +- Fixed (limited) keybinding support in PRINT_MODE:TEXT on macOS +- Added a new standardized ``Gui::refreshSidebar()`` function to fix behavior of + some plugins on the lowest z-level +- New ``Buildings`` module functions: ``markedForRemoval()``, ``getCageOccupants()`` +- Limited recursive command invocations to 20 to prevent crashes +- Added an ``onLoad.init-example`` file + +Lua +--- +- Improved C++ exception handling for some native functions that aren't direct + wrappers around C++ functions (in this case, error messages could be nil and + cause the Lua interpreter to quit) +- Added support for a ``key_pen`` option in Label widgets +- Fixed ``to_first`` argument to ``dfhack.screen.dismiss()`` +- Added optional ``map`` parameters to some screen functions +- Exposed some more functions to Lua: + + - ``dfhack.gui.refreshSidebar()`` + - ``dfhack.gui.getAnyUnit()`` + - ``dfhack.gui.getAnyBuilding()`` + - ``dfhack.gui.getAnyItem()`` + - ``dfhack.gui.getAnyPlant()`` + - ``dfhack.gui.getDepthAt()`` + - ``dfhack.units.getUnitsInBox()`` + - ``dfhack.units.isVisible()`` + - ``dfhack.maps.isTileVisible()`` + - ``dfhack.buildings.markedForRemoval()`` + - ``dfhack.buildings.getCageOccupants()`` + - ``dfhack.internal.md5()`` + - ``dfhack.internal.md5File()`` + - ``dfhack.internal.threadid()`` + +- New function: ``widgets.Pages:getSelectedPage()`` +- Added a ``key`` option to EditField and FilteredList widgets +- Fixed an issue preventing ``repeatUtil.cancel()`` from working when called + from the callback + +Ruby +---- +- Fixed a crash when creating new instances of DF virtual classes (e.g. fixes a + `lever` crash) +- Ruby scripts can now be loaded from any script paths specified (from script- + paths.txt or registered through the Lua API) +- ``unit_find()`` now uses ``Gui::getSelectedUnit()`` and works in more places + (e.g. `exterminate` now works from more screens, like `command-prompt`) + +New Internal Commands +--------------------- +- `alias`: allows configuring aliases for other commands + +New Plugins +----------- +- `orders`: Manipulate manager orders +- `pathable`: Back-end for `gui/pathable` + +New Scripts +----------- +- `clear-smoke`: Removes all smoke from the map +- `empty-bin`: Empty a bin onto the floor +- `fix/retrieve-units`: Spawns stuck invaders/guests +- `fix/stuck-merchants`: Dismisses stuck merchants that haven't entered the map yet +- `gui/pathable`: View whether tiles on the map can be pathed to +- `gui/teleport`: A front-end for the `teleport` script +- `warn-stuck-trees`: Detects citizens stuck in trees + +New Tweaks +---------- +- `tweak` burrow-name-cancel: Implements the "back" option when renaming a + burrow, which currently does nothing (:bug:`1518`) +- `tweak` cage-butcher: Adds an option to butcher units when viewing cages with "q" + +Fixes +----- +- Enforced use of ``stdout.log`` and ``stderr.log`` (instead of their ``.txt`` + counterparts) on Windows +- Fixed ``getItemBaseValue()`` for cheese, sheets and instruments +- Fixed alignment in: + + - ``viewscreen_choose_start_sitest`` + - ``viewscreen_export_graphical_mapst`` + - ``viewscreen_setupadventurest`` + - ``viewscreen_setupdwarfgamest`` + +- `adv-max-skills`: fixed error due to viewscreen changes +- `autolabor`: fixed a crash when assigning haulers while traders are active +- `buildingplan`: fixed an issue that prevented certain numbers from being used + in building names +- `confirm`: + + - dialogs are now closed permanently when disabled from the settings UI + - fixed an issue that could have prevented closing dialogs opened by pressing "s" + +- `embark-tools`: stopped the sand indicator from overlapping dialogs +- `exportlegends`: fixed some crashes and site map issues +- `devel/find-offsets`: fixed ``current_weather`` scan +- `gui/extended-status`: fixed an error when no beds are available +- `gui/family-affairs`: fixed issues with assigning lovers +- `gui/gm-editor`: + + - made keybinding display order consistent + - stopped keys from performing actions in help screen + +- `gui/manager-quantity`: + + - now allows orders with a limit of 0 + - fixed screen detection + +- `gui/mechanisms`, `gui/room-list`: fixed an issue when recentering the map when exiting +- `lever`: prevented pulling non-lever buildings, which can cause crashes +- `markdown`: fixed file encoding +- `modtools/create-unit`: + + - fixed when popup announcements are present + - added checks to ensure that the current game mode is restored + +- `resume`: stopped drawing on the map border +- `show-unit-syndromes`: fixed an error when handling some syndromes +- `strangemood`: fixed some issues with material searches +- `view-item-info`: fixed a color-related error for some materials + +Misc Improvements +----------------- +- Docs: prevented automatic hyphenation in some browsers, which was producing + excessive hyphenation sometimes +- `command-prompt`: invoking ``command-prompt`` a second time now hides the prompt +- `gui/extended-status`: added an option to assign/replace the manager +- `gui/load-screen`: + + - adjusted dialog width for long folder names + - added modification times and DF versions to dialog + +- `gui/mechanisms`, `gui/room-list`, `gui/siege-engine`: add and list "exit to map" options +- `lever`: added support for pulling levers at high priority +- `markdown`: now recognizes ``-n`` in addition to ``/n`` +- `remotefortressreader`: more data exported, used by Armok Vision v0.17.0 +- `resume`, `siege-engine`: improved compatibility with GUI-hooking plugins (like TWBT) +- `sc-script`: improved help text +- `teleport`: can now be used as a module +- `tweak` embark-profile-name: now enabled in ``dfhack.init-example`` +- `tweak` hotkey-clear: fixed display on larger screens + + +DFHack 0.43.05-r1 +================= + +Internals +--------- +- 64-bit support on all platforms +- Several structure fixes to match 64-bit DF's memory layout +- Added ``DFHack::Job::removeJob()`` function +- New module: ``Designations`` - handles designation creation (currently for plants only) +- Added ``Gui::getSelectedPlant()`` +- Added ``Units::getMainSocialActivity()``, ``Units::getMainSocialEvent()`` +- Visual Studio 2015 now required to build on Windows instead of 2010 +- GCC 4.8 or newer required to build on Linux and OS X (and now supported on OS X) +- Updated TinyXML from 2.5.3 to 2.6.2 +- Added the ability to download files manually before building + +Lua +--- +- Lua has been updated to 5.3 - see http://www.lua.org/manual/5.3/readme.html for details + + - Floats are no longer implicitly converted to integers in DFHack API calls + +- ``df.new()`` supports more types: ``char``, ``intptr_t``, ``uintptr_t``, ``long``, ``unsigned long`` +- String representations of vectors and a few other containers now include their lengths +- Added a ``tile-material`` module +- Added a ``Painter:key_string()`` method +- Made ``dfhack.gui.revealInDwarfmodeMap()`` available + +Ruby +---- +- Added support for loading ruby 2.x libraries + +New Plugins +----------- +- `dwarfvet` enables animal caretaking +- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs +- `labormanager` (formerly autolabor2): a more advanced alternative to `autolabor` +- `misery`: re-added and updated for the 0.4x series +- `title-folder`: shows DF folder name in window title bar when enabled + +New Scripts +----------- +- `adv-rumors`: improves the "Bring up specific incident or rumor" menu in adventure mode +- `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. +- `install-info`: Logs basic troubleshooting information about the current DFHack installation +- `load-save`: loads a save non-interactively +- `modtools/change-build-menu`: Edit the build mode sidebar menus +- `modtools/if-entity`: Run a command if the current entity matches a given ID +- `season-palette`: Swap color palettes with the changes of the seasons +- `unforbid`: Unforbids all items + +New Tweaks +---------- +- `tweak condition-material `: fixes a crash in the work order condition material list +- `tweak hotkey-clear `: adds an option to clear bindings from DF hotkeys + +Fixes +----- +- The DF path on OS X can now contain spaces and ``:`` characters +- Buildings::setOwner() changes now persist properly when saved +- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable +- Fixed ``plug`` output alignment for plugins with long names +- `add-thought`: fixed support for emotion names +- `autochop`: + + - fixed several issues with job creation and removal + - stopped designating the center tile (unreachable) for large trees + - stopped options from moving when enabling and disabling burrows + - fixed display of unnamed burrows + +- `devel/find-offsets`: fixed a crash when vtables used by globals aren't available +- `getplants`: + + - fixed several issues with job creation and removal + - stopped designating the center tile (unreachable) for large trees + +- `gui/workflow`: added extra keybinding to work with `gui/extended-status` +- `manipulator`: + + - Fixed crash when selecting a profession from an empty list + - Custom professions are now sorted alphabetically more reliably + +- `modtools/create-item`: + + - made gloves usable by specifying handedness + - now creates pairs of boots and gloves + +- `modtools/create-unit`: + + - stopped permanently overwriting the creature creation menu in arena mode + - now uses non-English names + - added ``-setUnitToFort`` option to make a unit a civ/group member more easily + - fixed some issues where units would appear in unrevealed areas of the map + +- `modtools/item-trigger`: fixed errors with plant growths +- `remotefortressreader`: fixed a crash when serializing the local map +- `ruby`: fixed a crash when unloading the plugin on Windows +- `stonesense`: disabled overlay in STANDARD-based print modes to prevent crashes +- `title-version`: now hidden when loading an arena + +Misc Improvements +----------------- +- Documented all default keybindings (from :file:`dfhack.init-example`) in the + docs for the relevant commands; updates enforced by build system. +- `autounsuspend`: reduced update frequency to address potential performance issues +- `gui/extended-status`: added a feature to queue beds +- `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.) +- `manipulator`: added social activities to job column +- `remotefortressreader`: Added support for + + - world map snow coverage + - spatters + - wall info + - site towers, world buildings + - surface material + - building items + - DF version info + +- `title-version`: Added a prerelease indicator +- `workflow`: Re-added ``Alt-W`` keybindings + + +DFHack 0.43.05-beta2 +==================== + +Fixes +----- +- Fixed Buildings::updateBuildings(), along with building creation/deletion events +- Fixed ``plug`` output alignment for plugins with long names +- Fixed a crash that happened when a ``LUA_PATH`` environment variable was set +- `add-thought`: fixed number conversion +- `gui/workflow`: fixed range editing producing the wrong results for certain numbers +- `modtools/create-unit`: now uses non-English names +- `modtools/item-trigger`: fixed errors with plant growths +- `remotefortressreader`: fixed a crash when serializing the local map +- `stockflow`: fixed an issue with non-integer manager order limits +- `title-folder`: fixed compatibility issues with certain SDL libraries on macOS + +Structures +---------- +- Added some missing renderer VTable addresses on macOS +- ``entity.resources.organic``: identified ``parchment`` +- ``entity_sell_category``: added ``Parchment`` and ``CupsMugsGoblets`` +- ``ui_advmode_menu``: added ``Build`` +- ``ui_unit_view_mode``: added ``PrefOccupation`` +- ``unit_skill``: identified ``natural_skill_lvl`` (was ``unk_1c``) +- ``viewscreen_jobmanagementst``: identified ``max_workshops`` +- ``viewscreen_overallstatusst``: made ``visible_pages`` an enum +- ``viewscreen_pricest``: identified fields +- ``viewscreen_workquota_conditionst``: gave some fields ``unk`` names + +API Changes +----------- +- Allowed the Lua API to accept integer-like floats and strings when expecting an integer +- Lua: New ``Painter:key_string()`` method +- Lua: Added ``dfhack.getArchitecture()`` and ``dfhack.getArchitectureName()`` + +Additions/Removals: +------------------- +- Added `adv-rumors` script: improves the "Bring up specific incident or rumor" menu in adventure mode +- Added `install-info` script for basic troubleshooting +- Added `tweak condition-material `: fixes a crash in the work order condition material list +- Added `tweak hotkey-clear `: adds an option to clear bindings from DF hotkeys +- `autofarm`: reverted local biome detection (from 0.43.05-alpha3) + +Other Changes +------------- +- Added a DOWNLOAD_RUBY CMake option, to allow use of a system/external ruby library +- Added the ability to download files manually before building +- `gui/extended-status`: added a feature to queue beds +- `remotefortressreader`: added building items, DF version info +- `stonesense`: Added support for 64-bit macOS and Linux + +DFHack 0.43.05-beta1 +==================== + +Fixes +----- +- Fixed various crashes on 64-bit Windows related to DFHack screens, notably `manipulator` +- Fixed addresses of next_id globals on 64-bit Linux (fixes an `automaterial`/box-select crash) +- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable +- `modtools/create-unit`: stopped permanently overwriting the creature creation + menu in arena mode +- `season-palette`: fixed an issue where only part of the screen was redrawn + after changing the color scheme +- `title-version`: now hidden when loading an arena + +Structures +---------- +- ``file_compressorst``: fixed field sizes on x64 +- ``historical_entity``: fixed alignment on x64 +- ``ui_sidebar_menus.command_line``: fixed field sizes on x64 +- ``viewscreen_choose_start_sitest``: added 3 missing fields, renamed ``in_embark_only_warning`` +- ``viewscreen_layer_arena_creaturest``: identified more fields +- ``world.math``: identified +- ``world.murky_pools``: identified + +Additions/Removals +------------------ +- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs + +Other Changes +------------- +- `title-version`: Added a prerelease indicator + +DFHack 0.43.05-alpha4 +===================== + +Fixes +----- +- Fixed an issue with uninitialized bitfields that was causing several issues + (disappearing buildings in `buildingplan`'s planning mode, strange behavior in + the extended `stocks` screen, and likely other problems). This issue was + introduced in 0.43.05-alpha3. +- `stockflow`: Fixed an "integer expected" error + +Structures +---------- +- Located several globals on 64-bit Linux: flows, timed_events, ui_advmode, + ui_building_assign_type, ui_building_assign_is_marked, + ui_building_assign_units, ui_building_assign_items, and ui_look_list. This + fixes `search-plugin`, `zone`, and `force`, among others. +- ``ui_sidebar_menus``: Fixed some x64 alignment issues + +Additions/Removals +------------------ +- Added `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile. + Useful for fixing blocked tiles introduced by the above buildingplan issue. +- Added a Lua ``tile-material`` module + +Other Changes +------------- +- `labormanager`: Add support for shell crafts +- `manipulator`: Custom professions are now sorted alphabetically more reliably + +DFHack 0.43.05-alpha3 +===================== + +Fixes +----- +- `add-thought`: fixed support for emotion names +- `autofarm`: Made surface farms detect local biome +- `devel/export-dt-ini`: fixed squad_schedule_entry size +- `labormanager`: + + - Now accounts for unit attributes + - Made instrument-building jobs work (constructed instruments) + - Fixed deconstructing constructed instruments + - Fixed jobs in bowyer's shops + - Fixed trap component jobs + - Fixed multi-material construction jobs + - Fixed deconstruction of buildings containing items + - Fixed interference caused by "store item in vehicle" jobs + +- `manipulator`: Fixed crash when selecting a profession from an empty list +- `ruby`: + + - Fixed crash on Win64 due to truncated global addresses + - Fixed compilation on Win64 + - Use correct raw string length with encodings + +Structures +---------- +- Changed many ``comment`` XML attributes with version numbers to use new + ``since`` attribute instead +- ``activity_event_conflictst.sides``: named many fields +- ``building_def.build_key``: fixed size on 64-bit Linux and OS X +- ``historical_kills``: + + - ``unk_30`` -> ``killed_underground_region`` + - ``unk_40`` -> ``killed_region`` + +- ``historical_kills.killed_undead``: removed ``skeletal`` flag +- ``ui_advmode``: aligned enough so that it doesn't crash (64-bit OS X/Linux) +- ``ui_advmode.show_menu``: changed from bool to enum +- ``unit_personality.emotions.flags``: now a bitfield + +API Changes +----------- +- Added ``DFHack::Job::removeJob()`` function +- C++: Removed bitfield constructors that take an initial value. These kept + bitfields from being used in unions. Set ``bitfield.whole`` directly instead. +- Lua: ``bitfield.whole`` now returns an integer, not a decimal + +Additions/Removals +------------------ +- Removed source for treefarm plugin (wasn't built) +- Added `modtools/change-build-menu`: Edit the build mode sidebar menus +- Added `modtools/if-entity`: Run a command if the current entity matches a + given ID +- Added `season-palette`: Swap color palettes with the changes of the seasons + +Other changes +------------- +- Changed minimum GCC version to 4.8 on OS X and Linux (earlier versions + wouldn't have worked on Linux anyway) +- Updated TinyXML from 2.5.3 to 2.6.2 + +DFHack 0.43.03-r1 +================= + +Lua +--- +- Label widgets can now easily register handlers for mouse clicks + +New Features +------------ +- `add-thought`: allow syndrome name as ``-thought`` argument +- `gui/gm-editor` + + - Added ability to insert default types into containers. For primitive types leave the type entry empty, and for references use ``*``. + - Added ``shift-esc`` binding to fully exit from editor + - Added ``gui/gm-editor toggle`` command to toggle editor visibility (saving position) + +- `modtools/create-unit`: + + - Added an option to attach units to an existing wild animal population + - Added an option to attach units to a map feature + +Fixes +----- +- `autofarm`: Can now handle crops that grow for more than a season +- `combine-plants`: Fixed recursion into sub-containers +- `createitem`: Now moves multiple created items to cursor correctly +- `exportlegends`: Improved handling of unknown enum items (fixes many errors) +- `gui/create-item`: Fixed quality when creating multiple items +- `gui/mod-manager`: Fixed error when mods folder doesn't exist +- `modtools/item-trigger`: Fixed handling of items with subtypes +- `reveal`: ``revflood`` now handles constructed stairs with floors in generated fortresses +- `stockflow`: + + - Can order metal mechanisms + - Fixed material category of thread-spinning jobs + +Misc Improvements +----------------- +- The built-in ``ls`` command now wraps the descriptions of commands +- `catsplosion`: now a lua script instead of a plugin +- `fix/diplomats`: replaces ``fixdiplomats`` +- `fix/merchants`: replaces ``fixmerchants`` +- `prefchange`: added a ``help`` option +- `probe`: now displays raw tiletype names +- Unified script documentation and in-terminal help options + +Removed +------- +- `tweak` manager-quantity: no longer needed + +DFHack 0.42.06-r1 +================= + +Internals +--------- +- Commands to run on startup can be specified on the command line with ``+`` + + Example:: + + ./dfhack +devel/print-args example + "Dwarf Fortress.exe" +devel/print-args example + +- Prevented plugins with active viewscreens from being unloaded and causing a crash +- Additional script search paths can be specified in dfhack-config/script-paths.txt + +Lua +--- +- `building-hacks` now supports ``auto_gears`` flags. It automatically finds and animates gears in building definition +- Changed how `eventful` triggers reaction complete. Now it has ``onReactionComplete`` and ``onReactionCompleting``. Second one can be canceled + +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 +- `modtools/extra-gamelog`: replaces ``log-region``, ``soundsense-season``, and ``soundsense`` + +New Features +------------ +- `buildingplan`: Support for floodgates, grates, and bars +- `colonies`: new ``place`` subcommand and supports any vermin (default honey bees) +- `confirm`: Added a confirmation for retiring locations +- `exportlegends`: Exports more information (poetic/musical/dance forms, written/artifact content, landmasses, extra histfig information, and more) +- `search-plugin`: Support for new screens: + + - location occupation assignment + - civilization animal training knowledge + - animal trainer assignment + +- `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 + +- `zone`: + + - Added ``unassign`` subcommand + - Added ``only`` option to ``assign`` subcommand + +Fixes +----- +- Fixed a crash bug caused by the historical figures DFHack uses to store persistent data. +- More plugins should recognize non-dwarf citizens +- Fixed a possible crash from cloning jobs +- moveToBuilding() now sets flags for items that aren't a structural part of the building properly +- `autotrade`, `stocks`: Made trading work when multiple caravans are present but only some can trade +- `confirm` note-delete: No longer interferes with name entry +- `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42 +- `fastdwarf`: Fixed a bug involving teleporting mothers but not the babies they're holding. +- `gaydar`: Fixed text display on OS X/Linux and failure with soul-less creatures +- `manipulator`: + + - allowed editing of non-dwarf citizens + - stopped ghosts and visitors from being editable + - fixed applying last custom profession + +- `modtools/create-unit`: Stopped making units without civs historical figures +- `modtools/force`: + + - Removed siege option + - Prevented a crash resulting from a bad civilization option + +- `showmood`: Fixed name display on OS X/Linux +- `view-item-info`: Fixed density units + +Misc Improvements +----------------- +- `autochop`: Can now edit log minimum/maximum directly and remove limit entirely +- `autolabor`, `autohauler`, `manipulator`: Added support for new jobs/labors/skills +- `colonies`: now implemented by a script +- `createitem`: Can now create items anywhere without specifying a unit, as long as a unit exists on the map +- `devel/export-dt-ini`: Updated for 0.42.06 +- `devel/find-offsets`: Automated several more scans +- `gui/gm-editor`: Now supports finding some items with a numeric ID (with ``i``) +- `lua`: Now supports some built-in variables like `gui/gm-editor`, e.g. ``unit``, ``screen`` +- `remotefortressreader`: Can now trigger keyboard events +- `stockflow`: Now offers better control over individual craft jobs +- `weather`: now implemented by a script +- `zone`: colored output + +Removed +------- +- DFusion: legacy script system, obsolete or replaced by better alternatives + + +DFHack 0.40.24-r5 +================= + +New Features +------------ +- `confirm`: + + - Added a ``uniform-delete`` option for military uniform deletion + - Added a basic in-game configuration UI + +Fixes +----- +- Fixed a rare crash that could result from running `keybinding` in onLoadWorld.init +- Script help that doesn't start with a space is now recognized correctly +- `confirm`: Fixed issues with haul-delete, route-delete, and squad-disband confirmations intercepting keys too aggressively +- `emigration` should work now +- `fix-unit-occupancy`: Significantly optimized - up to 2,000 times faster in large fortresses +- `gui/create-item`: Allow exiting quantity prompt +- `gui/family-affairs`: Fixed an issue where lack of relationships wasn't recognized and other issues +- `modtools/create-unit`: Fixed a possible issue in reclaim fortress mode +- `search-plugin`: Fixed a crash on the military screen +- `tweak` max-wheelbarrow: Fixed a minor display issue with large numbers +- `workflow`: Fixed a crash related to job postings (and added a fix for existing, broken jobs) + +Misc Improvements +----------------- +- Unrecognized command feedback now includes more information about plugins +- `fix/dry-buckets`: replaces the ``drybuckets`` plugin +- `feature`: now implemented by a script + +DFHack 0.40.24-r4 +================= + +Internals +--------- +- A method for caching screen output is now available to Lua (and C++) +- Developer plugins can be ignored on startup by setting the ``DFHACK_NO_DEV_PLUGINS`` environment variable +- The console on Linux and OS X now recognizes keyboard input between prompts +- JSON libraries available (C++ and Lua) +- More DFHack build information used in plugin version checks and available to plugins and lua scripts +- Fixed a rare overflow issue that could cause crashes on Linux and OS X +- Stopped DF window from receiving input when unfocused on OS X +- Fixed issues with keybindings involving :kbd:`Ctrl`:kbd:`A` and :kbd:`Ctrl`:kbd:`Z`, + as well as :kbd:`Alt`:kbd:`E`/:kbd:`U`/:kbd:`N` on OS X +- Multiple contexts can now be specified when adding keybindings +- Keybindings can now use :kbd:`F10`-:kbd:`F12` and :kbd:`0`-:kbd:`9` +- Plugin system is no longer restricted to plugins that exist on startup +- :file:`dfhack.init` file locations significantly generalized + +Lua +--- +- Scripts can be enabled with the built-in `enable`/`disable ` commands +- A new function, ``reqscript()``, is available as a safer alternative to ``script_environment()`` +- Lua viewscreens can choose not to intercept the OPTIONS keybinding + +New internal commands +--------------------- +- `kill-lua`: Interrupt running Lua scripts +- `type`: Show where a command is implemented + +New plugins +----------- +- `confirm`: Adds confirmation dialogs for several potentially dangerous actions +- `fix-unit-occupancy`: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (:bug:`3499`) +- `title-version` (formerly ``vshook``): Display DFHack version on title screen + +New scripts +----------- +- `armoks-blessing`: Adjust all attributes, personality, age and skills of all dwarves in play +- `brainwash`: brainwash a dwarf (modifying their personality) +- `burial`: sets all unowned coffins to allow burial ("-pets" to allow pets too) +- `deteriorateclothes`: make worn clothes on the ground wear far faster to boost FPS +- `deterioratecorpses`: make body parts wear away far faster to boost FPS +- `deterioratefood`: make food vanish after a few months if not used +- `elevate-mental`: elevate all the mental attributes of a unit +- `elevate-physical`: elevate all the physical attributes of a unit +- `emigration`: stressed dwarves may leave your fortress if they see a chance +- `fix-ster`: changes fertility/sterility of animals or dwarves +- `gui/family-affairs`: investigate and alter romantic relationships +- `make-legendary`: modify skill(s) of a single unit +- `modtools/create-unit`: create new units from nothing +- `modtools/equip-item`: a script to equip items on units +- `points`: set number of points available at embark screen +- `pref-adjust`: Adjust all preferences of all dwarves in play +- `rejuvenate`: make any "old" dwarf 20 years old +- `starvingdead`: make undead weaken after one month on the map, and crumble after six +- `view-item-info`: adds information and customisable descriptions to item viewscreens +- `warn-starving`: check for starving, thirsty, or very drowsy units and pause with warning if any are found + +New tweaks +---------- +- embark-profile-name: Allows the use of lowercase letters when saving embark profiles +- kitchen-keys: Fixes DF kitchen meal keybindings +- kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences +- kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs + +Fixes +----- +- Plugins with vmethod hooks can now be reloaded on OS X +- Lua's ``os.system()`` now works on OS X +- Fixed default arguments in Lua gametype detection functions +- Circular lua dependencies (reqscript/script_environment) fixed +- Prevented crash in ``Items::createItem()`` +- `buildingplan`: Now supports hatch covers +- `gui/create-item`: fixed assigning quality to items, made :kbd:`Esc` work properly +- `gui/gm-editor`: handles lua tables properly +- `help`: now recognizes built-in commands, like ``help`` +- `manipulator`: fixed crash when selecting custom professions when none are found +- `remotefortressreader`: fixed crash when attempting to send map info when no map was loaded +- `search-plugin`: fixed crash in unit list after cancelling a job; fixed crash when disabling stockpile category after searching in a subcategory +- `stockpiles`: now checks/sanitizes filenames when saving +- `stocks`: fixed a crash when right-clicking +- `steam-engine`: fixed a crash on arena load; number keys (e.g. 2/8) take priority over cursor keys when applicable +- tweak fps-min fixed +- tweak farm-plot-select: Stopped controls from appearing when plots weren't fully built +- `workflow`: Fixed some issues with stuck jobs. Existing stuck jobs must be cancelled and re-added +- `zone`: Fixed a crash when using ``zone set`` (and a few other potential crashes) + +Misc Improvements +----------------- +- DFHack documentation: + + - massively reorganised, into files of more readable size + - added many missing entries + - indexes, internal links, offline search all documents + - includes documentation of linked projects (df-structures, third-party scripts) + - better HTML generation with Sphinx + - documentation for scripts now located in source files + +- `autolabor`: + + - Stopped modification of labors that shouldn't be modified for brokers/diplomats + - Prioritize skilled dwarves more efficiently + - Prevent dwarves from running away with tools from previous jobs + +- `automaterial`: Fixed several issues with constructions being allowed/disallowed incorrectly when using box-select +- `dwarfmonitor`: + + - widgets' positions, formats, etc. are now customizable + - weather display now separated from the date display + - New mouse cursor widget + +- `gui/dfstatus`: Can enable/disable individual categories and customize metal bar list +- `full-heal`: ``-r`` option removes corpses +- `gui/gm-editor` + + - Pointers can now be displaced + - Added some useful aliases: "item" for the selected item, "screen" for the current screen, etc. + - Now avoids errors with unrecognized types + +- `gui/hack-wish`: renamed to `gui/create-item` +- `keybinding list ` accepts a context +- `lever`: + + - Lists lever names + - ``lever pull`` can be used to pull the currently-selected lever + +- ``memview``: Fixed display issue +- `modtools/create-item`: arguments are named more clearly, and you can specify the creator to be the unit with id ``df.global.unit_next_id-1`` (useful in conjunction with `modtools/create-unit`) +- ``nyan``: Can now be stopped with dfhack-run +- `plug`: lists all plugins; shows state and number of commands in plugins +- `prospect`: works from within command-prompt +- `quicksave`: Restricted to fortress mode +- `remotefortressreader`: Exposes more information +- `search-plugin`: + + - Supports noble suggestion screen (e.g. suggesting a baron) + - Supports fortress mode loo[k] menu + - Recognizes ? and ; keys + +- `stocks`: can now match beginning and end of item names +- `teleport`: Fixed cursor recognition +- `tidlers`, `twaterlvl`: now implemented by scripts instead of a plugin +- `tweak`: + + - debug output now logged to stderr.log instead of console - makes DFHack start faster + - farm-plot-select: Fixed issues with selecting undiscovered crops + +- `workflow`: Improved handling of plant reactions + +Removed +------- +- `embark-tools` nano: 1x1 embarks are now possible in vanilla 0.40.24 + +DFHack 0.40.24-r3 +================= + +Internals +--------- +- Ruby library now included on OS X - Ruby scripts should work on OS X 10.10 +- libstdc++ should work with older versions of OS X +- Added support for `onMapLoad.init / onMapUnload.init ` scripts +- game type detection functions are now available in the World module +- The ``DFHACK_LOG_MEM_RANGES`` environment variable can be used to log information to ``stderr.log`` on OS X +- Fixed adventure mode menu names +- Fixed command usage information for some commands + +Lua +--- +- Lua scripts will only be reloaded if necessary +- Added a ``df2console()`` wrapper, useful for printing DF (CP437-encoded) text to the console in a portable way +- Added a ``strerror()`` wrapper + +New Internal Commands +--------------------- +- `hide`, `show`: hide and show the console on Windows +- `sc-script`: Allows additional scripts to be run when certain events occur (similar to `onLoad.init` scripts) + +New Plugins +----------- +- `autohauler`: A hauling-only version of autolabor + +New Scripts +----------- +- `modtools/reaction-product-trigger`: triggers callbacks when products are produced (contrast with when reactions complete) + +New Tweaks +---------- +- `fps-min `: Fixes the in-game minimum FPS setting +- `shift-8-scroll `: Gives Shift+8 (or ``*``) priority when scrolling menus, instead of scrolling the map +- `tradereq-pet-gender `: Displays pet genders on the trade request screen + +Fixes +----- +- Fixed game type detection in `3dveins`, `gui/create-item`, `reveal`, `seedwatch` +- ``PRELOAD_LIB``: More extensible on Linux +- `add-spatter`, `eventful`: Fixed crash on world load +- `add-thought`: Now has a proper subthought arg. +- `building-hacks`: Made buildings produce/consume correct amount of power +- `fix-armory`: compiles and is available again (albeit with issues) +- `gui/gm-editor`: Added search option (accessible with "s") +- `hack-wish `: Made items stack properly. +- `modtools/skill-change`: Made level granularity work properly. +- `show-unit-syndromes`: should work +- `stockflow`: + + - Fixed error message in Arena mode + - no longer checks the DF version + - fixed ballistic arrow head orders + - convinces the bookkeeper to update records more often + +- `zone`: Stopped crash when scrolling cage owner list + +Misc Improvements +----------------- +- `autolabor`: A negative pool size can be specified to use the most unskilled dwarves +- `building-hacks`: + + - Added a way to allow building to work even if it consumes more power than is available. + - Added setPower/getPower functions. + +- `catsplosion`: Can now trigger pregnancies in (most) other creatures +- `exportlegends`: ``info`` and ``all`` options export ``legends_plus.xml`` with more data for legends utilities +- `manipulator`: + + - Added ability to edit nicknames/profession names + - added "Job" as a View Type, in addition to "Profession" and "Squad" + - added custom profession templates with masking + +- `remotefortressreader`: Exposes more information + + +DFHack 0.40.24-r2 +================= + +Internals +--------- +- Lua scripts can set environment variables of each other with ``dfhack.run_script_with_env`` +- Lua scripts can now call each others internal nonlocal functions with ``dfhack.script_environment(scriptName).functionName(arg1,arg2)`` +- `eventful`: Lua reactions no longer require LUA_HOOK as a prefix; you can register a callback for the completion of any reaction with a name +- Filesystem module now provides file access/modification times and can list directories (normally and recursively) +- Units Module: New functions:: + + isWar + isHunter + isAvailableForAdoption + isOwnCiv + isOwnRace + getRaceName + getRaceNamePlural + getRaceBabyName + getRaceChildName + isBaby + isChild + isAdult + isEggLayer + isGrazer + isMilkable + isTrainableWar + isTrainableHunting + isTamable + isMale + isFemale + isMerchant + isForest + isMarkedForSlaughter + +- Buildings Module: New Functions:: + + isActivityZone + isPenPasture + isPitPond + isActive + findPenPitAt + +Fixes +----- +- ``dfhack.run_script`` should correctly find save-specific scripts now. +- `add-thought`: updated to properly affect stress. +- `hfs-pit`: should work now +- `autobutcher`: takes gelding into account +- :file:`init.lua` existence checks should be more reliable (notably when using non-English locales) + +Misc Improvements +----------------- +Multiline commands are now possible inside dfhack.init scripts. See :file:`dfhack.init-example` for example usage. + + +DFHack 0.40.24-r1 +================= + +Internals +--------- +CMake shouldn't cache DFHACK_RELEASE anymore. People may need to manually update/delete their CMake cache files to get rid of it. + + +DFHack 0.40.24-r0 +================= + +Internals +--------- +- `EventManager`: fixed crash error with EQUIPMENT_CHANGE event. +- key modifier state exposed to Lua (ie :kbd:`Ctrl`, :kbd:`Alt`, :kbd:`Shift`) + +Fixes +----- +``dfhack.sh`` can now be run from other directories on OS X + +New Plugins +----------- +- `blueprint`: export part of your fortress to quickfort .csv files + +New Scripts +----------- +- `hotkey-notes`: print key, name, and jump position of hotkeys + +Removed +------- +- needs_porting/* + +Misc Improvements +----------------- +- Added support for searching more lists DFHack 0.40.23-r1 ================= Internals +--------- - plugins will not be loaded if globals they specify as required are not located (should prevent some crashes) Fixes diff --git a/docs/NEWS.rst b/docs/NEWS.rst index 60d180eaa..506999280 100644 --- a/docs/NEWS.rst +++ b/docs/NEWS.rst @@ -12,3 +12,10 @@ Changelog :depth: 2 .. include:: /docs/_auto/news.rst + + +Older Changelogs +================ +Are kept in a seperate file: `HISTORY` + +.. that's ``docs/history.rst``, if you're reading the raw text. From f8f67b1e2366155d656f07b033b03bc0bdfee38e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 21:36:18 -0400 Subject: [PATCH 0887/1012] gen_changelog: remove double colons, add --check flag --- docs/gen_changelog.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index 2336e9962..189591c1f 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -2,6 +2,7 @@ import collections import copy import itertools import os +import sys CHANGELOG_SECTIONS = [ 'New Plugins', @@ -37,7 +38,7 @@ class ChangelogEntry(object): self.children.insert(0, description.strip()) else: self.feature = text - self.feature = self.feature.replace(':\\', ':') + self.feature = self.feature.replace(':\\', ':').rstrip(':') self.sort_key = self.feature.upper() @@ -171,7 +172,20 @@ def generate_changelog(): print_changelog(versions, stable_entries, 'docs/_auto/news.rst') print_changelog(versions, dev_entries, 'docs/_auto/news-dev.rst') + return entries + if __name__ == '__main__': os.chdir(os.path.abspath(os.path.dirname(__file__))) os.chdir('..') - generate_changelog() + entries = generate_changelog() + if '--check' in sys.argv: + with open('docs/_auto/news.rst') as f: + content_stable = f.read() + with open('docs/_auto/news-dev.rst') as f: + content_dev = f.read() + for entry in entries: + for description in entry.children: + if not entry.dev_only and description not in content_stable: + print('stable missing: ' + description) + if description not in content_dev: + print('dev missing: ' + description) From ffb62d0e73151874cfc0ceb7adbd9464020f3391 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 21:36:41 -0400 Subject: [PATCH 0888/1012] Add remaining 0.44 changelog entries --- docs/changelog.txt | 214 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 213 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 80fb8e4e7..b413237ed 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,5 +1,6 @@ ================================================================================ # Future +=== Leave this section blank ================================================================================ @@ -32,15 +33,226 @@ # 0.44.07-alpha1 ## Fixes +- Support for building on Ubuntu 18.04 +- Fixed some CMake warnings (CMP0022) - `embark-assistant`: fixed detection of reanimating biomes +## Misc Improvements +- `embark-assistant`: + + - Added search for adamantine + - Now supports saving/loading profiles + +- `fillneeds`: added ``-all`` option to apply to all units +- `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs + ## Structures - Several new names in instrument raw structures - ``identity``: identified ``profession``, ``civ`` - ``manager_order_template``: fixed last field type - ``viewscreen_createquotast``: fixed layout - ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors`` -- ``world.reactions``, ``world.reaction_categories``:\ moved to new compound, ``world.reactions``. Requires renaming +- ``world.reactions``, ``world.reaction_categories``:\ moved to new compound, ``world.reactions``. Requires renaming: - ``world.reactions`` to ``world.reactions.reactions`` - ``world.reaction_categories`` to ``world.reactions.reaction_categories`` + + +================================================================================ +# 0.44.05-r2 + +## Fixes +- `devel/export-dt-ini`: fix language_name offsets for DT 39.2+ +- `devel/inject-raws`: fixed gloves and shoes (old typo causing errors) +- `remotefortressreader`: fixed an issue with not all engravings being included +- `view-item-info`: fixed an error with some shields + +## Misc Improvements +- `adv-rumors`: added more keywords, including names +- `autochop`: can now exclude trees that produce fruit, food, or cookable items +- `remotefortressreader`: added plant type support + +## New Plugins +- `embark-assistant`: adds more information and features to embark screen + +## New Scripts +- `adv-fix-sleepers`: fixes units in adventure mode who refuse to wake up (:bug:`6798`) +- `hermit`: blocks caravans, migrants, diplomats (for hermit challenge) + +## New Features +- With ``PRINT_MODE:TEXT``, setting the ``DFHACK_HEADLESS`` environment variable will hide DF's display and allow the console to be used normally. (Note that this is intended for testing and is not very useful for actual gameplay.) + + +================================================================================ +# 0.44.05-r1 + +## New Scripts +- `break-dance`: Breaks up a stuck dance activity +- `fillneeds`: Use with a unit selected to make them focused and unstressed +- `firestarter`: Lights things on fire: items, locations, entire inventories even! +- `flashstep`: Teleports adventurer to cursor +- `ghostly`: Turns an adventurer into a ghost or back +- `questport`: Sends your adventurer to the location of your quest log cursor +- `view-unit-reports`: opens the reports screen with combat reports for the selected unit + +## Fixes +- `devel/inject-raws`: now recognizes spaces in reaction names +- `dig`: added support for designation priorities - fixes issues with designations from ``digv`` and related commands having extremely high priority +- `dwarfmonitor`: + - fixed display of creatures and poetic/music/dance forms on ``prefs`` screen + - added "view unit" option + - now exposes the selected unit to other tools + +- `names`: fixed many errors +- `quicksave`: fixed an issue where the "Saving..." indicator often wouldn't appear + +## Misc Improvements +- `gui/gm-unit`: + - added a profession editor + - misc. layout improvements +- `remotefortressreader`: + - support for moving adventurers + - support for vehicles, gem shapes, item volume, art images, item improvements +- `binpatch`: now reports errors for empty patch files +- `force`: now provides useful help +- `full-heal`: + - can now select corpses to resurrect + - now resets body part temperatures upon resurrection to prevent creatures from freezing/melting again + - now resets units' vanish countdown to reverse effects of `exterminate` +- `launch`: can now ride creatures +- `names`: can now edit names of units + +## Removed +- `tweak`: ``kitchen-keys``: :bug:`614` fixed in DF 0.44.04 + +## Internals +- ``Gui::getAnyUnit()`` supports many more screens/menus + +## Structures +- New globals: ``soul_next_id`` + +================================================================================ +# 0.44.05-alpha1 + +## Misc Improvements +- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards + +## Structures +-@ ``incident``: re-aligned again to match disassembly + + +================================================================================ +# 0.44.04-alpha1 + +## Fixes +- `devel/inject-raws`: now recognizes spaces in reaction names +- `exportlegends`: fixed an error that could occur when exporting empty lists + + +## Structures +- ``artifact_record``: fixed layout (changed in 0.44.04) +- ``incident``: fixed layout (changed in 0.44.01) - note that many fields have moved + + +================================================================================ +# 0.44.03-beta1 + +## Fixes +- `autolabor`, `autohauler`, `labormanager`: added support for "put item on display" jobs and building/destroying display furniture +- `gui/gm-editor`: fixed an error when editing primitives in Lua tables + +## Misc Improvements +- @ `devel/dump-offsets`: now ignores ``index`` globals +- `gui/pathable`: added tile types to sidebar +- `modtools/skill-change`: + - now updates skill levels appropriately + - only prints output if ``-loud`` is passed + +## Structures +- New globals: + - ``version`` + - ``min_load_version`` + - ``movie_version`` + - ``basic_seed`` + - ``title`` + - ``title_spaced`` + - ``ui_building_resize_radius`` +- Added ``twbt_render_map`` code offset on x64 +- Fixed an issue preventing ``enabler`` from being allocated by DFHack +- Added ``job_type.PutItemOnDisplay`` +- Found ``renderer`` vtable on osx64 +- ``adventure_movement_optionst``, ``adventure_movement_hold_tilest``, ``adventure_movement_climbst``: named coordinate fields +- ``mission``: added type +- ``unit``: added 3 new vmethods: ``getCreatureTile``, ``getCorpseTile``, ``getGlowTile`` +- ``viewscreen_assign_display_itemst``: fixed layout on x64 and identified many fields +- ``viewscreen_reportlistst``: fixed layout, added ``mission_id`` vector +- ``world.status``: named ``missions`` vector + + +================================================================================ +# 0.44.03-alpha1 + +## Lua +- Improved ``json`` I/O error messages +- Stopped a crash when trying to create instances of classes whose vtable addresses are not available + + +================================================================================ +# 0.44.02-beta1 + +## New Scripts +- `devel/check-other-ids`: Checks the validity of "other" vectors in the ``world`` global +- `gui/cp437-table`: An in-game CP437 table + +## Fixes +- Fixed issues with the console output color affecting the prompt on Windows +- `createitem`: stopped items from teleporting away in some forts +- `gui/gm-unit`: can now edit mining skill +- `gui/quickcmd`: stopped error from adding too many commands +- `modtools/create-unit`: fixed error when domesticating units + +## Misc Improvements +- The console now provides suggestions for built-in commands +- `devel/export-dt-ini`: avoid hardcoding flags +- `exportlegends`: + - reordered some tags to match DF's order + - added progress indicators for exporting long lists +- `gui/gm-editor`: added enum names to enum edit dialogs +- `gui/gm-unit`: made skill search case-insensitive +- `gui/rename`: added "clear" and "special characters" options +- `remotefortressreader`: + - includes item stack sizes + - some performance improvements + +## Removed +- `warn-stuck-trees`: :bug:`9252` fixed in DF 0.44.01 + +## Lua +- Exposed ``get_vector()`` (from C++) for all types that support ``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all`` + +## Structures +- Located ``start_dwarf_count`` offset for all builds except 64-bit Linux; `startdwarf` should work now +- Added ``buildings_other_id.DISPLAY_CASE`` +- Fixed ``viewscreen_titlest.start_savegames`` alignment +- Fixed ``unit`` alignment +- Identified ``historical_entity.unknown1b.deities`` (deity IDs) + + +================================================================================ +# 0.44.02-alpha1 + +## New Scripts +- `devel/dump-offsets`: prints an XML version of the global table included in in DF + +## Fixes +- Fixed a crash that could occur if a symbol table in symbols.xml had no content + +## Lua +- Added a new ``dfhack.console`` API +- API can now wrap functions with 12 or 13 parameters + +## Structures +- The ``ui_menu_width`` global is now a 2-byte array; the second item is the former ``ui_area_map_width`` global, which is now removed +- The former ``announcements`` global is now a field in ``d_init`` +- ``world`` fields formerly beginning with ``job_`` are now fields of ``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list`` + From 26fb047aef147ed78c8e00351fd5b3406b8d370e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 22:21:03 -0400 Subject: [PATCH 0889/1012] Adjust which changelog files get installed --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66418270a..8f3c5947e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,7 +331,7 @@ endif() # build the lib itself IF(BUILD_LIBRARY) add_subdirectory (library) - install(FILES LICENSE.rst docs/NEWS.rst docs/NEWS-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES LICENSE.rst docs/changelog.txt DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH}) @@ -395,6 +395,7 @@ if (BUILD_DOCS) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/ DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs ) + install(FILES docs/_auto/news.rst docs/_auto/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}") endif() From 374243d697de92dadbe60da77a1519b79aa15836 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 22:22:20 -0400 Subject: [PATCH 0890/1012] Document changelog process --- Contributing.rst | 3 ++- docs/Compile.rst | 14 ++++++++++++++ docs/NEWS-dev.rst | 5 +++++ docs/NEWS.rst | 5 +++++ docs/changelog.txt | 30 ++++++++++++++++++++++++++++++ docs/gen_changelog.py | 15 +++++++++++++-- 6 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Contributing.rst b/Contributing.rst index 2c1e7bb45..59ff8285f 100644 --- a/Contributing.rst +++ b/Contributing.rst @@ -32,7 +32,8 @@ How to get new code into DFHack (i.e. not the master or develop branch of your fork). * If possible, compile on multiple platforms when changing anything that compiles * It must pass CI - run ``python travis/all.py`` to check this. -* Update ``NEWS.rst`` and ``docs/Authors.rst`` when applicable. +* Update ``changelog.txt`` and ``docs/Authors.rst`` when applicable. See + `build-changelog` for more information on the changelog format. * Create a GitHub pull request once finished * Submit ideas and bug reports as :issue:`issues on GitHub <>`. Posts in the forum thread can easily get missed or forgotten. diff --git a/docs/Compile.rst b/docs/Compile.rst index 74ffb4078..54d9c21c8 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -666,6 +666,20 @@ Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run:: pip install sphinx +.. _build-changelog: + +Building the changelogs +----------------------- +If you have Python installed, but do not want to build all of the documentation, +you can build the changelogs with the ``docs/gen_changelog.py`` script. + +All changes should be listed in ``changelog.txt``. A description of this file's +format follows: + +.. include:: /docs/changelog.txt + :start-after: ===help + :end-before: ===end + Misc. Notes =========== diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 68c16d272..7a9da7cb7 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -8,6 +8,11 @@ Development Changelog ##################### +This file contains changes grouped by the release (stable or development) in +which they first appeared. See `build-changelog` for more information. + +See `changelog` for a list of changes grouped by stable releases. + .. contents:: :depth: 2 diff --git a/docs/NEWS.rst b/docs/NEWS.rst index 506999280..22f9881b9 100644 --- a/docs/NEWS.rst +++ b/docs/NEWS.rst @@ -8,6 +8,11 @@ Changelog ######### +This file contains changes grouped by the stable release in which they first +appeared. See `build-changelog` for more information. + +See `dev-changelog` for a list of changes grouped by development releases. + .. contents:: :depth: 2 diff --git a/docs/changelog.txt b/docs/changelog.txt index b413237ed..d59930998 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,3 +1,33 @@ +===[[[ +===help + +Entries in docs/NEWS.rst and docs/NEWS-dev.rst are generated from +docs/changelog.txt. NEWS.rst groups entries by stable releases, and NEWS-dev.rst +groups them by all releases (stable and development). For example, an entry +listed under "0.44.05-alpha1" in changelog.txt will be listed under that in +NEWS-dev.rst as well, but under "0.44.05-r1" in NEWS.rst (assuming that is the +closest stable release after 0.44.05-alpha1). An entry listed under a stable +release in changelog.txt will be listed under that release in both NEWS.rst and +NEWS-dev.rst. + +changelog.txt uses a syntax similar to RST, with a few special sequences: + +- ``===`` indicates the start of a comment +- ``#`` indicates the start of a release name (do not include "DFHack") +- ``##`` indicates the start of a section name (this must be listed in ``gen_changelog.py``) +- ``-`` indicates the start of a changelog entry. **Note:** an entry currently must be only one line. +- ``:`` (colon followed by space) separates the name of a feature from a description of a change to that feature. + Changes made to the same feature are grouped if they end up in the same section. +- ``:\`` (colon, backslash, space) avoids the above behavior +- ``- @`` (the space is optional) indicates the start of an entry that should only be displayed in NEWS-dev.rst. + Use this sparingly, e.g. for immediate fixes to one development build in another development build that + are not of interest to users of stable builds only. +- Three ``[`` characters indicate the start of a block (possibly a comment) that + spans multiple lines. Three ``]`` characters indicate the end of such a block. + +===end +]]] + ================================================================================ # Future === Leave this section blank diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index 189591c1f..fcc6cb1f2 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -53,8 +53,21 @@ def parse_changelog(): entries = [] with open('docs/changelog.txt') as f: + multiline = '' for line_id, line in enumerate(f.readlines()): line_id += 1 + + if multiline: + multiline += line + elif '[[[' in line: + multiline = line + + if ']]]' in multiline: + line = multiline.replace(']]]', '') + multiline = '' + elif multiline: + continue + if not line.strip() or line.startswith('==='): continue @@ -72,8 +85,6 @@ def parse_changelog(): last_entry = ChangelogEntry(line.strip(), cur_section, cur_stable, cur_dev) entries.append(last_entry) - # entries.setdefault(cur_stable, []).append(last_entry) - # entries.setdefault(cur_dev, []).append(last_entry) elif line.lstrip().startswith('-'): if not cur_stable or not cur_dev: raise ValueError( From 02768cd019d263c1ea690b3d3d0760ca3f47d5db Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 2 Apr 2018 22:30:54 -0400 Subject: [PATCH 0891/1012] Update future section --- docs/changelog.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index d59930998..512f5f1cd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,3 +1,5 @@ +=== Scroll down for changes + ===[[[ ===help @@ -28,9 +30,13 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ===end ]]] +================================================================================ +======== IMPORTANT: rename this, and add a new "future" section, BEFORE ======== +======== making a new DFHack release! ======== ================================================================================ # Future -=== Leave this section blank +## Misc Improvements +- Reorganized changelogs and improved changelog editing process ================================================================================ From d6be5549aff63242f204af38812602ca08789db2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 3 Apr 2018 11:31:14 -0400 Subject: [PATCH 0892/1012] Update scripts (dfhack/scripts#50) --- docs/changelog.txt | 5 +++++ scripts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 512f5f1cd..1fa16e58f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -35,8 +35,13 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ======== making a new DFHack release! ======== ================================================================================ # Future + +## Fixes +- `modtools/item-trigger`: fixed token format in help text + ## Misc Improvements - Reorganized changelogs and improved changelog editing process +- `modtools/item-trigger`: added support for multiple type/material/contaminant conditions ================================================================================ diff --git a/scripts b/scripts index 4e3b9aa4e..7d7dd8a74 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 4e3b9aa4e32e0b552c01f9a1e4a6eaa3af0cdb58 +Subproject commit 7d7dd8a7498e975a19231435e7cb225025042a78 From f6a222751b3c5083a61c0b1d32d46b5eea86bf40 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 3 Apr 2018 11:36:06 -0400 Subject: [PATCH 0893/1012] Update xml (osx64 renderer) --- docs/changelog.txt | 2 ++ library/xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1fa16e58f..7f6c82bd2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,6 +43,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Reorganized changelogs and improved changelog editing process - `modtools/item-trigger`: added support for multiple type/material/contaminant conditions +## Structures +-@ ``renderer``: fixed vtable addresses on 64-bit OS X ================================================================================ # 0.44.09-alpha1 diff --git a/library/xml b/library/xml index 61a240140..d02e2063c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 61a240140332b9e3dbafbd8b3f0455c3bdd617a3 +Subproject commit d02e2063c52f685cd4b0d1bc28ba81f0c7b1e70a From 490946cc5ee834f043e557314210d46508c99ce9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 3 Apr 2018 11:36:11 -0400 Subject: [PATCH 0894/1012] Strip markers from multi-line changelog entries --- docs/gen_changelog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index fcc6cb1f2..f5746dbf2 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -60,7 +60,7 @@ def parse_changelog(): if multiline: multiline += line elif '[[[' in line: - multiline = line + multiline = line.replace('[[[', '') if ']]]' in multiline: line = multiline.replace(']]]', '') From 3e3571094d3da994b8783195c9ab7884023dc734 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 3 Apr 2018 16:44:30 -0400 Subject: [PATCH 0895/1012] Update xml (name attrs) --- docs/changelog.txt | 3 +++ library/xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7f6c82bd2..5e7613848 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -45,6 +45,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Structures -@ ``renderer``: fixed vtable addresses on 64-bit OS X +- ``building_type``: added human-readable ``name`` attribute +- ``furnace_type``: added human-readable ``name`` attribute +- ``workshop_type``: added human-readable ``name`` attribute ================================================================================ # 0.44.09-alpha1 diff --git a/library/xml b/library/xml index d02e2063c..327b859ba 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d02e2063c52f685cd4b0d1bc28ba81f0c7b1e70a +Subproject commit 327b859badbbe5c515cd2bbe3418696b0fa8a6c0 From 8f3701752d106890b9c1b079d9664fc9cfb8113f Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 11:36:24 -0400 Subject: [PATCH 0896/1012] Update xml --- docs/changelog.txt | 2 ++ library/xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 5e7613848..32b5159ec 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -48,6 +48,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``building_type``: added human-readable ``name`` attribute - ``furnace_type``: added human-readable ``name`` attribute - ``workshop_type``: added human-readable ``name`` attribute +- ``army``: added vector new in 0.44.07 +- ``site_reputation_report``: named ``reports`` vector ================================================================================ # 0.44.09-alpha1 diff --git a/library/xml b/library/xml index 327b859ba..3c0bf6367 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 327b859badbbe5c515cd2bbe3418696b0fa8a6c0 +Subproject commit 3c0bf63674d5430deadaf7befaec42f0ec1e8bc5 From 1ac896a87461de647e7f4cc36fdbea1b3732a008 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 11:52:18 -0400 Subject: [PATCH 0897/1012] Stop generate_headers from depending on symbols.xml Changes to just symbols.xml will no longer cause codegen.pl to be re-run --- library/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 0b28d4463..c47046f82 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -254,7 +254,7 @@ LIST(APPEND PROJECT_SOURCES ${PROJECT_HEADERS}) LIST(APPEND PROJECT_SOURCES ${GENERATED_HDRS}) FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR}/xml/*.xslt) -FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/*.xml) +FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml) ADD_CUSTOM_COMMAND( OUTPUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml From b7bd88352e63ac26221deab232b7a42c86397f8f Mon Sep 17 00:00:00 2001 From: ThiagoLira Date: Wed, 4 Apr 2018 17:42:46 -0300 Subject: [PATCH 0898/1012] fixed bug causing gui scripts to fail when sidebar is closed --- library/lua/gui.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index fb125e690..f8c75af5b 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -109,6 +109,7 @@ local function parse_inset(inset) end function inset_frame(rect, inset, gap) + if not rect then return mkdims_wh(0, 0, 0, 0) end gap = gap or 0 local l,t,r,b = parse_inset(inset) return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) From 0ee4010701f37e17f77ca4ca5236ecfc18a02227 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 17:15:08 -0400 Subject: [PATCH 0899/1012] Update Authors.rst (#1251) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index aecfc519a..2ebddff1a 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -123,6 +123,7 @@ stolencatkarma sv-esk sv-esk Tacomagic TheHologram TheHologram +ThiagoLira ThiagoLira Tim Walberg twalberg Timothy Collett danaris Tom Jobbins TheBloke From ceb4cc75777d250cf90f364377edc4a03736daf1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 19:45:44 -0400 Subject: [PATCH 0900/1012] Add downloads for OS X GCC 7 stdlib --- CMakeLists.txt | 56 +++++++++++++++++++++------- package/darwin/osx32-gcc7/.gitignore | 1 + package/darwin/osx64-gcc7/.gitignore | 1 + 3 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 package/darwin/osx32-gcc7/.gitignore create mode 100644 package/darwin/osx64-gcc7/.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f3c5947e..17d6e9cc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,20 +259,50 @@ if(APPLE) # libstdc++ (GCC 4.8.5 for OS X 10.6) # fixes crash-on-unwind bug in DF's libstdc++ set(LIBSTDCXX_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}) - if(${DFHACK_BUILD_ARCH} STREQUAL "64") - download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-libstdcxx.6.dylib.gz" - "gz" - ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz - "cf26ed588be8e83c8e3a49919793b416" - ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib - "16dc6dbd4ecde7f9b95bb6dc91f07404") + + if(${GCC_VERSION_OUT} VERSION_LESS "4.9") + set(LIBSTDCXX_GCC_VER "48") else() - download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libstdcxx.6.dylib.gz" - "gz" - ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz - "40f3d83871b114f0279240626311621b" - ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib - "c3f5678b8204917e03870834902c3e8b") + set(LIBSTDCXX_GCC_VER "7") + set(LIBSTDCXX_DOWNLOAD_DIR "${LIBSTDCXX_DOWNLOAD_DIR}-gcc7") + endif() + + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + if(${LIBSTDCXX_GCC_VER} STREQUAL "48") + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-gcc48-libstdcxx.6.dylib.gz" + "gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "cf26ed588be8e83c8e3a49919793b416" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "16dc6dbd4ecde7f9b95bb6dc91f07404") + else() + # GCC 7 + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-gcc7-libstdcxx.6.dylib.gz" + "gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "81314b7846f9e8806409bef2160c76e6" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "93b6cf4b01e9a9084a508fd6a4a88992") + endif() + + else() # 32-bit + + if(${LIBSTDCXX_GCC_VER} STREQUAL "48") + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-gcc48-libstdcxx.6.dylib.gz" + "gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "40f3d83871b114f0279240626311621b" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "c3f5678b8204917e03870834902c3e8b") + else() + # GCC 7 + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-gcc7-libstdcxx.6.dylib.gz" + "gz" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "dbd213171f66edb90d204d525f10c969" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "b14c857e7e485a097c70a9ccd3132da7") + endif() endif() endif() diff --git a/package/darwin/osx32-gcc7/.gitignore b/package/darwin/osx32-gcc7/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx32-gcc7/.gitignore @@ -0,0 +1 @@ +libstdc++* diff --git a/package/darwin/osx64-gcc7/.gitignore b/package/darwin/osx64-gcc7/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx64-gcc7/.gitignore @@ -0,0 +1 @@ +libstdc++* From c8089db5dfe0dba9d9e576034c3229bd66edc2f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 20:01:27 -0400 Subject: [PATCH 0901/1012] Install the correct libstdc++ --- CMakeLists.txt | 4 ++++ library/CMakeLists.txt | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17d6e9cc1..4413ca97b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,10 @@ if(APPLE) "b14c857e7e485a097c70a9ccd3132da7") endif() endif() + + install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + DESTINATION ./hack/) + endif() #### expose depends #### diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index c47046f82..020a38c4d 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -379,8 +379,6 @@ IF(UNIX) DESTINATION .) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run DESTINATION .) - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}/libstdc++.6.dylib - DESTINATION ./hack/) else() # On linux, copy our version of the df launch script which sets LD_PRELOAD install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack From ad6a96f2c58e2a2cad3edfb9fc9a53a054d4f529 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 20:01:52 -0400 Subject: [PATCH 0902/1012] Update Compile.rst for GCC 7 (OS X) --- docs/Compile.rst | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index 54d9c21c8..e9ddc157d 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -217,14 +217,14 @@ DFHack functions similarly on OS X and Linux, and the majority of the information above regarding the build process (cmake and make) applies here as well. -DFHack can officially be built on OS X with GCC 4.8. Anything newer than 4.8 +DFHack can officially be built on OS X with GCC 4.8 or 7. Anything newer than 7 will require you to perform extra steps to get DFHack to run (see `osx-new-gcc-notes`), and your build will likely not be redistributable. .. _osx-new-gcc-notes: -Notes for GCC 4.9+ or OS X 10.10+ users ---------------------------------------- +Notes for GCC 8+ or OS X 10.10+ users +------------------------------------- If none of these situations apply to you, skip to `osx-setup`. @@ -233,7 +233,7 @@ the following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 -If you build with a GCC version newer than 4.8, DFHack will probably crash +If you build with a GCC version newer than 7, DFHack will probably crash immediately on startup, or soon after. To fix this, you will need to replace ``hack/libstdc++.6.dylib`` with a symlink to the ``libstdc++.6.dylib`` included in your version of GCC:: @@ -241,16 +241,16 @@ in your version of GCC:: cd /hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig && ln -s [PATH_TO_LIBSTDC++] . -For example, with GCC 5.2.0, ``PATH_TO_LIBSTDC++`` would be:: +For example, with GCC 6.3.0, ``PATH_TO_LIBSTDC++`` would be:: - /usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/libstdc++.6.dylib # for 64-bit DFHack - /usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/i386/libstdc++.6.dylib # for 32-bit DFHack + /usr/local/Cellar/gcc@6/6.3.0/lib/gcc/6/libstdc++.6.dylib # for 64-bit DFHack + /usr/local/Cellar/gcc@6/6.3.0/lib/gcc/6/i386/libstdc++.6.dylib # for 32-bit DFHack **Note:** If you build with a version of GCC that requires this, your DFHack build will *not* be redistributable. (Even if you copy the ``libstdc++.6.dylib`` from your GCC version and distribute that too, it will fail on older OS X versions.) For this reason, if you plan on distributing DFHack, it is highly -recommended to use GCC 4.8. +recommended to use GCC 4.8 or 7. .. _osx-setup: @@ -277,11 +277,11 @@ Dependencies and system set-up brew tap homebrew/versions brew install git brew install cmake - brew install gcc@4.8 + brew install gcc@7 Using `MacPorts `_:: - sudo port install gcc48 +universal cmake +universal git-core +universal + sudo port install gcc7 +universal cmake +universal git-core +universal Macports will take some time - maybe hours. At some point it may ask you to install a Java environment; let it do so. @@ -323,16 +323,23 @@ Building Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: - export CC=/usr/local/bin/gcc-4.8 - export CXX=/usr/local/bin/g++-4.8 + export CC=/usr/local/bin/gcc-7 + export CXX=/usr/local/bin/g++-7 Macports:: - export CC=/opt/local/bin/gcc-mp-4.8 - export CXX=/opt/local/bin/g++-mp-4.8 + export CC=/opt/local/bin/gcc-mp-7 + export CXX=/opt/local/bin/g++-mp-7 Change the version numbers appropriately if you installed a different version of GCC. + If you are confident that you have GCC in your path, you can omit the absolute paths:: + + export CC=gcc-7 + export CXX=g++-7 + + etc. + * Build dfhack:: mkdir build-osx From 86e0cb4b3228501f6c5b7f223da4e9572ddf0649 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 20:07:41 -0400 Subject: [PATCH 0903/1012] Update changelog (OS X GCC, bump version) --- docs/changelog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 32b5159ec..aed7cf082 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +================================================================================ +# 0.44.09-r1 + +## Internals +- OS X: Can now build with GCC 7 (or older) + ## Fixes - `modtools/item-trigger`: fixed token format in help text From b6311ec6b8bcd4453948bbe416f5af0ee3fd302c Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Apr 2018 22:45:34 -0400 Subject: [PATCH 0904/1012] Bump to 0.44.09-r1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4413ca97b..e7d57636f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,8 +136,8 @@ endif() # set up versioning. set(DF_VERSION "0.44.09") -set(DFHACK_RELEASE "alpha1") -set(DFHACK_PRERELEASE TRUE) +set(DFHACK_RELEASE "r1") +set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From e9457b9f6542166876025dc512daa012033d8ec4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Apr 2018 02:15:32 -0400 Subject: [PATCH 0905/1012] liquids: use unique_ptr properly --- plugins/liquids.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 5b97f9ea2..f478ddccb 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -408,34 +408,32 @@ command_result df_liquids_execute(color_ostream &out) command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, df::coord cursor) { // create brush type depending on old parameters - Brush *brush; + std::unique_ptr brush; switch (cur_mode.brush) { case B_POINT: - brush = new RectangleBrush(1,1,1,0,0,0); + brush.reset(new RectangleBrush(1,1,1,0,0,0)); break; case B_RANGE: - brush = new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0); + brush.reset(new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0)); break; case B_BLOCK: - brush = new BlockBrush(); + brush.reset(new BlockBrush()); break; case B_COLUMN: - brush = new ColumnBrush(); + brush.reset(new ColumnBrush()); break; case B_FLOOD: - brush = new FloodBrush(&Core::getInstance()); + brush.reset(new FloodBrush(&Core::getInstance())); break; default: // this should never happen! out << "Old brushtype is invalid! Resetting to point brush.\n"; cur_mode.brush = B_POINT; - brush = new RectangleBrush(1,1,1,0,0,0); + brush.reset(new RectangleBrush(1,1,1,0,0,0)); } - std::auto_ptr brush_ref(brush); - if (!Maps::IsValid()) { out << "Can't see any DF map loaded." << endl; From 868c0325783d8cc106c857101ca71fa9fdb6097f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Apr 2018 02:16:40 -0400 Subject: [PATCH 0906/1012] liquids: initialize range dimensions --- docs/changelog.txt | 3 +++ plugins/liquids.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index aed7cf082..80a3cf92f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## Fixes +- `liquids`: fixed "range" command to default to 1 for dimensions consistently + ================================================================================ # 0.44.09-r1 diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index f478ddccb..f0dcf1ce3 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -271,7 +271,7 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "range" || command == "r") { - int width, height, z_levels; + int width = 1, height = 1, z_levels = 1; command_result res = parseRectangle(out, commands, 1, commands.size(), width, height, z_levels); if (res != CR_OK) From 1badadf53527057722a2a6e54949c48c3a3570ad Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Apr 2018 10:08:07 -0400 Subject: [PATCH 0907/1012] search: fix 4/6 keys in unit screen --- docs/changelog.txt | 1 + plugins/search.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 80a3cf92f..c87ab4cbb 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `liquids`: fixed "range" command to default to 1 for dimensions consistently +- `search`: fixed 4/6 keys in unit screen search ================================================================================ # 0.44.09-r1 diff --git a/plugins/search.cpp b/plugins/search.cpp index 80dcc0f61..1e919572b 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1154,7 +1154,8 @@ private: bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + if (input->count(interface_key::STANDARDSCROLL_LEFT) || + input->count(interface_key::STANDARDSCROLL_RIGHT) || (!in_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!in_entry_mode()) @@ -1165,11 +1166,14 @@ private: *cursor_pos = 0; clear_search(); reset_all(); + return false; } else - input->clear(); // Ignore cursor keys when typing - - return false; + { + // Ignore cursor keys when typing + input->erase(interface_key::STANDARDSCROLL_LEFT); + input->erase(interface_key::STANDARDSCROLL_RIGHT); + } } return true; From ef7dc06221e9c427277881bed1138d9c96307690 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Apr 2018 11:02:14 -0400 Subject: [PATCH 0908/1012] Fix search plugin link and block links to search page in changelog --- docs/changelog.txt | 3 ++- docs/gen_changelog.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index c87ab4cbb..6f50f70c5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -26,6 +26,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: are not of interest to users of stable builds only. - Three ``[`` characters indicate the start of a block (possibly a comment) that spans multiple lines. Three ``]`` characters indicate the end of such a block. +- ``!`` immediately before a blacklisted phrase (see gen_changelog.py) allows that occurrence only. ===end ]]] @@ -38,7 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `liquids`: fixed "range" command to default to 1 for dimensions consistently -- `search`: fixed 4/6 keys in unit screen search +- `search-plugin`: fixed 4/6 keys in unit screen search ================================================================================ # 0.44.09-r1 diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index f5746dbf2..521219dcd 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -19,6 +19,19 @@ CHANGELOG_SECTIONS = [ 'Ruby', ] +BLACKLIST = [ + '`search`', +] + +def find_all_indices(string, substr): + start = 0 + while True: + i = string.find(substr, start) + if i == -1: + return + yield i + start = i + 1 + class ChangelogEntry(object): def __init__(self, text, section, stable_version, dev_version): text = text.lstrip('- ') @@ -57,6 +70,14 @@ def parse_changelog(): for line_id, line in enumerate(f.readlines()): line_id += 1 + for phrase in BLACKLIST: + for i in find_all_indices(line, phrase): + if i == 0 or line[i - 1] != '!': + raise ValueError( + 'changelog.txt:%i: blacklisted phrase: %r' % + (line_id, phrase)) + line = line.replace('!' + phrase, phrase) + if multiline: multiline += line elif '[[[' in line: From cb463c34d826b9eeab41f0d17dc1e40910878160 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Apr 2018 11:21:45 -0400 Subject: [PATCH 0909/1012] Automatically replace search with search-plugin in changelog --- docs/changelog.txt | 4 ++-- docs/gen_changelog.py | 32 ++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6f50f70c5..a9bc0975e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -26,7 +26,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: are not of interest to users of stable builds only. - Three ``[`` characters indicate the start of a block (possibly a comment) that spans multiple lines. Three ``]`` characters indicate the end of such a block. -- ``!`` immediately before a blacklisted phrase (see gen_changelog.py) allows that occurrence only. +- ``!`` immediately before a phrase set up to be replaced (see gen_changelog.py) stops that occurrence from being replaced. ===end ]]] @@ -39,7 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `liquids`: fixed "range" command to default to 1 for dimensions consistently -- `search-plugin`: fixed 4/6 keys in unit screen search +- `search`: fixed 4/6 keys in unit screen search ================================================================================ # 0.44.09-r1 diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index 521219dcd..1586b32c9 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -19,9 +19,9 @@ CHANGELOG_SECTIONS = [ 'Ruby', ] -BLACKLIST = [ - '`search`', -] +REPLACEMENTS = { + '`search`': '`search-plugin`', +} def find_all_indices(string, substr): start = 0 @@ -32,6 +32,24 @@ def find_all_indices(string, substr): yield i start = i + 1 +def replace_text(string, replacements): + for old_text, new_text in replacements.items(): + new_string = '' + new_string_end = 0 # number of characters from string in new_string + for i in find_all_indices(string, old_text): + if i > 0 and string[i - 1] == '!': + # exempt if preceded by '!' + new_string += string[new_string_end:i - 1] + new_string += old_text + else: + # copy until this occurrence + new_string += string[new_string_end:i] + new_string += new_text + new_string_end = i + len(old_text) + new_string += string[new_string_end:] + string = new_string + return string + class ChangelogEntry(object): def __init__(self, text, section, stable_version, dev_version): text = text.lstrip('- ') @@ -70,13 +88,7 @@ def parse_changelog(): for line_id, line in enumerate(f.readlines()): line_id += 1 - for phrase in BLACKLIST: - for i in find_all_indices(line, phrase): - if i == 0 or line[i - 1] != '!': - raise ValueError( - 'changelog.txt:%i: blacklisted phrase: %r' % - (line_id, phrase)) - line = line.replace('!' + phrase, phrase) + line = replace_text(line, REPLACEMENTS) if multiline: multiline += line From 5ee73a5a73453e2b3c880f385d22a24f346f02c9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Apr 2018 11:55:25 -0400 Subject: [PATCH 0910/1012] Add an option to generate changelogs for individual versions --- conf.py | 1 + docs/_changelogs/.gitignore | 1 + docs/gen_changelog.py | 43 +++++++++++++++++++++++++++++++------ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 docs/_changelogs/.gitignore diff --git a/conf.py b/conf.py index 8ab449b01..618e35bfb 100644 --- a/conf.py +++ b/conf.py @@ -266,6 +266,7 @@ exclude_patterns = [ 'depends/*', 'build*', 'docs/_auto/news*', + 'docs/_changelogs/', ] # The reST default role (used for this markup: `text`) to use for all diff --git a/docs/_changelogs/.gitignore b/docs/_changelogs/.gitignore new file mode 100644 index 000000000..2211df63d --- /dev/null +++ b/docs/_changelogs/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index 1586b32c9..5456be6bb 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -88,8 +88,6 @@ def parse_changelog(): for line_id, line in enumerate(f.readlines()): line_id += 1 - line = replace_text(line, REPLACEMENTS) - if multiline: multiline += line elif '[[[' in line: @@ -151,10 +149,13 @@ def consolidate_changelog(all_entries): -def print_changelog(versions, all_entries, path): +def print_changelog(versions, all_entries, path, replace=True, prefix=''): # all_entries: version -> section -> entry with open(path, 'w') as f: - write = lambda s: f.write(s + '\n') + def write(line): + if replace: + line = replace_text(line, REPLACEMENTS) + f.write(prefix + line + '\n') for version in versions: sections = all_entries[version] if not sections: @@ -186,7 +187,7 @@ def print_changelog(versions, all_entries, path): write('') -def generate_changelog(): +def generate_changelog(all=False): entries = parse_changelog() # scan for unrecognized sections @@ -203,11 +204,15 @@ def generate_changelog(): collections.defaultdict(list)) dev_entries = collections.defaultdict(lambda: collections.defaultdict(list)) + for entry in entries: + # build list of all versions if entry.dev_version not in versions: versions.append(entry.dev_version) stable_version_map.setdefault(entry.dev_version, entry.stable_version) + if not entry.dev_only: + # add non-dev-only entries to both changelogs stable_entries[entry.stable_version][entry.section].append(entry) dev_entries[entry.dev_version][entry.section].append(entry) @@ -216,13 +221,37 @@ def generate_changelog(): print_changelog(versions, stable_entries, 'docs/_auto/news.rst') print_changelog(versions, dev_entries, 'docs/_auto/news-dev.rst') + if all: + for version in versions: + if stable_version_map[version] == version: + version_entries = {version: stable_entries[version]} + else: + version_entries = {version: dev_entries[version]} + print_changelog([version], version_entries, + 'docs/_changelogs/%s-github.txt' % version, + replace=False) + print_changelog([version], version_entries, + 'docs/_changelogs/%s-reddit.txt' % version, + replace=False, + prefix='> ') + print('ch ' + version) + return entries if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-a', '--all', action='store_true', + help='Print changelogs for all versions to docs/_changelogs') + parser.add_argument('-c', '--check', action='store_true', + help='Check that all entries are printed') + args = parser.parse_args() + os.chdir(os.path.abspath(os.path.dirname(__file__))) os.chdir('..') - entries = generate_changelog() - if '--check' in sys.argv: + entries = generate_changelog(all=args.all) + + if args.check: with open('docs/_auto/news.rst') as f: content_stable = f.read() with open('docs/_auto/news-dev.rst') as f: From 1ec96aa992fd700fa536c16ec4a6787d6faa7ef2 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 13:58:10 -0500 Subject: [PATCH 0911/1012] Disable pointless warnings in protobuf-generated code. --- depends/protobuf/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 24bac7988..206cfc1a9 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -191,7 +191,7 @@ LIST(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS}) IF(CMAKE_COMPILER_IS_GNUCC) SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result -Wno-unused-local-typedefs -Wno-misleading-indentation") ELSEIF(MSVC) # Disable warnings for integer conversion to smaller type SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") From 5d24aa8b621dd96e83bfea0c902f7284d840a1b3 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 14:28:00 -0500 Subject: [PATCH 0912/1012] Fix memset call in md5.cpp --- depends/md5/md5.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/md5/md5.cpp b/depends/md5/md5.cpp index 5ee4fb45f..044df259e 100644 --- a/depends/md5/md5.cpp +++ b/depends/md5/md5.cpp @@ -135,7 +135,7 @@ void MD5Final(unsigned char digest[16], MD5Context *ctx) MD5Transform(ctx->buf, (uint32_t *) ctx->in); byteReverse((unsigned char *) ctx->buf, 4); memcpy(digest, ctx->buf, 16); - memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ } From 8a53ad543f36dd0d59d515a87c52b0897bfd7aa5 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 14:39:18 -0500 Subject: [PATCH 0913/1012] Fix clsocket warnings. --- depends/clsocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/clsocket b/depends/clsocket index 0f0ad78c4..f638c8ea7 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit 0f0ad78c4fd429caacd0694b5c868dbeacea16b6 +Subproject commit f638c8ea7f9c50fe7f64159c9f173846856f87a6 From 9cbba0ae01f62e93f89361cd61ba309e6ca15e94 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 14:46:28 -0500 Subject: [PATCH 0914/1012] Hide warnings about non-virtual destructors from df-structures. --- library/include/DataDefs.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index ed37a91fc..e72a893e5 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -423,12 +423,15 @@ namespace df using DFHack::BitArray; using DFHack::DfArray; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" template void *allocator_fn(void *out, const void *in) { if (out) { *(T*)out = *(const T*)in; return out; } else if (in) { delete (T*)in; return (T*)in; } else return new T(); } +#pragma GCC diagnostic pop template void *allocator_nodel_fn(void *out, const void *in) { From a3d4c586ab15d9f01cc2bb3da944d23b1b5a16f6 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 14:48:29 -0500 Subject: [PATCH 0915/1012] Hide "complex enum" warning for dfhack-specific enums. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 3c0bf6367..f3a49dbf4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3c0bf63674d5430deadaf7befaec42f0ec1e8bc5 +Subproject commit f3a49dbf40e4ff0d47f9631adc350196252df854 From 91930a618f763f770caa82f2f28e9a60d77ac5e1 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 15:01:50 -0500 Subject: [PATCH 0916/1012] [dwarfvet] Fix infinite loop if an animal is not accepted at a hospital. --- plugins/dwarfvet.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 604f399a7..a98777b91 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -733,16 +733,12 @@ processUnits: // The master list handles all patients which are accepted // Check if this is a unit we're already aware of - bool patient_accepted = false; - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end();) { - if ((*animal_hospital)->acceptPatient(unit->id, out)) { - out.print("Accepted patient %d at hospital %d\n", unit->id, (*animal_hospital)->getID()); - patient_accepted = true; + for (auto animal_hospital : animal_hospital_zones) { + if (animal_hospital->acceptPatient(unit->id, out)) { + out.print("Accepted patient %d at hospital %d\n", unit->id, animal_hospital->getID()); tracked_units.push_back(unit->id); break; } - - } } } From a44b3b8f989d33c7d49b9b9514049f4ea11890e5 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:01:26 -0500 Subject: [PATCH 0917/1012] Move null pointer check from Job module to LuaApi, where it does not invoke undefined behavior. --- library/LuaApi.cpp | 14 ++++++++++++-- library/modules/Job.cpp | 6 ------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0bf19e93e..852f4e29d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1504,8 +1504,18 @@ static const luaL_Reg dfhack_gui_funcs[] = { /***** Job module *****/ -static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } -static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } +static bool jobEqual(const df::job *job1, const df::job *job2) +{ + CHECK_NULL_POINTER(job1); + CHECK_NULL_POINTER(job2); + return *job1 == *job2; +} +static bool jobItemEqual(const df::job_item *job1, const df::job_item *job2) +{ + CHECK_NULL_POINTER(job1); + CHECK_NULL_POINTER(job2); + return *job1 == *job2; +} static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,cloneJobStruct), diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 132dcfc4b..eccb70b7e 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -129,9 +129,6 @@ void DFHack::Job::deleteJobStruct(df::job *job, bool keptEverything) bool DFHack::operator== (const df::job_item &a, const df::job_item &b) { - CHECK_NULL_POINTER(&a); - CHECK_NULL_POINTER(&b); - if (!(CMP(item_type) && CMP(item_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(flags1.whole) && CMP(quantity) && CMP(vector_id) && @@ -152,9 +149,6 @@ bool DFHack::operator== (const df::job_item &a, const df::job_item &b) bool DFHack::operator== (const df::job &a, const df::job &b) { - CHECK_NULL_POINTER(&a); - CHECK_NULL_POINTER(&b); - if (!(CMP(job_type) && CMP(job_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(item_subtype) && CMP(item_category.whole) && From a37df92656ea5b8e620eb223b083ca5bc5079f4d Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:04:14 -0500 Subject: [PATCH 0918/1012] [labormanager] fix warning about non-virtual destructor in jlfunc --- plugins/labormanager/joblabormapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index f47077134..4f0124e93 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -229,6 +229,7 @@ static df::unit_labor construction_build_labor(df::building_actual* b) class jlfunc { public: + virtual ~jlfunc() {} virtual df::unit_labor get_labor(df::job* j) = 0; }; From ec5d238e5e617269c954c9637876fb3012dfafbf Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:46:04 -0500 Subject: [PATCH 0919/1012] Remove warnings in generated code for core protobufs. --- library/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 020a38c4d..6caab0a76 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -244,6 +244,10 @@ ADD_CUSTOM_COMMAND( DEPENDS protoc-bin ${PROJECT_PROTOS} ) +IF(UNIX) + SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} PROPERTIES COMPILE_FLAGS "-Wno-misleading-indentation") +ENDIF() + ADD_CUSTOM_TARGET(generate_proto_core DEPENDS ${PROJECT_PROTO_TMP_FILES}) # Merge headers into sources From 304e1d45f0c8f3a2baf1bcbccbdb718759ee8d5b Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:46:59 -0500 Subject: [PATCH 0920/1012] Remove unused labels in Console-posix.cpp --- library/Console-posix.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/Console-posix.cpp b/library/Console-posix.cpp index 8ef879d48..e68923f0e 100644 --- a/library/Console-posix.cpp +++ b/library/Console-posix.cpp @@ -532,7 +532,7 @@ namespace DFHack } if (seq[1] == 'D') { - left_arrow: + /* left arrow */ if (raw_cursor > 0) { raw_cursor--; @@ -541,7 +541,6 @@ namespace DFHack } else if ( seq[1] == 'C') { - right_arrow: /* right arrow */ if (size_t(raw_cursor) != raw_buffer.size()) { From 2eec5ee78d42690843317a215ba19bc2b5b9871a Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:47:47 -0500 Subject: [PATCH 0921/1012] Fix signed/unsigned comparison warnings in core. --- library/Console-posix.cpp | 4 ++-- library/modules/Job.cpp | 2 +- library/modules/Screen.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/Console-posix.cpp b/library/Console-posix.cpp index e68923f0e..ef74e67d2 100644 --- a/library/Console-posix.cpp +++ b/library/Console-posix.cpp @@ -635,7 +635,7 @@ namespace DFHack prompt_refresh(); break; case 11: // Ctrl+k, delete from current to end of line. - if (raw_cursor < raw_buffer.size()) + if (size_t(raw_cursor) < raw_buffer.size()) yank_buffer = raw_buffer.substr(raw_cursor); raw_buffer.erase(raw_cursor); prompt_refresh(); @@ -651,7 +651,7 @@ namespace DFHack case 20: // Ctrl+t, transpose current and previous characters if (raw_buffer.size() >= 2 && raw_cursor > 0) { - if (raw_cursor == raw_buffer.size()) + if (size_t(raw_cursor) == raw_buffer.size()) raw_cursor--; std::swap(raw_buffer[raw_cursor - 1], raw_buffer[raw_cursor]); raw_cursor++; diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index eccb70b7e..7673737b2 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -508,7 +508,7 @@ bool DFHack::Job::removePostings(df::job *job, bool remove_all) bool removed = false; if (!remove_all) { - if (job->posting_index >= 0 && job->posting_index < world->jobs.postings.size()) + if (job->posting_index >= 0 && size_t(job->posting_index) < world->jobs.postings.size()) { world->jobs.postings[job->posting_index]->flags.bits.dead = true; removed = true; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 96f385d8d..6ac39aa05 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -511,8 +511,8 @@ void PenArray::draw(unsigned int x, unsigned int y, unsigned int width, unsigned { for (unsigned int gridy = y; gridy < y + height; gridy++) { - if (gridx >= gps->dimx || - gridy >= gps->dimy || + if (gridx >= unsigned(gps->dimx) || + gridy >= unsigned(gps->dimy) || gridx - x + bufx >= dimx || gridy - y + bufy >= dimy) continue; From 0a2ec301993618110e747f5d5e91adf458239024 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:48:11 -0500 Subject: [PATCH 0922/1012] Remove or comment out unused code. --- library/Process-linux.cpp | 16 ---------------- library/modules/EventManager.cpp | 14 +++++++------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 7a72171ca..fa06d28e4 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -176,22 +176,6 @@ int Process::adjustOffset(int offset, bool /*to_file*/) return offset; } -static int getdir (string dir, vector &files) -{ - DIR *dp; - struct dirent *dirp; - if((dp = opendir(dir.c_str())) == NULL) - { - cout << "Error(" << errno << ") opening " << dir << endl; - return errno; - } - while ((dirp = readdir(dp)) != NULL) { - files.push_back(string(dirp->d_name)); - } - closedir(dp); - return 0; -} - uint32_t Process::getTickCount() { struct timeval tp; diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 8ce2c97f4..92a4f3f94 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -395,10 +395,10 @@ static void manageJobInitiatedEvent(color_ostream& out) { } //helper function for manageJobCompletedEvent -static int32_t getWorkerID(df::job* job) { - auto ref = findRef(job->general_refs, general_ref_type::UNIT_WORKER); - return ref ? ref->getID() : -1; -} +//static int32_t getWorkerID(df::job* job) { +// auto ref = findRef(job->general_refs, general_ref_type::UNIT_WORKER); +// return ref ? ref->getID() : -1; +//} /* TODO: consider checking item creation / experience gain just in case @@ -1150,7 +1150,7 @@ static void manageInteractionEvent(color_ostream& out) { df::report* lastAttackEvent = NULL; df::unit* lastAttacker = NULL; - df::unit* lastDefender = NULL; + //df::unit* lastDefender = NULL; unordered_map > history; for ( ; a < reports.size(); a++ ) { df::report* report = reports[a]; @@ -1164,7 +1164,7 @@ static void manageInteractionEvent(color_ostream& out) { if ( attack ) { lastAttackEvent = report; lastAttacker = NULL; - lastDefender = NULL; + //lastDefender = NULL; } vector relevantUnits = gatherRelevantUnits(out, lastAttackEvent, report); InteractionData data = getAttacker(out, lastAttackEvent, lastAttacker, attack ? NULL : report, relevantUnits); @@ -1201,7 +1201,7 @@ static void manageInteractionEvent(color_ostream& out) { } //out.print("%s,%d\n",__FILE__,__LINE__); lastAttacker = df::unit::find(data.attacker); - lastDefender = df::unit::find(data.defender); + //lastDefender = df::unit::find(data.defender); //fire event for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler handle = (*b).second; From 565c1e126078b08a303a310068b5890ef86951d6 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:49:30 -0500 Subject: [PATCH 0923/1012] Fix uninitialized pointer being returned from Gui::getAnyUnit when on the pet list with no selection. --- library/modules/Gui.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index e3cebd45e..53bacb129 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -917,11 +917,13 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_petst, top)) { + df::viewscreen_petst::T_animal animal_default; + animal_default.unit = NULL; switch (screen->mode) { case df::viewscreen_petst::List: if (!vector_get(screen->is_vermin, screen->cursor)) - return vector_get(screen->animal, screen->cursor).unit; + return vector_get(screen->animal, screen->cursor, animal_default).unit; return NULL; case df::viewscreen_petst::SelectTrainer: From 51661d7355835751bd4b3777ad50c4d89baf627a Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:50:04 -0500 Subject: [PATCH 0924/1012] Fix -Wswitch warning about UNDERWORLD_GATE in MapCache. --- library/modules/MapCache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index ffb9fc769..c0a2e4bed 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -929,6 +929,7 @@ t_matpair MapExtras::BlockInfo::getBaseMaterial(df::tiletype tt, df::coord2d pos case CONSTRUCTION: // just a fallback case MAGMA: case HFS: + case UNDERWORLD_GATE: // use generic 'rock' break; From 7aee061258cc88fb4b0d2c6435408455ea454036 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:50:28 -0500 Subject: [PATCH 0925/1012] Ignore warnings in plugin protobufs. --- plugins/Plugins.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index fe167ccb3..8142110cb 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -80,6 +80,10 @@ MACRO(DFHACK_PLUGIN) SET(PLUGIN_PROTOCPP ${PLUGIN_PROTOCPP} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) ENDFOREACH() + IF(UNIX) + SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES COMPILE_FLAGS "-Wno-misleading-indentation") + ENDIF() + # Tell CMake the source won't be available until build time. SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES GENERATED 1) From f3038fef0921ccf74933ccbabdbac8cc5cc66b87 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 5 Apr 2018 16:51:10 -0500 Subject: [PATCH 0926/1012] Mark static functions in uicommon as "static inline" to remove the warning if they are not used. --- plugins/uicommon.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 1261ed933..a9a4a0ea8 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -86,7 +86,7 @@ static void transform_(vector &src, vector &dst, Fn func) typedef int8_t UIColor; -static void OutputString(UIColor color, int &x, int &y, const std::string &text, +static inline void OutputString(UIColor color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0, const UIColor bg_color = 0, bool map = false) { Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text, map); @@ -99,7 +99,7 @@ static void OutputString(UIColor color, int &x, int &y, const std::string &text, x += text.length(); } -static void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, +static inline void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); @@ -108,14 +108,14 @@ static void OutputHotkeyString(int &x, int &y, const char *text, const char *hot OutputString(text_color, x, y, display, newline, left_margin, 0, map); } -static void OutputHotkeyString(int &x, int &y, const char *text, df::interface_key hotkey, +static inline void OutputHotkeyString(int &x, int &y, const char *text, df::interface_key hotkey, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputHotkeyString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), newline, left_margin, text_color, hotkey_color, map); } -static void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, +static inline void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); @@ -126,14 +126,14 @@ static void OutputLabelString(int &x, int &y, const char *text, const char *hotk OutputString(hotkey_color, x, y, label, newline, left_margin, 0, map); } -static void OutputLabelString(int &x, int &y, const char *text, df::interface_key hotkey, const string &label, bool newline = false, +static inline void OutputLabelString(int &x, int &y, const char *text, df::interface_key hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputLabelString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), label, newline, left_margin, text_color, hotkey_color, map); } -static void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, +static inline void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); @@ -141,7 +141,7 @@ static void OutputFilterString(int &x, int &y, const char *text, const char *hot OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin, 0, map); } -static void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, +static inline void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputHotkeyString(x, y, text, hotkey, false, 0, color, hotkey_color, map); @@ -152,7 +152,7 @@ static void OutputToggleString(int &x, int &y, const char *text, const char *hot OutputString(COLOR_GREY, x, y, "Off", newline, left_margin, 0, map); } -static void OutputToggleString(int &x, int &y, const char *text, df::interface_key hotkey, bool state, bool newline = true, +static inline void OutputToggleString(int &x, int &y, const char *text, df::interface_key hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputToggleString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), state, newline, left_margin, color, hotkey_color, map); @@ -163,7 +163,7 @@ inline string int_to_string(const int n) return static_cast( &(ostringstream() << n) )->str(); } -static void set_to_limit(int &value, const int maximum, const int min = 0) +static inline void set_to_limit(int &value, const int maximum, const int min = 0) { if (value < min) value = min; @@ -193,7 +193,7 @@ inline void paint_text(const UIColor color, const int &x, const int &y, const st Screen::paintString(Screen::Pen(' ', color, background), x, y, text); } -static string pad_string(string text, const int size, const bool front = true, const bool trim = false) +static inline string pad_string(string text, const int size, const bool front = true, const bool trim = false) { if (text.length() > size) { @@ -218,7 +218,7 @@ static string pad_string(string text, const int size, const bool front = true, c } } -static df::interface_key get_string_key(const std::set *input) +static inline df::interface_key get_string_key(const std::set *input) { for (auto it = input->begin(); it != input->end(); ++it) { @@ -228,7 +228,7 @@ static df::interface_key get_string_key(const std::set *input return df::interface_key::NONE; } -static char get_string_input(const std::set *input) +static inline char get_string_input(const std::set *input) { return DFHack::Screen::keyToChar(get_string_key(input)); } @@ -237,7 +237,7 @@ static char get_string_input(const std::set *input) * Utility Functions */ -static df::building_stockpilest *get_selected_stockpile() +static inline df::building_stockpilest *get_selected_stockpile() { if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || df::global::ui->main.mode != ui_sidebar_mode::QueryBuilding) @@ -248,7 +248,7 @@ static df::building_stockpilest *get_selected_stockpile() return virtual_cast(df::global::world->selected_building); } -static bool can_trade() +static inline bool can_trade() { if (df::global::ui->caravans.size() == 0) return false; @@ -266,19 +266,19 @@ static bool can_trade() return false; } -static bool is_metal_item(df::item *item) +static inline bool is_metal_item(df::item *item) { MaterialInfo mat(item); return (mat.getCraftClass() == craft_material_class::Metal); } -static bool is_set_to_melt(df::item* item) +static inline bool is_set_to_melt(df::item* item) { return item->flags.bits.melt; } // Copied from Kelly Martin's code -static bool can_melt(df::item* item) +static inline bool can_melt(df::item* item) { df::item_flags bad_flags; From a7dfacd1c57490d08fc92a43eea963cff47c05eb Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 6 Apr 2018 01:18:15 -0500 Subject: [PATCH 0927/1012] Fix warnings in supported plugins. --- plugins/Brushes.h | 2 +- plugins/autochop.cpp | 4 +- plugins/autohauler.cpp | 12 ++-- plugins/autolabor.cpp | 22 +++---- plugins/automaterial.cpp | 12 ++-- plugins/blueprint.cpp | 13 ++-- plugins/building-hacks.cpp | 2 +- plugins/buildingplan-lib.cpp | 15 ++++- plugins/buildingplan-lib.h | 22 +++---- plugins/buildingplan.cpp | 4 +- plugins/changelayer.cpp | 4 +- plugins/command-prompt.cpp | 12 ++-- plugins/createitem.cpp | 2 +- plugins/dig.cpp | 41 +++++++------ plugins/digFlood.cpp | 2 +- plugins/diggingInvaders/assignJob.cpp | 5 -- plugins/diggingInvaders/edgeCost.cpp | 2 +- plugins/dwarfvet.cpp | 6 +- plugins/embark-assistant/finder_ui.cpp | 9 +-- plugins/embark-assistant/help_ui.cpp | 1 - plugins/embark-assistant/matcher.cpp | 7 +-- plugins/embark-assistant/survey.cpp | 2 +- plugins/embark-tools.cpp | 4 +- plugins/eventful.cpp | 21 ++----- plugins/fastdwarf.cpp | 9 +++ plugins/filltraffic.cpp | 12 ++-- plugins/fix-unit-occupancy.cpp | 15 +---- plugins/follow.cpp | 4 +- plugins/forceequip.cpp | 24 ++++---- plugins/generated-creature-renamer.cpp | 10 ++-- plugins/hotkeys.cpp | 17 ++---- plugins/isoworldremote.cpp | 6 +- plugins/labormanager/joblabormapper.cpp | 4 +- plugins/labormanager/labormanager.cpp | 10 ++-- plugins/listcolumn.h | 15 +++-- plugins/luasocket.cpp | 2 +- plugins/manipulator.cpp | 60 ++++++++++--------- plugins/mousequery.cpp | 7 ++- plugins/probe.cpp | 6 +- plugins/prospector.cpp | 4 +- .../remotefortressreader.cpp | 6 +- plugins/rendermax/renderer_light.cpp | 5 +- plugins/reveal.cpp | 3 + plugins/search.cpp | 4 +- plugins/showmood.cpp | 4 +- plugins/stockpiles/CMakeLists.txt | 25 +------- plugins/stocks.cpp | 11 ---- plugins/strangemood.cpp | 14 ++++- plugins/tiletypes.cpp | 6 +- plugins/tweak/tweaks/max-wheelbarrow.h | 1 - plugins/uicommon.h | 9 ++- plugins/workflow.cpp | 2 +- plugins/zone.cpp | 2 +- 53 files changed, 246 insertions(+), 277 deletions(-) diff --git a/plugins/Brushes.h b/plugins/Brushes.h index eaf4240ea..6095a03da 100644 --- a/plugins/Brushes.h +++ b/plugins/Brushes.h @@ -130,7 +130,7 @@ public: while (mc.testCoord(start)) { df::tiletype tt = mc.tiletypeAt(start); - if(DFHack::LowPassable(tt) || juststarted && DFHack::HighPassable(tt)) + if(DFHack::LowPassable(tt) || (juststarted && DFHack::HighPassable(tt))) { v.push_back(start); juststarted = false; diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index a2a863540..858989a97 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -267,10 +267,8 @@ static bool skip_plant(const df::plant * plant, bool *restricted) if (skip.food_trees || skip.cook_trees) { - df::material * mat; - for (int idx = 0; idx < plant_raw->material.size(); idx++) + for (df::material * mat : plant_raw->material) { - mat = plant_raw->material[idx]; if (skip.food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) { if (restricted) diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index 62914c784..e222a9702 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -624,7 +624,7 @@ static void init_state() df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autohauler/labors/")).c_str()); // Ensure that the labor is defined in the existing list - if (labor >= 0 && labor <= labor_infos.size()) + if (labor >= 0 && size_t(labor) <= labor_infos.size()) { // Link the labor treatment with the associated persistent data item labor_infos[labor].set_config(*p); @@ -635,7 +635,7 @@ static void init_state() } // Add default labors for those not in save - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { // Determine if the labor is already present. If so, exit the for loop if (labor_infos[i].config.isValid()) @@ -806,7 +806,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // Scan the world and look for any citizens in the player's civilization. // Add these to the list of dwarves. // xxx Does it need to be ++i? - for (int i = 0; i < world->units.active.size(); ++i) + for (size_t i = 0; i < world->units.active.size(); ++i) { df::unit* cre = world->units.active[i]; if (Units::isCitizen(cre)) @@ -895,7 +895,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else { int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) dwarf_info[dwarf].state = dwarf_states[job]; else { @@ -960,7 +960,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) continue; // For every dwarf... - for(int dwarf = 0; dwarf < dwarfs.size(); dwarf++) + for(size_t dwarf = 0; dwarf < dwarfs.size(); dwarf++) { if (!Units::isValidLabor(dwarfs[dwarf], labor)) continue; @@ -1138,7 +1138,7 @@ command_result autohauler (color_ostream &out, std::vector & param return CR_FAILURE; } - for (int i = 0; i < labor_infos.size(); i++) + for (size_t i = 0; i < labor_infos.size(); i++) { reset_labor((df::unit_labor) i); } diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index a894a2891..6ca0a2658 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -606,7 +606,7 @@ static void init_state() { string key = p->key(); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); - if (labor >= 0 && labor <= labor_infos.size()) + if (labor >= 0 && size_t(labor) <= labor_infos.size()) { labor_infos[labor].config = *p; labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; @@ -615,7 +615,7 @@ static void init_state() } // Add default labors for those not in save - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { if (labor_infos[i].config.isValid()) continue; @@ -960,7 +960,7 @@ static void assign_labor(unit_labor::unit_labor labor, * Military and children/nobles will not have labors assigned. * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. */ - for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) + for (size_t i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) { int dwarf = candidates[i]; @@ -1048,7 +1048,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) bool has_fishery = false; bool trader_requested = false; - for (int i = 0; i < world->buildings.all.size(); ++i) + for (size_t i = 0; i < world->buildings.all.size(); ++i) { df::building *build = world->buildings.all[i]; auto type = build->getType(); @@ -1074,7 +1074,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) } } - for (int i = 0; i < world->units.active.size(); ++i) + for (size_t i = 0; i < world->units.active.size(); ++i) { df::unit* cre = world->units.active[i]; if (Units::isCitizen(cre)) @@ -1105,7 +1105,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); if(hf!=NULL) //can be NULL. E.g. script created citizens - for (int i = 0; i < hf->entity_links.size(); i++) + for (size_t i = 0; i < hf->entity_links.size(); i++) { df::histfig_entity_link* hfelink = hf->entity_links.at(i); if (hfelink->getType() == df::histfig_entity_link_type::POSITION) @@ -1140,7 +1140,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // identify dwarfs who are needed for meetings and mark them for exclusion - for (int i = 0; i < ui->activities.size(); ++i) + for (size_t i = 0; i < ui->activities.size(); ++i) { df::activity_info *act = ui->activities[i]; if (!act) continue; @@ -1230,7 +1230,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else { int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) dwarf_info[dwarf].state = dwarf_states[job]; else { @@ -1341,7 +1341,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (int i = 0; i < num_haulers; i++) { - assert(i < hauler_ids.size()); + assert(size_t(i) < hauler_ids.size()); int dwarf = hauler_ids[i]; @@ -1357,7 +1357,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); } - for (int i = num_haulers; i < hauler_ids.size(); i++) + for (size_t i = num_haulers; i < hauler_ids.size(); i++) { assert(i < hauler_ids.size()); @@ -1517,7 +1517,7 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_FAILURE; } - for (int i = 0; i < labor_infos.size(); i++) + for (size_t i = 0; i < labor_infos.size(); i++) { reset_labor((df::unit_labor) i); } diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index cfbc95ae7..40dc2bb48 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -69,12 +69,6 @@ struct MaterialDescriptor } }; - -static command_result automaterial_cmd(color_ostream &out, vector & parameters) -{ - return CR_OK; -} - DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; @@ -1124,6 +1118,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest break; case SELECT_SECOND: + { OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin); int32_t curr_x, curr_y, curr_z; @@ -1137,6 +1132,11 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest int cx = box_first.x; int cy = box_first.y; OutputString(COLOR_BROWN, cx, cy, "X", false, 0, 0, true /* map */); + break; + } + + default: + break; } OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index badc094c9..6ef56722a 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -106,9 +106,9 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) { if (! b) return " "; - bool at_nw_corner = x == b->x1 && y == b->y1; - bool at_se_corner = x == b->x2 && y == b->y2; - bool at_center = x == b->centerx && y == b->centery; + bool at_nw_corner = int32_t(x) == b->x1 && int32_t(y) == b->y1; + bool at_se_corner = int32_t(x) == b->x2 && int32_t(y) == b->y2; + bool at_center = int32_t(x) == b->centerx && int32_t(y) == b->centery; pair size = get_building_size(b); stringstream out;// = stringstream(); switch(b->getType()) @@ -227,7 +227,10 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) return "wy"; case workshop_type::Dyers: return "wd"; + case workshop_type::Kennels: + return "k"; case workshop_type::Custom: + case workshop_type::Tool: //can't do anything with custom workshop return "`"; } @@ -261,6 +264,8 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) case building_type::Construction: switch (((df::building_constructionst*) b)->type) { + case construction_type::NONE: + return "`"; case construction_type::Fortification: return "CF"; case construction_type::Wall: @@ -482,7 +487,7 @@ string get_tile_place(uint32_t x, uint32_t y, df::building* b) { if (! b || b->getType() != building_type::Stockpile) return " "; - if (b->x1 != x || b->y1 != y) + if (b->x1 != int32_t(x) || b->y1 != int32_t(y)) return "`"; pair size = get_building_size(b); df::building_stockpilest* sp = (df::building_stockpilest*) b; diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index bc989df30..17390cd5a 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -287,7 +287,7 @@ struct work_hook : df::building_workshopst{ } int w=db->x2-db->x1+1; std::vector &cur_frame=def->frames[frame]; - for(int i=0;i=0) { diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index 93efd60f6..f6023d2d7 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -17,7 +17,7 @@ void enable_quickfort_fn(pair& pair) { pair.secon * Material Choice Screen */ -static std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } +std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) { @@ -131,7 +131,7 @@ void ItemFilter::clear() materials.clear(); } -static DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } +DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) { @@ -386,7 +386,7 @@ void RoomMonitor::reset(color_ostream &out) } -static void delete_item_fn(df::job_item *x) { delete x; } +void delete_item_fn(df::job_item *x) { delete x; } // START Planning @@ -654,3 +654,12 @@ void Planner::cycleDefaultQuality(df::building_type type) if (*quality == item_quality::Artifact) (*quality) = item_quality::Ordinary; } + +map planmode_enabled, saved_planmodes; + +bool show_debugging = false; +bool show_help = false; + +Planner planner; + +RoomMonitor roomMonitor; diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 6975960d2..9720d5421 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -65,7 +65,7 @@ struct MaterialDescriptor #define MAX_MATERIAL 21 #define SIDEBAR_WIDTH 30 -static bool canReserveRoom(df::building *building) +static inline bool canReserveRoom(df::building *building) { if (!building) return false; @@ -76,7 +76,7 @@ static bool canReserveRoom(df::building *building) return building->is_room; } -static std::vector getUniqueNoblePositions(df::unit *unit) +static inline std::vector getUniqueNoblePositions(df::unit *unit) { std::vector np; Units::getNoblePositions(&np, unit); @@ -92,19 +92,19 @@ static std::vector getUniqueNoblePositions(df::unit *unit) return np; } -static void delete_item_fn(df::job_item *x); +void delete_item_fn(df::job_item *x); -static MaterialInfo &material_info_identity_fn(MaterialInfo &m); +MaterialInfo &material_info_identity_fn(MaterialInfo &m); -static map planmode_enabled, saved_planmodes; +extern map planmode_enabled, saved_planmodes; void enable_quickfort_fn(pair& pair); void debug(const std::string &msg); -static std::string material_to_string_fn(MaterialInfo m); +std::string material_to_string_fn(MaterialInfo m); -static bool show_debugging = false; -static bool show_help = false; +extern bool show_debugging; +extern bool show_help; struct ItemFilter { @@ -387,7 +387,7 @@ class Planner public: bool in_dummmy_screen; - Planner() : quickfort_mode(false), in_dummmy_screen(false) { } + Planner() : in_dummmy_screen(false), quickfort_mode(false) { } bool isPlanableBuilding(const df::building_type type) const { @@ -491,8 +491,8 @@ private: } }; -static Planner planner; +extern Planner planner; -static RoomMonitor roomMonitor; +extern RoomMonitor roomMonitor; #endif diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 747e5cfb7..eb6d2a661 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -194,7 +194,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest df::interface_key last_token = get_string_key(input); if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) { - int selection = last_token - interface_key::STRING_A048; + size_t selection = last_token - interface_key::STRING_A048; if (np.size() < selection) return false; roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); @@ -317,7 +317,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest int y = 24; OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); - for (int i = 0; i < np.size() && i < 9; i++) + for (size_t i = 0; i < np.size() && i < 9; i++) { bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id) == np[i].position->code); diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 27e4c12e6..cf7d4fdbc 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -227,7 +227,7 @@ command_result changelayer (color_ostream &out, std::vector & para { if(verbose) out << "---Biome: " << i; - if(!all_biomes && i!=biome) + if(!all_biomes && uint32_t(i)!=biome) { if(verbose) out << "-skipping" << endl; @@ -257,7 +257,7 @@ command_result changelayer (color_ostream &out, std::vector & para out << "geoindex: " << geoindex << endl; bool skip = false; - for(int g=0; gdisplay_frames; gps->display_frames=0; @@ -172,7 +172,7 @@ void viewscreen_commandpromptst::render() if(cursor_pos < (dim.x - 10)) { Screen::paintString(Screen::Pen(' ', 7, 0), 10,0 , entry); - if (entry.size() > dim.x - 10) + if (int16_t(entry.size()) > dim.x - 10) Screen::paintTile(Screen::Pen('\032', 7, 0), dim.x - 1, 0); if (cursor != " ") Screen::paintString(Screen::Pen(' ', 10, 0), 10 + cursor_pos, 0, cursor); @@ -243,7 +243,7 @@ void viewscreen_commandpromptst::feed(std::set *events) entry.erase(cursor_pos - 1, 1); cursor_pos--; } - if(cursor_pos > entry.size()) + if(size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); continue; } @@ -260,7 +260,7 @@ void viewscreen_commandpromptst::feed(std::set *events) if(events->count(interface_key::CURSOR_RIGHT)) { cursor_pos++; - if (cursor_pos > entry.size()) + if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); } else if(events->count(interface_key::CURSOR_LEFT)) @@ -294,10 +294,10 @@ void viewscreen_commandpromptst::feed(std::set *events) } else if(events->count(interface_key::CURSOR_DOWN)) { - if (history_idx < command_history.size() - 1) + if (size_t(history_idx) < command_history.size() - 1) { history_idx++; - if (history_idx >= command_history.size()) + if (size_t(history_idx) >= command_history.size()) history_idx = command_history.size() - 1; entry = get_entry(); cursor_pos = entry.size(); diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 1f2e8a5c4..70794e38c 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -92,7 +92,7 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it return false; // if we asked to make shoes and we got twice as many as we asked, then we're okay // otherwise, make a second set because shoes are normally made in pairs - if (is_shoes && out_items.size() == prod->count * 2) + if (is_shoes && out_items.size() == size_t(prod->count * 2)) is_shoes = false; MapExtras::MapCache mc; diff --git a/plugins/dig.cpp b/plugins/dig.cpp index d18176f5a..83ca2ef2d 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -812,14 +812,14 @@ bool stamp_pattern (uint32_t bx, uint32_t by, int z_level, int x = 0,mx = 16; if(bx == 0) x = 1; - if(bx == x_max - 1) + if(int(bx) == x_max - 1) mx = 15; for(; x < mx; x++) { int y = 0,my = 16; if(by == 0) y = 1; - if(by == y_max - 1) + if(int(by) == y_max - 1) my = 15; for(; y < my; y++) { @@ -838,8 +838,8 @@ bool stamp_pattern (uint32_t bx, uint32_t by, int z_level, if(dm[y][x]) { if(what == EXPLO_ALL - || des.bits.dig == tile_dig_designation::Default && what == EXPLO_DESIGNATED - || des.bits.hidden && what == EXPLO_HIDDEN) + || (des.bits.dig == tile_dig_designation::Default && what == EXPLO_DESIGNATED) + || (des.bits.hidden && what == EXPLO_HIDDEN)) { des.bits.dig = tile_dig_designation::Default; } @@ -948,7 +948,7 @@ command_result digexp (color_ostream &out, vector & parameters) int which; for(uint32_t x = 0; x < x_max; x++) { - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { which = (4*x + y) % 5; stamp_pattern(x,y_max - 1 - y, z_level, diag5[which], @@ -961,7 +961,7 @@ command_result digexp (color_ostream &out, vector & parameters) int which; for(uint32_t x = 0; x < x_max; x++) { - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { which = (4*x + 1000-y) % 5; stamp_pattern(x,y_max - 1 - y, z_level, diag5r[which], @@ -975,7 +975,7 @@ command_result digexp (color_ostream &out, vector & parameters) for(uint32_t x = 0; x < x_max; x++) { which = x % 3; - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { stamp_pattern(x, y, z_level, ladder[which], how, what, x_max, y_max); @@ -985,7 +985,7 @@ command_result digexp (color_ostream &out, vector & parameters) else if(how == EXPLO_LADDERR) { int which; - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { which = y % 3; for(uint32_t x = 0; x < x_max; x++) @@ -1023,7 +1023,7 @@ command_result digexp (color_ostream &out, vector & parameters) } else for(uint32_t x = 0; x < x_max; x++) { - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { stamp_pattern(x, y, z_level, all_tiles, how, what, x_max, y_max); @@ -1075,7 +1075,7 @@ command_result digv (color_ostream &out, vector & parameters) return CR_FAILURE; } DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); - if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) + if(xy.x == 0 || xy.x == int32_t(tx_max) - 1 || xy.y == 0 || xy.y == int32_t(ty_max) - 1) { con.printerr("I won't dig the borders. That would be cheating!\n"); return CR_FAILURE; @@ -1136,10 +1136,10 @@ command_result digv (color_ostream &out, vector & parameters) { MCache->setTagAt(current, 1); - if(current.x < tx_max - 2) + if(current.x < int32_t(tx_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y, current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y + 1,current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1,current.z)); @@ -1153,7 +1153,7 @@ command_result digv (color_ostream &out, vector & parameters) if(current.x > 1) { flood.push(DFHack::DFCoord(current.x - 1, current.y,current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x - 1, current.y + 1,current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1,current.z)); @@ -1178,7 +1178,7 @@ command_result digv (color_ostream &out, vector & parameters) des.bits.dig = tile_dig_designation::DownStair; } - if(current.z < z_max - 1 && above && vmat_plus == vmat2) + if(current.z < int32_t(z_max) - 1 && above && vmat_plus == vmat2) { flood.push(current+ 1); @@ -1262,7 +1262,7 @@ command_result digl (color_ostream &out, vector & parameters) return CR_FAILURE; } DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); - if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) + if(xy.x == 0 || xy.x == int32_t(tx_max) - 1 || xy.y == 0 || xy.y == int32_t(ty_max) - 1) { con.printerr("I won't dig the borders. That would be cheating!\n"); return CR_FAILURE; @@ -1320,10 +1320,10 @@ command_result digl (color_ostream &out, vector & parameters) if(MCache->testCoord(current)) { MCache->setTagAt(current, 1); - if(current.x < tx_max - 2) + if(current.x < int32_t(tx_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y, current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y + 1, current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1, current.z)); @@ -1337,7 +1337,7 @@ command_result digl (color_ostream &out, vector & parameters) if(current.x > 1) { flood.push(DFHack::DFCoord(current.x - 1, current.y, current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x - 1, current.y + 1, current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1, current.z)); @@ -1389,7 +1389,7 @@ command_result digl (color_ostream &out, vector & parameters) des.bits.dig = tile_dig_designation::DownStair; } - if(current.z < z_max - 1 && above && vmat_plus == -1 && bmat_plus == basemat) + if(current.z < int32_t(z_max) - 1 && above && vmat_plus == -1 && bmat_plus == basemat) { flood.push(current+ 1); @@ -1438,7 +1438,7 @@ command_result digtype (color_ostream &out, vector & parameters) return CR_FAILURE; } - uint32_t targetDigType; + int32_t targetDigType; if ( parameters.size() == 1 ) { string parameter = parameters[0]; @@ -1526,7 +1526,6 @@ command_result digtype (color_ostream &out, vector & parameters) continue; //designate it for digging - df::tile_designation des = mCache->designationAt(current); if ( !mCache->testCoord(current) ) { out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z); diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index ada6213a6..2556d25ac 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -137,7 +137,7 @@ void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, uint32_t xMax,yMax,zMax; Maps::getSize(xMax,yMax,zMax); - if ( pt.x == 0 || pt.y == 0 || pt.x+1 == xMax*16 || pt.y+1 == yMax*16 ) + if ( pt.x == 0 || pt.y == 0 || pt.x+1 == int32_t(xMax)*16 || pt.y+1 == int32_t(yMax)*16 ) return; if ( jobLocations.find(pt) != jobLocations.end() ) { return; diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index cdd3e6fc5..2b780bd63 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -68,8 +68,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map (%d,%d,%d)\n", pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z); - int32_t jobId = -1; - df::map_block* block1 = Maps::getTileBlock(pt1); df::map_block* block2 = Maps::getTileBlock(pt2); bool passable1 = block1->walkable[pt1.x&0xF][pt1.y&0xF]; @@ -111,7 +109,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjobs.clear(); building->jobs.push_back(job); Job::linkIntoWorld(job); - jobId = job->id; job->completion_timer = abilities.jobDelay[CostDimension::DestroyBuilding]; } else { df::tiletype* type1 = Maps::getTileType(pt1); @@ -136,7 +133,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjob.hunt_target = NULL; firstInvader->job.destroy_target = NULL; Job::linkIntoWorld(job); - jobId = job->id; df::construction* constr = df::construction::find(pt2); bool smooth = constr != NULL && constr->item_type != df::enums::item_type::BOULDER; if ( smooth ) @@ -204,7 +200,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mappath.path.y.clear(); firstInvader->path.path.z.clear(); Job::linkIntoWorld(job); - jobId = job->id; job->completion_timer = abilities.jobDelay[CostDimension::Dig]; //TODO: test if he already has a pick diff --git a/plugins/diggingInvaders/edgeCost.cpp b/plugins/diggingInvaders/edgeCost.cpp index b08227d94..cd52f4e17 100644 --- a/plugins/diggingInvaders/edgeCost.cpp +++ b/plugins/diggingInvaders/edgeCost.cpp @@ -246,7 +246,7 @@ cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilitie bool forbidden = false; if ( building1 && building1->getType() == df::building_type::Hatch ) { df::building_hatchst* hatch = (df::building_hatchst*)building1; - if ( hatch->door_flags.bits.forbidden || hatch->door_flags.bits.closed && hatch->door_flags.bits.operated_by_mechanisms ) + if ( hatch->door_flags.bits.forbidden || ( hatch->door_flags.bits.closed && hatch->door_flags.bits.operated_by_mechanisms ) ) forbidden = true; } diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index a98777b91..19f00e888 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -370,7 +370,7 @@ void AnimalHospital::processPatients(color_ostream &out) { // Where the magic happens for (vector::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { int id = (*patient)->getID(); - df::unit * real_unit; + df::unit * real_unit = nullptr; // Appears the health bits can get freed/realloced too -_-;, Find the unit from the main // index and check it there. auto units = world->units.all; @@ -383,7 +383,7 @@ void AnimalHospital::processPatients(color_ostream &out) { } // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed - if (real_unit->flags1.bits.dead || !real_unit->health->flags.bits.needs_healthcare) { + if (!real_unit || real_unit->flags1.bits.dead || !real_unit->health->flags.bits.needs_healthcare) { // discharge the patient from the hospital this->dischargePatient(*patient, out); return; @@ -459,7 +459,7 @@ bool compareAnimalHospitalZones(df::building * hospital1, df::building * hospita hospital1->x2 == hospital2->x2 && hospital1->y1 == hospital2->y1 && hospital1->y2 == hospital2->y2 && - hospital1->z == hospital1->z) { + hospital1->z == hospital2->z) { return true; } diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 80380ca60..a6a5005bd 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -185,9 +185,7 @@ namespace embark_assist { bool found; while (true) { - - fgets(line, count, infile); - if (line[0] != '[') { + if (!fgets(line, count, infile) || line[0] != '[') { out.printerr("Failed to find token start '[' at line %i\n", static_cast(i)); fclose(infile); return; @@ -251,7 +249,10 @@ namespace embark_assist { i = first_fields; while (true) { - fgets(line, count, infile); + if (!fgets(line, count, infile)) + { + break; + } for (int k = 1; k < count; k++) { if (line[k] == ':') { diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index fb5d05969..32fb28dbb 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -89,7 +89,6 @@ namespace embark_assist{ void ViewscreenHelpUi::render() { color_ostream_proxy out(Core::getInstance().getConsole()); - auto screen_size = DFHack::Screen::getWindowSize(); Screen::Pen pen(' ', COLOR_WHITE); Screen::Pen site_pen = Screen::Pen(' ', COLOR_YELLOW, COLOR_BLACK, false); Screen::Pen pen_lr(' ', COLOR_LIGHTRED); diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index eb73eda2c..d4a2f8ee3 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -41,7 +41,7 @@ namespace embark_assist { uint16_t aquifer_count = 0; bool river_found = false; bool waterfall_found = false; - uint16_t river_elevation; + uint16_t river_elevation = 0xffff; uint16_t elevation = mlt->at(start_x).at(start_y).elevation; bool clay_found = false; bool sand_found = false; @@ -394,7 +394,6 @@ namespace embark_assist { df::world_data *world_data = world->world_data; embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); const uint16_t embark_size = finder->x_dim * finder->y_dim; - uint16_t count; bool found; if (tile->surveyed) { @@ -750,7 +749,6 @@ namespace embark_assist { finder->mineral_1 != -1 || finder->mineral_2 != -1 || finder->mineral_3 != -1) { - count = 0; bool metal_1 = finder->metal_1 == -1; bool metal_2 = finder->metal_2 == -1; bool metal_3 = finder->metal_3 == -1; @@ -1104,7 +1102,6 @@ namespace embark_assist { finder->mineral_1 != -1 || finder->mineral_2 != -1 || finder->mineral_3 != -1) { - count = 0; bool metal_1 = finder->metal_1 == -1; bool metal_2 = finder->metal_2 == -1; bool metal_3 = finder->metal_3 == -1; @@ -1285,7 +1282,7 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter auto screen = Gui::getViewscreenByType(0); uint16_t x_end; uint16_t y_end; - bool turn; + bool turn = false; uint16_t count; uint16_t preliminary_matches; diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 9de42e57f..4b0d883e3 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -1002,7 +1002,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * // color_ostream_proxy out(Core::getInstance().getConsole()); auto screen = Gui::getViewscreenByType(0); - int16_t elevation; + int16_t elevation = 0; uint16_t x = screen->location.region_pos.x; uint16_t y = screen->location.region_pos.y; bool river_found = false; diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 81f7dd6cd..2358bf95f 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -268,6 +268,8 @@ public: case df::interface_key::CURSOR_DOWNRIGHT_FAST: is_motion = true; break; + default: + break; } if (is_motion && !moved_position) { @@ -681,7 +683,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest if (parts.size()) { std::string label = join_strings(", ", parts); - if (label.size() > dim.x - x - 1) + if (int16_t(label.size()) > dim.x - x - 1) { label.resize(dim.x - x - 1 - 3); label.append("..."); diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index f4056a3d5..5d8dfdb59 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -78,17 +78,6 @@ struct ReactionInfo { static std::map reactions; static std::map products; -static ReactionInfo *find_reaction(const std::string &name) -{ - auto it = reactions.find(name); - return (it != reactions.end()) ? &it->second : NULL; -} - -static bool is_lua_hook(const std::string &name) -{ - return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0; -} - /* * Hooks */ @@ -158,12 +147,12 @@ void ev_mng_jobCompleted(color_ostream& out, void* job) } void ev_mng_unitDeath(color_ostream& out, void* ptr) { - int32_t myId=*(int32_t*)&ptr; + int32_t myId=(int32_t)(intptr_t)ptr; onUnitDeath(out,myId); } void ev_mng_itemCreate(color_ostream& out, void* ptr) { - int32_t myId=*(int32_t*)&ptr; + int32_t myId=(int32_t)(intptr_t)ptr; onItemCreated(out,myId); } void ev_mng_construction(color_ostream& out, void* ptr) @@ -178,12 +167,12 @@ void ev_mng_syndrome(color_ostream& out, void* ptr) } void ev_mng_invasion(color_ostream& out, void* ptr) { - int32_t myId=*(int32_t*)&ptr; + int32_t myId=(int32_t)(intptr_t)ptr; onInvasion(out,myId); } static void ev_mng_building(color_ostream& out, void* ptr) { - int32_t id = *((int32_t*)ptr); + int32_t id=(int32_t)(intptr_t)ptr; onBuildingCreatedDestroyed(out, id); } static void ev_mng_inventory(color_ostream& out, void* ptr) @@ -204,7 +193,7 @@ static void ev_mng_inventory(color_ostream& out, void* ptr) onInventoryChange(out,unitId,itemId,item_old,item_new); } static void ev_mng_report(color_ostream& out, void* ptr) { - onReport(out,*(int32_t*)&ptr); + onReport(out,(int32_t)(intptr_t)ptr); } static void ev_mng_unitAttack(color_ostream& out, void* ptr) { EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index e8ae85c99..09d831e26 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -120,6 +120,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::unit_action *action = unit->actions[i]; switch (action->type) { + case unit_action_type::None: + break; case unit_action_type::Move: action->data.move.timer = 1; break; @@ -171,6 +173,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) case unit_action_type::SuckBlood: action->data.suckblood.timer = 1; break; + case unit_action_type::Jump: + case unit_action_type::ReleaseTerrain: + case unit_action_type::Parry: + case unit_action_type::Block: + case unit_action_type::HoldItem: + case unit_action_type::ReleaseItem: + break; } } } diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index 0f9a7bb6f..ddb5ef877 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -215,7 +215,7 @@ command_result filltraffic(color_ostream &out, std::vector & params { flood.push(DFCoord(xy.x - 1, xy.y, xy.z)); } - if (xy.x < tx_max - 1) + if (xy.x < int32_t(tx_max) - 1) { flood.push(DFCoord(xy.x + 1, xy.y, xy.z)); } @@ -223,7 +223,7 @@ command_result filltraffic(color_ostream &out, std::vector & params { flood.push(DFCoord(xy.x, xy.y - 1, xy.z)); } - if (xy.y < ty_max - 1) + if (xy.y < int32_t(ty_max) - 1) { flood.push(DFCoord(xy.x, xy.y + 1, xy.z)); } @@ -234,7 +234,7 @@ command_result filltraffic(color_ostream &out, std::vector & params { flood.push(DFCoord(xy.x, xy.y, xy.z - 1)); } - if (xy.z < z_max && HighPassable(tt)) + if (xy.z < int32_t(z_max) && HighPassable(tt)) { flood.push(DFCoord(xy.x, xy.y, xy.z + 1)); } @@ -337,11 +337,11 @@ command_result setAllMatching(color_ostream &out, checkTile checkProc, out.print("Setting traffic...\n"); //Loop through every single tile - for(uint32_t x = minCoord.x; x <= maxCoord.x; x++) + for(int32_t x = minCoord.x; x <= maxCoord.x; x++) { - for(uint32_t y = minCoord.y; y <= maxCoord.y; y++) + for(int32_t y = minCoord.y; y <= maxCoord.y; y++) { - for(uint32_t z = minCoord.z; z <= maxCoord.z; z++) + for(int32_t z = minCoord.z; z <= maxCoord.z; z++) { DFCoord tile = DFCoord(x, y, z); checkProc(tile, MCache); diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index 8b7be0c0f..b3cac47c5 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -21,26 +21,13 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(world); -static int run_interval = 1200; // daily +static unsigned run_interval = 1200; // daily inline float getClock() { return (float)clock() / (float)CLOCKS_PER_SEC; } -static std::string get_unit_description(df::unit *unit) -{ - if (!unit) - return ""; - std::string desc; - auto name = Units::getVisibleName(unit); - if (name->has_name) - desc = Translation::TranslateName(name, false); - desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too - - return desc; -} - struct uo_buf { uint32_t dim_x, dim_y, dim_z; size_t size; diff --git a/plugins/follow.cpp b/plugins/follow.cpp index 7a17f89d0..21a297bd1 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -116,8 +116,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) y_max *= 16; //Calculate a new screen position centered on the selected unit - x = unitPos.x + w/2 >= x_max ? x_max-w : (unitPos.x >= w/2 ? unitPos.x - w/2 : 0); - y = unitPos.y + h/2 >= y_max ? y_max-h : (unitPos.y >= h/2 ? unitPos.y - h/2 : 0); + x = unitPos.x + w/2 >= int32_t(x_max) ? x_max-w : (unitPos.x >= w/2 ? unitPos.x - w/2 : 0); + y = unitPos.y + h/2 >= int32_t(y_max) ? y_max-h : (unitPos.y >= h/2 ? unitPos.y - h/2 : 0); z = unitPos.z; //Set the new screen position! diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index da6f1aff4..19c1202d6 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -274,7 +274,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u // Step 2: Try to find a bodypart which is eligible to receive equipment AND which is appropriate for the specified item df::body_part_raw * confirmedBodyPart = NULL; - int bpIndex; + size_t bpIndex; for(bpIndex = 0; bpIndex < unit->body.body_plan->body_parts.size(); bpIndex++) { df::body_part_raw * currPart = unit->body.body_plan->body_parts[bpIndex]; @@ -358,10 +358,9 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u { confirmedBodyPart = currPart; // Assume that the bodypart is valid; we'll invalidate it if we detect too many collisions while looping int collisions = 0; - for (int inventoryID=0; inventoryID < unit->inventory.size(); inventoryID++) + for (df::unit_inventory_item * currInvItem : unit->inventory) { - df::unit_inventory_item * currInvItem = unit->inventory[inventoryID]; - if (currInvItem->body_part_id == bpIndex) + if (currInvItem->body_part_id == int32_t(bpIndex)) { // Collision detected; have we reached the limit? if (++collisions >= multiEquipLimit) @@ -415,6 +414,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) // The "here" option is hardcoded to true, because the plugin currently doesn't support // equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit) bool here = true; + (void)here; // For balance (anti-cheating) reasons, the plugin applies a limit on the number of // item that can be equipped on any bodypart. This limit defaults to 1 but can be // overridden with cmdline switches. @@ -512,7 +512,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) pos_cursor = DFCoord(cx,cy,cz); // Iterate over all units, process the first one whose pos == pos_cursor - df::unit * targetUnit; + df::unit * targetUnit = nullptr; size_t numUnits = world->units.all.size(); for(size_t i=0; i< numUnits; i++) { @@ -522,11 +522,13 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (pos_unit == pos_cursor) break; - if (i + 1 == numUnits) - { - out.printerr("No unit found at cursor!\n"); - return CR_FAILURE; - } + targetUnit = nullptr; + } + + if (!targetUnit) + { + out.printerr("No unit found at cursor!\n"); + return CR_FAILURE; } // Assert: unit found. @@ -534,7 +536,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) // If a specific bodypart was included in the command arguments, then search for it now df::body_part_raw * targetBodyPart = NULL; if (targetBodyPartCode.size() > 0) { - for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) + for (size_t bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) { // Tentatively assume that the part is a match targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex); diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 49444eaf4..e70324dd2 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -115,7 +115,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan int creatureCount = 0; - for (int i = 0; i < world->raws.creatures.all.size(); i++) + for (size_t i = 0; i < world->raws.creatures.all.size(); i++) { auto creatureRaw = world->raws.creatures.all[i]; if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) @@ -150,7 +150,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan auto descriptor = descriptors[foundIndex]; - for (int j = 0; j < descriptor.size(); j++) + for (size_t j = 0; j < descriptor.size(); j++) { if (descriptor[j] == ' ') descriptor[j] = '_'; @@ -194,7 +194,7 @@ command_result list_creatures(color_ostream &out, std::vector & pa } CoreSuspender suspend; - for (int i = 0; i < world->raws.creatures.all.size(); i++) + for (size_t i = 0; i < world->raws.creatures.all.size(); i++) { auto creatureRaw = world->raws.creatures.all[i]; if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) @@ -223,7 +223,7 @@ command_result save_generated_raw(color_ostream &out, std::vector int tileHeight = 24; std::string fileName = "graphics_procedural_creatures"; std::string pageName = "PROCEDURAL_FRIENDLY"; - int repeats = 128; + size_t repeats = 128; std::ofstream outputFile(fileName + ".txt", std::ios::out | std::ios::trunc); @@ -244,7 +244,7 @@ command_result save_generated_raw(color_ostream &out, std::vector { auto descriptor = descriptors[descIndex]; - for (int j = 0; j < descriptor.size(); j++) + for (size_t j = 0; j < descriptor.size(); j++) { if (descriptor[j] == ' ') descriptor[j] = '_'; diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 6ec281143..d3ee600e3 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -17,13 +17,6 @@ static map current_bindings; static vector sorted_keys; static bool show_usage = false; -static void send_key(const df::interface_key &key) -{ - set< df::interface_key > keys; - keys.insert(key); - Gui::getCurViewscreen(true)->feed(&keys); -} - static bool can_invoke(string cmdline, df::viewscreen *screen) { vector cmd_parts; @@ -116,7 +109,7 @@ static bool close_hotkeys_screen() } -static void invoke_command(const int index) +static void invoke_command(const size_t index) { if (sorted_keys.size() <= index) return; @@ -147,12 +140,12 @@ public: { hotkeys_column.clear(); - int max_key_length = 0; + size_t max_key_length = 0; for_each_(sorted_keys, [&] (const string &sym) { if (sym.length() > max_key_length) { max_key_length = sym.length(); } }); int padding = max_key_length + 2; - for (int i = 0; i < sorted_keys.size(); i++) + for (size_t i = 0; i < sorted_keys.size(); i++) { string text = pad_string(sorted_keys[i], padding, false); text += current_bindings[sorted_keys[i]]; @@ -230,7 +223,7 @@ public: Plugin *plugin = Core::getInstance().getPluginManager()->getPluginByCommand(first); if (plugin) { - for (auto i = 0; i < plugin->size(); i++) + for (size_t i = 0; i < plugin->size(); i++) { auto pc = plugin->operator[](i); if (pc.name == first) @@ -278,7 +271,7 @@ private: { vector result; string excess; - if (str.length() > width) + if (int(str.length()) > width) { auto cut_space = str.rfind(' ', width-1); int excess_start; diff --git a/plugins/isoworldremote.cpp b/plugins/isoworldremote.cpp index 017fb45f6..651f89700 100644 --- a/plugins/isoworldremote.cpp +++ b/plugins/isoworldremote.cpp @@ -225,7 +225,7 @@ bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCac tile->set_current_year(*cur_year); tile->set_current_season(*cur_season); int num_valid_layers = 0; - for(int z = 0; z < MP->maxZ(); z++) + for(uint32_t z = 0; z < MP->maxZ(); z++) { EmbarkTileLayer * tile_layer = tile->add_tile_layer(); num_valid_layers += gather_embark_tile_layer(EmbX, EmbY, z, tile_layer, MP); @@ -351,11 +351,11 @@ static command_result GetRawNames(color_ostream &stream, const MapRequest *in, R } } out->set_available(true); - for(int i = 0; i < world->raws.inorganics.size(); i++){ + for(size_t i = 0; i < world->raws.inorganics.size(); i++){ out->add_inorganic(world->raws.inorganics[i]->id); } - for(int i = 0; i < world->raws.plants.all.size(); i++){ + for(size_t i = 0; i < world->raws.plants.all.size(); i++){ out->add_organic(world->raws.plants.all[i]->id); } return CR_OK; diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index 4f0124e93..becddeed3 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -210,8 +210,8 @@ static df::unit_labor construction_build_labor(df::building_actual* b) df::item* i = 0; for (auto p = b->contained_items.begin(); p != b->contained_items.end(); p++) - if (b->construction_stage > 0 && (*p)->use_mode == 2 || - b->construction_stage == 0 && (*p)->use_mode == 0) + if ((b->construction_stage > 0 && (*p)->use_mode == 2) || + (b->construction_stage == 0 && (*p)->use_mode == 0)) i = (*p)->item; MaterialInfo matinfo; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 3ef1808a2..5c408c354 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1147,7 +1147,7 @@ private: tool_count[TOOL_AXE]++; else if (weaponsk == df::job_skill::MINING) tool_count[TOOL_PICK]++; - else if (weaponsk2 = df::job_skill::CROSSBOW) + else if (weaponsk2 == df::job_skill::CROSSBOW) tool_count[TOOL_CROSSBOW]++; } @@ -1493,10 +1493,12 @@ private: if (labor != df::unit_labor::NONE) { if (d->dwarf->status.labors[labor]) + { if (labor == df::unit_labor::OPERATE_PUMP) score += 50000; else score += 25000; + } if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) score += 10000000; @@ -2088,7 +2090,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_onupdate(color_ostream &out) { - static int step_count = 0; + // static int step_count = 0; // check run conditions if (!initialized || !world || !world->map.block_index || !enable_labormanager) { @@ -2102,7 +2104,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) if (*df::global::process_jobs) return CR_OK; - step_count = 0; + // step_count = 0; debug_stream = &out; AutoLaborManager alm(out); @@ -2263,7 +2265,7 @@ command_result labormanager(color_ostream &out, std::vector & para out << "All labors reset." << endl; return CR_OK; } - else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") + else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) { if (!enable_labormanager) { diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index f7747d96d..0abe07b9d 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -16,7 +16,7 @@ public: UIColor color; ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) : - elem(elem), text(text), selected(false), keywords(keywords), color(color) + elem(elem), text(text), keywords(keywords), selected(false), color(color) { } }; @@ -73,14 +73,14 @@ public: void add(const ListEntry &entry) { list.push_back(entry); - if (entry.text.length() > max_item_width) + if (entry.text.length() > size_t(max_item_width)) max_item_width = entry.text.length(); } void add(const string &text, const T &elem) { list.push_back(ListEntry(text, elem)); - if (text.length() > max_item_width) + if (text.length() > size_t(max_item_width)) max_item_width = text.length(); } @@ -110,7 +110,7 @@ public: paint_text(COLOR_TITLE, left_margin, y, title); int last_index_able_to_display = display_start_offset + display_max_rows; - for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) + for (int i = display_start_offset; size_t(i) < display_list.size() && i < last_index_able_to_display; i++) { ++y; UIColor fg_color = (is_selected_column && display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; @@ -336,8 +336,7 @@ public: void selectItem(const T elem) { - int i = 0; - for (; i < display_list.size(); i++) + for (size_t i = 0; i < display_list.size(); i++) { if (display_list[i]->elem == elem) { @@ -447,7 +446,7 @@ public: gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) { int new_index = display_start_offset + gps->mouse_y - 3; - if (new_index < display_list.size()) + if (size_t(new_index) < display_list.size()) { setHighlight(new_index); feed_mouse_set_highlight = true; @@ -472,7 +471,7 @@ public: void setTitle(const string t) { title = t; - if (title.length() > max_item_width) + if (title.length() > size_t(max_item_width)) max_item_width = title.length(); } diff --git a/plugins/luasocket.cpp b/plugins/luasocket.cpp index 0259c562f..2903616e7 100644 --- a/plugins/luasocket.cpp +++ b/plugins/luasocket.cpp @@ -223,7 +223,7 @@ static void lua_client_send(int server_id,int client_id,std::string data) throw std::runtime_error("Client does with this id not exist"); } CActiveSocket *sock=(*target)[client_id]; - if(sock->Send((const uint8_t*)data.c_str(),data.size())!=data.size()) + if(size_t(sock->Send((const uint8_t*)data.c_str(),data.size()))!=data.size()) { throw std::runtime_error(sock->DescribeError()); } diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 2940b493b..5dab2ab09 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -596,7 +596,7 @@ namespace unit_ops { } string get_short_profname(UnitInfo *u) { - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (columns[i].profession == u->unit->profession) return string(columns[i].label); @@ -657,7 +657,7 @@ struct ProfessionTemplate continue; } - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (line == ENUM_KEY_STR(unit_labor, columns[i].labor)) { @@ -678,7 +678,7 @@ struct ProfessionTemplate if (mask) outfile << "MASK" << std::endl; - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (hasLabor(columns[i].labor)) { @@ -696,7 +696,7 @@ struct ProfessionTemplate if (!mask && name.size() > 0) unit_ops::set_profname(u, name); - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { df::unit_labor labor = columns[i].labor; bool status = hasLabor(labor); @@ -709,7 +709,7 @@ struct ProfessionTemplate void fromUnit(UnitInfo* u) { - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (u->unit->status.labors[columns[i].labor]) labors.push_back(columns[i].labor); @@ -899,7 +899,7 @@ public: } OutputString(COLOR_LIGHTGREEN, x, y, itos(units.size())); OutputString(COLOR_GREY, x, y, string(" ") + (units.size() > 1 ? "dwarves" : "dwarf") + " selected: "); - int max_x = gps->dimx - 2; + size_t max_x = gps->dimx - 2; size_t i = 0; for ( ; i < units.size(); i++) { @@ -1046,7 +1046,7 @@ public: menu_options.display(true); OutputString(COLOR_LIGHTGREEN, x, y, itos(units.size())); OutputString(COLOR_GREY, x, y, string(" ") + (units.size() > 1 ? "dwarves" : "dwarf") + " selected: "); - int max_x = gps->dimx - 2; + size_t max_x = gps->dimx - 2; size_t i = 0; for ( ; i < units.size(); i++) { @@ -1138,7 +1138,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur df::unit *unit = src[i]; if (!unit) { - if (cursor_pos > i) + if (cursor_pos > int(i)) cursor_pos--; continue; } @@ -1192,7 +1192,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur if (first_row > sel_row) first_row = sel_row - num_rows + 1; // don't scroll beyond the end - if (first_row > units.size() - num_rows) + if (first_row > int(units.size()) - num_rows) first_row = units.size() - num_rows; last_selection = -1; @@ -1207,7 +1207,7 @@ void viewscreen_unitlaborsst::calcIDs() if (!initialized) { initialized = true; - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) group_map.insert(std::pair(columns[i].profession, columns[i].group)); } memset(list_prof_ids, 0, sizeof(list_prof_ids)); @@ -1267,7 +1267,7 @@ void viewscreen_unitlaborsst::calcSize() auto dim = Screen::getWindowSize(); num_rows = dim.y - 11; - if (num_rows > units.size()) + if (num_rows > int(units.size())) num_rows = units.size(); int num_columns = dim.x - DISP_COLUMN_MAX - 1; @@ -1289,7 +1289,7 @@ void viewscreen_unitlaborsst::calcSize() // get max_name/max_prof from strings length for (size_t i = 0; i < units.size(); i++) { - if (col_maxwidth[DISP_COLUMN_NAME] < units[i]->name.size()) + if (size_t(col_maxwidth[DISP_COLUMN_NAME]) < units[i]->name.size()) col_maxwidth[DISP_COLUMN_NAME] = units[i]->name.size(); size_t detail_cmp; @@ -1300,7 +1300,7 @@ void viewscreen_unitlaborsst::calcSize() } else { detail_cmp = units[i]->profession.size(); } - if (col_maxwidth[DISP_COLUMN_DETAIL] < detail_cmp) + if (size_t(col_maxwidth[DISP_COLUMN_DETAIL]) < detail_cmp) col_maxwidth[DISP_COLUMN_DETAIL] = detail_cmp; } @@ -1383,7 +1383,7 @@ void viewscreen_unitlaborsst::calcSize() return; // if the window grows vertically, scroll upward to eliminate blank rows from the bottom - if (first_row > units.size() - num_rows) + if (first_row > int(units.size()) - num_rows) first_row = units.size() - num_rows; // if it shrinks vertically, scroll downward to keep the cursor visible @@ -1391,7 +1391,7 @@ void viewscreen_unitlaborsst::calcSize() first_row = sel_row - num_rows + 1; // if the window grows horizontally, scroll to the left to eliminate blank columns from the right - if (first_column > NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS]) + if (first_column > int(NUM_COLUMNS) - col_widths[DISP_COLUMN_LABORS]) first_column = NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS]; // if it shrinks horizontally, scroll to the right to keep the cursor visible @@ -1437,7 +1437,7 @@ void viewscreen_unitlaborsst::feed(set *events) { sel_row = 0; } - if ((sel_row < units.size()-1) && events->count(interface_key::CURSOR_DOWN_Z_AUX)) + if ((size_t(sel_row) < units.size()-1) && events->count(interface_key::CURSOR_DOWN_Z_AUX)) { sel_row = units.size()-1; } @@ -1450,9 +1450,9 @@ void viewscreen_unitlaborsst::feed(set *events) sel_row = 0; } - if (sel_row > units.size() - 1) + if (size_t(sel_row) > units.size() - 1) { - if (old_sel_row == units.size()-1 && events->count(interface_key::CURSOR_DOWN)) + if (size_t(old_sel_row) == units.size()-1 && events->count(interface_key::CURSOR_DOWN)) sel_row = 0; else sel_row = units.size() - 1; @@ -1487,7 +1487,7 @@ void viewscreen_unitlaborsst::feed(set *events) { // go to beginning of next group int cur = columns[sel_column].group; - int next = sel_column+1; + size_t next = sel_column+1; while ((next < NUM_COLUMNS) && (columns[next].group == cur)) next++; if ((next < NUM_COLUMNS) && (columns[next].group != cur)) @@ -1499,14 +1499,14 @@ void viewscreen_unitlaborsst::feed(set *events) if (sel_column < 0) sel_column = 0; - if (sel_column > NUM_COLUMNS - 1) + if (size_t(sel_column) > NUM_COLUMNS - 1) sel_column = NUM_COLUMNS - 1; if (events->count(interface_key::CURSOR_DOWN_Z) || events->count(interface_key::CURSOR_UP_Z)) { // when moving by group, ensure the whole group is shown onscreen int endgroup_column = sel_column; - while ((endgroup_column < NUM_COLUMNS-1) && columns[endgroup_column+1].group == columns[sel_column].group) + while ((size_t(endgroup_column) < NUM_COLUMNS-1) && columns[endgroup_column+1].group == columns[sel_column].group) endgroup_column++; if (first_column < endgroup_column - col_widths[DISP_COLUMN_LABORS] + 1) @@ -1674,7 +1674,7 @@ void viewscreen_unitlaborsst::feed(set *events) { if (newstatus) { - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if ((columns[i].labor != unit_labor::NONE) && columns[i].special) unit->status.labors[columns[i].labor] = false; @@ -1688,7 +1688,7 @@ void viewscreen_unitlaborsst::feed(set *events) { const SkillColumn &col = columns[input_column]; bool newstatus = !unit->status.labors[col.labor]; - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (columns[i].group != col.group) continue; @@ -1698,7 +1698,7 @@ void viewscreen_unitlaborsst::feed(set *events) { if (newstatus) { - for (int j = 0; j < NUM_COLUMNS; j++) + for (size_t j = 0; j < NUM_COLUMNS; j++) { if ((columns[j].labor != unit_labor::NONE) && columns[j].special) unit->status.labors[columns[j].labor] = false; @@ -1767,6 +1767,8 @@ void viewscreen_unitlaborsst::feed(set *events) case ALTSORT_ARRIVAL: altsort = ALTSORT_NAME; break; + case ALTSORT_MAX: + break; } } if (events->count(interface_key::OPTION20)) @@ -1847,7 +1849,7 @@ void viewscreen_unitlaborsst::feed(set *events) { if (events->count(interface_key::UNITJOB_VIEW_UNIT) || events->count(interface_key::UNITJOB_ZOOM_CRE)) { - for (int i = 0; i < unitlist->units[unitlist->page].size(); i++) + for (size_t i = 0; i < unitlist->units[unitlist->page].size(); i++) { if (unitlist->units[unitlist->page][i] == units[input_row]->unit) { @@ -1893,7 +1895,7 @@ void viewscreen_unitlaborsst::render() for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++) { int col_offset = col + first_column; - if (col_offset >= NUM_COLUMNS) + if (size_t(col_offset) >= NUM_COLUMNS) break; int8_t fg = columns[col_offset].color; @@ -1922,7 +1924,7 @@ void viewscreen_unitlaborsst::render() for (int row = 0; row < num_rows; row++) { int row_offset = row + first_row; - if (row_offset >= units.size()) + if (size_t(row_offset) >= units.size()) break; UnitInfo *cur = units[row_offset]; @@ -1998,7 +2000,7 @@ void viewscreen_unitlaborsst::render() skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); if ((skill != NULL) && (skill->rating || skill->experience)) { - int level = skill->rating; + size_t level = skill->rating; if (level > NUM_SKILL_LEVELS - 1) level = NUM_SKILL_LEVELS - 1; c = skill_levels[level].abbrev; @@ -2060,7 +2062,7 @@ void viewscreen_unitlaborsst::render() skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); if (skill) { - int level = skill->rating; + size_t level = skill->rating; if (level > NUM_SKILL_LEVELS - 1) level = NUM_SKILL_LEVELS - 1; str = stl_sprintf("%s %s", skill_levels[level].name, ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 7a70f29c4..4e0143fb6 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -46,7 +46,7 @@ static bool live_view = true; static bool skip_tracking_once = false; static bool mouse_moved = false; -static int scroll_delay = 100; +static uint32_t scroll_delay = 100; static df::coord get_mouse_pos(int32_t &mx, int32_t &my) { @@ -231,9 +231,10 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest case Burrows: return ui->burrows.in_define_mode; - }; - return false; + default: + return false; + } } bool isInTrackableMode() diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 63acfe3da..5f4a59ceb 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -401,9 +401,9 @@ command_result df_bprobe (color_ostream &out, vector & parameters) Buildings::t_building building; if (!Buildings::Read(i, building)) continue; - if (!(building.x1 <= cursor->x && cursor->x <= building.x2 && - building.y1 <= cursor->y && cursor->y <= building.y2 && - building.z == cursor->z)) + if (int32_t(building.x1) > cursor->x || cursor->x > int32_t(building.x2) || + int32_t(building.y1) > cursor->y || cursor->y > int32_t(building.y2) || + int32_t(building.z) != cursor->z) continue; string name; building.origin->getName(&name); diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 32e360a3a..18efeac98 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -152,7 +152,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool for (MatSorter::const_iterator it = sorting_vector.begin(); it != sorting_vector.end(); ++it) { - if(it->first >= materials.size()) + if(size_t(it->first) >= materials.size()) { con << "Bad index: " << it->first << " out of " << materials.size() << endl; @@ -747,7 +747,7 @@ command_result prospector (color_ostream &con, vector & parameters) for (PlantList::const_iterator it = plants->begin(); it != plants->end(); it++) { const df::plant & plant = *(*it); - if (plant.pos.z != z) + if (uint32_t(plant.pos.z) != z) continue; df::coord2d loc(plant.pos.x, plant.pos.y); loc = loc % 16; diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index e1282d600..f1645a84f 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -737,7 +737,6 @@ bool IsBuildingChanged(DFCoord pos) for (int x = 0; x < 16; x++) for (int y = 0; y < 16; y++) { - DFCoord localPos = DFCoord(pos.x * 16 + x, pos.y * 16 + y, pos.z); auto bld = block->occupancy[x][y].bits.building; if (buildingHashes[pos] != bld) { @@ -1517,7 +1516,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in bool spatterChanged = IsspatterChanged(pos); bool itemsChanged = block->items.size() > 0; bool flows = block->flows.size() > 0; - RemoteFortressReader::MapBlock *net_block; + RemoteFortressReader::MapBlock *net_block = nullptr; if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged || flows) net_block = out->add_map_blocks(); if (tileChanged) @@ -2813,13 +2812,10 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque df::world * world = df::global::world; int list_start = 0; - int list_end = world->raws.plants.all.size(); if (in != nullptr) { list_start = in->list_start(); - if (in->list_end() < list_end) - list_end = in->list_end(); } for (int i = 0; i < world->raws.plants.all.size(); i++) diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 199e174af..fa2f18d5c 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -335,7 +335,6 @@ void lightingEngineViewscreen::fixAdvMode(int mode) int window_x=*df::global::window_x; int window_y=*df::global::window_y; int window_z=*df::global::window_z; - coord2d vpSize=rect_size(vp); //mode 0-> make dark non-visible parts if(mode==0) { @@ -939,14 +938,14 @@ matLightDef lua_parseMatDef(lua_State* L) matLightDef ret; lua_getfield(L,-1,"tr"); - if(ret.isTransparent=!lua_isnil(L,-1)) + if((ret.isTransparent=!lua_isnil(L,-1))) { ret.transparency=lua_parseLightCell(L); } lua_pop(L,1); lua_getfield(L,-1,"em"); - if(ret.isEmiting=!lua_isnil(L,-1)) + if((ret.isEmiting=!lua_isnil(L,-1))) { ret.emitColor=lua_parseLightCell(L); lua_pop(L,1); diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index c2479bedc..6908225ea 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -418,6 +418,8 @@ command_result revflood(color_ostream &out, vector & params) case tiletype_shape::STAIR_DOWN: tt = ctt; break; + default: + break; } bool below = 0; @@ -433,6 +435,7 @@ command_result revflood(color_ostream &out, vector & params) unhide = 0; break; // air/free space + case tiletype_shape::NONE: case tiletype_shape::EMPTY: case tiletype_shape::RAMP_TOP: case tiletype_shape::STAIR_UPDOWN: diff --git a/plugins/search.cpp b/plugins/search.cpp index 1e919572b..ed8934a0c 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1459,12 +1459,12 @@ public: // About to make an assignment, so restore original list (it will be changed by the game) int32_t *cursor = get_viewscreen_cursor(); auto list = get_primary_list(); - if (*cursor >= list->size()) + if (size_t(*cursor) >= list->size()) return false; df::unit *selected_unit = list->at(*cursor); clear_search(); - for (*cursor = 0; *cursor < list->size(); (*cursor)++) + for (*cursor = 0; size_t(*cursor) < list->size(); (*cursor)++) { if (list->at(*cursor) == selected_unit) break; diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index d2ae73f08..9e3fa7ff7 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -98,6 +98,8 @@ command_result df_showmood (color_ostream &out, vector & parameters) case mood_type::Possessed: out.print("possessed"); break; + default: + break; } out.print(" with intent to "); switch (job->job_type) @@ -275,7 +277,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) int count_got = 0; for (size_t j = 0; j < job->items.size(); j++) { - if(job->items[j]->job_item_idx == i) + if(job->items[j]->job_item_idx == int32_t(i)) { if (item->item_type == item_type::BAR || item->item_type == item_type::CLOTH) count_got += job->items[j]->item->getTotalDimension(); diff --git a/plugins/stockpiles/CMakeLists.txt b/plugins/stockpiles/CMakeLists.txt index 713c3d6d1..7d5cce921 100644 --- a/plugins/stockpiles/CMakeLists.txt +++ b/plugins/stockpiles/CMakeLists.txt @@ -14,31 +14,10 @@ SET(PROJECT_SRCS ) SET(PROJECT_PROTOS -${CMAKE_CURRENT_SOURCE_DIR}/proto/stockpiles.proto + stockpiles.proto ) -#Create new lists of what sources and headers protoc will output after we invoke it -STRING(REPLACE ".proto" ".pb.cc;" PROJECT_PROTO_SRCS ${PROJECT_PROTOS}) -STRING(REPLACE ".proto" ".pb.h;" PROJECT_PROTO_HDRS ${PROJECT_PROTOS}) - -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_HDRS} PROPERTIES GENERATED TRUE) -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_SRCS} PROPERTIES GENERATED TRUE) - -LIST(APPEND PROJECT_HDRS ${PROJECT_PROTO_HDRS}) -LIST(APPEND PROJECT_SRCS ${PROJECT_PROTO_SRCS}) - SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) -#Generate sources from our proto files and store them in the source tree -ADD_CUSTOM_COMMAND( -OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} -COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ ${PROJECT_PROTOS} -DEPENDS protoc-bin ${PROJECT_PROTOS} -) - -IF(WIN32) - DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua) -ELSE() - DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua) -ENDIF() +DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} PROTOBUFS ${PROJECT_PROTOS} LINK_LIBRARIES protobuf-lite lua) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index a5b84672d..154a58dc3 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -42,17 +42,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) #define MAX_NAME 30 #define SIDEBAR_WIDTH 30 -static bool show_debugging = false; - -static void debug(const string &msg) -{ - if (!show_debugging) - return; - - color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG (stocks): " << msg << endl; -} - /* * Utility diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 7d967b006..9a7f7f5fb 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -106,6 +106,8 @@ df::job_skill getMoodSkill (df::unit *unit) if (skill->rating == level) skills.push_back(skill->id); break; + default: + break; } } if (!skills.size() && civ) @@ -175,7 +177,7 @@ void generateName(df::language_name &output, int language, int mode, const df::l int32_t word; df::enum_field part; output.first_name.clear(); selectWord(table1, word, part, 2); - if (word >= 0 && word < world->raws.language.words.size()) + if (word >= 0 && size_t(word) < world->raws.language.words.size()) output.first_name = *world->raws.language.translations[language]->words[word]; } if (mode != 10) @@ -616,6 +618,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) for (int j = 0; j < 15; j++) tickets.push_back(i); break; + default: + break; } } if (!tickets.size()) @@ -765,6 +769,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::MECHANICS: job->job_type = job_type::StrangeMoodMechanics; break; + default: + break; } } // Check which types of glass are available - we'll need this information later @@ -1111,6 +1117,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } item->quantity = base_item_count; break; + default: + break; } } @@ -1157,8 +1165,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::GLASSMAKER: avoid_glass = 1; break; + default: + break; } - for (size_t i = 0; i < extra_items; i++) + for (int i = 0; i < extra_items; i++) { if ((job->job_type == job_type::StrangeMoodBrooding) && (rng.df_trandom(2))) { diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 76da1df3e..36bde1ac7 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -78,7 +78,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) void help( color_ostream & out, std::vector &commands, int start, int end) { - std::string option = commands.size() > start ? commands[start] : ""; + std::string option = commands.size() > size_t(start) ? commands[start] : ""; if (option.empty()) { out << "Commands:" << std::endl @@ -812,7 +812,7 @@ command_result executePaintJob(color_ostream &out) */ // Remove direction from directionless tiles DFHack::TileDirection direction = tileDirection(source); - if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || special == tiletype_special::TRACK || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH))) + if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || special == tiletype_special::TRACK || (shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH)))) { direction.whole = 0; } @@ -894,7 +894,7 @@ command_result executePaintJob(color_ostream &out) command_result processCommand(color_ostream &out, std::vector &commands, int start, int end, bool & endLoop, bool hasConsole = false) { - if (commands.size() == start) + if (commands.size() == size_t(start)) { return executePaintJob(out); } diff --git a/plugins/tweak/tweaks/max-wheelbarrow.h b/plugins/tweak/tweaks/max-wheelbarrow.h index 37f292d6b..4df02abd2 100644 --- a/plugins/tweak/tweaks/max-wheelbarrow.h +++ b/plugins/tweak/tweaks/max-wheelbarrow.h @@ -45,7 +45,6 @@ struct max_wheelbarrow_hook : df::viewscreen_dwarfmodest { bool handled = false; if (stockpile) { - auto dims = Gui::getDwarfmodeViewDims(); handled = true; if (!in_wheelbarrow_entry && input->count(df::interface_key::BUILDJOB_STOCKPILE_WHEELBARROW)) diff --git a/plugins/uicommon.h b/plugins/uicommon.h index a9a4a0ea8..17fcd18bd 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -195,7 +195,7 @@ inline void paint_text(const UIColor color, const int &x, const int &y, const st static inline string pad_string(string text, const int size, const bool front = true, const bool trim = false) { - if (text.length() > size) + if (text.length() > size_t(size)) { if (trim && size > 10) { @@ -318,6 +318,8 @@ static inline bool can_melt(df::item* item) } } break; + default: + break; } } @@ -334,12 +336,13 @@ static inline bool can_melt(df::item* item) class StockpileInfo { public: - StockpileInfo() : id(0), sp(nullptr) + StockpileInfo() : id(0), sp(nullptr), x1(-30000), x2(-30000), y1(-30000), y2(-30000), z(-30000) { } - StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) + StockpileInfo(df::building_stockpilest *sp_) : StockpileInfo() { + sp = sp_; readBuilding(); } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index cc3ac8cd7..bfc4b639f 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -438,7 +438,7 @@ static int fix_job_postings (color_ostream *out, bool dry_run) for (size_t i = 0; i < world->jobs.postings.size(); ++i) { df::job_handler::T_postings *posting = world->jobs.postings[i]; - if (posting->job == job && i != job->posting_index && !posting->flags.bits.dead) + if (posting->job == job && i != size_t(job->posting_index) && !posting->flags.bits.dead) { ++count; if (out) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 608216092..c67c5b051 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -2073,7 +2073,7 @@ command_result df_zone (color_ostream &out, vector & parameters) if(target_count > 0) { vector units_for_cagezone; - size_t count = 0; + int count = 0; for(auto unit_it = world->units.all.begin(); unit_it != world->units.all.end(); ++unit_it) { df::unit *unit = *unit_it; From 1dd66252704d4a82e7dd01587af1a608a411024a Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 6 Apr 2018 14:17:34 -0500 Subject: [PATCH 0928/1012] Fix remaining warnings in supported plugins --- plugins/Plugins.cmake | 64 +++++--- plugins/embark-assistant/finder_ui.cpp | 10 +- plugins/embark-assistant/overlay.cpp | 2 +- plugins/embark-assistant/screen.cpp | 2 +- plugins/labormanager/joblabormapper.cpp | 4 + plugins/labormanager/labormanager.cpp | 32 ++-- plugins/remotefortressreader/CMakeLists.txt | 11 +- .../remotefortressreader/building_reader.cpp | 4 +- plugins/remotefortressreader/item_reader.cpp | 35 +++-- plugins/remotefortressreader/proto/readme.txt | 1 - .../remotefortressreader.cpp | 139 +++++++++--------- plugins/rendermax/renderer_light.cpp | 27 ++-- plugins/rendermax/renderer_light.hpp | 10 +- plugins/ruby/ruby.cpp | 1 + plugins/stockpiles/CMakeLists.txt | 2 +- plugins/stockpiles/StockpileSerializer.cpp | 22 +-- plugins/stockpiles/StockpileUtils.h | 12 +- plugins/stockpiles/proto/tmp/.gitignore | 1 + plugins/stockpiles/stockpiles.cpp | 6 - plugins/tweak/tweak.cpp | 4 +- plugins/tweak/tweaks/block-labors.h | 2 +- plugins/tweak/tweaks/cage-butcher.h | 2 +- plugins/tweak/tweaks/craft-age-wear.h | 4 +- plugins/tweak/tweaks/pausing-fps-counter.h | 4 +- plugins/tweak/tweaks/tradereq-pet-gender.h | 2 +- 25 files changed, 215 insertions(+), 188 deletions(-) delete mode 100644 plugins/remotefortressreader/proto/readme.txt create mode 100644 plugins/stockpiles/proto/tmp/.gitignore diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 8142110cb..44a8dc5fb 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -73,20 +73,11 @@ MACRO(DFHACK_PLUGIN) CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS}) CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS}) - SET(PLUGIN_PROTOCPP) + SET(PLUGIN_PROTOS) FOREACH(pbuf ${PLUGIN_PROTOBUFS}) - SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) - SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.proto) - SET(PLUGIN_PROTOCPP ${PLUGIN_PROTOCPP} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) + LIST(APPEND PLUGIN_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.proto) ENDFOREACH() - IF(UNIX) - SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES COMPILE_FLAGS "-Wno-misleading-indentation") - ENDIF() - - # Tell CMake the source won't be available until build time. - SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES GENERATED 1) - ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES}) IDE_FOLDER(${PLUGIN_NAME} "Plugins") @@ -95,24 +86,63 @@ MACRO(DFHACK_PLUGIN) # Make sure the source is generated before the executable builds. ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto) - LIST(LENGTH PLUGIN_PROTOBUFS NUM_PROTO) + LIST(LENGTH PLUGIN_PROTOS NUM_PROTO) IF(NUM_PROTO) + STRING(REPLACE ".proto" ".pb.cc" PLUGIN_PROTO_SRCS "${PLUGIN_PROTOS}") + STRING(REPLACE ".proto" ".pb.h" PLUGIN_PROTO_HDRS "${PLUGIN_PROTOS}") + STRING(REPLACE "/proto/" "/proto/tmp/" PLUGIN_PROTO_TMP_FILES "${PLUGIN_PROTO_SRCS};${PLUGIN_PROTO_HDRS}") + SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTO_SRCS} ${PLUGIN_PROTO_HDRS} PROPERTIES GENERATED TRUE) + + # Force a re-gen if any *.pb.* files are missing + # (only runs when cmake is run, but better than nothing) + FOREACH(file IN LISTS PLUGIN_PROTO_SRCS PLUGIN_PROTO_HDRS) + IF(NOT EXISTS ${file}) + # MESSAGE("Resetting generate_proto_${PLUGIN_NAME} because '${file}' is missing") + FILE(REMOVE ${PLUGIN_PROTO_TMP_FILES}) + BREAK() + ENDIF() + ENDFOREACH() + + ADD_CUSTOM_COMMAND( + OUTPUT ${PLUGIN_PROTO_TMP_FILES} + COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ + --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ + ${PLUGIN_PROTOS} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl + ${PLUGIN_PROTO_TMP_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ + COMMENT "Generating plugin ${PLUGIN_NAME} protobufs" + DEPENDS protoc-bin ${PLUGIN_PROTOS} + ) + + IF(UNIX) + SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTO_SRCS} PROPERTIES COMPILE_FLAGS "-Wno-misleading-indentation") + ENDIF() + + ADD_CUSTOM_TARGET(generate_proto_${PLUGIN_NAME} DEPENDS ${PLUGIN_PROTO_TMP_FILES}) + ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto_${PLUGIN_NAME}) + + # Merge headers into sources + SET_SOURCE_FILES_PROPERTIES( ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE ) + LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_HDRS}) + LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_SRCS}) + TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite dfhack-version ${PLUGIN_LINK_LIBRARIES}) IF(UNIX) - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "-include Export.h") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} -include Export.h") ELSE() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} /FI\"Export.h\"") ENDIF() ELSE() TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) ENDIF() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}") IF(UNIX) - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_GCC}") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} ${PLUGIN_COMPILE_FLAGS_GCC}") ELSE() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_MSVC}") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} ${PLUGIN_COMPILE_FLAGS_MSVC}") ENDIF() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}") IF(APPLE) SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "") diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index a6a5005bd..8f8a2599d 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -151,7 +151,7 @@ namespace embark_assist { fields i = first_fields; while (true) { - for (int k = 0; k < state->ui[static_cast(i)]->list.size(); k++) { + for (size_t k = 0; k < state->ui[static_cast(i)]->list.size(); k++) { if (state->ui[static_cast(i)]->current_value == state->ui[static_cast(i)]->list[k].key) { fprintf(outfile, "[%s:%s]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->list[k].text.c_str()); break; @@ -203,7 +203,7 @@ namespace embark_assist { found = false; - for (int l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { + for (size_t l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { for (int m = k + 1; m < count; m++) { if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) { if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' && @@ -259,7 +259,7 @@ namespace embark_assist { found = false; - for (int l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { + for (size_t l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { for (int m = k + 1; m < count; m++) { if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) { if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' && @@ -1334,7 +1334,7 @@ namespace embark_assist { } // Implement scrolling lists if they don't fit on the screen. - if (state->ui[state->finder_list_focus]->list.size() > screen_size.y - 3) { + if (int32_t(state->ui[state->finder_list_focus]->list.size()) > screen_size.y - 3) { offset = (screen_size.y - 3) / 2; if (state->ui[state->finder_list_focus]->current_index < offset) { offset = 0; @@ -1343,7 +1343,7 @@ namespace embark_assist { offset = state->ui[state->finder_list_focus]->current_index - offset; } - if (state->ui[state->finder_list_focus]->list.size() - offset < screen_size.y - 3) { + if (int32_t(state->ui[state->finder_list_focus]->list.size() - offset) < screen_size.y - 3) { offset = static_cast(state->ui[state->finder_list_focus]->list.size()) - (screen_size.y - 3); } } diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index 4df74b18e..13bbb4fc1 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -179,7 +179,7 @@ namespace embark_assist { } } - for (auto i = 0; i < state->embark_info.size(); i++) { + for (size_t i = 0; i < state->embark_info.size(); i++) { embark_assist::screen::paintString(state->embark_info[i].pen, 1, i + 19, state->embark_info[i].text, false); } diff --git a/plugins/embark-assistant/screen.cpp b/plugins/embark-assistant/screen.cpp index 477e5c63c..34a0d815c 100644 --- a/plugins/embark-assistant/screen.cpp +++ b/plugins/embark-assistant/screen.cpp @@ -13,7 +13,7 @@ bool embark_assist::screen::paintString(const DFHack::Screen::Pen &pen, int x, i return false; // Won't paint outside of the screen or on the frame } - if (x + text.length() - 1 < screen_size.x - 2) { + if (x + int32_t(text.length()) - 1 < screen_size.x - 2) { DFHack::Screen::paintString(pen, x, y, text, map); } else if (x < screen_size.x - 2) { diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index becddeed3..2328e14ef 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -295,6 +295,8 @@ public: df::building* bld = get_building_from_job(j); switch (bld->getType()) { + case df::building_type::NONE: + return df::unit_labor::NONE; case df::building_type::Hive: return df::unit_labor::BEEKEEPING; case df::building_type::Workshop: @@ -399,6 +401,8 @@ public: switch (bld->getType()) { + case df::building_type::NONE: + return df::unit_labor::NONE; case df::building_type::Hive: return df::unit_labor::BEEKEEPING; case df::building_type::Workshop: diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 5c408c354..cf183f452 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -525,8 +525,8 @@ struct dwarf_info_t df::unit_labor using_labor; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), - state(OTHER), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) + dwarf_info_t(df::unit* dw) : dwarf(dw), state(OTHER), + clear_all(false), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) has_tool[e] = false; @@ -614,7 +614,7 @@ static void init_state() { string key = p->key(); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("labormanager/2.0/labors/")).c_str()); - if (labor >= 0 && labor <= labor_infos.size()) + if (labor >= 0 && size_t(labor) <= labor_infos.size()) { labor_infos[labor].config = *p; labor_infos[labor].active_dwarfs = 0; @@ -622,7 +622,7 @@ static void init_state() } // Add default labors for those not in save - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { if (labor_infos[i].config.isValid()) continue; @@ -966,12 +966,12 @@ private: int worker = -1; int bld = -1; - for (int r = 0; r < j->general_refs.size(); ++r) + for (auto ref : j->general_refs) { - if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; - if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) - bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; + if (ref->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)ref)->unit_id; + if (ref->getType() == df::general_ref_type::BUILDING_HOLDER) + bld = ((df::general_ref_building_holderst *)ref)->building_id; } if (bld != -1) @@ -985,7 +985,7 @@ private: b->getType() != df::building_type::TradeDepot) { int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) + for (size_t jn = 0; jn < b->jobs.size(); jn++) { if (b->jobs[jn]->flags.bits.suspend) continue; @@ -1062,7 +1062,7 @@ private: plant_count = 0; detail_count = 0; - for (int i = 0; i < world->map.map_blocks.size(); ++i) + for (size_t i = 0; i < world->map.map_blocks.size(); ++i) { df::map_block* bl = world->map.map_blocks[i]; @@ -1187,7 +1187,7 @@ private: dwarf_info_t* dwarf = add_dwarf(cre); df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); - for (int i = 0; i < hf->entity_links.size(); i++) + for (size_t i = 0; i < hf->entity_links.size(); i++) { df::histfig_entity_link* hfelink = hf->entity_links.at(i); if (hfelink->getType() == df::histfig_entity_link_type::POSITION) @@ -1213,7 +1213,7 @@ private: // identify dwarfs who are needed for meetings and mark them for exclusion - for (int i = 0; i < ui->activities.size(); ++i) + for (size_t i = 0; i < ui->activities.size(); ++i) { df::activity_info *act = ui->activities[i]; if (!act) continue; @@ -1259,7 +1259,7 @@ private: // check if dwarf has an axe, pick, or crossbow - for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + for (size_t j = 0; j < dwarf->dwarf->inventory.size(); j++) { df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) @@ -1335,7 +1335,7 @@ private: else { df::job_type job = dwarf->dwarf->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) state = dwarf_states[job]; else { @@ -2258,7 +2258,7 @@ command_result labormanager(color_ostream &out, std::vector & para return CR_FAILURE; } - for (int i = 0; i < labor_infos.size(); i++) + for (size_t i = 0; i < labor_infos.size(); i++) { reset_labor((df::unit_labor) i); } diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index ad61c94ce..1d022a95f 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -15,15 +15,16 @@ SET(PROJECT_HDRS ) #proto files to include. SET(PROJECT_PROTO - ../../proto/RemoteFortressReader - ../../proto/AdventureControl - ../../proto/ItemdefInstrument + ${CMAKE_CURRENT_SOURCE_DIR}/../proto/RemoteFortressReader.pb.cc + ${CMAKE_CURRENT_SOURCE_DIR}/../proto/AdventureControl.pb.cc + ${CMAKE_CURRENT_SOURCE_DIR}/../proto/ItemdefInstrument.pb.cc ) SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO} PROPERTIES GENERATED TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) -LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS};${PROJECT_PROTO}) #linux IF(UNIX) @@ -41,4 +42,4 @@ ELSE(UNIX) ) ENDIF(UNIX) # this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS} PROTOBUFS ${PROJECT_PROTO} ) +DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS} COMPILE_FLAGS_MSVC "/FI\"Export.h\"" COMPILE_FLAGS_GCC "-include Export.h -Wno-misleading-indentation" ) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 12598ea81..5a0dd5c96 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -104,7 +104,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D if (st == furnace_type::Custom) { - for (int i = 0; i < df::global::world->raws.buildings.furnaces.size(); i++) + for (size_t i = 0; i < df::global::world->raws.buildings.furnaces.size(); i++) { auto cust = df::global::world->raws.buildings.furnaces[i]; @@ -152,7 +152,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D if (st == workshop_type::Custom) { - for (int i = 0; i < df::global::world->raws.buildings.workshops.size(); i++) + for (size_t i = 0; i < df::global::world->raws.buildings.workshops.size(); i++) { auto cust = df::global::world->raws.buildings.workshops[i]; diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index c0170f2c4..3884d269f 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -61,7 +61,7 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) auto id = netImage->mutable_id(); id->set_mat_type(image->id); id->set_mat_index(image->subid); - for (int i = 0; i < image->elements.size(); i++) + for (size_t i = 0; i < image->elements.size(); i++) { auto element = image->elements[i]; auto netElement = netImage->add_elements(); @@ -121,7 +121,7 @@ void CopyImage(const df::art_image * image, ArtImage * netImage) break; } } - for (int i = 0; i < image->properties.size(); i++) + for (size_t i = 0; i < image->properties.size(); i++) { auto dfProperty = image->properties[i]; auto netProperty = netImage->add_properties(); @@ -236,7 +236,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) } else { - for (int i = 0; i < world->art_image_chunks.size(); i++) + for (size_t i = 0; i < world->art_image_chunks.size(); i++) { if (world->art_image_chunks[i]->id == statue->image.id) chunk = world->art_image_chunks[i]; @@ -433,7 +433,7 @@ void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); if (constructed_item) { - for (int i = 0; i < constructed_item->improvements.size(); i++) + for (size_t i = 0; i < constructed_item->improvements.size(); i++) { auto improvement = constructed_item->improvements[i]; @@ -528,7 +528,7 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: case df::enums::item_type::GEM: case df::enums::item_type::SMALLGEM: { - for (int i = 0; i < world->raws.descriptors.shapes.size(); i++) + for (size_t i = 0; i < world->raws.descriptors.shapes.size(); i++) { auto shape = world->raws.descriptors.shapes[i]; if (shape->gems_use.whole == 0) @@ -542,7 +542,7 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: } case df::enums::item_type::PLANT: { - for (int i = 0; i < world->raws.plants.all.size(); i++) + for (size_t i = 0; i < world->raws.plants.all.size(); i++) { auto plantRaw = world->raws.plants.all[i]; mat_def = out->add_material_list(); @@ -574,7 +574,10 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: mat_def->mutable_mat_pair()->set_mat_type((int)it); mat_def->mutable_mat_pair()->set_mat_index(1); mat_def->set_id("THREAD/WEB"); + break; } + default: + break; } int subtypes = Items::getSubtypeCount(it); if (subtypes >= 0) @@ -606,7 +609,7 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: send_instrument->set_size(instrument->size); send_instrument->set_value(instrument->value); send_instrument->set_material_size(instrument->material_size); - for (int j = 0; j < instrument->pieces.size(); j++) + for (size_t j = 0; j < instrument->pieces.size(); j++) { auto piece = send_instrument->add_pieces(); piece->set_type(instrument->pieces[j]->type); @@ -616,39 +619,39 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack:: } send_instrument->set_pitch_range_min(instrument->pitch_range_min); send_instrument->set_pitch_range_max(instrument->pitch_range_max); - for (int j = 0; j < instrument->sound_production.size(); j++) + for (size_t j = 0; j < instrument->sound_production.size(); j++) { send_instrument->add_sound_production((SoundProductionType)instrument->sound_production[j]); } - for (int j = 0; j < instrument->sound_production_parm1.size(); j++) + for (size_t j = 0; j < instrument->sound_production_parm1.size(); j++) { send_instrument->add_sound_production_parm1(*(instrument->sound_production_parm1[j])); } - for (int j = 0; j < instrument->sound_production_parm2.size(); j++) + for (size_t j = 0; j < instrument->sound_production_parm2.size(); j++) { send_instrument->add_sound_production_parm2(*(instrument->sound_production_parm2[j])); } - for (int j = 0; j < instrument->pitch_choice.size(); j++) + for (size_t j = 0; j < instrument->pitch_choice.size(); j++) { send_instrument->add_pitch_choice((PitchChoiceType)instrument->pitch_choice[j]); } - for (int j = 0; j < instrument->pitch_choice_parm1.size(); j++) + for (size_t j = 0; j < instrument->pitch_choice_parm1.size(); j++) { send_instrument->add_pitch_choice_parm1(*(instrument->pitch_choice_parm1[j])); } - for (int j = 0; j < instrument->pitch_choice_parm2.size(); j++) + for (size_t j = 0; j < instrument->pitch_choice_parm2.size(); j++) { send_instrument->add_pitch_choice_parm2(*(instrument->pitch_choice_parm2[j])); } - for (int j = 0; j < instrument->tuning.size(); j++) + for (size_t j = 0; j < instrument->tuning.size(); j++) { send_instrument->add_tuning((TuningType)instrument->tuning[j]); } - for (int j = 0; j < instrument->tuning_parm.size(); j++) + for (size_t j = 0; j < instrument->tuning_parm.size(); j++) { send_instrument->add_tuning_parm(*(instrument->tuning_parm[j])); } - for (int j = 0; j < instrument->registers.size(); j++) + for (size_t j = 0; j < instrument->registers.size(); j++) { auto reg = send_instrument->add_registers(); reg->set_pitch_range_min(instrument->registers[j]->pitch_range_min); diff --git a/plugins/remotefortressreader/proto/readme.txt b/plugins/remotefortressreader/proto/readme.txt deleted file mode 100644 index 8cd56aefd..000000000 --- a/plugins/remotefortressreader/proto/readme.txt +++ /dev/null @@ -1 +0,0 @@ -placeholder to fix protobufs in plugins/remotefortressreader/CMakeLists.txt diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index f1645a84f..289652191 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -216,13 +216,13 @@ command_result dump_bp_mods(color_ostream &out, vector & parameters) output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; - for (int creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) + for (size_t creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) { auto creatureRaw = world->raws.creatures.all[creatureIndex]; - for (int casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) + for (size_t casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) { df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; - for (int partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) + for (size_t partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) { output << creatureIndex << ";"; output << creatureRaw->creature_id << ";"; @@ -665,7 +665,7 @@ RemoteFortressReader::TiletypeVariant TranslateVariant(df::tiletype_variant vari static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) { clock_t start = clock(); - for (int i = 0; i < world->map.map_blocks.size(); i++) + for (size_t i = 0; i < world->map.map_blocks.size(); i++) { df::map_block * block = world->map.map_blocks[i]; fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); @@ -765,13 +765,13 @@ bool IsspatterChanged(DFCoord pos) uint16_t hash = 0; - for (int i = 0; i < materials.size(); i++) + for (size_t i = 0; i < materials.size(); i++) { auto mat = materials[i]; hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); } #if DF_VERSION_INT > 34011 - for (int i = 0; i < items.size(); i++) + for (size_t i = 0; i < items.size(); i++) { auto item = items[i]; hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); @@ -806,7 +806,7 @@ bool isItemChanged(int i) bool areItemsChanged(vector * items) { bool result = false; - for (int i = 0; i < items->size(); i++) + for (size_t i = 0; i < items->size(); i++) { if (isItemChanged(items->at(i))) result = true; @@ -862,7 +862,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage df::world_raws *raws = &world->raws; df::world_history *history = &world->history; MaterialInfo mat; - for (int i = 0; i < raws->inorganics.size(); i++) + for (size_t i = 0; i < raws->inorganics.size(); i++) { mat.decode(0, i); MaterialDefinition *mat_def = out->add_material_list(); @@ -870,7 +870,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->descriptors.colors.size()) + if (size_t(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); } @@ -888,16 +888,16 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(j); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->descriptors.colors.size()) + if (size_t(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); } } } - for (int i = 0; i < raws->creatures.all.size(); i++) + for (size_t i = 0; i < raws->creatures.all.size(); i++) { df::creature_raw * creature = raws->creatures.all[i]; - for (int j = 0; j < creature->material.size(); j++) + for (size_t j = 0; j < creature->material.size(); j++) { mat.decode(j + MaterialInfo::CREATURE_BASE, i); MaterialDefinition *mat_def = out->add_material_list(); @@ -905,13 +905,13 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->descriptors.colors.size()) + if (size_t(creature->material[j]->state_color[GetState(creature->material[j])]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); } } } - //for (int i = 0; i < history->figures.size(); i++) + //for (size_t i = 0; i < history->figures.size(); i++) //{ // df::historical_figure * figure = history->figures[i]; // if (figure->race < 0) @@ -936,10 +936,10 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage // } // } //} - for (int i = 0; i < raws->plants.all.size(); i++) + for (size_t i = 0; i < raws->plants.all.size(); i++) { df::plant_raw * plant = raws->plants.all[i]; - for (int j = 0; j < plant->material.size(); j++) + for (size_t j = 0; j < plant->material.size(); j++) { mat.decode(j + 419, i); MaterialDefinition *mat_def = out->add_material_list(); @@ -947,7 +947,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->descriptors.colors.size()) + if (size_t(plant->material[j]->state_color[GetState(plant->material[j])]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); } @@ -970,7 +970,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i return CR_OK;//'. - for (int i = 0; i < raws->plants.all.size(); i++) + for (size_t i = 0; i < raws->plants.all.size(); i++) { df::plant_raw * pp = raws->plants.all[i]; if (!pp) @@ -981,7 +981,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i basePlant->mutable_mat_pair()->set_mat_type(-1); basePlant->mutable_mat_pair()->set_mat_index(i); #if DF_VERSION_INT > 40001 - for (int g = 0; g < pp->growths.size(); g++) + for (size_t g = 0; g < pp->growths.size(); g++) { df::plant_growth* growth = pp->growths[g]; if (!growth) @@ -1023,7 +1023,7 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc #if DF_VERSION_INT > 34011 df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; - for (int i = 0; i < column->plants.size(); i++) + for (size_t i = 0; i < column->plants.size(); i++) { df::plant* plant = column->plants[i]; if (plant->tree_info == NULL) @@ -1179,7 +1179,7 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N } } #if DF_VERSION_INT > 34011 - for (int i = 0; i < world->jobs.postings.size(); i++) + for (size_t i = 0; i < world->jobs.postings.size(); i++) { auto job = world->jobs.postings[i]->job; if (job == nullptr) @@ -1260,7 +1260,7 @@ void CopyProjectiles(RemoteFortressReader::MapBlock * NetBlock) NetItem->set_velocity_z(diff.z / max_dist); } } - for (int i = 0; i < world->vehicles.active.size(); i++) + for (size_t i = 0; i < world->vehicles.active.size(); i++) { bool isProj = false; auto vehicle = world->vehicles.active[i]; @@ -1296,7 +1296,7 @@ void CopyProjectiles(RemoteFortressReader::MapBlock * NetBlock) void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) { - for (int i = 0; i < df::global::world->buildings.all.size(); i++) + for (size_t i = 0; i < df::global::world->buildings.all.size(); i++) { df::building * bld = df::global::world->buildings.all[i]; if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) @@ -1327,7 +1327,7 @@ void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * Ne df::building_actual* actualBuilding = virtual_cast(bld); if (actualBuilding) { - for (int i = 0; i < actualBuilding->contained_items.size(); i++) + for (size_t i = 0; i < actualBuilding->contained_items.size(); i++) { auto buildingItem = out_bld->add_items(); buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); @@ -1356,7 +1356,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB for (int xx = 0; xx < 16; xx++) { auto send_pile = NetBlock->add_spatterpile(); - for (int i = 0; i < materials.size(); i++) + for (size_t i = 0; i < materials.size(); i++) { auto mat = materials[i]; if (mat->amount[xx][yy] == 0) @@ -1367,7 +1367,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB send_spat->set_amount(mat->amount[xx][yy]); } #if DF_VERSION_INT > 34011 - for (int i = 0; i < items.size(); i++) + for (size_t i = 0; i < items.size(); i++) { auto item = items[i]; if (item->amount[xx][yy] == 0) @@ -1380,7 +1380,7 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB send_item->set_mat_index(item->item_subtype); } int grassPercent = 0; - for (int i = 0; i < grasses.size(); i++) + for (size_t i = 0; i < grasses.size(); i++) { auto grass = grasses[i]; if (grass->amount[xx][yy] > grassPercent) @@ -1396,7 +1396,7 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc NetBlock->set_map_x(DfBlock->map_pos.x); NetBlock->set_map_y(DfBlock->map_pos.y); NetBlock->set_map_z(DfBlock->map_pos.z); - for (int i = 0; i < DfBlock->items.size(); i++) + for (size_t i = 0; i < DfBlock->items.size(); i++) { int id = DfBlock->items[i]; @@ -1441,7 +1441,7 @@ void CopyFlows(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc NetBlock->set_map_x(DfBlock->map_pos.x); NetBlock->set_map_y(DfBlock->map_pos.y); NetBlock->set_map_z(DfBlock->map_pos.z); - for (int i = 0; i < DfBlock->flows.size(); i++) + for (size_t i = 0; i < DfBlock->flows.size(); i++) { CopyFlow(DfBlock->flows[i], NetBlock->add_flows(), i); } @@ -1568,7 +1568,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in } } - for (int i = 0; i < world->engravings.size(); i++) + for (size_t i = 0; i < world->engravings.size(); i++) { auto engraving = world->engravings[i]; if (engraving->pos.x < (min_x * 16) || engraving->pos.x >(max_x * 16)) @@ -1588,7 +1588,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in } else { - for (int i = 0; i < world->art_image_chunks.size(); i++) + for (size_t i = 0; i < world->art_image_chunks.size(); i++) { if (world->art_image_chunks[i]->id == engraving->art_id) chunk = world->art_image_chunks[i]; @@ -1615,7 +1615,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in netEngraving->set_southwest(engraving->flags.bits.southwest); netEngraving->set_southeast(engraving->flags.bits.southeast); } - for (int i = 0; i < world->ocean_waves.size(); i++) + for (size_t i = 0; i < world->ocean_waves.size(); i++) { auto wave = world->ocean_waves[i]; auto netWave = out->add_ocean_waves(); @@ -1665,7 +1665,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) continue; df::map_block_column * column = world->map.column_index[xx][yy]; - for (int i = 0; i < column->plants.size(); i++) + for (size_t i = 0; i < column->plants.size(); i++) { df::plant * plant = column->plants[i]; if (!plant->tree_info) @@ -1705,7 +1705,7 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) { auto world = df::global::world; - for (int i = 0; i < world->units.all.size(); i++) + for (size_t i = 0; i < world->units.all.size(); i++) { df::unit * unit = world->units.all[i]; auto send_unit = out->add_creature_list(); @@ -1742,11 +1742,11 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques } auto appearance = send_unit->mutable_appearance(); - for (int j = 0; j < unit->appearance.body_modifiers.size(); j++) + for (size_t j = 0; j < unit->appearance.body_modifiers.size(); j++) appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); - for (int j = 0; j < unit->appearance.bp_modifiers.size(); j++) + for (size_t j = 0; j < unit->appearance.bp_modifiers.size(); j++) appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); - for (int j = 0; j < unit->appearance.colors.size(); j++) + for (size_t j = 0; j < unit->appearance.colors.size(); j++) appearance->add_colors(unit->appearance.colors[j]); appearance->set_size_modifier(unit->appearance.size_modifier); @@ -1756,7 +1756,7 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques if (Units::getNoblePositions(&pvec, unit)) { - for (int j = 0; j < pvec.size(); j++) + for (size_t j = 0; j < pvec.size(); j++) { auto noble_positon = pvec[j]; send_unit->add_noble_positions(noble_positon.position->code); @@ -1768,7 +1768,7 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques auto creatureRaw = world->raws.creatures.all[unit->race]; auto casteRaw = creatureRaw->caste[unit->caste]; - for (int j = 0; j < unit->appearance.tissue_style_type.size(); j++) + for (size_t j = 0; j < unit->appearance.tissue_style_type.size(); j++) { auto type = unit->appearance.tissue_style_type[j]; if (type < 0) @@ -1801,7 +1801,7 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques } } - for (int j = 0; j < unit->inventory.size(); j++) + for (size_t j = 0; j < unit->inventory.size(); j++) { auto inventory_item = unit->inventory[j]; auto sent_item = send_unit->add_inventory(); @@ -1911,7 +1911,7 @@ DFCoord GetMapCenter() } #if DF_VERSION_INT > 34011 else - for (int i = 0; i < df::global::world->armies.all.size(); i++) + for (size_t i = 0; i < df::global::world->armies.all.size(); i++) { df::army * thisArmy = df::global::world->armies.all[i]; if (thisArmy->flags.is_set(df::enums::army_flags::player)) @@ -2055,7 +2055,7 @@ static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) out->set_water_elevation(99); int topLayer = 0; - for (int i = 0; i < geoBiome->layers.size(); i++) + for (size_t i = 0; i < geoBiome->layers.size(); i++) { auto layer = geoBiome->layers[i]; if (layer->top_height == 0) @@ -2075,7 +2075,7 @@ static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) surfaceMat->set_mat_index(topLayer); surfaceMat->set_mat_type(0); - for (int i = 0; i < region->population.size(); i++) + for (size_t i = 0; i < region->population.size(); i++) { auto pop = region->population[i]; if (pop->type == world_population_type::Grass) @@ -2285,7 +2285,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w df::world_region_details * east = NULL; df::world_region_details * southEast = NULL; - for (int i = 0; i < worldData->region_details.size(); i++) + for (size_t i = 0; i < worldData->region_details.size(); i++) { auto region = worldData->region_details[i]; if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) @@ -2372,7 +2372,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w df::world_region_details * east = NULL; df::world_region_details * southEast = NULL; - for (int i = 0; i < worldData->region_details.size(); i++) + for (size_t i = 0; i < worldData->region_details.size(); i++) { auto region = worldData->region_details[i]; if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) @@ -2481,7 +2481,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w auto regionMap = worldData->region_map[pos_x][pos_y]; - for (int i = 0; i < worldData->sites.size(); i++) + for (size_t i = 0; i < worldData->sites.size(); i++) { df::world_site* site = worldData->sites[i]; if (!site) @@ -2509,7 +2509,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) continue; - for (int j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) + for (size_t j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) { auto in_building = realization->building_map[site_x][site_y].buildings[j]; auto out_building = outputTiles[region_x][region_y]->add_buildings(); @@ -2564,7 +2564,7 @@ static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *i return CR_FAILURE; } df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) + for (size_t i = 0; i < data->region_details.size(); i++) { df::world_region_details * region = data->region_details[i]; if (!region) @@ -2582,7 +2582,7 @@ static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage return CR_FAILURE; } df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) + for (size_t i = 0; i < data->region_details.size(); i++) { df::world_region_details * region = data->region_details[i]; if (!region) @@ -2640,7 +2640,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_creature->set_adultsize(orig_creature->adultsize); - for (int j = 0; j < orig_creature->caste.size(); j++) + for (size_t j = 0; j < orig_creature->caste.size(); j++) { auto orig_caste = orig_creature->caste[j]; if (!orig_caste) @@ -2662,7 +2662,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_caste->add_child_name(orig_caste->child_name[1]); send_caste->set_gender(orig_caste->gender); - for (int partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) + for (size_t partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) { auto orig_part = orig_caste->body_info.body_parts[partIndex]; if (!orig_part) @@ -2678,7 +2678,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); } - for (int layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) + for (size_t layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) { auto orig_layer = orig_part->layers[layerIndex]; if (!orig_layer) @@ -2688,7 +2688,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_layer->set_layer_name(orig_layer->layer_name); send_layer->set_tissue_id(orig_layer->tissue_id); send_layer->set_layer_depth(orig_layer->layer_depth); - for (int layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) + for (size_t layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) { send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); } @@ -2699,7 +2699,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_caste->set_total_relsize(orig_caste->body_info.total_relsize); - for (int k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) + for (size_t k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) { auto send_mod = send_caste->add_modifiers(); auto orig_mod = orig_caste->bp_appearance.modifiers[k]; @@ -2719,13 +2719,13 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe } } - for (int k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) + for (size_t k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) { send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); } - for (int k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) + for (size_t k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) { auto send_mod = send_caste->add_body_appearance_modifiers(); auto orig_mod = orig_caste->body_appearance_modifiers[k]; @@ -2745,17 +2745,17 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_mod->set_mod_max(orig_mod->ranges[6]); } } - for (int k = 0; k < orig_caste->color_modifiers.size(); k++) + for (size_t k = 0; k < orig_caste->color_modifiers.size(); k++) { auto send_mod = send_caste->add_color_modifiers(); auto orig_mod = orig_caste->color_modifiers[k]; - for (int l = 0; l < orig_mod->pattern_index.size(); l++) + for (size_t l = 0; l < orig_mod->pattern_index.size(); l++) { auto orig_pattern = world->raws.descriptors.patterns[orig_mod->pattern_index[l]]; auto send_pattern = send_mod->add_patterns(); - for (int m = 0; m < orig_pattern->colors.size(); m++) + for (size_t m = 0; m < orig_pattern->colors.size(); m++) { auto send_color = send_pattern->add_colors(); auto orig_color = world->raws.descriptors.colors[orig_pattern->colors[m]]; @@ -2768,7 +2768,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_pattern->set_pattern((PatternType)orig_pattern->pattern); } - for (int l = 0; l < orig_mod->body_part_id.size(); l++) + for (size_t l = 0; l < orig_mod->body_part_id.size(); l++) { send_mod->add_body_part_id(orig_mod->body_part_id[l]); send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); @@ -2782,7 +2782,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe send_caste->set_adult_size(orig_caste->misc.adult_size); } - for (int j = 0; j < orig_creature->tissue.size(); j++) + for (size_t j = 0; j < orig_creature->tissue.size(); j++) { auto orig_tissue = orig_creature->tissue[j]; auto send_tissue = send_creature->add_tissues(); @@ -2811,14 +2811,7 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque df::world * world = df::global::world; - int list_start = 0; - - if (in != nullptr) - { - list_start = in->list_start(); - } - - for (int i = 0; i < world->raws.plants.all.size(); i++) + for (size_t i = 0; i < world->raws.plants.all.size(); i++) { df::plant_raw* plant_local = world->raws.plants.all[i]; PlantRaw* plant_remote = out->add_plant_raws(); @@ -2831,14 +2824,14 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque else plant_remote->set_tile(plant_local->tiles.tree_tile); #if DF_VERSION_INT > 34011 - for (int j = 0; j < plant_local->growths.size(); j++) + for (size_t j = 0; j < plant_local->growths.size(); j++) { df::plant_growth* growth_local = plant_local->growths[j]; TreeGrowth * growth_remote = plant_remote->add_growths(); growth_remote->set_index(j); growth_remote->set_id(growth_local->id); growth_remote->set_name(growth_local->name); - for (int k = 0; k < growth_local->prints.size(); k++) + for (size_t k = 0; k < growth_local->prints.size(); k++) { df::plant_growth_print* print_local = growth_local->prints[k]; GrowthPrint* print_remote = growth_remote->add_prints(); @@ -3018,7 +3011,7 @@ static command_result GetReports(color_ostream & stream, const EmptyMessage * in break; } } - for (int i = lastSentIndex + 1; i < world->status.reports.size(); i++) + for (size_t i = lastSentIndex + 1; i < world->status.reports.size(); i++) { auto local_rep = world->status.reports[i]; if (!local_rep) @@ -3046,7 +3039,7 @@ static command_result GetLanguage(color_ostream & stream, const EmptyMessage * i if (!world) return CR_FAILURE; - for (int i = 0; i < world->raws.descriptors.shapes.size(); i++) + for (size_t i = 0; i < world->raws.descriptors.shapes.size(); i++) { auto shape = world->raws.descriptors.shapes[i]; auto netShape = out->add_shapes(); diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index fa2f18d5c..90c38a06a 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -104,7 +104,7 @@ rect2d getMapViewport() } return mkrect_wh(1,1,view_rb,view_height+1); } -lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),doDebug(false),threading(this) +lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),threading(this),doDebug(false) { reinit(); defaultSettings(); @@ -391,7 +391,7 @@ rgbf getStandartColor(int colorId) int getPlantNumber(const std::string& id) { std::vector& vec=df::plant_raw::get_vector(); - for(int i=0;iid==id) return i; @@ -605,7 +605,7 @@ rgbf lightingEngineViewscreen::getSkyColor(float v) float pos=v*(dayColors.size()-1); int pre=floor(pos); pos-=pre; - if(pre==dayColors.size()-1) + if(pre==int(dayColors.size())-1) return dayColors[pre]; return dayColors[pre]*(1-pos)+dayColors[pre+1]*pos; } @@ -719,7 +719,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() if(!block) continue; //flows - for(int i=0;iflows.size();i++) + for(size_t i=0;iflows.size();i++) { df::flow_info* f=block->flows[i]; if(f && f->density>0 && (f->type==df::flow_type::Dragonfire || f->type==df::flow_type::Fire)) @@ -749,7 +749,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() } //blood and other goo - for(int i=0;iblock_events.size();i++) + for(size_t i=0;iblock_events.size();i++) { df::block_square_event* ev=block->block_events[i]; df::block_square_event_type ev_type=ev->getType(); @@ -789,7 +789,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() //citizen only emit light, if defined //or other creatures if(matCitizen.isEmiting || creatureDefs.size()>0) - for (int i=0;iunits.active.size();++i) + for (size_t i=0;iunits.active.size();++i) { df::unit *u = df::global::world->units.active[i]; coord2d pos=worldToViewportCoord(coord2d(u->pos.x,u->pos.y),vp,window2d); @@ -1247,7 +1247,7 @@ void lightingEngineViewscreen::loadSettings() /* * Threading stuff */ -lightThread::lightThread( lightThreadDispatch& dispatch ):dispatch(dispatch),isDone(false),myThread(0) +lightThread::lightThread( lightThreadDispatch& dispatch ):dispatch(dispatch),myThread(0),isDone(false) { } @@ -1309,7 +1309,7 @@ void lightThread::work() void lightThread::combine() { - for(int i=0;ilights),occlusion(parent->ocupancy),num_diffusion(parent->num_diffuse), - lightMap(parent->lightMap),writeCount(0),occlusionReady(false) +lightThreadDispatch::lightThreadDispatch( lightingEngineViewscreen* p ):parent(p),lights(parent->lights), + occlusionReady(false),occlusion(parent->ocupancy),num_diffusion(parent->num_diffuse), + lightMap(parent->lightMap),writeCount(0) { } void lightThreadDispatch::shutdown() { - for(int i=0;iisDone=true; } occlusionDone.notify_all();//if stuck signal that you are done with stuff. - for(int i=0;imyThread->join(); } @@ -1473,7 +1474,7 @@ void lightThreadDispatch::start(int count) void lightThreadDispatch::waitForWrites() { tthread::lock_guard guard(writeLock); - while(threadPool.size()>writeCount)//missed it somehow already. + while(threadPool.size()>size_t(writeCount))//missed it somehow already. { writesDone.wait(writeLock); //if not, wait a bit } diff --git a/plugins/rendermax/renderer_light.hpp b/plugins/rendermax/renderer_light.hpp index ec1561c76..b0fb8c5d5 100644 --- a/plugins/rendermax/renderer_light.hpp +++ b/plugins/rendermax/renderer_light.hpp @@ -181,11 +181,11 @@ struct matLightDef bool flicker; rgbf emitColor; int radius; - matLightDef():isTransparent(false),isEmiting(false),transparency(0,0,0),emitColor(0,0,0),radius(0){} - matLightDef(rgbf transparency,rgbf emit,int rad):isTransparent(true),isEmiting(true), - transparency(transparency),emitColor(emit),radius(rad){} - matLightDef(rgbf emit,int rad):isTransparent(false),isEmiting(true),emitColor(emit),radius(rad),transparency(0,0,0){} - matLightDef(rgbf transparency):isTransparent(true),isEmiting(false),transparency(transparency){} + matLightDef():isTransparent(false),transparency(0,0,0),isEmiting(false),emitColor(0,0,0),radius(0){} + matLightDef(rgbf transparency,rgbf emit,int rad):isTransparent(true),transparency(transparency), + isEmiting(true),emitColor(emit),radius(rad){} + matLightDef(rgbf emit,int rad):isTransparent(false),transparency(0,0,0),isEmiting(true),emitColor(emit),radius(rad){} + matLightDef(rgbf transparency):isTransparent(true),transparency(transparency),isEmiting(false){} lightSource makeSource(float size=1) const { if(size>0.999 && size<1.001) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 15f61ac35..7133dbed8 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -236,6 +236,7 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch std::string cmd = "DFHack.onstatechange "; switch (e) { #define SCASE(s) case SC_ ## s : cmd += ":" # s ; break + case SC_UNKNOWN : return CR_OK; SCASE(WORLD_LOADED); SCASE(WORLD_UNLOADED); SCASE(MAP_LOADED); diff --git a/plugins/stockpiles/CMakeLists.txt b/plugins/stockpiles/CMakeLists.txt index 7d5cce921..3f4dcb28e 100644 --- a/plugins/stockpiles/CMakeLists.txt +++ b/plugins/stockpiles/CMakeLists.txt @@ -14,7 +14,7 @@ SET(PROJECT_SRCS ) SET(PROJECT_PROTOS - stockpiles.proto + stockpiles ) SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index e9a8143a5..16c240f67 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -214,7 +214,7 @@ void StockpileSerializer::unserialize_list_organic_mat ( FuncReadImport get_valu std::string token = get_value ( i ); int16_t idx = OrganicMatLookup::food_idx_by_token ( debug(), cat, token ); debug() << " organic_material " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) + if ( size_t(idx) >= pile_list->size() ) { debug() << "error organic mat index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -247,7 +247,7 @@ void StockpileSerializer::unserialize_list_item_type ( FuncItemAllowed is_allowe { pile_list->clear(); pile_list->resize ( 112, '\0' ); // TODO remove hardcoded list size value - for ( int i = 0; i < pile_list->size(); ++i ) + for ( size_t i = 0; i < pile_list->size(); ++i ) { pile_list->at ( i ) = is_allowed ( ( item_type::item_type ) i ) ? 0 : 1; } @@ -261,7 +261,7 @@ void StockpileSerializer::unserialize_list_item_type ( FuncItemAllowed is_allowe const item_type type = ( item_type ) idx; if ( !is_allowed ( type ) ) continue; debug() << " item_type " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) + if ( size_t(idx) >= pile_list->size() ) { debug() << "error item_type index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -296,7 +296,7 @@ void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_all std::set idx_set; pile_list->clear(); pile_list->resize ( world->raws.inorganics.size(), 0 ); - for ( int i = 0; i < pile_list->size(); ++i ) + for ( size_t i = 0; i < pile_list->size(); ++i ) { MaterialInfo mi ( 0, i ); pile_list->at ( i ) = is_allowed ( mi ) ? 0 : 1; @@ -308,7 +308,7 @@ void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_all mi.find ( token ); if ( !is_allowed ( mi ) ) continue; debug() << " material " << mi.index << " is " << token << endl; - if ( mi.index >= pile_list->size() ) + if ( size_t(mi.index) >= pile_list->size() ) { debug() << "error material index too large! idx[" << mi.index << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -435,7 +435,7 @@ void StockpileSerializer::unserialize_list_itemdef ( FuncReadImport read_value, ItemTypeInfo ii; if ( !ii.find ( token ) ) continue; debug() << " itemdef " << ii.subtype << " is " << token << endl; - if ( ii.subtype >= pile_list->size() ) + if ( size_t(ii.subtype) >= pile_list->size() ) { debug() << "error itemdef index too large! idx[" << ii.subtype << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -528,7 +528,7 @@ void StockpileSerializer::read_animals() std::string id = mBuffer.animals().enabled ( i ); int idx = find_creature ( id ); debug() << id << " " << idx << endl; - if ( idx < 0 || idx >= mPile->settings.animals.enabled.size() ) + if ( idx < 0 || size_t(idx) >= mPile->settings.animals.enabled.size() ) { debug() << "WARNING: animal index invalid: " << idx << endl; continue; @@ -895,7 +895,7 @@ void StockpileSerializer::read_furniture() const std::string type = furniture.type ( i ); df::enum_traits::base_type idx = linear_index ( debug(), type_traits, type ); debug() << " type " << idx << " is " << type << endl; - if ( idx < 0 || idx >= mPile->settings.furniture.type.size() ) + if ( idx < 0 || size_t(idx) >= mPile->settings.furniture.type.size() ) { debug() << "WARNING: furniture type index invalid " << type << ", idx=" << idx << endl; continue; @@ -1048,7 +1048,7 @@ void StockpileSerializer::refuse_read_helper ( std::function= pile_list->size() ) + if ( idx < 0 || !refuse_creature_is_allowed ( creature ) || size_t(idx) >= pile_list->size() ) { debug() << "WARNING invalid refuse creature " << creature_id << ", idx=" << idx << endl; continue; @@ -1519,7 +1519,7 @@ void StockpileSerializer::read_gems() const std::string token = gems.rough_other_mats ( i ); MaterialInfo mi; mi.find ( token ); - if ( !mi.isValid() || mi.type >= builtin_size ) + if ( !mi.isValid() || size_t(mi.type) >= builtin_size ) { debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; continue; @@ -1536,7 +1536,7 @@ void StockpileSerializer::read_gems() const std::string token = gems.cut_other_mats ( i ); MaterialInfo mi; mi.find ( token ); - if ( !mi.isValid() || mi.type >= builtin_size ) + if ( !mi.isValid() || size_t(mi.type) >= builtin_size ) { debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; continue; diff --git a/plugins/stockpiles/StockpileUtils.h b/plugins/stockpiles/StockpileUtils.h index 4cb5669df..65672128e 100644 --- a/plugins/stockpiles/StockpileUtils.h +++ b/plugins/stockpiles/StockpileUtils.h @@ -21,7 +21,7 @@ /** * Retrieve creature raw from index */ -static df::creature_raw* find_creature ( int32_t idx ) +static inline df::creature_raw* find_creature ( int32_t idx ) { return df::global::world->raws.creatures.all[idx]; } @@ -30,7 +30,7 @@ static df::creature_raw* find_creature ( int32_t idx ) * Retrieve creature index from id string * @return -1 if not found */ -static int16_t find_creature ( const std::string &creature_id ) +static inline int16_t find_creature ( const std::string &creature_id ) { return linear_index ( df::global::world->raws.creatures.all, &df::creature_raw::creature_id, creature_id ); } @@ -38,7 +38,7 @@ static int16_t find_creature ( const std::string &creature_id ) /** * Retrieve plant raw from index */ -static df::plant_raw* find_plant ( size_t idx ) +static inline df::plant_raw* find_plant ( size_t idx ) { return df::global::world->raws.plants.all[idx]; } @@ -47,7 +47,7 @@ static df::plant_raw* find_plant ( size_t idx ) * Retrieve plant index from id string * @return -1 if not found */ -static size_t find_plant ( const std::string &plant_id ) +static inline size_t find_plant ( const std::string &plant_id ) { return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id ); } @@ -60,7 +60,7 @@ struct less_than_no_case: public std::binary_function< char,char,bool > } }; -static bool CompareNoCase(const std::string &a, const std::string &b) +static inline bool CompareNoCase(const std::string &a, const std::string &b) { return std::lexicographical_compare( a.begin(),a.end(), b.begin(),b.end(), less_than_no_case() ); } @@ -70,7 +70,7 @@ static bool CompareNoCase(const std::string &a, const std::string &b) * Checks if the parameter has the dfstock extension. * Doesn't check if the file exists or not. */ -static bool is_dfstockfile ( const std::string& filename ) +static inline bool is_dfstockfile ( const std::string& filename ) { return filename.rfind ( ".dfstock" ) != std::string::npos; } diff --git a/plugins/stockpiles/proto/tmp/.gitignore b/plugins/stockpiles/proto/tmp/.gitignore new file mode 100644 index 000000000..75feca5b1 --- /dev/null +++ b/plugins/stockpiles/proto/tmp/.gitignore @@ -0,0 +1 @@ +*.pb.* diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index ab9d9c8f7..d24a20a7a 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -504,12 +504,6 @@ static std::vector clean_dfstock_list ( const std::string &path ) return files; } -static bool isEnabled( lua_State *L ) -{ - Lua::Push(L, is_enabled); - return 1; -} - static int stockpiles_list_settings ( lua_State *L ) { auto path = luaL_checkstring ( L, 1 ); diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 6b5b1f687..37e606b1f 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -133,8 +133,8 @@ using namespace DFHack::Gui; class tweak_onupdate_hookst { public: typedef void(*T_callback)(void); - tweak_onupdate_hookst(std::string name_, T_callback cb) - :name(name_), callback(cb), enabled(false) {} +tweak_onupdate_hookst(std::string name_, T_callback cb) + :enabled(false), name(name_), callback(cb) {} bool enabled; std::string name; T_callback callback; diff --git a/plugins/tweak/tweaks/block-labors.h b/plugins/tweak/tweaks/block-labors.h index 47d30e530..d90c97506 100644 --- a/plugins/tweak/tweaks/block-labors.h +++ b/plugins/tweak/tweaks/block-labors.h @@ -56,7 +56,7 @@ struct block_labors_hook : df::viewscreen_dwarfmodest { df::unit *unit = Gui::getAnyUnit(this); for (int y = 5, i = (*ui_look_cursor/13)*13; - y <= 17 && i < unit_labors_sidemenu.size(); + y <= 17 && size_t(i) < unit_labors_sidemenu.size(); ++y, ++i) { df::unit_labor labor = unit_labors_sidemenu[i]; diff --git a/plugins/tweak/tweaks/cage-butcher.h b/plugins/tweak/tweaks/cage-butcher.h index 917d41e57..61a989d50 100644 --- a/plugins/tweak/tweaks/cage-butcher.h +++ b/plugins/tweak/tweaks/cage-butcher.h @@ -50,7 +50,7 @@ struct cage_butcher_hook : df::viewscreen_dwarfmodest { auto dims = Gui::getDwarfmodeViewDims(); for (int y = 4, i = (*ui_building_item_cursor/11)*11; - y <= 14 && i < units.size(); + y <= 14 && size_t(i) < units.size(); ++y, ++i) { df::unit *unit = vector_get(units, i); diff --git a/plugins/tweak/tweaks/craft-age-wear.h b/plugins/tweak/tweaks/craft-age-wear.h index edc3e9a18..ca822ced6 100644 --- a/plugins/tweak/tweaks/craft-age-wear.h +++ b/plugins/tweak/tweaks/craft-age-wear.h @@ -3,7 +3,7 @@ struct craft_age_wear_hook : df::item_crafted { DEFINE_VMETHOD_INTERPOSE(bool, ageItem, (int amount)) { - int orig_age = age; + uint32_t orig_age = age; age += amount; if (age > 200000000) age = 200000000; @@ -24,7 +24,7 @@ struct craft_age_wear_hook : df::item_crafted { wear = 1; else return false; - wear = ((orig_age % wear) + (age - orig_age)) / wear; + wear = ((orig_age % wear) + int32_t(age - orig_age)) / wear; if (wear > 0) return incWearTimer(wear); else diff --git a/plugins/tweak/tweaks/pausing-fps-counter.h b/plugins/tweak/tweaks/pausing-fps-counter.h index ee76aab15..d80fb2974 100644 --- a/plugins/tweak/tweaks/pausing-fps-counter.h +++ b/plugins/tweak/tweaks/pausing-fps-counter.h @@ -47,7 +47,7 @@ struct dwarfmode_pausing_fps_counter_hook : df::viewscreen_dwarfmodest { if (prev_clock == 0) { // init - for (int i = 0; i < history_length; i++) + for (uint32_t i = 0; i < history_length; i++) history[i] = 0.0; } @@ -91,7 +91,7 @@ struct dwarfmode_pausing_fps_counter_hook : df::viewscreen_dwarfmodest { // average fps over a few seconds to stabilize the counter. double fps_sum = 0.0; int fps_count = 0; - for (int i = 0; i < history_length; i++) + for (uint32_t i = 0; i < history_length; i++) { if (history[i] > 0.0) { diff --git a/plugins/tweak/tweaks/tradereq-pet-gender.h b/plugins/tweak/tweaks/tradereq-pet-gender.h index df03008b8..ca786bef2 100644 --- a/plugins/tweak/tweaks/tradereq-pet-gender.h +++ b/plugins/tweak/tweaks/tradereq-pet-gender.h @@ -18,7 +18,7 @@ struct pet_gender_hook : df::viewscreen_topicmeeting_takerequestsst { df::historical_entity* entity = df::historical_entity::find(meeting->civ_id); vector& races = entity->resources.animals.pet_races; vector& castes = entity->resources.animals.pet_castes; - for (int i = (good_idx / 17) * 17, y = 4; i < (good_idx / 17) * 17 + 17 && i < races.size(); i++, y++) { + for (int i = (good_idx / 17) * 17, y = 4; i < (good_idx / 17) * 17 + 17 && size_t(i) < races.size(); i++, y++) { int x = 30 + 1 + world->raws.creatures.all[races[i]]->caste[castes[i]]->caste_name[0].size(); bool male = (bool)world->raws.creatures.all[races[i]]->caste[castes[i]]->gender; OutputString((i == good_idx) ? COLOR_WHITE : COLOR_GREY, From 67de61a1fe308d621a34b53b606d99f1727c025b Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 6 Apr 2018 18:18:48 -0500 Subject: [PATCH 0929/1012] Fix GCC warnings in stonesense. --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 0430344c7..9724981c4 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 0430344c7bd8621b1af45bf27a8b6335dd89013e +Subproject commit 9724981c4c3ed5ce308bdde716877ef94cd52f02 From afc2c476bb63c4723fc44a1ca0c8d4b481479dd2 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 6 Apr 2018 21:22:48 -0500 Subject: [PATCH 0930/1012] Fix remaining warnings on Windows. --- CMakeLists.txt | 7 +++++- depends/lua/CMakeLists.txt | 2 +- depends/md5/md5wrapper.cpp | 4 --- plugins/Plugins.cmake | 25 ++++++++++-------- plugins/embark-tools.cpp | 6 ++--- plugins/fortplan.cpp | 2 +- plugins/remotefortressreader/CMakeLists.txt | 2 +- plugins/search.cpp | 12 ++++----- plugins/stockpiles/OrganicMatLookup.cpp | 4 +-- plugins/stockpiles/StockpileSerializer.cpp | 28 ++++++++++----------- plugins/stonesense | 2 +- plugins/zone.cpp | 2 +- 12 files changed, 50 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7d57636f..78790a4f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,13 +53,18 @@ if(MSVC) add_definitions( "/wd4819" ) # Disable use of POSIX name warnings -add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS") +add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS") # supress C4503 - VC++ dislikes if a name is too long. If you get # weird and mysterious linking errors, you can disable this, but you'll have to # deal with a LOT of compiler noise over it # see https://msdn.microsoft.com/en-us/library/074af4b6.aspx add_definitions( "/wd4503") + +# suppress C4267 - VC++ complains whenever we implicitly convert an integer to +# a smaller type, and most of the time this is just conversion from 64 to 32 bits +# for things like vector sizes, which are never that big anyway. +add_definitions( "/wd4267") endif() # Automatically detect architecture based on Visual Studio generator diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index 8b9ce8e2e..7dcae8068 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -7,7 +7,7 @@ SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_ ADD_DEFINITIONS(-DLUA_COMPAT_BITLIB) IF(WIN32) - ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) + ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE /wd4334 ) ELSE() ADD_DEFINITIONS ( -DLUA_USE_POSIX -DLUA_USE_DLOPEN ) SET ( LIBS m dl ) diff --git a/depends/md5/md5wrapper.cpp b/depends/md5/md5wrapper.cpp index 7feb25035..7e64f7914 100644 --- a/depends/md5/md5wrapper.cpp +++ b/depends/md5/md5wrapper.cpp @@ -16,10 +16,6 @@ * Petr Mrázek */ -#if defined(_MSC_VER) && _MSC_VER >= 1400 -#define _CRT_SECURE_NO_WARNINGS -#endif - //---------------------------------------------------------------------- //basic includes #include diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 44a8dc5fb..b97db055d 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -78,14 +78,6 @@ MACRO(DFHACK_PLUGIN) LIST(APPEND PLUGIN_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.proto) ENDFOREACH() - ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES}) - IDE_FOLDER(${PLUGIN_NAME} "Plugins") - - ADD_DEPENDENCIES(${PLUGIN_NAME} dfhack-version) - - # Make sure the source is generated before the executable builds. - ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto) - LIST(LENGTH PLUGIN_PROTOS NUM_PROTO) IF(NUM_PROTO) STRING(REPLACE ".proto" ".pb.cc" PLUGIN_PROTO_SRCS "${PLUGIN_PROTOS}") @@ -106,7 +98,7 @@ MACRO(DFHACK_PLUGIN) ADD_CUSTOM_COMMAND( OUTPUT ${PLUGIN_PROTO_TMP_FILES} COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ - --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ + --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PLUGIN_PROTOS} COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl ${PLUGIN_PROTO_TMP_FILES} @@ -120,23 +112,34 @@ MACRO(DFHACK_PLUGIN) ENDIF() ADD_CUSTOM_TARGET(generate_proto_${PLUGIN_NAME} DEPENDS ${PLUGIN_PROTO_TMP_FILES}) - ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto_${PLUGIN_NAME}) # Merge headers into sources SET_SOURCE_FILES_PROPERTIES( ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE ) LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_HDRS}) LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_SRCS}) - TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite dfhack-version ${PLUGIN_LINK_LIBRARIES}) IF(UNIX) SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} -include Export.h") ELSE() SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} /FI\"Export.h\"") ENDIF() + ENDIF() + + ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES}) + IDE_FOLDER(${PLUGIN_NAME} "Plugins") + + IF(NUM_PROTO) + ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto_${PLUGIN_NAME}) + TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite dfhack-version ${PLUGIN_LINK_LIBRARIES}) ELSE() TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) ENDIF() + ADD_DEPENDENCIES(${PLUGIN_NAME} dfhack-version) + + # Make sure the source is generated before the executable builds. + ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto) + IF(UNIX) SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} ${PLUGIN_COMPILE_FLAGS_GCC}") ELSE() diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 2358bf95f..34613cafb 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -286,7 +286,7 @@ protected: // Used for event handling int prev_x; int prev_y; - bool prev_lbut; + int8_t prev_lbut; // Used for controls bool base_max_x; bool base_max_y; @@ -306,7 +306,7 @@ protected: return in_local_move || in_local_edge_resize_x || in_local_edge_resize_y || in_local_corner_resize; } - void lbut_press(start_sitest* screen, bool pressed, int x, int y) + void lbut_press(start_sitest* screen, int8_t pressed, int x, int y) { GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); in_local_move = in_local_edge_resize_x = in_local_edge_resize_y = @@ -462,7 +462,7 @@ public: :EmbarkTool(), prev_x(0), prev_y(0), - prev_lbut(false), + prev_lbut(0), base_max_x(false), base_max_y(false), in_local_move(false), diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index a1934c4ab..8609cb13c 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -165,7 +165,7 @@ command_result fortplan(color_ostream &out, vector & params) { con.print("Loading file '%s'...\n",filename.c_str()); try { layout = tokenizeFile(filename); - } catch (int e) { + } catch (int) { con.print("Could not open the file.\n"); return CR_FAILURE; } diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 1d022a95f..f47f03c5f 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -42,4 +42,4 @@ ELSE(UNIX) ) ENDIF(UNIX) # this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS} COMPILE_FLAGS_MSVC "/FI\"Export.h\"" COMPILE_FLAGS_GCC "-include Export.h -Wno-misleading-indentation" ) +DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES protobuf-lite ${PROJECT_LIBS} COMPILE_FLAGS_MSVC "/FI\"Export.h\"" COMPILE_FLAGS_GCC "-include Export.h -Wno-misleading-indentation" ) diff --git a/plugins/search.cpp b/plugins/search.cpp index ed8934a0c..aca97d4a9 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -957,14 +957,14 @@ class animal_trainer_search : public animal_trainer_search_base 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); + Screen::paintTile(Screen::Pen('\xBA', 8, 0), 14, 2); + Screen::paintTile(Screen::Pen('\xBA', 8, 0), gps->dimx - 14, 2); + Screen::paintTile(Screen::Pen('\xC9', 8, 0), 14, 1); + Screen::paintTile(Screen::Pen('\xBB', 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); + Screen::paintTile(Screen::Pen('\xCD', 8, 0), x, 1); + Screen::paintTile(Screen::Pen('\x00', 0, 0), x, 2); } print_search_option(16, 2); } diff --git a/plugins/stockpiles/OrganicMatLookup.cpp b/plugins/stockpiles/OrganicMatLookup.cpp index ca9670a96..4f1076cd1 100644 --- a/plugins/stockpiles/OrganicMatLookup.cpp +++ b/plugins/stockpiles/OrganicMatLookup.cpp @@ -71,8 +71,8 @@ void OrganicMatLookup::food_build_map ( std::ostream &out ) df::world_raws &raws = world->raws; df::special_mat_table table = raws.mat_table; using df::enums::organic_mat_category::organic_mat_category; - df::enum_traits traits; - for ( int32_t mat_category = traits.first_item_value; mat_category <= traits.last_item_value; ++mat_category ) + using traits = df::enum_traits; + for ( int32_t mat_category = traits::first_item_value; mat_category <= traits::last_item_value; ++mat_category ) { for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i ) { diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index 16c240f67..80e438701 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -227,14 +227,14 @@ void StockpileSerializer::unserialize_list_organic_mat ( FuncReadImport get_valu void StockpileSerializer::serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) { using df::enums::item_type::item_type; - df::enum_traits type_traits; - debug() << "item_type size = " << list.size() << " size limit = " << type_traits.last_item_value << " typecasted: " << ( size_t ) type_traits.last_item_value << endl; - for ( size_t i = 0; i <= ( size_t ) type_traits.last_item_value; ++i ) + using type_traits = df::enum_traits; + debug() << "item_type size = " << list.size() << " size limit = " << type_traits::last_item_value << " typecasted: " << ( size_t ) type_traits::last_item_value << endl; + for ( size_t i = 0; i <= ( size_t ) type_traits::last_item_value; ++i ) { if ( list.at ( i ) ) { const item_type type = ( item_type ) ( ( df::enum_traits::base_type ) i ); - std::string r_type ( type_traits.key_table[i+1] ); + std::string r_type ( type_traits::key_table[i+1] ); if ( !is_allowed ( type ) ) continue; add_value ( r_type ); debug() << "item_type key_table[" << i+1 << "] type[" << ( int16_t ) type << "] is " << r_type < quality_traits; + using quality_traits = df::enum_traits; for ( size_t i = 0; i < 7; ++i ) { if ( quality_list[i] ) { - const std::string f_type ( quality_traits.key_table[i] ); + const std::string f_type ( quality_traits::key_table[i] ); add_value ( f_type ); debug() << " quality: " << i << " is " << f_type <set_prepared_meals ( mPile->settings.food.prepared_meals ); using df::enums::organic_mat_category::organic_mat_category; - df::enum_traits traits; - for ( int32_t mat_category = traits.first_item_value; mat_category ; + for ( int32_t mat_category = traits::first_item_value; mat_category traits; + using traits = df::enum_traits; if ( mBuffer.has_food() ) { mPile->settings.flags.bits.food = 1; @@ -784,7 +784,7 @@ void StockpileSerializer::read_food() debug() << " prepared_meals: " << mPile->settings.food.prepared_meals << endl; - for ( int32_t mat_category = traits.first_item_value; mat_category type_traits; + using type_traits = df::enum_traits; for ( size_t i = 0; i < mPile->settings.furniture.type.size(); ++i ) { if ( mPile->settings.furniture.type.at ( i ) ) { - std::string f_type ( type_traits.key_table[i] ); + std::string f_type ( type_traits::key_table[i] ); furniture->add_type ( f_type ); debug() << "furniture_type " << i << " is " << f_type < & parameters) if (p == "race") { race_filter_set = true; } - } catch (const exception& err) { + } catch (const exception&) { return CR_FAILURE; } } From 165167defec2cf1de388257e9f03c85b1f41398e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 7 Apr 2018 10:47:25 -0400 Subject: [PATCH 0931/1012] Update scripts --- docs/changelog.txt | 1 + scripts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a9bc0975e..02e0dfe0e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `liquids`: fixed "range" command to default to 1 for dimensions consistently - `search`: fixed 4/6 keys in unit screen search +- `view-item-info`: fixed an error with some armor ================================================================================ # 0.44.09-r1 diff --git a/scripts b/scripts index 7d7dd8a74..ec1d86ab8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7d7dd8a7498e975a19231435e7cb225025042a78 +Subproject commit ec1d86ab8929605f005cf38cdce7f2f7ab3ad2c2 From ae6b8a16e8fc49393f2501e50a7fb92d3ff89b15 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Apr 2018 03:00:58 -0400 Subject: [PATCH 0932/1012] Handle most complex enum metadata on the C++ side --- library/DataDefs.cpp | 20 +++++++- library/include/DataDefs.h | 95 +++++++++++++++++++++++++++++++++++--- library/xml | 2 +- 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index ab50eb942..ce97904ad 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -143,11 +143,29 @@ enum_identity::enum_identity(size_t size, type_identity *base_type, int64_t first_item_value, int64_t last_item_value, const char *const *keys, + const ComplexData *complex, const void *attrs, struct_identity *attr_type) : compound_identity(size, NULL, scope_parent, dfhack_name), - keys(keys), first_item_value(first_item_value), last_item_value(last_item_value), + keys(keys), complex(complex), + first_item_value(first_item_value), last_item_value(last_item_value), base_type(base_type), attrs(attrs), attr_type(attr_type) { + if (complex) { + count = complex->size(); + } + else { + count = int(last_item_value-first_item_value+1); + } +} + +enum_identity::ComplexData::ComplexData(std::initializer_list values) +{ + size_t i = 0; + for (int64_t value : values) { + value_index_map[value] = i; + index_value_map.push_back(value); + i++; + } } struct_identity::struct_identity(size_t size, TAllocateFn alloc, diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index ed37a91fc..1da1be939 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -188,9 +188,22 @@ namespace DFHack class struct_identity; class DFHACK_EXPORT enum_identity : public compound_identity { + public: + struct ComplexData { + std::map value_index_map; + std::vector index_value_map; + ComplexData(std::initializer_list values); + size_t size() const { + return index_value_map.size(); + } + }; + + private: const char *const *keys; + const ComplexData *complex; int64_t first_item_value; int64_t last_item_value; + int count; type_identity *base_type; @@ -209,13 +222,14 @@ namespace DFHack type_identity *base_type, int64_t first_item_value, int64_t last_item_value, const char *const *keys, + const ComplexData *complex, const void *attrs, struct_identity *attr_type); virtual identity_type type() { return IDTYPE_ENUM; } int64_t getFirstItem() { return first_item_value; } int64_t getLastItem() { return last_item_value; } - int getCount() { return int(last_item_value-first_item_value+1); } + int getCount() { return count; } const char *const *getKeys() { return keys; } type_identity *getBaseType() { return base_type; } @@ -496,33 +510,100 @@ namespace DFHack { */ /** - * Return the next item in the enum, wrapping to the first one at the end. + * Return the next item in the enum, wrapping to the first one at the end if 'wrap' is true (otherwise an invalid item). */ template - inline typename df::enum_traits::enum_type next_enum_item(T v) { + inline typename std::enable_if< + !df::enum_traits::is_complex, + typename df::enum_traits::enum_type + >::type next_enum_item(T v, bool wrap = true) + { typedef df::enum_traits traits; typedef typename traits::base_type base_type; base_type iv = base_type(v); - return (iv < traits::last_item_value) ? T(iv+1) : traits::first_item; + if (iv < traits::last_item_value) + { + return T(iv + 1); + } + else + { + if (wrap) + return traits::first_item; + else + return T(traits::last_item_value + 1); + } + } + + template + inline typename std::enable_if< + df::enum_traits::is_complex, + typename df::enum_traits::enum_type + >::type next_enum_item(T v, bool wrap = true) + { + typedef df::enum_traits traits; + const auto &complex = traits::complex; + const auto it = complex.value_index_map.find(v); + if (it != complex.value_index_map.end()) + { + if (!wrap && it->second + 1 == complex.size()) + { + return T(traits::last_item_value + 1); + } + size_t next_index = (it->second + 1) % complex.size(); + return T(complex.index_value_map[next_index]); + } + else + return T(traits::last_item_value + 1); } /** * Check if the value is valid for its enum type. */ template - inline bool is_valid_enum_item(T v) { + inline typename std::enable_if< + !df::enum_traits::is_complex, + bool + >::type is_valid_enum_item(T v) + { return df::enum_traits::is_valid(v); } + template + inline typename std::enable_if< + df::enum_traits::is_complex, + bool + >::type is_valid_enum_item(T v) + { + const auto &complex = df::enum_traits::complex; + return complex.value_index_map.find(v) != complex.value_index_map.end(); + } + /** * Return the enum item key string pointer, or NULL if none. */ template - inline const char *enum_item_raw_key(T val) { + inline typename std::enable_if< + !df::enum_traits::is_complex, + const char * + >::type enum_item_raw_key(T val) { typedef df::enum_traits traits; return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL; } + template + inline typename std::enable_if< + df::enum_traits::is_complex, + const char * + >::type enum_item_raw_key(T val) { + typedef df::enum_traits traits; + const auto &value_index_map = traits::complex.value_index_map; + auto it = value_index_map.find(val); + if (it != value_index_map.end()) + return traits::key_table[it->second]; + else + return NULL; + } + /** * Return the enum item key string pointer, or "?" if none. */ @@ -700,7 +781,7 @@ namespace DFHack { #define ENUM_NEXT_ITEM(enum,val) \ (DFHack::next_enum_item(val)) #define FOR_ENUM_ITEMS(enum,iter) \ - for(df::enum iter = ENUM_FIRST_ITEM(enum); is_valid_enum_item(iter); iter = df::enum(1+int(iter))) + for(df::enum iter = ENUM_FIRST_ITEM(enum); DFHack::is_valid_enum_item(iter); iter = DFHack::next_enum_item(iter, false)) /* * Include mandatory generated headers. diff --git a/library/xml b/library/xml index 3c0bf6367..d3491fa87 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3c0bf63674d5430deadaf7befaec42f0ec1e8bc5 +Subproject commit d3491fa8790469e5325cf3f1ef1bf064b8011248 From 61539bf34528ab53167d3173e6994def0a623c4c Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Apr 2018 11:04:37 -0400 Subject: [PATCH 0933/1012] Lua: implement key lookup and ipairs for complex enums --- library/LuaWrapper.cpp | 113 ++++++++++++++++++++++++++++++------- library/include/DataDefs.h | 1 + 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 69fdd734e..b80bbdba7 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1349,6 +1349,58 @@ static int wtype_next_item(lua_State *state) return 1; } +/* + * Complex enums + * + * upvalues for all of these: + * 1: key table? unsure, taken from wtype stuff + * 2: enum_identity::ComplexData + */ + +static bool complex_enum_next_item_helper(lua_State *L, int64_t &item) +{ + const auto *complex = (enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2)); + auto it = complex->value_index_map.find(item); + if (it != complex->value_index_map.end()) + { + size_t index = it->second; + if (index >= complex->size() - 1) + return false; + + item = complex->index_value_map[index + 1]; + return true; + } + return false; +} + +static int complex_enum_inext(lua_State *L) +{ + int64_t first_item = ((enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2)))->index_value_map[0]; + int64_t i = (lua_isuserdata(L, 2)) ? first_item : luaL_checkint(L, 2); + if (complex_enum_next_item_helper(L, i)) + { + lua_pushinteger(L, i); + lua_rawgeti(L, lua_upvalueindex(1), i); + return 2; + } + else + { + lua_pushnil(L); + return 1; + } +} + +static int complex_enum_ipairs(lua_State *L) +{ + lua_pushvalue(L, lua_upvalueindex(1)); + lua_pushvalue(L, lua_upvalueindex(2)); + lua_pushcclosure(L, complex_enum_inext, 2); + lua_pushnil(L); + lua_pushlightuserdata(L, (void*)1); + return 3; +} + + static void RenderTypeChildren(lua_State *state, const std::vector &children); void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name) @@ -1371,31 +1423,54 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit int base = lua_gettop(state); lua_newtable(state); + auto *complex = eid->getComplex(); + // For enums, set mapping between keys and values - for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++) + if (complex) + { + for (size_t i = 0; i < complex->size(); i++) + { + if (keys[i]) + AssociateId(state, base+1, complex->index_value_map[i], keys[i]); + } + } + else { - if (keys[j]) - AssociateId(state, base+1, i, keys[j]); + for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++) + { + if (keys[j]) + AssociateId(state, base+1, i, keys[j]); + } } - if (eid->getFirstItem() <= eid->getLastItem()) + if (complex) { - lua_pushvalue(state, base+1); - lua_pushinteger(state, eid->getFirstItem()-1); - lua_pushinteger(state, eid->getLastItem()); - lua_pushcclosure(state, wtype_ipairs, 3); + lua_pushvalue(state, base + 1); + lua_pushlightuserdata(state, (void*)complex); + lua_pushcclosure(state, complex_enum_ipairs, 2); lua_setfield(state, ix_meta, "__ipairs"); - - lua_pushinteger(state, eid->getFirstItem()); - lua_pushinteger(state, eid->getLastItem()); - lua_pushcclosure(state, wtype_next_item, 2); - lua_setfield(state, ftable, "next_item"); - - lua_pushinteger(state, eid->getFirstItem()); - lua_setfield(state, ftable, "_first_item"); - - lua_pushinteger(state, eid->getLastItem()); - lua_setfield(state, ftable, "_last_item"); + } + else + { + if (eid->getFirstItem() <= eid->getLastItem()) + { + lua_pushvalue(state, base + 1); + lua_pushinteger(state, eid->getFirstItem() - 1); + lua_pushinteger(state, eid->getLastItem()); + lua_pushcclosure(state, wtype_ipairs, 3); + lua_setfield(state, ix_meta, "__ipairs"); + + lua_pushinteger(state, eid->getFirstItem()); + lua_pushinteger(state, eid->getLastItem()); + lua_pushcclosure(state, wtype_next_item, 2); + lua_setfield(state, ftable, "next_item"); + + lua_pushinteger(state, eid->getFirstItem()); + lua_setfield(state, ftable, "_first_item"); + + lua_pushinteger(state, eid->getLastItem()); + lua_setfield(state, ftable, "_last_item"); + } } SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 1da1be939..bf5782812 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -231,6 +231,7 @@ namespace DFHack int64_t getLastItem() { return last_item_value; } int getCount() { return count; } const char *const *getKeys() { return keys; } + const ComplexData *getComplex() { return complex; } type_identity *getBaseType() { return base_type; } const void *getAttrs() { return attrs; } From c1dd21730d1352620036824fd3ba1690489c4711 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Apr 2018 11:27:02 -0400 Subject: [PATCH 0934/1012] Fix complex enum ipairs for first element --- library/LuaWrapper.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index b80bbdba7..1a079a8a4 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1375,9 +1375,11 @@ static bool complex_enum_next_item_helper(lua_State *L, int64_t &item) static int complex_enum_inext(lua_State *L) { - int64_t first_item = ((enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2)))->index_value_map[0]; - int64_t i = (lua_isuserdata(L, 2)) ? first_item : luaL_checkint(L, 2); - if (complex_enum_next_item_helper(L, i)) + bool is_first = lua_isuserdata(L, 2); + int64_t i = (is_first) + ? ((enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2)))->index_value_map[0] + : luaL_checkint(L, 2); + if (is_first || complex_enum_next_item_helper(L, i)) { lua_pushinteger(L, i); lua_rawgeti(L, lua_upvalueindex(1), i); From 7da879317256b70707c5f6d98a965e8f5c7dc112 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Apr 2018 16:11:47 -0400 Subject: [PATCH 0935/1012] Set last_item_value properly for complex enums --- library/DataDefs.cpp | 1 + library/LuaWrapper.cpp | 6 ++++++ library/xml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index ce97904ad..3d802638f 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -152,6 +152,7 @@ enum_identity::enum_identity(size_t size, { if (complex) { count = complex->size(); + last_item_value = complex->index_value_map.back(); } else { count = int(last_item_value-first_item_value+1); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 1a079a8a4..a32e9263c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1451,6 +1451,12 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit lua_pushlightuserdata(state, (void*)complex); lua_pushcclosure(state, complex_enum_ipairs, 2); lua_setfield(state, ix_meta, "__ipairs"); + + lua_pushinteger(state, eid->getFirstItem()); + lua_setfield(state, ftable, "_first_item"); + + lua_pushinteger(state, eid->getLastItem()); + lua_setfield(state, ftable, "_last_item"); } else { diff --git a/library/xml b/library/xml index d3491fa87..028c4a303 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d3491fa8790469e5325cf3f1ef1bf064b8011248 +Subproject commit 028c4a3031d828122d7ad4efdbc0522251542891 From 939826265425096f4ecb7cc5261bb4c7bede86ad Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Apr 2018 16:12:03 -0400 Subject: [PATCH 0936/1012] Make enum attrs work for complex enums (lever_target_type) --- library/LuaWrapper.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index a32e9263c..154bcc0c5 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1025,11 +1025,23 @@ static int meta_enum_attr_index(lua_State *state) luaL_error(state, "Invalid index in enum.attrs[]"); auto id = (enum_identity*)lua_touserdata(state, lua_upvalueindex(2)); + auto *complex = id->getComplex(); int64_t idx = lua_tonumber(state, 2); - if (idx < id->getFirstItem() || idx > id->getLastItem()) - idx = id->getLastItem()+1; - idx -= id->getFirstItem(); + if (complex) + { + auto it = complex->value_index_map.find(idx); + if (it != complex->value_index_map.end()) + idx = int64_t(it->second); + else + idx = id->getLastItem() + 1; + } + else + { + if (idx < id->getFirstItem() || idx > id->getLastItem()) + idx = id->getLastItem()+1; + idx -= id->getFirstItem(); + } uint8_t *ptr = (uint8_t*)id->getAttrs(); auto atype = id->getAttrType(); From b0e1ae93eabf3dbb31272e74828707cd614bd08c Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Apr 2018 19:46:12 -0400 Subject: [PATCH 0937/1012] Add next_item, _complex for complex enums --- library/LuaWrapper.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 154bcc0c5..fe8309736 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1369,17 +1369,17 @@ static int wtype_next_item(lua_State *state) * 2: enum_identity::ComplexData */ -static bool complex_enum_next_item_helper(lua_State *L, int64_t &item) +static bool complex_enum_next_item_helper(lua_State *L, int64_t &item, bool wrap = false) { const auto *complex = (enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2)); auto it = complex->value_index_map.find(item); if (it != complex->value_index_map.end()) { size_t index = it->second; - if (index >= complex->size() - 1) + if (!wrap && index >= complex->size() - 1) return false; - item = complex->index_value_map[index + 1]; + item = complex->index_value_map[(index + 1) % complex->size()]; return true; } return false; @@ -1404,6 +1404,14 @@ static int complex_enum_inext(lua_State *L) } } +static int complex_enum_next_item(lua_State *L) +{ + int64_t cur = luaL_checkint(L, lua_gettop(L) > 1 ? 2 : 1); // 'self' optional + complex_enum_next_item_helper(L, cur, true); + lua_pushinteger(L, cur); + return 1; +} + static int complex_enum_ipairs(lua_State *L) { lua_pushvalue(L, lua_upvalueindex(1)); @@ -1464,11 +1472,19 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit lua_pushcclosure(state, complex_enum_ipairs, 2); lua_setfield(state, ix_meta, "__ipairs"); + lua_pushinteger(state, 0); // unused; to align ComplexData + lua_pushlightuserdata(state, (void*)complex); + lua_pushcclosure(state, complex_enum_next_item, 2); + lua_setfield(state, ftable, "next_item"); + lua_pushinteger(state, eid->getFirstItem()); lua_setfield(state, ftable, "_first_item"); lua_pushinteger(state, eid->getLastItem()); lua_setfield(state, ftable, "_last_item"); + + lua_pushboolean(state, true); + lua_setfield(state, ftable, "_complex"); } else { @@ -1490,6 +1506,9 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit lua_pushinteger(state, eid->getLastItem()); lua_setfield(state, ftable, "_last_item"); + + lua_pushboolean(state, false); + lua_setfield(state, ftable, "_complex"); } } From f9ad71f682776315c0d23d7f2eb73705b0e30534 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 10 Apr 2018 13:29:00 -0500 Subject: [PATCH 0938/1012] Fix fencepost error in autolabor/autohauler/labormanager. https://github.com/DFHack/dfhack/pull/1253#pullrequestreview-110903215 --- plugins/autohauler.cpp | 2 +- plugins/autolabor.cpp | 2 +- plugins/labormanager/labormanager.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index e222a9702..c43ec110e 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -624,7 +624,7 @@ static void init_state() df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autohauler/labors/")).c_str()); // Ensure that the labor is defined in the existing list - if (labor >= 0 && size_t(labor) <= labor_infos.size()) + if (labor >= 0 && size_t(labor) < labor_infos.size()) { // Link the labor treatment with the associated persistent data item labor_infos[labor].set_config(*p); diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 6ca0a2658..936ce4022 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -606,7 +606,7 @@ static void init_state() { string key = p->key(); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); - if (labor >= 0 && size_t(labor) <= labor_infos.size()) + if (labor >= 0 && size_t(labor) < labor_infos.size()) { labor_infos[labor].config = *p; labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index cf183f452..7c749edce 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -614,7 +614,7 @@ static void init_state() { string key = p->key(); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("labormanager/2.0/labors/")).c_str()); - if (labor >= 0 && size_t(labor) <= labor_infos.size()) + if (labor >= 0 && size_t(labor) < labor_infos.size()) { labor_infos[labor].config = *p; labor_infos[labor].active_dwarfs = 0; From 3b2112a56ecfa7f0543f2ead90679c5e68b65301 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 19 Apr 2018 12:13:33 -0400 Subject: [PATCH 0939/1012] Update scripts/exterminate --- docs/changelog.txt | 3 +++ scripts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 02e0dfe0e..db2027698 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `search`: fixed 4/6 keys in unit screen search - `view-item-info`: fixed an error with some armor +## Misc Improvements +- `exterminate`: added more words for current unit, removed warning + ================================================================================ # 0.44.09-r1 diff --git a/scripts b/scripts index ec1d86ab8..53abecc96 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ec1d86ab8929605f005cf38cdce7f2f7ab3ad2c2 +Subproject commit 53abecc969936ad20c372737e1187dac4a54ef9a From 5cd8aa0458e892c26e782b304e833d9164cb3340 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 19 Apr 2018 12:35:00 -0400 Subject: [PATCH 0940/1012] Update downloads to 0.44.09 --- CMakeLists.txt | 16 ++++++++-------- plugins/ruby/CMakeLists.txt | 10 +++++----- plugins/stonesense | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78790a4f9..2d1ded60b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,11 +229,11 @@ if(WIN32) # Download zlib on Windows set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH}) if(${DFHACK_BUILD_ARCH} STREQUAL "64") - download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-zlib.lib" + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-zlib.lib" ${ZLIB_DOWNLOAD_DIR}/zlib.lib "a3b2fc6b68efafa89b0882e354fc8418") else() - download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-zlib.lib" + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-zlib.lib" ${ZLIB_DOWNLOAD_DIR}/zlib.lib "f4ebaa21d9de28566e88b1edfcdff901") endif() @@ -250,11 +250,11 @@ if(WIN32) # (DFHack doesn't require this at build time, so no need to move it to the build folder) set(SDLREAL_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) if(${DFHACK_BUILD_ARCH} STREQUAL "64") - download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-SDL.dll" + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-SDL.dll" ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll "1ae242c4b94cb03756a1288122a66faf") else() - download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-SDL.dll" + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-SDL.dll" ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll "5a09604daca6b2b5ce049d79af935d6a") endif() @@ -274,7 +274,7 @@ if(APPLE) if(${DFHACK_BUILD_ARCH} STREQUAL "64") if(${LIBSTDCXX_GCC_VER} STREQUAL "48") - download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-gcc48-libstdcxx.6.dylib.gz" + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx64-gcc48-libstdcxx.6.dylib.gz" "gz" ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz "cf26ed588be8e83c8e3a49919793b416" @@ -282,7 +282,7 @@ if(APPLE) "16dc6dbd4ecde7f9b95bb6dc91f07404") else() # GCC 7 - download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-gcc7-libstdcxx.6.dylib.gz" + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx64-gcc7-libstdcxx.6.dylib.gz" "gz" ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz "81314b7846f9e8806409bef2160c76e6" @@ -293,7 +293,7 @@ if(APPLE) else() # 32-bit if(${LIBSTDCXX_GCC_VER} STREQUAL "48") - download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-gcc48-libstdcxx.6.dylib.gz" + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-gcc48-libstdcxx.6.dylib.gz" "gz" ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz "40f3d83871b114f0279240626311621b" @@ -301,7 +301,7 @@ if(APPLE) "c3f5678b8204917e03870834902c3e8b") else() # GCC 7 - download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-gcc7-libstdcxx.6.dylib.gz" + download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-gcc7-libstdcxx.6.dylib.gz" "gz" ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz "dbd213171f66edb90d204d525f10c969" diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index f1ef12ac5..c2c80e79e 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -9,7 +9,7 @@ IF (DOWNLOAD_RUBY) IF(${DFHACK_BUILD_ARCH} STREQUAL 64) # MESSAGE("No ruby lib for 64-bit OS X yet") ELSE() - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libruby187.dylib.gz" + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-libruby187.dylib.gz" "gz" ${RUBYLIB}.gz "e9bc4263557e652121b055a46abb4f97" @@ -20,14 +20,14 @@ IF (DOWNLOAD_RUBY) SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) SET(RUBYLIB_INSTALL_NAME "libruby.so") IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux64-libruby187.so.gz" + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux64-libruby187.so.gz" "gz" ${RUBYLIB}.gz "8eb757bb9ada08608914d8ca8906c427" ${RUBYLIB} "e8c36a06f031cfbf02def28169bb5f1f") ELSE() - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/linux32-libruby187.so.gz" + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux32-libruby187.so.gz" "gz" ${RUBYLIB}.gz "2d06f5069ff07ea934ecd40db55a4ac5" @@ -38,14 +38,14 @@ IF (DOWNLOAD_RUBY) SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) SET(RUBYLIB_INSTALL_NAME "libruby.dll") IF(${DFHACK_BUILD_ARCH} STREQUAL 64) - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-libruby200.dll.gz" + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-libruby200.dll.gz" "gz" ${RUBYLIB}.gz "81db54a8b8b3090c94c6ae2147d30b8f" ${RUBYLIB} "8a8564418aebddef3dfee1e96690e713") ELSE() - DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-libruby187.dll.gz" + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-libruby187.dll.gz" "gz" ${RUBYLIB}.gz "ffc0f1b5b33748e2a36128e90c97f6b2" diff --git a/plugins/stonesense b/plugins/stonesense index 9bc7acc11..6d38bf884 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 9bc7acc114e25c7399f4b85c95d544fe7e7c3d8e +Subproject commit 6d38bf884eef435d99df5c4e27b3b36ac6f2ec04 From a708b0b3039781ac71055e04b6b38c40df248056 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 19 Apr 2018 21:11:43 -0400 Subject: [PATCH 0941/1012] Add some includes in DataDefs.h --- library/include/DataDefs.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index f964455a8..31a949df1 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -24,11 +24,13 @@ distribution. #pragma once -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include "Core.h" #include "BitArray.h" From c58ffdb922998e31d6a4e1a0ed92aa83de0a6ce7 Mon Sep 17 00:00:00 2001 From: AtomicChicken Date: Fri, 20 Apr 2018 17:14:03 +0200 Subject: [PATCH 0942/1012] Added function to remove syndrome wound data The presence of syndrome data in unit.syndromes.active generates corresponding wound data in unit.body.wounds. This wound data acts to produce all of the syndrome's actual effects, including but not limited to flag changes, interaction abilities, body transformation and display name alterations. Wound data persists when syndrome data is cleared from unit.syndromes.active. Since syndrome-util did not touch wound data at all, the erase function was completely ineffective at actually removing syndromes. Note that syndromes also generate a bunch of data in the historical figure information of units. I have observed that this historical data is sufficient to restore the syndrome in a unit following map reload (at least in adventure mode), so its clearance (which needs to also include any corresponding interaction effects) will have to be addressed in a future update. As is, syndrome erasure remains incomplete. --- library/lua/syndrome-util.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/library/lua/syndrome-util.lua b/library/lua/syndrome-util.lua index 52cd8b008..efbaa5dea 100644 --- a/library/lua/syndrome-util.lua +++ b/library/lua/syndrome-util.lua @@ -22,6 +22,15 @@ ResetPolicy = ResetPolicy or utils.invert({ "NewInstance" }) +function eraseSyndromeData(unit,syndromeId,oldestFirst) + for i = (oldestFirst and 0) or #unit.body.wounds-1,(oldestFirst and #unit.body.wounds-1) or 0,(oldestFirst and 1) or -1 do + if unit.body.wounds[i].syndrome_id == syndromeId then + unit.body.wounds:erase(i) + break + end + end +end + function eraseSyndrome(unit,syndromeId,oldestFirst) local i1 local iN @@ -38,6 +47,7 @@ function eraseSyndrome(unit,syndromeId,oldestFirst) local syndromes = unit.syndromes.active for i=i1,iN,d do if syndromes[i]['type'] == syndromeId then + eraseSyndromeData(unit,syndromeId,oldestFirst) syndromes:erase(i) return true end @@ -50,6 +60,7 @@ local function eraseSyndromeClassHelper(unit,synclass) local syndrome = df.syndrome.find(unitSyndrome.type) for _,class in ipairs(syndrome.syn_class) do if class.value == synclass then + eraseSyndromeData(unit,syndrome.id) unit.syndromes.active:erase(i) return true end @@ -172,4 +183,3 @@ function infectWithSyndromeIfValidTarget(target,syndrome,resetPolicy) end return _ENV - From 8f609d0ddf488b258026180862fdcad2e2c43e5c Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 23 Apr 2018 16:56:27 -0500 Subject: [PATCH 0943/1012] Replace submodule URLs with relative URLs They will be downloaded via the same transport as the main repo (HTTPS or SSH, usually) --- .gitmodules | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 687dae006..d57d82200 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,15 @@ [submodule "plugins/stonesense"] path = plugins/stonesense - url = git://github.com/DFHack/stonesense.git + url = ../../DFHack/stonesense.git [submodule "plugins/isoworld"] path = plugins/isoworld - url = git://github.com/DFHack/isoworld.git + url = ../../DFHack/isoworld.git [submodule "library/xml"] path = library/xml - url = git://github.com/DFHack/df-structures.git + url = ../../DFHack/df-structures.git [submodule "depends/clsocket"] path = depends/clsocket - url = git://github.com/DFHack/clsocket.git + url = ../../DFHack/clsocket.git [submodule "scripts2"] path = scripts - url = git://github.com/dfhack/scripts.git + url = ../../DFHack/scripts.git From 60d1c270c2173633f4d74473084bd541a5441d2f Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 12 Jul 2017 16:07:44 -0400 Subject: [PATCH 0944/1012] Display autogems config option --- plugins/autogems.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index 013038834..b77c5d249 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -50,6 +50,7 @@ const char *usage = ( "While this option is enabled, jobs will be created in Jeweler's Workshops\n" "to cut any accessible rough gems.\n" ); +std::set blacklist; void add_task(mat_index gem_type, df::building_workshopst *workshop) { // Create a single task in the specified workshop. @@ -121,6 +122,7 @@ bool valid_gem(df::item* item) { if (item->flags.bits.construction) return false; if (item->flags.bits.garbage_collect) return false; if (item->flags.bits.in_building) return false; + if (blacklist.count(item->getMaterialIndex())) return false; return true; } @@ -245,6 +247,8 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { running = !running; return true; + } else if (input->count(interface_key::CUSTOM_SHIFT_G)) { + Core::getInstance().setHotkeyCmd("gui/autogems"); } return false; @@ -269,7 +273,11 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { } if (pen.valid()) { - OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), + interface_key::CUSTOM_G, false, x, COLOR_WHITE, COLOR_LIGHTRED); + x += running ? 5 : 2; + OutputHotkeyString(x, y, "Opts", interface_key::CUSTOM_SHIFT_G, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); } } } From 9d7feaf39df2afec1794a1278339d77524bd2ebb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 May 2018 12:46:10 -0400 Subject: [PATCH 0945/1012] autogems: load blacklist from autogems.json Closes #1027 --- plugins/CMakeLists.txt | 2 +- plugins/autogems.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++ scripts | 2 +- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e85e0aaf4..231ff656d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -85,7 +85,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(autogems autogems.cpp LINK_LIBRARIES jsoncpp) 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 index b77c5d249..ee255846d 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -4,9 +4,12 @@ * For best effect, include "enable autogems" in your dfhack.init configuration. */ +#include + #include "uicommon.h" #include "modules/Buildings.h" +#include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/Job.h" #include "modules/World.h" @@ -19,6 +22,8 @@ #include "df/job_item.h" #include "df/viewscreen_dwarfmodest.h" +#include "jsoncpp-ex.h" + #define CONFIG_KEY "autogems/config" #define DELTA_TICKS 1200 #define MAX_WORKSHOP_JOBS 10 @@ -286,6 +291,44 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render); +bool read_config(color_ostream &out) { + std::string path = "data/save/" + World::ReadWorldFolder() + "/autogems.json"; + if (!Filesystem::isfile(path)) { + // no need to require the config file to exist + return true; + } + + std::ifstream f(path); + Json::Value config; + try { + if (!f.good() || !(f >> config)) { + out.printerr("autogems: failed to read autogems.json\n"); + return false; + } + } + catch (Json::Exception &e) { + out.printerr("autogems: failed to read autogems.json: %s\n", e.what()); + return false; + } + + if (config["blacklist"].isArray()) { + blacklist.clear(); + for (int i = 0; i < int(config["blacklist"].size()); i++) { + Json::Value item = config["blacklist"][i]; + if (item.isInt()) { + blacklist.insert(mat_index(item.asInt())); + } + else { + out.printerr("autogems: illegal item at position %i in blacklist\n", i); + } + } + } + return true; +} + +command_result cmd_reload_config(color_ostream &out, std::vector&) { + return read_config(out) ? CR_OK : CR_FAILURE; +} DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_MAP_LOADED) { @@ -294,6 +337,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan auto config = World::GetPersistentData(CONFIG_KEY); running = config.isValid() && !config.ival(0); last_frame_count = world->frame_counter; + read_config(out); } } else if (event == DFHack::SC_MAP_UNLOADED) { running = false; @@ -313,10 +357,20 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { } running = enabled && World::isFortressMode(); + if (running) { + read_config(out); + } return CR_OK; } DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + "autogems-reload", + "Reload autogems config file", + cmd_reload_config, + false, + "Reload autogems config file" + )); return CR_OK; } diff --git a/scripts b/scripts index 53abecc96..65b102ed6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 53abecc969936ad20c372737e1187dac4a54ef9a +Subproject commit 65b102ed6e0fe5b56b20f0c157f5a000e750432b From c9d7f433a9a0e28f2586d9a1cd377de572fdabb2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 May 2018 15:39:16 -0400 Subject: [PATCH 0946/1012] Update docs for autogems UI --- docs/Plugins.rst | 3 +++ docs/changelog.txt | 4 ++++ scripts | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 030aa9de1..d8cfdd270 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1057,6 +1057,9 @@ autogems Creates a new Workshop Order setting, automatically cutting rough gems when `enabled `. +See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload`` +command reloads the configuration file produced by that script. + .. _stockflow: stockflow diff --git a/docs/changelog.txt b/docs/changelog.txt index db2027698..e0b26afee 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,12 +37,16 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## New Scripts +- `gui/autogems`: a configuration UI for the `autogems` plugin + ## Fixes - `liquids`: fixed "range" command to default to 1 for dimensions consistently - `search`: fixed 4/6 keys in unit screen search - `view-item-info`: fixed an error with some armor ## Misc Improvements +- `autogems`: can now blacklist arbitrary gem types (see `gui/autogems`) - `exterminate`: added more words for current unit, removed warning ================================================================================ diff --git a/scripts b/scripts index 65b102ed6..6295e5ac6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 65b102ed6e0fe5b56b20f0c157f5a000e750432b +Subproject commit 6295e5ac6a93cf460d4721510335a15541f6efbf From b0c007cae6f42116d464ffa4757efe282b483fb8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 May 2018 16:05:26 -0400 Subject: [PATCH 0947/1012] Add some build scripts for sublime --- build/sublime/dfhack.sublime-project | 34 +++++++++++++++ build/sublime/make.py | 63 ++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 build/sublime/dfhack.sublime-project create mode 100644 build/sublime/make.py diff --git a/build/sublime/dfhack.sublime-project b/build/sublime/dfhack.sublime-project new file mode 100644 index 000000000..1b1aa272b --- /dev/null +++ b/build/sublime/dfhack.sublime-project @@ -0,0 +1,34 @@ +{ + "folders": + [ + { + "path": "." + } + ], + "build_systems": + [ + { + "name": "DFHack make", + "working_dir": "$project_path", + "cmd": ["python", "$project_path/build/sublime/make.py", "$file"], + "variants": [ + { + "name": "Build all", + "cmd": ["python", "$project_path/build/sublime/make.py", "-a"] + }, + { + "name": "Build+install all", + "cmd": ["python", "$project_path/build/sublime/make.py", "-ai"] + }, + { + "name": "Build plugin", + "cmd": ["python", "$project_path/build/sublime/make.py", "-ap", "$file"] + }, + { + "name": "Build+install plugin", + "cmd": ["python", "$project_path/build/sublime/make.py", "-aip", "$file"] + } + ] + } + ] +} diff --git a/build/sublime/make.py b/build/sublime/make.py new file mode 100644 index 000000000..526810a40 --- /dev/null +++ b/build/sublime/make.py @@ -0,0 +1,63 @@ +from __future__ import print_function +import argparse +import os +import re +import sys +import subprocess + +class BuildError(Exception): pass + +parser = argparse.ArgumentParser() +parser.add_argument('file', nargs='?', default='', help='current filename') +parser.add_argument('-a', '--all', action='store_true', help='Build all targets') +parser.add_argument('-i', '--install', action='store_true', help='Install') +parser.add_argument('-p', '--plugin', action='store_true', help='Build specified plugin') + +def find_build_folder(): + # search for the one with the most recently modified Makefile + folder = None + mtime = 0 + for f in os.listdir('.'): + if f.startswith('build'): + makefile = os.path.join(f, 'Makefile') + if os.path.isfile(makefile) and os.path.getmtime(makefile) > mtime: + folder = f + mtime = os.path.getmtime(makefile) + if not folder: + raise BuildError('No valid build folder found') + return folder + +def get_plugin_name(filename): + filename = filename.replace('/devel', '') + match = re.search(r'plugins/(.+?)[/\.]', filename) + if match: + return match.group(1) + +def run_command(cmd): + print('$ ' + ' '.join(cmd)) + sys.stdout.flush() + if subprocess.call(cmd) != 0: + raise BuildError('command execution failed: ' + ' '.join(cmd)) + +def main(args): + os.chdir(find_build_folder()) + print('Build folder:', os.getcwd()) + cmd = ['make', '-j3'] + + if args.plugin: + plugin = get_plugin_name(args.file) + if not plugin: + raise BuildError('Cannot determine current plugin name from %r' % args.file) + cmd += [plugin + '/fast'] + + run_command(cmd) + + if args.install: + run_command(['make', 'install/fast']) + +if __name__ == '__main__': + try: + main(parser.parse_args()) + except BuildError as e: + print('** Error: ' + str(e)) + sys.exit(1) From b266c7322f55c515f7b1fb3372956bfba1682abd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 May 2018 16:08:06 -0400 Subject: [PATCH 0948/1012] Make fpause pause worldgen Closes #1256 --- library/Core.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 58b3f85f5..49aa61ecd 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -69,6 +69,7 @@ using namespace DFHack; #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_game_cleanerst.h" #include "df/viewscreen_loadgamest.h" +#include "df/viewscreen_new_regionst.h" #include "df/viewscreen_savegamest.h" #include @@ -1149,6 +1150,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v else if (builtin == "fpause") { World::SetPauseState(true); + if (auto scr = Gui::getViewscreenByType()) + { + scr->worldgen_paused = true; + } con.print("The game was forced to pause!\n"); } else if (builtin == "cls") From f4088dbd2884eb00ecfb49f37cd24578a6a9c0a4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 May 2018 16:09:20 -0400 Subject: [PATCH 0949/1012] Update xml, scripts --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 028c4a303..ba2871a84 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 028c4a3031d828122d7ad4efdbc0522251542891 +Subproject commit ba2871a84f47b13f5616136be7b4af585fad0b31 diff --git a/scripts b/scripts index 6295e5ac6..1a78d1ebf 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6295e5ac6a93cf460d4721510335a15541f6efbf +Subproject commit 1a78d1ebf9a53f06d4f91ca870baed7459955c4d From 32d83be84a209a19b78688c60935089de0fa0da4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 May 2018 16:36:16 -0400 Subject: [PATCH 0950/1012] Add some recent stuff to changelog.txt --- docs/changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index e0b26afee..4cd3d3ab1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -48,6 +48,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `autogems`: can now blacklist arbitrary gem types (see `gui/autogems`) - `exterminate`: added more words for current unit, removed warning +- `fpause`: now pauses worldgen as well + +## Internals +- Added some build scripts for Sublime Text +- Changed submodule URLs to relative URLs so that they can be cloned consistently over different protocols (e.g. SSH) ================================================================================ # 0.44.09-r1 From d2f59c5f90ece0658df90875919963384f7f7e24 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 6 May 2018 10:26:14 -0600 Subject: [PATCH 0951/1012] Update Contributing (IDA Freeware 7.0, cl-linux-debug being 32-bit only) Also add my IDC script for doing RTTI on 64-bit Windows binaries --- Contributing.rst | 10 +- reversing/ms_rtti64.idc | 1086 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1091 insertions(+), 5 deletions(-) create mode 100644 reversing/ms_rtti64.idc diff --git a/Contributing.rst b/Contributing.rst index 59ff8285f..e358e66b3 100644 --- a/Contributing.rst +++ b/Contributing.rst @@ -49,16 +49,16 @@ In general, you'll need a good memory viewer and optionally something to look at machine code without getting crazy :) Using publicly known information and analyzing the game's data is preferred. -Good windows tools include: +Good Windows tools include: +* IDA Freeware 7.0 (for non-commercial use, supports 32-bit and 64-bit) * Cheat Engine -* IDA Pro 5.0 (freely available for non-commercial use) -Good linux tools: +Good Linux tools: -* angavrilov's df-structures gui (visit us on IRC for details). +* angavrilov's df-structures gui (32-bit only, visit us on IRC for details) +* IDA Freeware 7.0 (see above) * edb (Evan's Debugger) -* IDA Pro 5.0 running under Wine * Some of the tools residing in the ``legacy`` dfhack branch. Using the library as a developer diff --git a/reversing/ms_rtti64.idc b/reversing/ms_rtti64.idc new file mode 100644 index 000000000..da26ae916 --- /dev/null +++ b/reversing/ms_rtti64.idc @@ -0,0 +1,1086 @@ +#include + +#define isRef(F) ((F & FF_REF) != 0) +#define hasName(F) ((F & FF_NAME) != 0) + +static GetVtableSize(a) +{ + auto b,c,f; + b = BADADDR; + f = GetFlags(a); + do { + f = GetFlags(a); + if (b == BADADDR) //first entry + { + b=a; + if (!(isRef(f) && (hasName(f) || (f&FF_LABL)))) + { + return 0; + } + } + else if (isRef(f)) //might mean start of next vtable + break; + + if (!hasValue(f) || !isData(f)) + break; + c = Qword(a); + if (c) + { + f = GetFlags(c); + if (!hasValue(f) || !isCode(f) || Dword(c)==0) + break; + } + a = a+8; + } + while (1); + if (b!=BADADDR) + { + c = (a-b)/8; + return c; + } + else + { + return 0; + } +} + +static Unknown( ea, length ) +{ + auto i; + if (ea==BADADDR) + return; + for(i=0; i < length; i++) + { + MakeUnkn(ea+i,0); + } +} + +static DwordRel(x) +{ + x = Dword(x) + 0x140000000; + return x; +} + +static ForceQword( x ) { //Make dword, undefine as needed + if (x==BADADDR || x==0) + return; + if (!MakeQword( x )) + { + Unknown(x,8); + MakeQword(x); + } +} + +static ForceDword( x ) { //Make dword, undefine as needed + if (x==BADADDR || x==0) + return; + if (!MakeDword( x )) + { + Unknown(x,4); + MakeDword(x); + } +} + +static SoftOff64 ( x ) { //Make 64-bit offset if !=0 + if (x==BADADDR || x==0) + return; + ForceQword(x); + if (Qword(x)>0 && Qword(x)<=MaxEA()) OpOff(x, 0, 0); +} + +static SoftOff ( x ) { //Make 32-bit base-relative offset if !=0 + if (x==BADADDR || x==0) + return; + ForceDword(x); + if (Dword(x)>0 && Dword(x)<=MaxEA()) OpOffEx(x,0,REF_OFF32|REFINFO_RVA|REFINFO_SIGNEDOP, -1, 0, 0); +} + +//check if pointer is to typeinfo record and extract the type name from it +static GetTypeName(vtbl) +{ + auto x; + if (vtbl==BADADDR) + return; + x = DwordRel(vtbl+12); + if ((!x) || (x==BADADDR)) return ""; + return GetAsciizStr(x+16); +} + +static DwordCmt(x, cmt) +{ + if (x==BADADDR || x==0) + return; + ForceDword(x); + MakeComm(x, cmt); +} +static OffCmt(x, cmt) +{ + if (x==BADADDR || x==0) + return; + SoftOff(x); + if (cmt != "") + MakeComm(x, cmt); +} +static OffCmt64(x, cmt) +{ + if (x==BADADDR || x==0) + return; + SoftOff64(x); + MakeComm(x, cmt); +} +static StrCmt(x, cmt) +{ + auto save_str; + if (x==BADADDR || x==0) + return; + MakeUnkn(x, 0); + save_str = GetLongPrm(INF_STRTYPE); + SetLongPrm(INF_STRTYPE,0); + MakeStr(x, BADADDR); + MakeName(x, ""); + MakeComm(x, cmt); + SetLongPrm(INF_STRTYPE,save_str); +} +static DwordArrayCmt(x, n, cmt) +{ + if (x==BADADDR || x==0) + return; + Unknown(x,4*n); + ForceDword(x); + MakeArray(x,n); + MakeComm(x, cmt); +} + +//check if values match a pattern +static matchBytes(addr,match) +{ + auto i,len,s; + len = strlen(match); + if (len%2) + { + Warning("Bad match string in matchBytes: %s",match); + return 0; + } + i=0; + while (i 2147483647) x = x - 4294967296; + if (x<0) + { + sign = 1; + x = -x; + } + if (x==0) + return "A@"; + else if (x<=10) + return form("%s%d",sign?"?":"",x-1); + else + { + while (x>0) + { + s = form("%c%s",'A'+x%16,s); + x = x / 16; + } + return sign?"?":""+s+"@"; + } +} +static Parse_BCD(x, indent) +{ + auto indent_str,i,a,s; + if (x==BADADDR || x==0) + return; + indent_str="";i=0; + while(i0) //check numContainedBases + DumpNestedClass(a+4, indent+1, n); //nested classes following + a=a+4*(n+1); + i=i+n+1; + } +} + +static Parse_CHD(x, indent) +{ + auto indent_str,i,a,n,p,s; + if (x==BADADDR || x==0) + return; + indent_str="";i=0; + while(i ea ) // InSort + { + for ( ; idx != -1; idx = GetNextIndex(AR_LONG, id, idx) ) + { + val = GetArrayElement(AR_LONG, id, idx); + SetArrayLong(id, idx, ea); + ea = val; + } + } + } + SetArrayLong(id, GetLastIndex(AR_LONG, id) + 1, ea); +} +static getArraySize(id) +{ + auto idx, count; + count = 0; + for ( idx = GetFirstIndex(AR_LONG, id); idx != -1; idx = GetNextIndex(AR_LONG, id, idx) ) + { + count++; + } + return count; +} + +static doAddrList(name) +{ + auto idx, id, val, ctr, dtr; + id = GetArrayId("AddrList"); + ctr = 0; dtr = 0; + if ( name!=0 && id != -1 ) + { + if (getArraySize(id)!=2) + return; + for ( idx = GetFirstIndex(AR_LONG, id); idx != -1; idx = GetNextIndex(AR_LONG, id, idx) ) + { + val = GetArrayElement(AR_LONG, id, idx); + if (Byte(val)==0xE9) + val = getRelJmpTarget(val); + if ((substr(Name(val),0,3)=="??1")) + dtr = val; + else + ctr = val; + } + } + if (ctr!=0 && dtr!=0) + { + MakeName(ctr, MakeSpecialName(name,SN_constructor,0)); + } + DeleteArray(GetArrayId("AddrList")); +} + +//check if there's a vtable at a and dump into to f +//returns position after the end of vtable +static DoVtable(a) +{ + auto x,y,s,p,q,i,name; + + //check if it looks like a vtable + y = GetVtableSize(a); + if (y == 0) + return a+8; + + //check if it's named as a vtable + name = Name(a); + if (substr(name,0,4)!="??_7") name=0; + + x = Qword(a-8); + //otherwise try to get it from RTTI + if (IsValidCOL(x)) + { + Parse_Vtable(a); + if (name==0) + name = GetVtblName(x); + MakeName(a, name); + } + if (name!=0) + { + name = ".?AV"+substr(name, 4, strstr(name,"@@6B")+2); + } + { + DeleteArray(GetArrayId("AddrList")); + q = 0; i = 1; + for ( x=DfirstB(a); x != BADADDR; x=DnextB(a,x) ) + { + p = funcStart(x); + if (p!=-1) + { + if (q==p) + i++; + else + { + if (q) { + AddAddr(q); + } + i = 1; + q = p; + } + } + } + if (q) + { + AddAddr(q); + } + + x = a; + while (y>0) + { + p = Dword(x); + if (GetFunctionFlags(p) == -1) + { + MakeCode(p); + MakeFunction(p, BADADDR); + } + checkSDD(p,name,a,0); + y--; + x = x+8; + } + doAddrList(name); + } + return x; +} + +static scan_for_vtables(void) +{ + auto rmin, rmax, cmin, cmax, s, a, x, y; + s = FirstSeg(); + rmin = 0; rmax = 0; + while (s!=BADADDR) + { + if (SegName(s)==".rdata") + { + rmin = s; + rmax = NextSeg(s); + } + else if (SegName(s)==".text") + { + cmin = s; + cmax = NextSeg(s); + } + s = NextSeg(s); + } + if (rmin==0) {rmin=cmin; rmax=cmax;} + a = rmin; + while (a=cmin && x + //74 07 jz short @@no_free + //56 push esi + //E8 CA 2D 0D 00 call operator delete(void *) + //59 pop ecx + // @@no_free: + //8B C6 mov eax, esi + //5E pop esi + //C2 04 00 retn 4 + t = SN_scalardtr; + } + else if (matchBytes(x,"538A5C2408568BF1F6C302742B8B46FC578D7EFC68????????506A??56E8") || + matchBytes(x,"538A5C2408F6C302568BF1742E8B46FC5768????????8D7EFC5068????????56E8")) + { + //53 push ebx + //8A 5C 24 08 mov bl, [esp+arg_0] + //56 push esi + //8B F1 mov esi, ecx + //F6 C3 02 test bl, 2 + //74 2B jz short loc_100037F8 + //8B 46 FC mov eax, [esi-4] + //57 push edi + //8D 7E FC lea edi, [esi-4] + //68 xx xx xx xx push offset class::~class(void) + //50 push eax + //6A xx push xxh + //56 push esi + //E8 xx xx xx xx call `eh vector destructor iterator'(void *,uint,int,void (*)(void *)) + t = SN_vectordtr; + if (name!=0) + a = Dword(x+21); + if (gate && Byte(a)==0xE9) + { + a = getRelJmpTarget(a); + } + } + + if (t>0) + { + if (t==SN_vectordtr) + s = "vector"; + else + s = "scalar"; + if (name!=0) + MakeName(x, MakeSpecialName(name,t,0)); + if (a!=BADADDR) + { + if (name!=0) + MakeName(a, MakeSpecialName(name,SN_vdestructor,0)); + } + CommentStack(x, 4, "__flags$",-1); + } + return t; +} + +static main(void) +{ + scan_for_vtables(); +} \ No newline at end of file From 4fe55639be3d004d2226314d8023fbf7c4a6faec Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 7 May 2018 00:25:21 -0400 Subject: [PATCH 0952/1012] Update version/submodules to 0.44.10-alpha1 --- CMakeLists.txt | 6 +++--- docs/changelog.txt | 2 ++ library/xml | 2 +- scripts | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d1ded60b..3849ef462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,9 +140,9 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.44.09") -set(DFHACK_RELEASE "r1") -set(DFHACK_PRERELEASE FALSE) +set(DF_VERSION "0.44.10") +set(DFHACK_RELEASE "alpha1") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index 4cd3d3ab1..fe1b05f90 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +# 0.44.10-alpha1 + ## New Scripts - `gui/autogems`: a configuration UI for the `autogems` plugin diff --git a/library/xml b/library/xml index ba2871a84..03eedecfb 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ba2871a84f47b13f5616136be7b4af585fad0b31 +Subproject commit 03eedecfb3bb0eb3d71986e7e897770d9be0fad6 diff --git a/scripts b/scripts index 1a78d1ebf..a1cd002d6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1a78d1ebf9a53f06d4f91ca870baed7459955c4d +Subproject commit a1cd002d60fdbf7b4c4bf833db41ec691746552b From 554814920b090e7857a459f3e5fae61c55aa6727 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 8 May 2018 11:42:41 -0700 Subject: [PATCH 0953/1012] this allows the blueprint plugin to be called from lua --- plugins/blueprint.cpp | 42 +++++++++++++++++++++++++++++++++++++++ plugins/lua/blueprint.lua | 11 ++++++++++ 2 files changed, 53 insertions(+) create mode 100644 plugins/lua/blueprint.lua diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 6ef56722a..9742b16e7 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -6,6 +6,7 @@ #include #include +#include "LuaTools.h" #include "modules/Buildings.h" #include "modules/Gui.h" @@ -687,3 +688,44 @@ command_result blueprint(color_ostream &out, vector ¶meters) option |= QUERY; return do_transform(start, end, parameters[3], option); } + +static int create(lua_State *L, uint32_t options) { + df::coord start, end; + + lua_settop(L, 3); + Lua::CheckDFAssign(L, &start, 1); + if (!start.isValid()) + luaL_argerror(L, 1, "invalid start position"); + Lua::CheckDFAssign(L, &end, 2); + if (!end.isValid()) + luaL_argerror(L, 2, "invalid end position"); + string filename(lua_tostring(L, 3)); + + lua_pushboolean(L, do_transform(start, end, filename, options)); + return 1; + +} + +static int dig(lua_State *L) { + return create(L, DIG); +} + +static int build(lua_State *L) { + return create(L, BUILD); +} + +static int place(lua_State *L) { + return create(L, PLACE); +} + +static int query(lua_State *L) { + return create(L, QUERY); +} + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(dig), + DFHACK_LUA_COMMAND(build), + DFHACK_LUA_COMMAND(place), + DFHACK_LUA_COMMAND(query), + DFHACK_LUA_END +}; diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua new file mode 100644 index 000000000..d2cd8cf15 --- /dev/null +++ b/plugins/lua/blueprint.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.blueprint') + +--[[ + + Native functions: + + * paint(pos,brush,paint,amount,size,setmode,flowmode) + +--]] + +return _ENV From d15f87715e30af58645d6f87b3c83d654b4baed1 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 8 May 2018 13:04:14 -0700 Subject: [PATCH 0954/1012] update comments --- plugins/lua/blueprint.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index d2cd8cf15..98f24f487 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -4,7 +4,10 @@ local _ENV = mkmodule('plugins.blueprint') Native functions: - * paint(pos,brush,paint,amount,size,setmode,flowmode) + * dig(start, end, name) + * build(start, end, name) + * place(start, end, name) + * query(start, end, name) --]] From 1eac476db74db7ea76468eb536ec1209685a2296 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 8 May 2018 16:04:35 -0700 Subject: [PATCH 0955/1012] add a note to the docs --- docs/Lua API.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index d79924cc1..2429efde1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3349,6 +3349,19 @@ module file is still necessary for ``require`` to read. The following plugins have lua support. +blueprint +========= + +Native functions: + +* ``dig(start, end, name)`` +* ``build(start, end, name)`` +* ``place(start, end, name)`` +* ``query(start, end, name)`` + + ``start`` and ``end`` are tables containing positions (see + ``xyz2pos``). ``name`` is used as the basis for the filename. + burrows ======= From 2a52582c1f0305ae26d3931a50b276763681ef89 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 10:23:05 -0400 Subject: [PATCH 0956/1012] Add Gui::inRenameBuilding --- library/LuaApi.cpp | 1 + library/include/modules/Gui.h | 2 ++ library/modules/Gui.cpp | 18 ++++++++++++++---- plugins/buildingplan.cpp | 4 +--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 852f4e29d..b78d621ce 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1493,6 +1493,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, resetDwarfmodeView), WRAPM(Gui, revealInDwarfmodeMap), WRAPM(Gui, refreshSidebar), + WRAPM(Gui, inRenameBuilding), WRAPM(Gui, getDepthAt), { NULL, NULL } }; diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index ec5d1ea94..819ad1558 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -150,6 +150,8 @@ namespace DFHack DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false); DFHACK_EXPORT bool refreshSidebar(); + DFHACK_EXPORT bool inRenameBuilding(); + DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z); DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 53bacb129..271aedcaf 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -111,14 +111,16 @@ using namespace DFHack; #include "df/world.h" using namespace df::enums; + +using df::global::gamemode; +using df::global::gps; using df::global::gview; using df::global::init; -using df::global::gps; -using df::global::ui; -using df::global::world; using df::global::selection_rect; +using df::global::ui; using df::global::ui_menu_width; -using df::global::gamemode; +using df::global::ui_sidebar_menus; +using df::global::world; static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx) { @@ -1757,6 +1759,14 @@ bool Gui::refreshSidebar() return false; } +bool Gui::inRenameBuilding() +{ + if (!ui_sidebar_menus) + return false; + + return ui_sidebar_menus->barracks.in_rename; +} + bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) { x = *df::global::window_x; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index eb6d2a661..37bb069a4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,11 +1,9 @@ #include "buildingplan-lib.h" -#include "df/ui_sidebar_menus.h" DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.14 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); -REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_shutdown ( color_ostream &out ) @@ -188,7 +186,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (isInNobleRoomQueryMode()) { - if (ui_sidebar_menus->barracks.in_rename) + if (Gui::inRenameBuilding()) return false; auto np = getNoblePositionOfSelectedBuildingOwner(); df::interface_key last_token = get_string_key(input); From ead0092765f5053ac6d4c1f78e5d8fdbe1549b29 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 10:25:55 -0400 Subject: [PATCH 0957/1012] Ignore added stockpile keys when renaming stockpiles Fixes #1267 --- plugins/autodump.cpp | 3 +++ plugins/automelt.cpp | 3 +++ plugins/autotrade.cpp | 3 +++ plugins/stockpiles/stockpiles.cpp | 3 +++ plugins/stocks.cpp | 3 +++ 5 files changed, 15 insertions(+) diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 3fbe1ae77..b70c7f83c 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -181,6 +181,9 @@ struct dump_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index b4c93e15f..c01981603 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -194,6 +194,9 @@ struct melt_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index a645d579e..c4257c195 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -389,6 +389,9 @@ struct trade_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index d24a20a7a..ae3df03d1 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -376,6 +376,9 @@ struct stockpiles_import_hook : public df::viewscreen_dwarfmodest bool handleInput ( set *input ) { + if ( Gui::inRenameBuilding() ) + return false; + df::building_stockpilest *sp = get_selected_stockpile(); if ( !sp ) return false; diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 154a58dc3..83eb01c4e 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1451,6 +1451,9 @@ struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + df::building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; From 2ada907420c10ba15b2a4de6270f86478ff694ab Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 10:33:41 -0400 Subject: [PATCH 0958/1012] Move stockpiles option (load/save) below stocks/autodump/etc Fixes #1161 --- plugins/stockpiles/stockpiles.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index ae3df03d1..51d4ccb53 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -409,7 +409,7 @@ struct stockpiles_import_hook : public df::viewscreen_dwarfmodest auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - int y = dims.y2 - 7; + int y = dims.y2 - 3; int links = 0; links += sp->links.give_to_pile.size(); From b95e8f433ade53fefcc4ff3d3a76de15e7008033 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 10:37:03 -0400 Subject: [PATCH 0959/1012] Update changelog, scripts --- docs/changelog.txt | 10 ++++++++++ scripts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index fe1b05f90..beaa1665a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,16 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## New Scripts +- `devel/find-primitive`: Finds a primitive variable in memory + +## Fixes +- `stockpiles`: stopped sidebar option from overlapping with `autodump` +-@ `autodump`, `automelt`, `autotrade`, `stocks`, `stockpiles`: fixed conflict with building renaming + +## Internals +- Added ``Gui::inRenameBuilding()`` + # 0.44.10-alpha1 ## New Scripts diff --git a/scripts b/scripts index a1cd002d6..313655344 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a1cd002d60fdbf7b4c4bf833db41ec691746552b +Subproject commit 313655344562956f2c3a2c3d547ac04816730306 From cd717a4fe8676bac94e753aa21b906a819d23581 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 10:39:14 -0400 Subject: [PATCH 0960/1012] Fix stockpiles header in Plugins.rst --- docs/Plugins.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index d8cfdd270..49c86ca8a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -721,8 +721,8 @@ Replaces the DF stocks screen with an improved version. .. _stocksettings: .. _stockpiles: -stocksettings -============= +stockpiles +========== Offers the following commands to save and load stockpile settings. See `gui/stockpiles` for an in-game interface. From a550c81628c79d84ee8a1f7f5e14677f593a7774 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 9 May 2018 18:25:26 -0700 Subject: [PATCH 0961/1012] update the CMakeList to link the blueprint plugin against the lua library --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 231ff656d..e8e1b6bc8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -91,7 +91,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(automaterial automaterial.cpp) DFHACK_PLUGIN(automelt automelt.cpp) DFHACK_PLUGIN(autotrade autotrade.cpp) - DFHACK_PLUGIN(blueprint blueprint.cpp) + DFHACK_PLUGIN(blueprint blueprint.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(buildingplan buildingplan.cpp LINK_LIBRARIES buildingplan-lib) From 5b2cc7dc6eaa1e9d82c9ea48a669e7f91d602fe0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 16:14:50 -0400 Subject: [PATCH 0962/1012] Linux: Check for unresolved symbols at link time --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3849ef462..c45f22f1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,8 @@ if(APPLE) set(CMAKE_MACOSX_RPATH 1) elseif(UNIX) add_definitions(-D_LINUX) + # Check for unresolved symbols at link time + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-z,defs") elseif(WIN32) add_definitions(-DWIN32) endif() From 77ee74be50d673fef1b4b3aecc907e0b15fb27cd Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 21:15:22 -0400 Subject: [PATCH 0963/1012] Only require symbols to be defined in plugins Core uses some unresolved symbols on Linux --- CMakeLists.txt | 2 -- plugins/Plugins.cmake | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c45f22f1e..3849ef462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,8 +217,6 @@ if(APPLE) set(CMAKE_MACOSX_RPATH 1) elseif(UNIX) add_definitions(-D_LINUX) - # Check for unresolved symbols at link time - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-z,defs") elseif(WIN32) add_definitions(-DWIN32) endif() diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index b97db055d..bf1ebd88b 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -6,6 +6,11 @@ IF(UNIX) IF(DFHACK_BUILD_64) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -mno-avx") + IF(NOT APPLE) + # Linux: Check for unresolved symbols at link time + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-z,defs") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-z,defs") + ENDIF() ELSE() SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") From 8e76c612f6fb9e1637ff6c6510ab144b3fecb4db Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 21:45:41 -0400 Subject: [PATCH 0964/1012] Add SDL to RFR libs on Linux --- plugins/remotefortressreader/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index f47f03c5f..571098e18 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -33,6 +33,9 @@ IF(UNIX) # add any extra linux libs here ${PROJECT_LIBS} ) + IF(NOT APPLE) + SET(PROJECT_LIBS ${PROJECT_LIBS} SDL) + ENDIF() # windows ELSE(UNIX) SET(PROJECT_LIBS From 1bb90f6436c6dd9399a9ebd056764dcff00df543 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 9 May 2018 22:47:42 -0400 Subject: [PATCH 0965/1012] Update required libs for Linux in Compile.rst --- docs/Compile.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index e9ddc157d..7d7dbeccd 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -124,8 +124,9 @@ Before you can build anything, you'll also need ``cmake``. It is advisable to also get ``ccmake`` on distributions that split the cmake package into multiple parts. -You also need perl and the XML::LibXML and XML::LibXSLT perl packages (for the code generation parts). -You should be able to find them in your distro repositories. +You also need zlib, libsdl (1.2, not sdl2, like DF), perl, and the XML::LibXML +and XML::LibXSLT perl packages (for the code generation parts). You should be +able to find them in your distro repositories. To build `stonesense`, you'll also need OpenGL headers. @@ -137,7 +138,7 @@ Here are some package install commands for various platforms: * On Ubuntu:: - apt-get install gcc cmake git zlib1g-dev libxml-libxml-perl libxml-libxslt-perl + apt-get install gcc cmake git zlib1g-dev libsdl1.2-dev libxml-libxml-perl libxml-libxslt-perl * Debian and derived distros should have similar requirements to Ubuntu. From 58636f5a27d8b6ba846e9629bb0a4bc4386588c8 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 10 May 2018 11:20:19 -0500 Subject: [PATCH 0966/1012] Make 32-bit Linux builds also check for undefined symbols. --- plugins/Plugins.cmake | 10 +++++----- plugins/stonesense | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index bf1ebd88b..905752219 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -6,15 +6,15 @@ IF(UNIX) IF(DFHACK_BUILD_64) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -mno-avx") - IF(NOT APPLE) - # Linux: Check for unresolved symbols at link time - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-z,defs") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-z,defs") - ENDIF() ELSE() SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") ENDIF() + IF(NOT APPLE) + # Linux: Check for unresolved symbols at link time + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-z,defs") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-z,defs") + ENDIF() ENDIF() include_directories("${dfhack_SOURCE_DIR}/library/include") diff --git a/plugins/stonesense b/plugins/stonesense index 6d38bf884..b5c02d8a9 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 6d38bf884eef435d99df5c4e27b3b36ac6f2ec04 +Subproject commit b5c02d8a979c6ff44640dbc76ae9bb8a4eb58708 From 272cdcb2b544258da35c088d9a72b32b96e4b995 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 09:49:27 -0400 Subject: [PATCH 0967/1012] Add function names to NullPointer and InvalidArgument exceptions --- library/Error.cpp | 8 ++++---- library/include/Error.h | 16 +++++++++------- library/include/MiscUtils.h | 8 ++++++++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/library/Error.cpp b/library/Error.cpp index ba1482918..3cd0eb31d 100644 --- a/library/Error.cpp +++ b/library/Error.cpp @@ -8,13 +8,13 @@ inline std::string safe_str(const char *s) return s ? s : "(NULL)"; } -NullPointer::NullPointer(const char *varname) - :All("NULL pointer: " + safe_str(varname)), +NullPointer::NullPointer(const char *varname, const char *func) + :All("In " + safe_str(func) + ": NULL pointer: " + safe_str(varname)), varname(varname) {} -InvalidArgument::InvalidArgument(const char *expr) - :All("Invalid argument; expected: " + safe_str(expr)), +InvalidArgument::InvalidArgument(const char *expr, const char *func) + :All("In " + safe_str(func) + ": Invalid argument; expected: " + safe_str(expr)), expr(expr) {} diff --git a/library/include/Error.h b/library/include/Error.h index d3d208b1f..29564d69d 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -24,11 +24,13 @@ distribution. #pragma once +#include +#include +#include + #include "Export.h" +#include "MiscUtils.h" #include "Pragma.h" -#include -#include -#include namespace DFHack { @@ -68,20 +70,20 @@ namespace DFHack class DFHACK_EXPORT NullPointer : public All { public: const char *const varname; - NullPointer(const char *varname = NULL); + NullPointer(const char *varname = NULL, const char *func = NULL); }; #define CHECK_NULL_POINTER(var) \ - { if (var == NULL) throw DFHack::Error::NullPointer(#var); } + { if (var == NULL) throw DFHack::Error::NullPointer(#var, DFHACK_FUNCTION_SIG); } class DFHACK_EXPORT InvalidArgument : public All { public: const char *const expr; - InvalidArgument(const char *expr = NULL); + InvalidArgument(const char *expr = NULL, const char *func = NULL); }; #define CHECK_INVALID_ARGUMENT(expr) \ - { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); } + { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr, DFHACK_FUNCTION_SIG); } class DFHACK_EXPORT VTableMissing : public All { public: diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 917b67489..378b7a728 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -36,6 +36,14 @@ using std::ostream; using std::stringstream; using std::endl; +#if defined(_MSC_VER) + #define DFHACK_FUNCTION_SIG __FUNCSIG__ +#elif defined(__GNUC__) + #define DFHACK_FUNCTION_SIG __PRETTY_FUNCTION__ +#else + #define DFHACK_FUNCTION_SIG __func__ +#endif + template void print_bits ( T val, ostream& out ) { From 10abece564dd9205340405b3a84ab20bfa9521e8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 09:58:40 -0400 Subject: [PATCH 0968/1012] Fix duplicate condition in Gui::getAnyUnit() --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 271aedcaf..ee604fb22 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1024,7 +1024,7 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) switch (ui->main.mode) { case ViewUnits: { - if (!ui_selected_unit || !ui_selected_unit) + if (!ui_selected_unit || !world) return NULL; return vector_get(world->units.active, *ui_selected_unit); From ceaa518be9d7235bc56706b4c29bb77f149f9bc3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 10:04:10 -0400 Subject: [PATCH 0969/1012] Fix a potential segfault in getAnyUnit See #1218 --- library/modules/Gui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index ee604fb22..fe8333c41 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1032,10 +1032,10 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) case ZonesPitInfo: // (i) zone -> (P)it case ZonesPenInfo: // (i) zone -> pe(N) { - if (ui_building_assign_units || ui_building_item_cursor) - return vector_get(*ui_building_assign_units, *ui_building_item_cursor); + if (!ui_building_assign_units || !ui_building_item_cursor) + return NULL; - return NULL; + return vector_get(*ui_building_assign_units, *ui_building_item_cursor); } case Burrows: { From 9d353c1ea72f43a909613d62ef962544944f9e22 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 10:21:05 -0400 Subject: [PATCH 0970/1012] tweak block-labors: fix crash if unit is null Fixes #1057 --- plugins/tweak/tweaks/block-labors.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/tweak/tweaks/block-labors.h b/plugins/tweak/tweaks/block-labors.h index d90c97506..8243afbf6 100644 --- a/plugins/tweak/tweaks/block-labors.h +++ b/plugins/tweak/tweaks/block-labors.h @@ -16,12 +16,13 @@ struct block_labors_hook : df::viewscreen_dwarfmodest { inline bool valid_mode() { return ui->main.mode == df::ui_sidebar_mode::ViewUnits && - ui_unit_view_mode->value == df::ui_unit_view_mode::T_value::PrefLabor; + ui_unit_view_mode->value == df::ui_unit_view_mode::T_value::PrefLabor && + Gui::getAnyUnit(this); } inline bool forbidden_labor (df::unit *unit, df::unit_labor labor) { - return is_valid_enum_item(labor) && !Units::isValidLabor(unit, labor); + return is_valid_enum_item(labor) && unit && !Units::isValidLabor(unit, labor); } inline bool all_labors_enabled (df::unit *unit, df::unit_labor_category cat) From d51a1d700c3b764d48512be594257736332832ad Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 10:35:26 -0400 Subject: [PATCH 0971/1012] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 313655344..eddfb8e1c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 313655344562956f2c3a2c3d547ac04816730306 +Subproject commit eddfb8e1c97bef7af86583caaeb536fb11c80c80 From 759df34fceb17e02333a779f76d4c74639e7a994 Mon Sep 17 00:00:00 2001 From: Dan Amlund Date: Fri, 11 May 2018 17:48:19 +0200 Subject: [PATCH 0972/1012] fix potential segfault --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index fe8333c41..758395f40 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -955,7 +955,7 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_announcelistst, top)) { - if (screen->unit) { + if (world && screen->unit) { // in (r)eports -> enter auto *report = vector_get(screen->reports, screen->sel_idx); if (report) From 55181bb81c5f783baa4fe7948d9478514da98cb0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 18:50:26 -0400 Subject: [PATCH 0973/1012] Add Bumber to authors dfhack/scripts#54 --- docs/Authors.rst | 1 + scripts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index 2ebddff1a..e18ac377b 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -23,6 +23,7 @@ belal jimhester Ben Lubar BenLubar Ben Rosser TC01 brndd brndd +Bumber Bumber64 Caldfir caldfir Carter Bray Qartar Chris Dombroski cdombroski diff --git a/scripts b/scripts index eddfb8e1c..9504fc075 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit eddfb8e1c97bef7af86583caaeb536fb11c80c80 +Subproject commit 9504fc075ee04948d146834e15a79d17107cf873 From 018deec91250a17717e41af2d8dd9181c6c3826d Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 11 May 2018 22:18:26 -0400 Subject: [PATCH 0974/1012] Fix another occasional segfault in block-labors Fixes #1057 --- plugins/tweak/tweaks/block-labors.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/tweak/tweaks/block-labors.h b/plugins/tweak/tweaks/block-labors.h index 8243afbf6..bc407ed19 100644 --- a/plugins/tweak/tweaks/block-labors.h +++ b/plugins/tweak/tweaks/block-labors.h @@ -75,12 +75,12 @@ struct block_labors_hook : df::viewscreen_dwarfmodest { 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); + df::unit *unit = Gui::getAnyUnit(this); + df::unit_labor labor = vector_get(unit_labors_sidemenu, *ui_look_cursor, df::unit_labor::NONE); + df::unit_labor_category cat = df::unit_labor_category(labor); + if (valid_mode() && labor != df::unit_labor::NONE) + { if ((input->count(SELECT) || input->count(SELECT_ALL)) && forbidden_labor(unit, labor)) { unit->status.labors[labor] = false; From 5742e1868a04881a36ae41437e6309e6cf1bc723 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 00:07:45 -0400 Subject: [PATCH 0975/1012] Update many things for 0.44.10-beta1 Including authors update for #1265 --- CMakeLists.txt | 2 +- docs/Authors.rst | 1 + docs/changelog.txt | 29 ++++++++++++++++++++++++++++- scripts | 2 +- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3849ef462..1a7d2e2bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.10") -set(DFHACK_RELEASE "alpha1") +set(DFHACK_RELEASE "beta1") set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/Authors.rst b/docs/Authors.rst index e18ac377b..d3a98a59c 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -30,6 +30,7 @@ Chris Dombroski cdombroski Clayton Hughes Clément Vuchener cvuchener Dan Amlund danamlund +Daniel Brooks db48x David Corbett dscorbett David Seguin dseguin David Timm dtimm diff --git a/docs/changelog.txt b/docs/changelog.txt index beaa1665a..cc6bb191a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,15 +37,42 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +# 0.44.10-beta1 + ## New Scripts -- `devel/find-primitive`: Finds a primitive variable in memory +- `devel/find-primitive`: finds a primitive variable in memory ## Fixes +- Units::getAnyUnit(): fixed a couple problematic conditions and potential segfaults if global addresses are missing - `stockpiles`: stopped sidebar option from overlapping with `autodump` -@ `autodump`, `automelt`, `autotrade`, `stocks`, `stockpiles`: fixed conflict with building renaming +- `tweak` block-labors: fixed two causes of crashes related in the v-p-l menu +- `full-heal`: + - units no longer have a tendency to melt after being healed + - healed units are no longer treated as patients by hospital staff + - healed units no longer attempt to clean themselves unsuccessfully + - wounded fliers now regain the ability to fly upon being healing + - now heals suffocation, numbness, infection, spilled guts and gelding +- `modtools/create-unit`: + - creatures of the appropriate age are now spawned as babies or children where applicable + - fix: civ_id is now properly assigned to historical_figure, resolving several hostility issues (spawned pets are no longer attacked by fortress military!) + - fix: unnamed creatures are no longer spawned with a string of numbers as a first name +- `exterminate`: fixed documentation of ``this`` option + +## Misc Improvements +- `blueprint`: added a basic Lua API +- `devel/export-dt-ini`: added tool offsets for DT 40 +- `devel/save-version`: added current DF version +- `install-info`: added information on tweaks ## Internals - Added ``Gui::inRenameBuilding()`` +- Added function names to DFHack's NullPointer and InvalidArgument exceptions +- Linux: required plugins to have symbols resolved at link time, for consistency with other platforms + +## Structures +- ``item_flags2``: renamed ``has_written_content`` to ``unk_book`` +- ``world_data.feature_map``: added documentation (in XML) # 0.44.10-alpha1 diff --git a/scripts b/scripts index 9504fc075..f76e41744 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9504fc075ee04948d146834e15a79d17107cf873 +Subproject commit f76e41744d0479d2e390b8092726df5534c26acf From a0ea7567899325120136f2bfca2a3bb71bfb44c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 11:02:34 -0400 Subject: [PATCH 0976/1012] Add logo to docs --- conf.py | 2 +- docs/styles/dfhack-logo.png | Bin 0 -> 13900 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/styles/dfhack-logo.png diff --git a/conf.py b/conf.py index 618e35bfb..bfd69d828 100644 --- a/conf.py +++ b/conf.py @@ -291,7 +291,7 @@ html_style = 'dfhack.css' # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - #'logo': 'logo.png', + 'logo': 'dfhack-logo.png', 'github_user': 'DFHack', 'github_repo': 'dfhack', 'github_button': False, diff --git a/docs/styles/dfhack-logo.png b/docs/styles/dfhack-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a617816bff16ef8671614e371931a8cfc98e01 GIT binary patch literal 13900 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 AC;$Ke literal 0 HcmV?d00001 From a34b34d3cbae47ecf2e0f2760b3ffecc53aac73b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 11:51:36 -0400 Subject: [PATCH 0977/1012] Move a few item functions from autotrade/stocks into DFHack::Items --- docs/Lua API.rst | 15 ++++++ docs/changelog.txt | 6 +++ docs/gen_changelog.py | 10 +++- library/LuaApi.cpp | 3 ++ library/include/modules/Items.h | 8 ++++ library/modules/Items.cpp | 84 ++++++++++++++++++++++++++++++++ library/xml | 2 +- plugins/autotrade.cpp | 32 ++----------- plugins/stocks.cpp | 85 +-------------------------------- 9 files changed, 130 insertions(+), 115 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2429efde1..be935c822 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1290,6 +1290,21 @@ Items module Calculates the Basic Value of an item, as seen in the View Item screen. +* ``dfhack.items.createItem(item_type, item_subtype, mat_type, mat_index, unit)`` + + Creates an item, similar to the `createitem` plugin. + +* ``dfhack.items.checkMandates(item)`` + + Returns true if the item is free from mandates, or false if mandates prevent trading the item. + +* ``dfhack.items.canTrade(item)`` + + Checks whether the item can be traded. + +* ``dfhack.items.canTradeWithContents(item)`` + + Checks whether the item and all items it contains, if any, can be traded. Maps module ----------- diff --git a/docs/changelog.txt b/docs/changelog.txt index cc6bb191a..b5fa62a9f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## API +- New functions (all available to Lua as well): + - ``Items::checkMandates()`` + - ``Items::canTrade()`` + - ``Items::canTradeWithContents()`` + # 0.44.10-beta1 ## New Scripts diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index 5456be6bb..89db98052 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -13,6 +13,7 @@ CHANGELOG_SECTIONS = [ 'Fixes', 'Misc Improvements', 'Removed', + 'API', 'Internals', 'Structures', 'Lua', @@ -23,6 +24,12 @@ REPLACEMENTS = { '`search`': '`search-plugin`', } +def to_title_case(word): + if word == word.upper(): + # Preserve acronyms + return word + return word[0].upper() + word[1:].lower() + def find_all_indices(string, substr): start = 0 while True: @@ -54,8 +61,7 @@ class ChangelogEntry(object): def __init__(self, text, section, stable_version, dev_version): text = text.lstrip('- ') # normalize section to title case - self.section = ' '.join(word[0].upper() + word[1:].lower() - for word in section.strip().split()) + self.section = ' '.join(map(to_title_case, section.strip().split())) self.stable_version = stable_version self.dev_version = dev_version self.dev_only = text.startswith('@') diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b78d621ce..9e5ddd1ea 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1755,6 +1755,9 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getItemBaseValue), WRAPM(Items, getValue), WRAPM(Items, createItem), + WRAPM(Items, checkMandates), + WRAPM(Items, canTrade), + WRAPM(Items, canTradeWithContents), WRAPN(moveToGround, items_moveToGround), WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToInventory, items_moveToInventory), diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index d77b5b369..a09900ddf 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -182,6 +182,14 @@ DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int1 DFHACK_EXPORT int getValue(df::item *item); DFHACK_EXPORT int32_t createItem(df::item_type type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, df::unit* creator); + +/// Returns true if the item is free from mandates, or false if mandates prevent trading the item +DFHACK_EXPORT bool checkMandates(df::item *item); +/// Checks whether the item can be traded +DFHACK_EXPORT bool canTrade(df::item *item); +/// Checks whether the item and all items it contains, if any, can be traded +DFHACK_EXPORT bool canTradeWithContents(df::item *item); + } } diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 654345e34..5c08065f9 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -75,6 +75,7 @@ using namespace std; #include "df/itemdef_trapcompst.h" #include "df/itemdef_weaponst.h" #include "df/job_item.h" +#include "df/mandate.h" #include "df/map_block.h" #include "df/proj_itemst.h" #include "df/proj_list_link.h" @@ -1401,3 +1402,86 @@ int32_t Items::createItem(df::item_type item_type, int16_t item_subtype, int16_t return out_items[0]->id; } +/* + * Trade Info + */ + +bool Items::checkMandates(df::item *item) +{ + CHECK_NULL_POINTER(item); + + for (df::mandate *mandate : world->mandates) + { + if (mandate->mode != df::mandate::T_mode::Export) + continue; + + if (item->getType() != mandate->item_type || + (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) + continue; + + if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) + continue; + + if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) + continue; + + return false; + } + + return true; +} + +bool Items::canTrade(df::item *item) +{ + CHECK_NULL_POINTER(item); + + if (item->flags.bits.owned || item->flags.bits.artifact || item->flags.bits.spider_web || item->flags.bits.in_job) + return false; + + for (df::general_ref *ref : item->general_refs) + { + switch (ref->getType()) + { + case general_ref_type::UNIT_HOLDER: + return false; + + case general_ref_type::BUILDING_HOLDER: + return false; + + default: + break; + } + } + + for (df::specific_ref *ref : item->specific_refs) + { + if (ref->type == specific_ref_type::JOB) + { + // Ignore any items assigned to a job + return false; + } + } + + return checkMandates(item); +} + +bool Items::canTradeWithContents(df::item *item) +{ + CHECK_NULL_POINTER(item); + + if (item->flags.bits.in_inventory) + return false; + + if (!canTrade(item)) + return false; + + vector contained_items; + getContainedItems(item, &contained_items); + for (df::item *cit : contained_items) + { + if (!canTrade(cit)) + return false; + } + + return true; +} diff --git a/library/xml b/library/xml index 03eedecfb..6c4020d5a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 03eedecfb3bb0eb3d71986e7e897770d9be0fad6 +Subproject commit 6c4020d5a0ebca365fe3074087110cc8b0f12168 diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index c4257c195..ca3f456ec 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -134,33 +134,9 @@ static TradeDepotInfo depot_info; * Item Manipulation */ -static bool check_mandates(df::item *item) -{ - for (auto it = world->mandates.begin(); it != world->mandates.end(); it++) - { - auto mandate = *it; - - if (mandate->mode != 0) - continue; - - if (item->getType() != mandate->item_type || - (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) - continue; - - if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) - continue; - - if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) - continue; - - return false; - } - - return true; -} - static bool is_valid_item(df::item *item) { + // Similar to Items::canTrade() with a few checks changed for (size_t i = 0; i < item->general_refs.size(); i++) { df::general_ref *ref = item->general_refs[i]; @@ -192,7 +168,7 @@ static bool is_valid_item(df::item *item) } } - if (!check_mandates(item)) + if (!Items::checkMandates(item)) return false; return true; @@ -236,9 +212,9 @@ static void mark_all_in_stockpiles(vector &stockpiles) bool mandates_ok = true; vector contained_items; Items::getContainedItems(item, &contained_items); - for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++) + for (df::item *cit : contained_items) { - if (!check_mandates(*cit)) + if (!Items::checkMandates(cit)) { mandates_ok = false; break; diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 83eb01c4e..a533f4669 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -72,89 +72,6 @@ static df::item *get_container_of(df::unit *unit) * Trade Info */ -static bool check_mandates(df::item *item) -{ - for (auto it = world->mandates.begin(); it != world->mandates.end(); it++) - { - auto mandate = *it; - - if (mandate->mode != 0) - continue; - - if (item->getType() != mandate->item_type || - (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) - continue; - - if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) - continue; - - if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) - continue; - - return false; - } - - return true; -} - -static bool can_trade_item(df::item *item) -{ - if (item->flags.bits.owned || item->flags.bits.artifact || item->flags.bits.spider_web || item->flags.bits.in_job) - return false; - - for (size_t i = 0; i < item->general_refs.size(); i++) - { - df::general_ref *ref = item->general_refs[i]; - - switch (ref->getType()) - { - case general_ref_type::UNIT_HOLDER: - return false; - - case general_ref_type::BUILDING_HOLDER: - return false; - - default: - break; - } - } - - for (size_t i = 0; i < item->specific_refs.size(); i++) - { - df::specific_ref *ref = item->specific_refs[i]; - - if (ref->type == specific_ref_type::JOB) - { - // Ignore any items assigned to a job - return false; - } - } - - return check_mandates(item); -} - -static bool can_trade_item_and_container(df::item *item) -{ - item = get_container_of(item); - - if (item->flags.bits.in_inventory) - return false; - - if (!can_trade_item(item)) - return false; - - vector contained_items; - Items::getContainedItems(item, &contained_items); - for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++) - { - if (!can_trade_item(*cit)) - return false; - } - - return true; -} - - class TradeDepotInfo { public: @@ -185,7 +102,7 @@ public: { auto item = *it; item = get_container_of(item); - if (!can_trade_item_and_container(item)) + if (!Items::canTradeWithContents(item)) return false; auto href = df::allocate(); From abb903d28f6c58e557e9e6ecbe840274c96851e1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 13:02:52 -0400 Subject: [PATCH 0978/1012] command-prompt: support getSelectedPlant() --- plugins/command-prompt.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index b38961224..449da0e80 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -54,6 +54,7 @@ public: df::unit* getSelectedUnit() { return Gui::getAnyUnit(parent); } df::item* getSelectedItem() { return Gui::getAnyItem(parent); } df::building* getSelectedBuilding() { return Gui::getAnyBuilding(parent); } + df::plant* getSelectedPlant() { return Gui::getAnyPlant(parent); } std::string getFocusString() { return "commandprompt"; } viewscreen_commandpromptst(std::string entry):submitted(false), is_response(false) From c15571caa02591067432e2cd91ef7fd1419446bc Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 14:49:30 -0400 Subject: [PATCH 0979/1012] Move isRouteVehicle, isSquadEquipment from workflow to Items module --- docs/Lua API.rst | 8 ++++++++ docs/changelog.txt | 2 ++ library/LuaApi.cpp | 2 ++ library/include/modules/Items.h | 5 +++++ library/modules/Items.cpp | 21 +++++++++++++++++++++ plugins/workflow.cpp | 20 ++------------------ 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index be935c822..7eacd7b96 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1306,6 +1306,14 @@ Items module Checks whether the item and all items it contains, if any, can be traded. +* ``dfhack.items.isRouteVehicle(item)`` + + Checks whether the item is an assigned hauling vehicle. + +* ``dfhack.items.isSquadEquipment(item)`` + + Checks whether the item is assigned to a squad. + Maps module ----------- diff --git a/docs/changelog.txt b/docs/changelog.txt index b5fa62a9f..0fefa4b8c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``Items::checkMandates()`` - ``Items::canTrade()`` - ``Items::canTradeWithContents()`` + - ``Items::isRouteVehicle()`` + - ``Items::isSquadEquipment()`` # 0.44.10-beta1 diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 9e5ddd1ea..af71e729b 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1758,6 +1758,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, checkMandates), WRAPM(Items, canTrade), WRAPM(Items, canTradeWithContents), + WRAPM(Items, isRouteVehicle), + WRAPM(Items, isSquadEquipment), WRAPN(moveToGround, items_moveToGround), WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToInventory, items_moveToInventory), diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index a09900ddf..776e935eb 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -190,6 +190,11 @@ DFHACK_EXPORT bool canTrade(df::item *item); /// Checks whether the item and all items it contains, if any, can be traded DFHACK_EXPORT bool canTradeWithContents(df::item *item); +/// Checks whether the item is an assigned hauling vehicle +DFHACK_EXPORT bool isRouteVehicle(df::item *item); +/// Checks whether the item is assigned to a squad +DFHACK_EXPORT bool isSquadEquipment(df::item *item); + } } diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 5c08065f9..a1b4ace4d 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -85,6 +85,7 @@ using namespace std; #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/vehicle.h" #include "df/vermin.h" #include "df/viewscreen_itemst.h" #include "df/world.h" @@ -1485,3 +1486,23 @@ bool Items::canTradeWithContents(df::item *item) return true; } + +bool Items::isRouteVehicle(df::item *item) +{ + CHECK_NULL_POINTER(item); + int id = item->getVehicleID(); + if (id < 0) return false; + + auto vehicle = df::vehicle::find(id); + return vehicle && vehicle->route_id >= 0; +} + +bool Items::isSquadEquipment(df::item *item) +{ + CHECK_NULL_POINTER(item); + if (!ui) + return false; + + auto &vec = ui->equipment.items_assigned[item->getType()]; + return binsearch_index(vec, &df::item::id, item->id) >= 0; +} diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index bfc4b639f..8396a974e 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -40,7 +40,6 @@ #include "df/plant_raw.h" #include "df/inorganic_raw.h" #include "df/builtin_mats.h" -#include "df/vehicle.h" using std::vector; using std::string; @@ -1159,21 +1158,6 @@ static bool itemInRealJob(df::item *item) != job_type_class::Hauling; } -static bool isRouteVehicle(df::item *item) -{ - int id = item->getVehicleID(); - if (id < 0) return false; - - auto vehicle = df::vehicle::find(id); - return vehicle && vehicle->route_id >= 0; -} - -static bool isAssignedSquad(df::item *item) -{ - auto &vec = ui->equipment.items_assigned[item->getType()]; - return binsearch_index(vec, &df::item::id, item->id) >= 0; -} - static void map_job_items(color_ostream &out) { for (size_t i = 0; i < constraints.size(); i++) @@ -1288,10 +1272,10 @@ static void map_job_items(color_ostream &out) item->flags.bits.owned || item->flags.bits.in_chest || item->isAssignedToStockpile() || - isRouteVehicle(item) || + Items::isRouteVehicle(item) || itemInRealJob(item) || itemBusy(item) || - isAssignedSquad(item)) + Items::isSquadEquipment(item)) { is_invalid = true; cv->item_inuse_count++; From ad518faeecca67ec7195bd9287c70b4618d96038 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 14:59:04 -0400 Subject: [PATCH 0980/1012] cursecheck: Replace setUnitNickname (older) with Units::setNickname --- plugins/cursecheck.cpp | 58 +----------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index 263f91490..e006bd17c 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -68,62 +68,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } - -// code for setting nicknames is copypasta from rename.cpp -// will not work in all cases, some vampires don't like to get nicks - -static void set_nickname(df::language_name *name, std::string nick) -{ - if (!name->has_name) - { - *name = df::language_name(); - - name->language = 0; - name->has_name = true; - } - - name->nickname = nick; -} - -void setUnitNickname(df::unit *unit, const std::string &nick) -{ - // There are >=3 copies of the name, and the one - // in the unit is not the authoritative one. - // This is the reason why military units often - // lose nicknames set from Dwarf Therapist. - set_nickname(&unit->name, nick); - - if (unit->status.current_soul) - set_nickname(&unit->status.current_soul->name, nick); - - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - if (figure) - { - set_nickname(&figure->name, nick); - - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) - { - auto identity = df::identity::find(figure->info->reputation->cur_identity); - - if (identity) - { - auto id_hfig = df::historical_figure::find(identity->histfig_id); - - if (id_hfig) - { - // Even DF doesn't do this bit, because it's apparently - // only used for demons masquerading as gods, so you - // can't ever change their nickname in-game. - set_nickname(&id_hfig->name, nick); - } - else - set_nickname(&identity->name, nick); - } - } - } -} - std::string determineCurse(df::unit * unit) { string cursetype = "unknown"; @@ -237,7 +181,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) if(giveNick) { - setUnitNickname(unit, cursetype); //"CURSED"); + Units::setNickname(unit, cursetype); //"CURSED"); } if(giveDetails) From 7d0df117eef85c4042409621d54a591e79dc7ce2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 14:59:26 -0400 Subject: [PATCH 0981/1012] dwarfmonitor: fix typo in function name --- plugins/dwarfmonitor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 56ca53091..cf920e84e 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -162,7 +162,7 @@ static void move_cursor(df::coord &pos) Gui::refreshSidebar(); } -static void open_stats_srceen(); +static void open_stats_screen(); namespace dm_lua { static color_ostream_proxy *out; @@ -444,7 +444,7 @@ public: else if (input->count(interface_key::CUSTOM_SHIFT_D)) { Screen::dismiss(this); - open_stats_srceen(); + open_stats_screen(); } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { @@ -1735,7 +1735,7 @@ private: }; -static void open_stats_srceen() +static void open_stats_screen() { Screen::show(new ViewscreenFortStats(), plugin_self); } From e9ec08f419a20225eb9fc81e907b0360979b75c3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 17:00:21 -0400 Subject: [PATCH 0982/1012] Add Buildings::getRoomDescription(), moved from search plugin, +cleanup --- docs/Lua API.rst | 7 +++ docs/changelog.txt | 4 ++ library/LuaApi.cpp | 3 +- library/include/modules/Buildings.h | 9 +++ library/modules/Buildings.cpp | 65 ++++++++++++++++++++ library/xml | 2 +- plugins/search.cpp | 93 ++++++++++------------------- scripts | 2 +- 8 files changed, 122 insertions(+), 63 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 7eacd7b96..2f35fabf6 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1571,6 +1571,13 @@ Low-level building creation functions: Returns *true* if the building is marked for removal (with :kbd:`x`), *false* otherwise. +* ``dfhack.buildings.getRoomDescription(building[, unit])`` + + If the building is a room, returns a description including quality modifiers, e.g. "Royal Bedroom". + Otherwise, returns an empty string. + + The unit argument is passed through to DF and may modify the room's value depending on the unit given. + High-level ~~~~~~~~~~ More high-level functions are implemented in lua and can be loaded by diff --git a/docs/changelog.txt b/docs/changelog.txt index 0fefa4b8c..5905fbbd8 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,8 +37,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## Misc Improvements +- `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` + ## API - New functions (all available to Lua as well): + - ``Buildings::getRoomDescription()`` - ``Items::checkMandates()`` - ``Items::canTrade()`` - ``Items::canTradeWithContents()`` diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index af71e729b..c71a0de9d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2001,11 +2001,12 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { WRAPM(Buildings, constructWithItems), WRAPM(Buildings, constructWithFilters), WRAPM(Buildings, deconstruct), + WRAPM(Buildings, markedForRemoval), + WRAPM(Buildings, getRoomDescription), WRAPM(Buildings, isActivityZone), WRAPM(Buildings, isPenPasture), WRAPM(Buildings, isPitPond), WRAPM(Buildings, isActive), - WRAPM(Buildings, markedForRemoval), { NULL, NULL } }; diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 4519b8164..4032b96af 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -198,6 +198,15 @@ DFHACK_EXPORT bool markedForRemoval(df::building *bld); void updateBuildings(color_ostream& out, void* ptr); void clearBuildings(color_ostream& out); +/** + * If the building is a room, returns a description including quality modifiers, e.g. "Royal Bedroom". + * Otherwise, returns an empty string. + * + * The unit argument is passed through to DF and may modify the room's value depending on the unit given. + */ + +DFHACK_EXPORT std::string getRoomDescription(df::building *building, df::unit *unit = nullptr); + /** * Iterates over the items stored on a stockpile. * (For stockpiles with containers, yields the containers, not their contents.) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index e3d8e1d69..732956a3e 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -72,6 +72,7 @@ using namespace DFHack; #include "df/building_workshopst.h" #include "df/buildings_other_id.h" #include "df/d_init.h" +#include "df/dfhack_room_quality_level.h" #include "df/general_ref_building_holderst.h" #include "df/general_ref_contains_unitst.h" #include "df/item.h" @@ -1246,6 +1247,70 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) } } +static std::map> room_quality_names = { + {df::building_type::Bed, { + "Meager Quarters", + "Modest Quarters", + "Quarters", + "Decent Quarters", + "Fine Quarters", + "Great Bedroom", + "Grand Bedroom", + "Royal Bedroom"}}, + {df::building_type::Table, { + "Meager Dining Room", + "Modest Dining Room", + "Dining Room", + "Decent Dining Room", + "Fine Dining Room", + "Great Dining Room", + "Grand Dining Room", + "Royal Dining Room"}}, + {df::building_type::Chair, { + "Meager Office", + "Modest Office", + "Office", + "Decent Office", + "Splendid Office", + "Throne Room", + "Opulent Throne Room", + "Royal Throne Room"}}, + {df::building_type::Coffin, { + "Grave", + "Servant's Burial Chamber", + "Burial Chamber", + "Tomb", + "Fine Tomb", + "Mausoleum", + "Grand Mausoleum", + "Royal Mausoleum"}} +}; + +std::string Buildings::getRoomDescription(df::building *building, df::unit *unit) +{ + CHECK_NULL_POINTER(building); + // unit can be null + + if (!building->is_room) + return ""; + + auto btype = building->getType(); + if (room_quality_names.find(btype) == room_quality_names.end()) + return ""; + + int32_t value = building->getRoomValue(unit); + auto level = ENUM_FIRST_ITEM(dfhack_room_quality_level); + for (auto i_level = level; is_valid_enum_item(i_level); i_level = next_enum_item(i_level, false)) + { + if (value >= ENUM_ATTR(dfhack_room_quality_level, min_value, i_level)) + { + level = i_level; + } + } + + return vector_get(room_quality_names[btype], size_t(level), string("")); +} + void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector *items) { CHECK_NULL_POINTER(stockpile); diff --git a/library/xml b/library/xml index 6c4020d5a..007a22bfe 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 6c4020d5a0ebca365fe3074087110cc8b0f12168 +Subproject commit 007a22bfef6ca4007bab7e2b8f7316165dc833a3 diff --git a/plugins/search.cpp b/plugins/search.cpp index aca97d4a9..7fda3d8c9 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1,40 +1,40 @@ -#include -#include -#include -#include - -#include - +#include "MiscUtils.h" +#include "VTableInterpose.h" #include "uicommon.h" +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Screen.h" +#include "modules/Translation.h" +#include "modules/Units.h" + #include "df/creature_raw.h" -#include "df/ui_look_list.h" -#include "df/viewscreen_announcelistst.h" -#include "df/viewscreen_petst.h" -#include "df/viewscreen_storesst.h" -#include "df/viewscreen_layer_stockpilest.h" -#include "df/viewscreen_layer_militaryst.h" -#include "df/viewscreen_layer_noblelistst.h" -#include "df/viewscreen_workshop_profilest.h" -#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" -#include "df/viewscreen_tradegoodsst.h" -#include "df/viewscreen_unitlistst.h" -#include "df/viewscreen_buildinglistst.h" -#include "df/viewscreen_joblistst.h" +#include "df/global_objects.h" #include "df/historical_figure.h" -#include "df/viewscreen_locationsst.h" #include "df/interface_key.h" #include "df/interfacest.h" -#include "df/layer_object_listst.h" #include "df/job.h" +#include "df/layer_object_listst.h" +#include "df/misc_trait_type.h" #include "df/report.h" -#include "modules/Job.h" -#include "df/global_objects.h" -#include "df/viewscreen_dwarfmodest.h" -#include "modules/Gui.h" +#include "df/ui_look_list.h" #include "df/unit.h" -#include "df/misc_trait_type.h" #include "df/unit_misc_trait.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_joblistst.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_locationsst.h" +#include "df/viewscreen_petst.h" +#include "df/viewscreen_storesst.h" +#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" +#include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_workshop_profilest.h" using namespace std; using std::set; @@ -1487,8 +1487,6 @@ IMPLEMENT_HOOKS_PRIO(df::viewscreen_layer_militaryst, military_search, 100); // // START: Room list search // -static map< df::building_type, vector > room_quality_names; -static int32_t room_value_bounds[] = {1, 100, 250, 500, 1000, 1500, 2500, 10000}; typedef search_twocolumn_modifiable roomlist_search_base; class roomlist_search : public roomlist_search_base { @@ -1509,33 +1507,21 @@ private: { if (!bld) return ""; - bool is_ownable_room = (bld->is_room && room_quality_names.find(bld->getType()) != room_quality_names.end()); string desc; desc.reserve(100); if (bld->owner) desc += get_unit_description(bld->owner); - else if (is_ownable_room) - desc += "no owner"; desc += "."; - if (is_ownable_room) + string room_desc = Buildings::getRoomDescription(bld, nullptr); + desc += room_desc; + if (room_desc.empty()) { - int32_t value = bld->getRoomValue(NULL); - vector *names = &room_quality_names[bld->getType()]; - string *room_name = &names->at(0); - for (int i = 1; i < 8; i++) - { - if (room_value_bounds[i] > value) - break; - room_name = &names->at(i); - } + if (!bld->owner) + desc += "no owner"; - desc += *room_name; - } - else - { string name; bld->getName(&name); if (!name.empty()) @@ -2168,26 +2154,13 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) is_enabled = enable; } +#undef HOOK_ACTION return CR_OK; } DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { -#undef HOOK_ACTION - - const string a[] = {"Meager Quarters", "Modest Quarters", "Quarters", "Decent Quarters", "Fine Quarters", "Great Bedroom", "Grand Bedroom", "Royal Bedroom"}; - room_quality_names[df::building_type::Bed] = vector(a, a + 8); - - const string b[] = {"Meager Dining Room", "Modest Dining Room", "Dining Room", "Decent Dining Room", "Fine Dining Room", "Great Dining Room", "Grand Dining Room", "Royal Dining Room"}; - room_quality_names[df::building_type::Table] = vector(b, b + 8); - - const string c[] = {"Meager Office", "Modest Office", "Office", "Decent Office", "Splendid Office", "Throne Room", "Opulent Throne Room", "Royal Throne Room"}; - room_quality_names[df::building_type::Chair] = vector(c, c + 8); - - const string d[] = {"Grave", "Servants Burial Chamber", "Burial Chamber", "Tomb", "Fine Tomb", "Mausoleum", "Grand Mausoleum", "Royal Mausoleum"}; - room_quality_names[df::building_type::Coffin] = vector(d, d + 8); - return CR_OK; } diff --git a/scripts b/scripts index f76e41744..ed1b046dd 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f76e41744d0479d2e390b8092726df5534c26acf +Subproject commit ed1b046dda50571c66d03916b62b547db13bb0e0 From fcd8c9606e5912f35130ae24fdb8fbb4eaf52441 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 18:46:58 -0400 Subject: [PATCH 0983/1012] Fix some warnings on OS X --- depends/clsocket | 2 +- library/Process-darwin.cpp | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/depends/clsocket b/depends/clsocket index f638c8ea7..6a9153d05 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit f638c8ea7f9c50fe7f64159c9f173846856f87a6 +Subproject commit 6a9153d053a250be34996b3fd86ac1166c3e17cb diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 69defbf92..2091d9a96 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -208,8 +208,10 @@ void Process::getMemRanges( vector & ranges ) if (log_ranges) { fprintf(stderr, - "%p-%p %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", - address, (address + vmsize), (vmsize >> 10), + "%p-%p %8zuK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", + (void*)address, + (void*)(address + vmsize), + size_t(vmsize >> 10), (info.protection & VM_PROT_READ) ? 'r' : '-', (info.protection & VM_PROT_WRITE) ? 'w' : '-', (info.protection & VM_PROT_EXECUTE) ? 'x' : '-', @@ -250,22 +252,6 @@ int Process::adjustOffset(int offset, bool /*to_file*/) return offset; } -static int getdir (string dir, vector &files) -{ - DIR *dp; - struct dirent *dirp; - if((dp = opendir(dir.c_str())) == NULL) - { - cout << "Error(" << errno << ") opening " << dir << endl; - return errno; - } - while ((dirp = readdir(dp)) != NULL) { - files.push_back(string(dirp->d_name)); - } - closedir(dp); - return 0; -} - uint32_t Process::getTickCount() { struct timeval tp; @@ -289,6 +275,11 @@ string Process::getPath() if (_NSGetExecutablePath(path, &size) == 0) { real_path = realpath(path, NULL); } + else { + fprintf(stderr, "_NSGetExecutablePath failed!\n"); + cached_path = "."; + return cached_path; + } std::string path_string(real_path); int last_slash = path_string.find_last_of("/"); cached_path = path_string.substr(0,last_slash); From 016dab39906226e2d6e4c597e5adabee5bbb6b69 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 May 2018 22:10:07 -0400 Subject: [PATCH 0984/1012] Fix more warnings, including stonesense --- depends/md5/CMakeLists.txt | 5 ++++- plugins/embark-assistant/finder_ui.cpp | 2 +- plugins/stonesense | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/depends/md5/CMakeLists.txt b/depends/md5/CMakeLists.txt index 69e0cf0b3..84cc24e14 100644 --- a/depends/md5/CMakeLists.txt +++ b/depends/md5/CMakeLists.txt @@ -1,3 +1,6 @@ project(dfhack-md5) ADD_LIBRARY(dfhack-md5 STATIC EXCLUDE_FROM_ALL md5.cpp md5wrapper.cpp) -IDE_FOLDER(dfhack-md5 "Depends") \ No newline at end of file +IDE_FOLDER(dfhack-md5 "Depends") +IF(UNIX) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing") +ENDIF() diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 8f8a2599d..3f972635f 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -986,7 +986,7 @@ namespace embark_assist { void find() { // color_ostream_proxy out(Core::getInstance().getConsole()); - embark_assist::defs::finders finder; + embark_assist::defs::finders finder = {}; fields i = first_fields; while (true) { diff --git a/plugins/stonesense b/plugins/stonesense index b5c02d8a9..daa1ba70c 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit b5c02d8a979c6ff44640dbc76ae9bb8a4eb58708 +Subproject commit daa1ba70cb96afc3a0155870a1efaada6ce6ff59 From 586051510c20f3273f97ae4040aab3d139d9eee0 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sun, 13 May 2018 10:38:59 -0500 Subject: [PATCH 0985/1012] Update stonesense. --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index daa1ba70c..2b84dd1f5 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit daa1ba70cb96afc3a0155870a1efaada6ce6ff59 +Subproject commit 2b84dd1f526faf16b2d71c09230a4ddf5a43f43f From e2cd1fca58929511613bf384b4bdcf23a1e4b288 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 13 May 2018 13:09:51 -0400 Subject: [PATCH 0986/1012] Reorder dfhack-md5 GCC flags, fix another embark-assistant warning --- depends/md5/CMakeLists.txt | 2 +- plugins/embark-assistant/survey.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/md5/CMakeLists.txt b/depends/md5/CMakeLists.txt index 84cc24e14..45cbe7e06 100644 --- a/depends/md5/CMakeLists.txt +++ b/depends/md5/CMakeLists.txt @@ -2,5 +2,5 @@ project(dfhack-md5) ADD_LIBRARY(dfhack-md5 STATIC EXCLUDE_FROM_ALL md5.cpp md5wrapper.cpp) IDE_FOLDER(dfhack-md5 "Depends") IF(UNIX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing") + SET_TARGET_PROPERTIES(dfhack-md5 PROPERTIES COMPILE_FLAGS "-Wno-strict-aliasing") ENDIF() diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 4b0d883e3..b057ff290 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -781,7 +781,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data survey_results->at(x).at(y).evilness_count[2] = 0; bool river_elevation_found = false; - int16_t river_elevation; + int16_t river_elevation = 0; for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { @@ -1006,7 +1006,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * uint16_t x = screen->location.region_pos.x; uint16_t y = screen->location.region_pos.y; bool river_found = false; - int16_t river_elevation; + int16_t river_elevation = 0; std::vector metals(state->max_inorganic); std::vector economics(state->max_inorganic); std::vector minerals(state->max_inorganic); From 9cf4cdbc1f4a537870902f4a4b8079ca37da48c9 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sun, 13 May 2018 16:55:38 -0500 Subject: [PATCH 0987/1012] Simplify CMakeLists.txt for protobuf. All of the compilers we support have std::unordered_map and std::unordered_set in their standard locations. --- depends/protobuf/CMakeLists.txt | 52 ++++----------------------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 206cfc1a9..ce35d25f1 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -5,53 +5,13 @@ IF(CMAKE_COMPILER_IS_GNUCC) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") ENDIF() -SET(HAVE_HASH_MAP 0) +SET(HASH_MAP_H ) +SET(HASH_SET_H ) +SET(HASH_NAMESPACE std) SET(HASH_MAP_CLASS unordered_map) - -#Check for all of the possible combinations of unordered_map and hash_map - -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_BINARY_DIR}/testHashMap.cpp") - IF(CMAKE_CROSSCOMPILING) - 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_BINARY_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}) - SET(HASH_NAMESPACE ${namespace}) - SET(HASH_MAP_CLASS unordered_map) - SET(HASH_SET_CLASS unordered_set) - SET(HAVE_HASH_MAP 1) - SET(HAVE_HASH_SET 1) - ENDIF() - ENDIF() - ENDFOREACH(namespace) -ENDFOREACH(header) -IF (HAVE_HASH_MAP EQUAL 0) - SET(HASH_MAP_CLASS hash_map) - 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_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}) - SET(HASH_NAMESPACE ${namespace}) - SET(HASH_MAP_CLASS hash_map) - SET(HASH_SET_CLASS hash_set) - SET(HAVE_HASH_MAP 1) - SET(HAVE_HASH_SET 1) - ENDIF() - ENDIF() - ENDFOREACH() - ENDFOREACH() -ENDIF() +SET(HASH_SET_CLASS unordered_set) +SET(HAVE_HASH_MAP 1) +SET(HAVE_HASH_SET 1) IF (HAVE_HASH_MAP EQUAL 0) MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.") From c6f12e4fef159b2c2988a43158a675986ed250e8 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sun, 13 May 2018 17:16:22 -0500 Subject: [PATCH 0988/1012] Remove /O2 flag for MSVC in RelWithDebInfo mode. (causes a warning when building MSVC in Makefile mode) --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a7d2e2bc..842103da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,6 +205,7 @@ ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od") + STRING(REPLACE "/O2" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") STRING(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") ENDIF() From f838b5695d416ad372c2d5af7a090899765332fd Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 14 May 2018 00:12:22 -0500 Subject: [PATCH 0989/1012] Remove $(NOINHERIT) which was inexplicably included in a bunch of plugins. --- plugins/diggingInvaders/CMakeLists.txt | 19 +--------------- plugins/embark-assistant/CMakeLists.txt | 20 +---------------- plugins/labormanager/CMakeLists.txt | 19 +--------------- plugins/remotefortressreader/CMakeLists.txt | 22 ++++-------------- plugins/rendermax/CMakeLists.txt | 22 +----------------- plugins/skeleton/CMakeLists.txt | 25 ++++++++++++--------- 6 files changed, 22 insertions(+), 105 deletions(-) diff --git a/plugins/diggingInvaders/CMakeLists.txt b/plugins/diggingInvaders/CMakeLists.txt index a88c93ca1..fa4279b0f 100644 --- a/plugins/diggingInvaders/CMakeLists.txt +++ b/plugins/diggingInvaders/CMakeLists.txt @@ -15,21 +15,4 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) -# this makes sure all the stuff is put in proper places and linked to dfhack - -DFHACK_PLUGIN(diggingInvaders ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +DFHACK_PLUGIN(diggingInvaders ${PROJECT_SRCS}) diff --git a/plugins/embark-assistant/CMakeLists.txt b/plugins/embark-assistant/CMakeLists.txt index e57561ec1..917d2ec5a 100644 --- a/plugins/embark-assistant/CMakeLists.txt +++ b/plugins/embark-assistant/CMakeLists.txt @@ -27,22 +27,4 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) -# option to use a thread for no particular reason -#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra windows libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) -# this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(embark-assistant ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +DFHACK_PLUGIN(embark-assistant ${PROJECT_SRCS}) diff --git a/plugins/labormanager/CMakeLists.txt b/plugins/labormanager/CMakeLists.txt index 8e9e28b81..8337ac786 100644 --- a/plugins/labormanager/CMakeLists.txt +++ b/plugins/labormanager/CMakeLists.txt @@ -14,21 +14,4 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) -# this makes sure all the stuff is put in proper places and linked to dfhack - -DFHACK_PLUGIN(labormanager ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +DFHACK_PLUGIN(labormanager ${PROJECT_SRCS}) diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 571098e18..1ff1cac7f 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -26,23 +26,9 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO} PROPERTIES GENERATED TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS};${PROJECT_PROTO}) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) - IF(NOT APPLE) - SET(PROJECT_LIBS ${PROJECT_LIBS} SDL) - ENDIF() -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra windows libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) +IF(UNIX AND NOT APPLE) + SET(PROJECT_LIBS ${PROJECT_LIBS} SDL) +ENDIF() + # this makes sure all the stuff is put in proper places and linked to dfhack DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES protobuf-lite ${PROJECT_LIBS} COMPILE_FLAGS_MSVC "/FI\"Export.h\"" COMPILE_FLAGS_GCC "-include Export.h -Wno-misleading-indentation" ) diff --git a/plugins/rendermax/CMakeLists.txt b/plugins/rendermax/CMakeLists.txt index ba589732d..c83790412 100644 --- a/plugins/rendermax/CMakeLists.txt +++ b/plugins/rendermax/CMakeLists.txt @@ -15,27 +15,7 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) - -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - lua - dfhack-tinythread - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra windows libs here - lua - dfhack-tinythread - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) # this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(rendermax ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +DFHACK_PLUGIN(rendermax ${PROJECT_SRCS} LINK_LIBRARIES lua dfhack-tinythread) install(FILES rendermax.lua DESTINATION ${DFHACK_DATA_DESTINATION}/raw) diff --git a/plugins/skeleton/CMakeLists.txt b/plugins/skeleton/CMakeLists.txt index 794f4a5d8..69fac825a 100644 --- a/plugins/skeleton/CMakeLists.txt +++ b/plugins/skeleton/CMakeLists.txt @@ -14,20 +14,23 @@ LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) # option to use a thread for no particular reason OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -#linux IF(UNIX) - add_definitions(-DLINUX_BUILD) + IF(APPLE) + SET(PROJECT_LIBS + # add any extra mac libraries here + ${PROJECT_LIBS} + ) + ELSE() + SET(PROJECT_LIBS + # add any extra linux libraries here + ${PROJECT_LIBS} + ) + ENDIF() +ELSE() SET(PROJECT_LIBS - # add any extra linux libs here + # add any extra windows libraries here ${PROJECT_LIBS} ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra windows libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) +ENDIF() # this makes sure all the stuff is put in proper places and linked to dfhack DFHACK_PLUGIN(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) From ee85ed40116f195776493b78098348c6bddb1cd0 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 14 May 2018 00:56:18 -0500 Subject: [PATCH 0990/1012] Remove $(NOINHERIT) from Stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 2b84dd1f5..164d2cd13 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 2b84dd1f526faf16b2d71c09230a4ddf5a43f43f +Subproject commit 164d2cd1349c090a91c1c19200ad03df9bb16650 From 43be1c7a6a2aedcd8eed4a204bf98bae91b4481f Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 14 May 2018 14:15:38 -0500 Subject: [PATCH 0991/1012] Make the win64 build/install batch files work with non-C system drives. --- build/win64/build-debug.bat | 4 ++-- build/win64/build-release.bat | 4 ++-- build/win64/install-debug.bat | 4 ++-- build/win64/install-release.bat | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/win64/build-debug.bat b/build/win64/build-debug.bat index f53df9063..08ef6d3a9 100644 --- a/build/win64/build-debug.bat +++ b/build/win64/build-debug.bat @@ -1,4 +1,4 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 cd VC2015 msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj -cd .. \ No newline at end of file +cd .. diff --git a/build/win64/build-release.bat b/build/win64/build-release.bat index 551e27b69..dfeb108b3 100644 --- a/build/win64/build-release.bat +++ b/build/win64/build-release.bat @@ -1,5 +1,5 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 cd VC2015 msbuild /m /p:Platform=x64 /p:Configuration=Release ALL_BUILD.vcxproj cd .. -pause \ No newline at end of file +pause diff --git a/build/win64/install-debug.bat b/build/win64/install-debug.bat index 0b8758461..34668945c 100644 --- a/build/win64/install-debug.bat +++ b/build/win64/install-debug.bat @@ -1,4 +1,4 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 cd VC2015 msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj -cd .. \ No newline at end of file +cd .. diff --git a/build/win64/install-release.bat b/build/win64/install-release.bat index 8c0b8a83d..24acd5de8 100644 --- a/build/win64/install-release.bat +++ b/build/win64/install-release.bat @@ -1,4 +1,4 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 cd VC2015 msbuild /m /p:Platform=x64 /p:Configuration=Release INSTALL.vcxproj -cd .. \ No newline at end of file +cd .. From 521da26c40521780726050303c3870d413143496 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 14 May 2018 21:35:32 -0400 Subject: [PATCH 0992/1012] Document all dfhack.gui functions --- docs/Lua API.rst | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2f35fabf6..aa96e90eb 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -882,6 +882,9 @@ proper display on all platforms. Gui module ---------- +Screens +~~~~~~~ + * ``dfhack.gui.getCurViewscreen([skip_dismissed])`` Returns the topmost viewscreen. If ``skip_dismissed`` is *true*, @@ -902,6 +905,9 @@ Gui module the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match. If ``depth`` is not specified or is less than 1, all viewscreens are checked. +General-purpose selections +~~~~~~~~~~~~~~~~~~~~~~~~~~ + * ``dfhack.gui.getSelectedWorkshopJob([silent])`` When a job is selected in :kbd:`q` mode, returns the job, else @@ -931,6 +937,53 @@ Gui module Returns the plant selected via :kbd:`k`. +* ``dfhack.gui.getAnyUnit(screen)`` +* ``dfhack.gui.getAnyItem(screen)`` +* ``dfhack.gui.getAnyBuilding(screen)`` +* ``dfhack.gui.getAnyPlant(screen)`` + + Similar to the corresponding ``getSelected`` functions, but operate on the + screen given instead of the current screen and always return ``nil`` silently + on failure. + +Fortress mode +~~~~~~~~~~~~~ + +* ``dfhack.gui.getDwarfmodeViewDims()`` + + Returns dimensions of the main fortress mode screen. See ``getPanelLayout()`` + in the ``gui.dwarfmode`` module for a more Lua-friendly version. + +* ``dfhack.gui.resetDwarfmodeView([pause])`` + + Resets the fortress mode sidebar menus and cursors to their default state. If + ``pause`` is true, also pauses the game. + +* ``dfhack.gui.revealInDwarfmodeMap(pos)`` + + Centers the view on the given position, which can be a ``df.coord`` instance + or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`), + e.g.:: + + {x = 5, y = 7, z = 11} + getSelectedUnit().pos + xyz2pos(pos2xyz(df.global.cursor)) + + Returns false if unsuccessful. + +* ``dfhack.gui.refreshSidebar()`` + + Refreshes the fortress mode sidebar. This can be useful when making changes to + the map, for example, because DF only updates the sidebar when the cursor + position changes. + +* ``dfhack.gui.inRenameBuilding()`` + + Returns ``true`` if a building is being renamed. + +Announcements +~~~~~~~~~~~~~ + * ``dfhack.gui.writeToGamelog(text)`` Writes a string to :file:`gamelog.txt` without doing an announcement. @@ -975,6 +1028,13 @@ Gui module Uses the type to look up options from announcements.txt, and calls the above operations accordingly. The units are used to call ``addCombatReportAuto``. +Other +~~~~~ + +* ``dfhack.gui.getDepthAt(x, y)`` + + Returns the distance from the z-level of the tile at map coordinates (x, y) to + the closest ground z-level below. Defaults to 0, unless overriden by plugins. Job module ---------- From 914b37608262318fd2420cd7196851082eef9567 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 14 May 2018 22:30:21 -0400 Subject: [PATCH 0993/1012] Add stone status search Suggested by xq on IRC --- docs/changelog.txt | 1 + plugins/search.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 5905fbbd8..464950660 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -39,6 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` +- `search`: added support for stone restrictions screen (under ``z``: Status) ## API - New functions (all available to Lua as well): diff --git a/plugins/search.cpp b/plugins/search.cpp index 7fda3d8c9..1592e26fd 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -28,6 +28,7 @@ #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_stone_restrictionst.h" #include "df/viewscreen_locationsst.h" #include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" @@ -55,6 +56,7 @@ REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_look_cursor); REQUIRE_GLOBAL(ui_look_list); +REQUIRE_GLOBAL(world); /* Search Plugin @@ -2116,6 +2118,94 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); // END: Location occupation assignment search // +// +// START: Stone status screen search +// +typedef layered_search stone_search_layer; +class stone_search : public search_twocolumn_modifiable +{ + // bool in_update = false; +public: + void render() const override + { + print_search_option(21, 23); + } + + vector *get_primary_list() override + { + return &viewscreen->stone_type[viewscreen->type_tab]; + } + + vector *get_secondary_list() override + { + return &viewscreen->stone_economic[viewscreen->type_tab]; + } + + string get_element_description(int32_t stone_type) const override + { + auto iraw = vector_get(world->raws.inorganics, stone_type); + if (!iraw) + return ""; + return iraw->material.stone_name + " " + iraw->material.state_name[0]; + } + + bool should_check_input(set *input) override + { + // if (in_update) + // return false; + + if (input->count(interface_key::CHANGETAB)) + { + // Restore original list + clear_search(); + reset_all(); + } + + return true; + } + + // virtual void do_post_input_feed() override + // { + // auto *list1 = get_primary_list(); + // auto *list2 = get_secondary_list(); + // bool appended = false; + // if (list1->empty()) + // { + // // Clear uses + // auto *use_list = virtual_cast(viewscreen->layer_objects[4]); + // if (use_list) + // use_list->num_entries = 0; + // return; + // } + // else if (list1->size() == 1) + // { + // list1->push_back(list1->back()); + // list2->push_back(list2->back()); + // appended = true; + // } + + // in_update = true; + // Core::printerr("updating\n"); + // viewscreen->feed_key(interface_key::STANDARDSCROLL_DOWN); + // viewscreen->feed_key(interface_key::STANDARDSCROLL_UP); + // Core::printerr("updating done\n"); + // in_update = false; + + // if (appended) + // { + // list1->pop_back(); + // list2->pop_back(); + // } + // } +}; + +IMPLEMENT_HOOKS(df::viewscreen_layer_stone_restrictionst, stone_search); + +// +// END: Stone status screen search +// + + #define SEARCH_HOOKS \ HOOK_ACTION(unitlist_search_hook) \ HOOK_ACTION(roomlist_search_hook) \ @@ -2135,7 +2225,9 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); HOOK_ACTION(stockpile_search_hook) \ HOOK_ACTION(room_assign_search_hook) \ HOOK_ACTION(noble_suggest_search_hook) \ - HOOK_ACTION(location_assign_occupation_search_hook) + HOOK_ACTION(location_assign_occupation_search_hook) \ + HOOK_ACTION(stone_search_hook) \ + DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { From 0be16d44228fe01f2e5509111b84a95b9be871aa Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 14 May 2018 22:54:20 -0400 Subject: [PATCH 0994/1012] New tweak: stone-status-all Adds an option to toggle the economic status of all stones Also suggested by xq on IRC --- dfhack.init-example | 1 + docs/Plugins.rst | 1 + docs/changelog.txt | 3 +++ plugins/tweak/tweak.cpp | 6 +++++ plugins/tweak/tweaks/stone-status-all.h | 34 +++++++++++++++++++++++++ 5 files changed, 45 insertions(+) create mode 100644 plugins/tweak/tweaks/stone-status-all.h diff --git a/dfhack.init-example b/dfhack.init-example index 82210a2a6..490b1befd 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -213,6 +213,7 @@ tweak hide-priority tweak kitchen-prefs-empty tweak max-wheelbarrow tweak shift-8-scroll +tweak stone-status-all tweak title-start-rename tweak tradereq-pet-gender diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 49c86ca8a..33a2c452a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -327,6 +327,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. +:stone-status-all: Adds an option to toggle the economic status of all stones :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 diff --git a/docs/changelog.txt b/docs/changelog.txt index 464950660..b4e66ff59 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## New Tweaks +- `tweak` stone-status-all: adds an option to toggle the economic status of all stones + ## Misc Improvements - `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` - `search`: added support for stone restrictions screen (under ``z``: Status) diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 37e606b1f..8d6d61db8 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -102,6 +102,7 @@ #include "tweaks/nestbox-color.h" #include "tweaks/shift-8-scroll.h" #include "tweaks/stable-cursor.h" +#include "tweaks/stone-status-all.h" #include "tweaks/title-start-rename.h" #include "tweaks/tradereq-pet-gender.h" @@ -239,6 +240,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + if (input->count(interface_key::SELECT_ALL)) + { + if (VIRTUAL_CAST_VAR(list, df::layer_object_listst, layer_objects[0])) + { + bool new_state = !*stone_economic[type_tab][list->cursor]; + for (bool *economic : stone_economic[type_tab]) + *economic = new_state; + } + } + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + int x = 2, y = 23; + OutputHotkeyString(x, y, "All", interface_key::SELECT_ALL, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(stone_status_all_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(stone_status_all_hook, feed); From c85274eb01ea7b4f85cf18e5e8618e5efa340922 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 16 May 2018 16:51:45 -0400 Subject: [PATCH 0995/1012] tweak max-wheelbarrow: fix conflict with building renaming Reported by xordae on bay12: http://www.bay12forums.com/smf/index.php?topic=164123.msg7762438#msg7762438 --- docs/changelog.txt | 3 +++ plugins/tweak/tweaks/max-wheelbarrow.h | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index b4e66ff59..bdb1eaab9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks - `tweak` stone-status-all: adds an option to toggle the economic status of all stones +## Fixes +- `tweak` max-wheelbarrow: fixed conflict with building renaming + ## Misc Improvements - `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` - `search`: added support for stone restrictions screen (under ``z``: Status) diff --git a/plugins/tweak/tweaks/max-wheelbarrow.h b/plugins/tweak/tweaks/max-wheelbarrow.h index 4df02abd2..a7af186ef 100644 --- a/plugins/tweak/tweaks/max-wheelbarrow.h +++ b/plugins/tweak/tweaks/max-wheelbarrow.h @@ -43,7 +43,7 @@ struct max_wheelbarrow_hook : df::viewscreen_dwarfmodest { { df::building_stockpilest* stockpile = getStockpile(); bool handled = false; - if (stockpile) + if (stockpile && !Gui::inRenameBuilding()) { handled = true; if (!in_wheelbarrow_entry && @@ -72,9 +72,8 @@ struct max_wheelbarrow_hook : df::viewscreen_dwarfmodest { } else { - for (auto iter = input->begin(); iter != input->end(); ++iter) + for (df::interface_key key : *input) { - df::interface_key key = *iter; if (key >= Screen::charToKey('0') && key <= Screen::charToKey('9') && wheelbarrow_entry.size() < 3) { From 3c5a9457b4f076f1b492735564cbca0829f4d92b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 16 May 2018 19:45:47 -0400 Subject: [PATCH 0996/1012] Add some suggested keybindings from docs to dfhack.init-example Ref #988 --- dfhack.init-example | 5 +++++ docs/changelog.txt | 3 +++ scripts | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dfhack.init-example b/dfhack.init-example index 490b1befd..17a36fb0c 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -58,6 +58,9 @@ keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity # view combat reports for the selected unit/corpse/spatter keybinding add Ctrl-Shift-R view-unit-reports +# view extra unit information +keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer + ############################## # Generic adv mode bindings # ############################## @@ -65,6 +68,8 @@ keybinding add Ctrl-Shift-R view-unit-reports keybinding add Ctrl-B@dungeonmode adv-bodyswap keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force" keybinding add Shift-O@dungeonmode gui/companion-order +keybinding add Ctrl-T@dungeonmode gui/advfort +keybinding add Ctrl-A@dungeonmode/ConversationSpeak adv-rumors ############################## # Generic legends bindings # diff --git a/docs/changelog.txt b/docs/changelog.txt index bdb1eaab9..3e6923c92 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,7 +44,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `tweak` max-wheelbarrow: fixed conflict with building renaming ## Misc Improvements +- `adv-rumors`: bound to Ctrl-A +- `gui/advfort`: bound to Ctrl-T - `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` +- `gui/unit-info-viewer`: bound to Alt-I - `search`: added support for stone restrictions screen (under ``z``: Status) ## API diff --git a/scripts b/scripts index ed1b046dd..5e8f85957 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ed1b046dda50571c66d03916b62b547db13bb0e0 +Subproject commit 5e8f85957bb6863632a5984181b64ed07e055d9c From 79a079465819c18e7502a5eee3af24586d659acf Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 00:11:52 -0400 Subject: [PATCH 0997/1012] Add kitchen preferences search --- docs/changelog.txt | 4 +- plugins/search.cpp | 124 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 3e6923c92..8610fc994 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -48,7 +48,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `gui/advfort`: bound to Ctrl-T - `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` - `gui/unit-info-viewer`: bound to Alt-I -- `search`: added support for stone restrictions screen (under ``z``: Status) +- `search`: + - added support for stone restrictions screen (under ``z``: Status) + - added support for kitchen preferences (also under ``z``) ## API - New functions (all available to Lua as well): diff --git a/plugins/search.cpp b/plugins/search.cpp index 1592e26fd..2827f0efd 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -25,6 +25,7 @@ #include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_joblistst.h" +#include "df/viewscreen_kitchenprefst.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_stockpilest.h" @@ -2118,6 +2119,128 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); // END: Location occupation assignment search // +// +// START: Kitchen preferences search +// + +typedef search_multicolumn_modifiable kitchen_pref_search_base; +class kitchen_pref_search : public kitchen_pref_search_base +{ +public: + + string get_element_description(string *s) const override + { + return s ? *s : ""; + } + + void render() const override + { + print_search_option(2, gps->dimy - 2); + } + + int32_t *get_viewscreen_cursor() override + { + return &viewscreen->cursor; + } + + vector *get_primary_list() override + { + return &viewscreen->item_str[viewscreen->page]; + } + + bool should_check_input(set *input) override + { + if (input->count(interface_key::CHANGETAB) || input->count(interface_key::SEC_CHANGETAB)) + { + // Restore original list + clear_search(); + reset_all(); + } + + return true; + } + + +#define KITCHEN_VECTORS \ + KVEC(df::item_type, item_type); \ + KVEC(int16_t, item_subtype); \ + KVEC(int16_t, mat_type); \ + KVEC(int32_t, mat_index); \ + KVEC(int32_t, count); \ + KVEC(uint8_t, forbidden); \ + KVEC(uint8_t, possible) + + + virtual void do_post_init() + { + kitchen_pref_search_base::do_post_init(); + #define KVEC(type, name) name = &viewscreen->name[viewscreen->page] + KITCHEN_VECTORS; + #undef KVEC + } + + void save_secondary_values() + { + #define KVEC(type, name) name##_s = *name + KITCHEN_VECTORS; + #undef KVEC + } + + void reset_secondary_viewscreen_vectors() + { + #define KVEC(type, name) name = nullptr + KITCHEN_VECTORS; + #undef KVEC + } + + virtual void update_saved_secondary_list_item(size_t i, size_t j) + { + #define KVEC(type, name) name##_s[i] = (*name)[j]; + KITCHEN_VECTORS; + #undef KVEC + } + + void clear_secondary_viewscreen_vectors() + { + #define KVEC(type, name) name->clear() + KITCHEN_VECTORS; + #undef KVEC + } + + void add_to_filtered_secondary_lists(size_t i) + { + #define KVEC(type, name) name->push_back(name##_s[i]) + KITCHEN_VECTORS; + #undef KVEC + } + + void clear_secondary_saved_lists() + { + #define KVEC(type, name) name##_s.clear() + KITCHEN_VECTORS; + #undef KVEC + } + + void restore_secondary_values() + { + #define KVEC(type, name) *name = name##_s + KITCHEN_VECTORS; + #undef KVEC + } + + #define KVEC(type, name) vector *name, name##_s + KITCHEN_VECTORS; + #undef KVEC +#undef KITCHEN_VECTORS +}; + +IMPLEMENT_HOOKS(df::viewscreen_kitchenprefst, kitchen_pref_search); + +// +// END: Kitchen preferences search +// + + // // START: Stone status screen search // @@ -2226,6 +2349,7 @@ IMPLEMENT_HOOKS(df::viewscreen_layer_stone_restrictionst, stone_search); HOOK_ACTION(room_assign_search_hook) \ HOOK_ACTION(noble_suggest_search_hook) \ HOOK_ACTION(location_assign_occupation_search_hook) \ + HOOK_ACTION(kitchen_pref_search_hook) \ HOOK_ACTION(stone_search_hook) \ From c90745f1dfd04fdec0697c2524aabd6121166f10 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 00:12:40 -0400 Subject: [PATCH 0998/1012] Update scripts/view-item-info (#1273) --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 5e8f85957..fc7a37d9d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 5e8f85957bb6863632a5984181b64ed07e055d9c +Subproject commit fc7a37d9d6df4d0c1b84435c8ed7392869e8e7d8 From 97a79893e7c461084c11a60577490729ee775a51 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 11:08:13 -0400 Subject: [PATCH 0999/1012] Change Kitchen.{h,cpp} to title case --- library/CMakeLists.txt | 4 ++-- library/include/modules/{kitchen.h => Kitchen.h} | 0 library/modules/{kitchen.cpp => Kitchen.cpp} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename library/include/modules/{kitchen.h => Kitchen.h} (100%) rename library/modules/{kitchen.cpp => Kitchen.cpp} (100%) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6caab0a76..8541f37cd 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -120,7 +120,7 @@ include/modules/Gui.h include/modules/GuiHooks.h include/modules/Items.h include/modules/Job.h -include/modules/kitchen.h +include/modules/Kitchen.h include/modules/MapCache.h include/modules/Maps.h include/modules/Materials.h @@ -147,7 +147,7 @@ modules/Graphic.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp -modules/kitchen.cpp +modules/Kitchen.cpp modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp diff --git a/library/include/modules/kitchen.h b/library/include/modules/Kitchen.h similarity index 100% rename from library/include/modules/kitchen.h rename to library/include/modules/Kitchen.h diff --git a/library/modules/kitchen.cpp b/library/modules/Kitchen.cpp similarity index 100% rename from library/modules/kitchen.cpp rename to library/modules/Kitchen.cpp From 17ba4929926fac3902d406440956743784dd2c1c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 19:34:03 -0400 Subject: [PATCH 1000/1012] Add some exclusion functions to Kitchen module, expose to Lua, fix build --- docs/Lua API.rst | 21 +++++++++ docs/changelog.txt | 3 ++ library/LuaApi.cpp | 35 +++++++++------ library/include/modules/Kitchen.h | 22 ++++++++-- library/modules/Kitchen.cpp | 71 ++++++++++++++++++++++++++----- library/xml | 2 +- plugins/search.cpp | 4 +- 7 files changed, 128 insertions(+), 30 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index aa96e90eb..08aaf84fe 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1735,6 +1735,27 @@ Constructions module Returns *true, was_only_planned* if removed; or *false* if none found. +Kitchen module +-------------- + +* ``dfhack.kitchen.findExclusion(type, item_type, item_index, mat_type, mat_index)`` + + Finds a kitchen exclusion in the vectors in ``df.global.ui.kitchen``. Returns + -1 if not found. + + * ``type`` is a ``df.kitchen_exc_type``, i.e. ``df.kitchen_exc_type.Cook`` or + ``df.kitchen_exc_type.Brew``. + * ``item_type`` is a ``df.item_type`` + * ``item_index``, ``mat_type``, and ``mat_index`` are all numeric + +* ``dfhack.kitchen.addExclusion(type, item_type, item_index, mat_type, mat_index)`` +* ``dfhack.kitchen.removeExclusion(type, item_type, item_index, mat_type, mat_index)`` + + Adds or removes a kitchen exclusion, using the same parameters as + ``findExclusion``. Both return ``true`` on success and ``false`` on failure, + e.g. when adding an exclusion that already exists or removing one that does + not. + Screen API ---------- diff --git a/docs/changelog.txt b/docs/changelog.txt index 8610fc994..55086abfa 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -60,6 +60,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``Items::canTradeWithContents()`` - ``Items::isRouteVehicle()`` - ``Items::isSquadEquipment()`` + - ``Kitchen::addExclusion()`` + - ``Kitchen::findExclusion()`` + - ``Kitchen::removeExclusion()`` # 0.44.10-beta1 diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c71a0de9d..27199c651 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -43,22 +43,23 @@ distribution. #include "tinythread.h" #include "md5wrapper.h" -#include "modules/World.h" +#include "modules/Buildings.h" +#include "modules/Burrows.h" +#include "modules/Constructions.h" +#include "modules/Designations.h" +#include "modules/Filesystem.h" #include "modules/Gui.h" -#include "modules/Screen.h" -#include "modules/Job.h" -#include "modules/Translation.h" -#include "modules/Units.h" #include "modules/Items.h" -#include "modules/Materials.h" -#include "modules/Maps.h" +#include "modules/Job.h" +#include "modules/Kitchen.h" #include "modules/MapCache.h" -#include "modules/Burrows.h" -#include "modules/Buildings.h" -#include "modules/Constructions.h" +#include "modules/Maps.h" +#include "modules/Materials.h" #include "modules/Random.h" -#include "modules/Filesystem.h" -#include "modules/Designations.h" +#include "modules/Screen.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "modules/World.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -2446,6 +2447,15 @@ static const luaL_Reg dfhack_designations_funcs[] = { {NULL, NULL} }; +/***** Kitchen module *****/ + +static const LuaWrapper::FunctionReg dfhack_kitchen_module[] = { + WRAPM(Kitchen, findExclusion), + WRAPM(Kitchen, addExclusion), + WRAPM(Kitchen, removeExclusion), + {NULL, NULL} +}; + /***** Console module *****/ namespace console { @@ -2998,6 +3008,7 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs); + OpenModule(state, "kitchen", dfhack_kitchen_module); OpenModule(state, "console", dfhack_console_module); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } diff --git a/library/include/modules/Kitchen.h b/library/include/modules/Kitchen.h index ca88e9163..1ddf239bd 100644 --- a/library/include/modules/Kitchen.h +++ b/library/include/modules/Kitchen.h @@ -32,6 +32,7 @@ distribution. #include "VersionInfo.h" #include "Core.h" #include "modules/Items.h" +#include "df/kitchen_exc_type.h" /** * \defgroup grp_kitchen Kitchen settings @@ -42,14 +43,11 @@ namespace DFHack { namespace Kitchen { -typedef uint8_t t_exclusionType; - const unsigned int seedLimit = 400; // a limit on the limits which can be placed on seeds const t_itemSubtype organicSubtype = -1; // seems to fixed -const t_exclusionType cookingExclusion = 1; // seems to be fixed const df::enums::item_type::item_type limitType = df::enums::item_type::BAR; // used to store limit as an entry in the exclusion list. 0 = BAR const t_itemSubtype limitSubtype = 0; // used to store limit as an entry in the exclusion list -const t_exclusionType limitExclusion = 4; // used to store limit as an entry in the exclusion list +const df::kitchen_exc_type limitExclusion = df::kitchen_exc_type(4); // used to store limit as an entry in the exclusion list /** * Kitchen exclusions manipulator. Currently geared towards plants and seeds. @@ -79,5 +77,21 @@ DFHACK_EXPORT void setLimit(t_materialIndex materialIndex, unsigned int limit); DFHACK_EXPORT void clearLimits(); DFHACK_EXPORT std::size_t size(); + +// Finds the index of a kitchen exclusion in ui.kitchen.exc_types. Returns -1 if not found. +DFHACK_EXPORT int findExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index); + +// Adds an exclusion. Returns false if already excluded. +DFHACK_EXPORT bool addExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index); + +// Removes an exclusion. Returns false if not excluded. +DFHACK_EXPORT bool removeExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index); + } } diff --git a/library/modules/Kitchen.cpp b/library/modules/Kitchen.cpp index aa235780d..17b54896c 100644 --- a/library/modules/Kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -57,7 +57,7 @@ void Kitchen::allowPlantSeedCookery(t_materialIndex materialIndex) { if(ui->kitchen.mat_indices[i] == materialIndex && (ui->kitchen.item_types[i] == item_type::SEEDS || ui->kitchen.item_types[i] == item_type::PLANT) - && ui->kitchen.exc_types[i] == cookingExclusion + && ui->kitchen.exc_types[i] == df::kitchen_exc_type::Cook ) { match = true; @@ -73,7 +73,7 @@ void Kitchen::allowPlantSeedCookery(t_materialIndex materialIndex) ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + matchIndex); } } while(match); -}; +} void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) { @@ -83,7 +83,7 @@ void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) for(std::size_t i = 0; i < size(); ++i) { if(ui->kitchen.mat_indices[i] == materialIndex - && ui->kitchen.exc_types[i] == cookingExclusion) + && ui->kitchen.exc_types[i] == df::kitchen_exc_type::Cook) { if(ui->kitchen.item_types[i] == item_type::SEEDS) SeedAlreadyIn = true; @@ -97,7 +97,7 @@ void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) ui->kitchen.item_subtypes.push_back(organicSubtype); ui->kitchen.mat_types.push_back(type->material_defs.type_seed); ui->kitchen.mat_indices.push_back(materialIndex); - ui->kitchen.exc_types.push_back(cookingExclusion); + ui->kitchen.exc_types.push_back(df::kitchen_exc_type::Cook); } if(!PlantAlreadyIn) { @@ -105,9 +105,9 @@ void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) ui->kitchen.item_subtypes.push_back(organicSubtype); ui->kitchen.mat_types.push_back(type->material_defs.type_basic_mat); ui->kitchen.mat_indices.push_back(materialIndex); - ui->kitchen.exc_types.push_back(cookingExclusion); + ui->kitchen.exc_types.push_back(df::kitchen_exc_type::Cook); } -}; +} void Kitchen::fillWatchMap(std::map& watchMap) { @@ -119,7 +119,7 @@ void Kitchen::fillWatchMap(std::map& watchMap) watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i]; } } -}; +} void Kitchen::removeLimit(t_materialIndex materialIndex) { @@ -148,7 +148,7 @@ void Kitchen::removeLimit(t_materialIndex materialIndex) ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + matchIndex); } } while(match); -}; +} void Kitchen::setLimit(t_materialIndex materialIndex, unsigned int limit) { @@ -162,7 +162,7 @@ void Kitchen::setLimit(t_materialIndex materialIndex, unsigned int limit) ui->kitchen.mat_indices.push_back(materialIndex); ui->kitchen.mat_types.push_back((t_materialType) (limit < seedLimit) ? limit : seedLimit); ui->kitchen.exc_types.push_back(limitExclusion); -}; +} void Kitchen::clearLimits() { @@ -190,9 +190,58 @@ void Kitchen::clearLimits() ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + matchIndex); } } while(match); -}; +} size_t Kitchen::size() { return ui->kitchen.item_types.size(); -}; +} + +int Kitchen::findExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index) +{ + for (size_t i = 0; i < size(); i++) + { + if (ui->kitchen.item_types[i] == item_type && + ui->kitchen.item_subtypes[i] == item_subtype && + ui->kitchen.mat_types[i] == mat_type && + ui->kitchen.mat_indices[i] == mat_index && + ui->kitchen.exc_types[i] == type) + { + return int(i); + } + } + return -1; +} + +bool Kitchen::addExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index) +{ + if (findExclusion(type, item_type, item_subtype, mat_type, mat_index) >= 0) + return false; + + ui->kitchen.item_types.push_back(item_type); + ui->kitchen.item_subtypes.push_back(item_subtype); + ui->kitchen.mat_types.push_back(mat_type); + ui->kitchen.mat_indices.push_back(mat_index); + ui->kitchen.exc_types.push_back(type); + return true; +} + +bool Kitchen::removeExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index) +{ + int i = findExclusion(type, item_type, item_subtype, mat_type, mat_index); + if (i < 0) + return false; + + ui->kitchen.item_types.erase(ui->kitchen.item_types.begin() + i); + ui->kitchen.item_subtypes.erase(ui->kitchen.item_subtypes.begin() + i); + ui->kitchen.mat_types.erase(ui->kitchen.mat_types.begin() + i); + ui->kitchen.mat_indices.erase(ui->kitchen.mat_indices.begin() + i); + ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + i); + return true; +} diff --git a/library/xml b/library/xml index 007a22bfe..238b3053f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 007a22bfef6ca4007bab7e2b8f7316165dc833a3 +Subproject commit 238b3053f37feffdc0ab2d5d4415987c33a5d0e4 diff --git a/plugins/search.cpp b/plugins/search.cpp index 2827f0efd..58fca7d44 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -2167,8 +2167,8 @@ public: KVEC(int16_t, mat_type); \ KVEC(int32_t, mat_index); \ KVEC(int32_t, count); \ - KVEC(uint8_t, forbidden); \ - KVEC(uint8_t, possible) + KVEC(df::kitchen_pref_flag, forbidden); \ + KVEC(df::kitchen_pref_flag, possible) virtual void do_post_init() From d502dce2dbb365533f471bc198699cd0f680cc87 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 19:36:02 -0400 Subject: [PATCH 1001/1012] Add TOC to "C++ function wrappers" section --- docs/Lua API.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 08aaf84fe..5c2484475 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -804,6 +804,9 @@ Random number generation C++ function wrappers ===================== +.. contents:: + :local: + Thin wrappers around C++ functions, similar to the ones for virtual methods. One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments From 759ba5c420f65ae9bd9be72abbe1a476a4b800a6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 19:47:56 -0400 Subject: [PATCH 1002/1012] New tweak: kitchen-prefs-all Options to toggle brew/cook for all listed items Also fixed kitchen lua docs and moved kitchen search option --- dfhack.init-example | 1 + docs/Lua API.rst | 8 +-- docs/Plugins.rst | 1 + docs/changelog.txt | 1 + plugins/search.cpp | 2 +- plugins/tweak/tweak.cpp | 7 +++ plugins/tweak/tweaks/kitchen-prefs-all.h | 69 ++++++++++++++++++++++++ 7 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 plugins/tweak/tweaks/kitchen-prefs-all.h diff --git a/dfhack.init-example b/dfhack.init-example index 17a36fb0c..bdf035bd3 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -215,6 +215,7 @@ tweak civ-view-agreement tweak eggs-fertile tweak fps-min tweak hide-priority +tweak kitchen-prefs-all tweak kitchen-prefs-empty tweak max-wheelbarrow tweak shift-8-scroll diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 5c2484475..8190de4c1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1741,7 +1741,7 @@ Constructions module Kitchen module -------------- -* ``dfhack.kitchen.findExclusion(type, item_type, item_index, mat_type, mat_index)`` +* ``dfhack.kitchen.findExclusion(type, item_type, item_subtype, mat_type, mat_index)`` Finds a kitchen exclusion in the vectors in ``df.global.ui.kitchen``. Returns -1 if not found. @@ -1749,10 +1749,10 @@ Kitchen module * ``type`` is a ``df.kitchen_exc_type``, i.e. ``df.kitchen_exc_type.Cook`` or ``df.kitchen_exc_type.Brew``. * ``item_type`` is a ``df.item_type`` - * ``item_index``, ``mat_type``, and ``mat_index`` are all numeric + * ``item_subtype``, ``mat_type``, and ``mat_index`` are all numeric -* ``dfhack.kitchen.addExclusion(type, item_type, item_index, mat_type, mat_index)`` -* ``dfhack.kitchen.removeExclusion(type, item_type, item_index, mat_type, mat_index)`` +* ``dfhack.kitchen.addExclusion(type, item_type, item_subtype, mat_type, mat_index)`` +* ``dfhack.kitchen.removeExclusion(type, item_type, item_subtype, mat_type, mat_index)`` Adds or removes a kitchen exclusion, using the same parameters as ``findExclusion``. Both return ``true`` on success and ``false`` on failure, diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 33a2c452a..082dff360 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -311,6 +311,7 @@ Subcommands that persist until disabled or DF quits: :import-priority-category: Allows changing the priority of all goods in a category when discussing an import agreement with the liaison +:kitchen-prefs-all: Adds an option to toggle cook/brew for all visible items in kitchen preferences :kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences :kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) :max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile diff --git a/docs/changelog.txt b/docs/changelog.txt index 55086abfa..927500af8 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -39,6 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks - `tweak` stone-status-all: adds an option to toggle the economic status of all stones +- `tweak` kitchen-prefs-all: adds an option to toggle cook/brew for all visible items in kitchen preferences ## Fixes - `tweak` max-wheelbarrow: fixed conflict with building renaming diff --git a/plugins/search.cpp b/plugins/search.cpp index 58fca7d44..b43daa942 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -2135,7 +2135,7 @@ public: void render() const override { - print_search_option(2, gps->dimy - 2); + print_search_option(40, gps->dimy - 2); } int32_t *get_viewscreen_cursor() override diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 8d6d61db8..35f0f4705 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -94,6 +94,7 @@ #include "tweaks/hide-priority.h" #include "tweaks/hotkey-clear.h" #include "tweaks/import-priority-category.h" +#include "tweaks/kitchen-prefs-all.h" #include "tweaks/kitchen-prefs-color.h" #include "tweaks/kitchen-prefs-empty.h" #include "tweaks/max-wheelbarrow.h" @@ -219,6 +220,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + df::kitchen_pref_flag flag; + df::kitchen_exc_type exc_type; + if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + flag.bits.Cook = true; + exc_type = df::kitchen_exc_type::Cook; + } + else if (input->count(interface_key::CUSTOM_SHIFT_B)) + { + flag.bits.Brew = true; + exc_type = df::kitchen_exc_type::Brew; + } + + if (flag.whole && cursor < forbidden[page].size()) + { + bool was_forbidden = forbidden[page][cursor].whole & flag.whole; + for (size_t i = 0; i < forbidden[page].size(); i++) + { + if (possible[page][i].whole & flag.whole) + { + if (was_forbidden) + { + // unset flag + forbidden[page][i].whole &= ~flag.whole; + Kitchen::removeExclusion(exc_type, + item_type[page][i], item_subtype[page][i], + mat_type[page][i], mat_index[page][i]); + } + else + { + // set flag + forbidden[page][i].whole |= flag.whole; + Kitchen::addExclusion(exc_type, + item_type[page][i], item_subtype[page][i], + mat_type[page][i], mat_index[page][i]); + } + } + } + } + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + int x = 2, y = gps->dimy - 2; + OutputHotkeyString(x, y, "Cook all", interface_key::CUSTOM_SHIFT_C, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); + x = 20; + OutputHotkeyString(x, y, "Brew all", interface_key::CUSTOM_SHIFT_B, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(kitchen_prefs_all_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(kitchen_prefs_all_hook, feed); From b551e70ffa72a3339889edd5ce6e61ec069c8e37 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 19:56:48 -0400 Subject: [PATCH 1003/1012] prospector: Avoid crashing due to invalid vein materials Fixes #1276, ref #1277 --- docs/Plugins.rst | 1 + docs/changelog.txt | 1 + plugins/prospector.cpp | 15 ++++++++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 082dff360..03ccd809f 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -129,6 +129,7 @@ probe Can be used to determine tile properties like temperature. .. _prospect: +.. _prospector: prospect ======== diff --git a/docs/changelog.txt b/docs/changelog.txt index 927500af8..ee11c6123 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,6 +43,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `tweak` max-wheelbarrow: fixed conflict with building renaming +- `prospector`: fixed crash due to invalid vein materials ## Misc Improvements - `adv-rumors`: bound to Ctrl-A diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 18efeac98..fd2d959eb 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -177,16 +177,21 @@ void printVeins(color_ostream &con, MatMap &mat_map, MatMap gems; MatMap rest; - for (MatMap::const_iterator it = mat_map.begin(); it != mat_map.end(); ++it) + for (const auto &kv : mat_map) { - df::inorganic_raw *gloss = world->raws.inorganics[it->first]; + df::inorganic_raw *gloss = vector_get(world->raws.inorganics, kv.first); + if (!gloss) + { + con.printerr("invalid material gloss: %hi\n", kv.first); + continue; + } if (gloss->material.isGem()) - gems[it->first] = it->second; + gems[kv.first] = kv.second; else if (gloss->isOre()) - ores[it->first] = it->second; + ores[kv.first] = kv.second; else - rest[it->first] = it->second; + rest[kv.first] = kv.second; } con << "Ores:" << std::endl; From 50a863a48eacb67b4d759dd5bcb4bace78bd6d07 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 17 May 2018 20:09:57 -0400 Subject: [PATCH 1004/1012] Fix Kitchen.h include (case-sensitive) --- library/modules/Kitchen.cpp | 2 +- plugins/seedwatch.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/Kitchen.cpp b/library/modules/Kitchen.cpp index 17b54896c..705cb7bee 100644 --- a/library/modules/Kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -13,7 +13,7 @@ using namespace std; #include "MemAccess.h" #include "Types.h" #include "Error.h" -#include "modules/kitchen.h" +#include "modules/Kitchen.h" #include "ModuleFactory.h" #include "Core.h" using namespace DFHack; diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index b7767e046..5f254a955 100755 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -9,7 +9,7 @@ #include "Export.h" #include "PluginManager.h" #include "modules/World.h" -#include "modules/kitchen.h" +#include "modules/Kitchen.h" #include "VersionInfo.h" #include "df/world.h" #include "df/plant_raw.h" From fe0049db8c69e4a9b22ef77162f38820cb483453 Mon Sep 17 00:00:00 2001 From: grubsteak Date: Thu, 17 May 2018 23:29:10 -0500 Subject: [PATCH 1005/1012] enable lua api construction functions it seems this has been left out on accident? --- library/LuaApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0bf19e93e..437101e25 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2977,7 +2977,7 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs); OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs); OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs); - OpenModule(state, "constructions", dfhack_constructions_module); + OpenModule(state, "constructions", dfhack_constructions_module, dfhack_constructions_funcs); OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs); From 8c8585d4eee5a9bad2b7aca58dde6ae032d03f00 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 18 May 2018 00:50:23 -0400 Subject: [PATCH 1006/1012] Update changelog/authors (#1278) --- docs/Authors.rst | 1 + docs/changelog.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index d3a98a59c..a67266006 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -46,6 +46,7 @@ expwnent expwnent Feng figment figment gchristopher gchristopher +grubsteak grubsteak Harlan Playford playfordh Hayati Ayguen hayguen IndigoFenix diff --git a/docs/changelog.txt b/docs/changelog.txt index ee11c6123..b07188465 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,8 +42,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `tweak` kitchen-prefs-all: adds an option to toggle cook/brew for all visible items in kitchen preferences ## Fixes -- `tweak` max-wheelbarrow: fixed conflict with building renaming +- Lua: registered ``dfhack.constructions.designateRemove()`` correctly - `prospector`: fixed crash due to invalid vein materials +- `tweak` max-wheelbarrow: fixed conflict with building renaming ## Misc Improvements - `adv-rumors`: bound to Ctrl-A From badc0e3a3dcb69ef15314ca90bd9f9fa737c4a6c Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 18 May 2018 14:29:45 -0400 Subject: [PATCH 1007/1012] Update changelog, submodules for r1 --- docs/changelog.txt | 27 ++++++++++++++++++++++++++- library/xml | 2 +- scripts | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index b07188465..8843f4ed3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +# 0.44.10-r1 + +## New Scripts +- `bodyswap`: shifts player control over to another unit in adventure mode + ## New Tweaks - `tweak` stone-status-all: adds an option to toggle the economic status of all stones - `tweak` kitchen-prefs-all: adds an option to toggle cook/brew for all visible items in kitchen preferences @@ -45,16 +50,25 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Lua: registered ``dfhack.constructions.designateRemove()`` correctly - `prospector`: fixed crash due to invalid vein materials - `tweak` max-wheelbarrow: fixed conflict with building renaming +- `view-item-info`: stopped appending extra newlines permanently to descriptions ## Misc Improvements +- Added logo to documentation +- Documented several missing ``dfhack.gui`` Lua functions - `adv-rumors`: bound to Ctrl-A +- `command-prompt`: added support for ``Gui::getSelectedPlant()`` - `gui/advfort`: bound to Ctrl-T - `gui/room-list`: added support for ``Gui::getSelectedBuilding()`` - `gui/unit-info-viewer`: bound to Alt-I +- `modtools/create-unit`: made functions available to other scripts - `search`: - added support for stone restrictions screen (under ``z``: Status) - added support for kitchen preferences (also under ``z``) +## Internals +- Fixed compiler warnings on all supported build configurations +- Windows build scripts now work with non-C system drives + ## API - New functions (all available to Lua as well): - ``Buildings::getRoomDescription()`` @@ -66,6 +80,17 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``Kitchen::addExclusion()`` - ``Kitchen::findExclusion()`` - ``Kitchen::removeExclusion()`` +- syndrome-util: added ``eraseSyndromeData()`` + +## Structures +- ``dfhack_room_quality_level``: new enum +- ``glowing_barrier``: identified ``triggered``, added comments +- ``item_flags2.has_written_content``: removed name +- ``kitchen_exc_type``: new enum (for ``ui.kitchen``) +- ``mandate.mode``: now an enum +- ``unit_personality.emotions.flags.memory``: identified +- ``viewscreen_kitchenprefst.forbidden``, ``possible``: now a bitfield, ``kitchen_pref_flag`` +- ``world_data.feature_map``: extensive comments # 0.44.10-beta1 @@ -92,7 +117,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `blueprint`: added a basic Lua API - `devel/export-dt-ini`: added tool offsets for DT 40 -- `devel/save-version`: added current DF version +- `devel/save-version`: added current DF version to output - `install-info`: added information on tweaks ## Internals diff --git a/library/xml b/library/xml index 238b3053f..c3025feb8 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 238b3053f37feffdc0ab2d5d4415987c33a5d0e4 +Subproject commit c3025feb80c6f8e7441ea5dcf4f463a9cf89cbbd diff --git a/scripts b/scripts index fc7a37d9d..0b6e4a563 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit fc7a37d9d6df4d0c1b84435c8ed7392869e8e7d8 +Subproject commit 0b6e4a56392e125c01e24694ddd880bbc652451d From 6047ddedd31809216200eb22d1175ef87896e966 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 18 May 2018 14:35:28 -0400 Subject: [PATCH 1008/1012] removed structure updates from wrong changelog (not part of 0.44.10-beta1) --- docs/changelog.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8843f4ed3..f8335c6ca 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -85,12 +85,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Structures - ``dfhack_room_quality_level``: new enum - ``glowing_barrier``: identified ``triggered``, added comments -- ``item_flags2.has_written_content``: removed name +- ``item_flags2``: renamed ``has_written_content`` to ``unk_book`` - ``kitchen_exc_type``: new enum (for ``ui.kitchen``) - ``mandate.mode``: now an enum - ``unit_personality.emotions.flags.memory``: identified - ``viewscreen_kitchenprefst.forbidden``, ``possible``: now a bitfield, ``kitchen_pref_flag`` -- ``world_data.feature_map``: extensive comments +- ``world_data.feature_map``: added extensive documentation (in XML) # 0.44.10-beta1 @@ -125,10 +125,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Added function names to DFHack's NullPointer and InvalidArgument exceptions - Linux: required plugins to have symbols resolved at link time, for consistency with other platforms -## Structures -- ``item_flags2``: renamed ``has_written_content`` to ``unk_book`` -- ``world_data.feature_map``: added documentation (in XML) - # 0.44.10-alpha1 ## New Scripts From 0f3244336b7cdfbfa7dce77ff94cce8507b904e5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 18 May 2018 14:52:25 -0400 Subject: [PATCH 1009/1012] Fix "gen_changelog.py -a" with empty future section --- docs/gen_changelog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py index 89db98052..d042ca2e7 100644 --- a/docs/gen_changelog.py +++ b/docs/gen_changelog.py @@ -229,6 +229,9 @@ def generate_changelog(all=False): if all: for version in versions: + if version not in stable_version_map: + print('warn: skipping ' + version) + continue if stable_version_map[version] == version: version_entries = {version: stable_entries[version]} else: @@ -240,7 +243,6 @@ def generate_changelog(all=False): 'docs/_changelogs/%s-reddit.txt' % version, replace=False, prefix='> ') - print('ch ' + version) return entries From 8cb5e7fa88a9be00c88f5506c1d3d134dd0f237a Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 18 May 2018 15:08:05 -0400 Subject: [PATCH 1010/1012] Bump version to 0.44.10-r1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 842103da1..6e469546b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,8 +141,8 @@ endif() # set up versioning. set(DF_VERSION "0.44.10") -set(DFHACK_RELEASE "beta1") -set(DFHACK_PRERELEASE TRUE) +set(DFHACK_RELEASE "r1") +set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 5f81cac3b700e7f9c8cdc638ef5847067238caea Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 21 May 2018 21:23:52 -0400 Subject: [PATCH 1011/1012] dwarfmonitor.lua: use dmerror() consistently --- plugins/lua/dwarfmonitor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/dwarfmonitor.lua b/plugins/lua/dwarfmonitor.lua index 35a667bd4..7637c50f7 100644 --- a/plugins/lua/dwarfmonitor.lua +++ b/plugins/lua/dwarfmonitor.lua @@ -162,7 +162,7 @@ function load_config() end widgets = {} for _, opts in pairs(config.widgets) do - if type(opts) ~= 'table' then qerror('"widgets" is not an array') end + if type(opts) ~= 'table' then dmerror('"widgets" is not an array') end if not opts.type then dmerror('Widget missing type field') end local cls = _ENV['Widget_' .. opts.type] if not cls then From e206a6d88b99a69ee00b9f6ff468c2df761a36b0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 22 May 2018 16:37:44 -0400 Subject: [PATCH 1012/1012] mousequery: leave menus safely, without looping Fixes #1285 --- plugins/mousequery.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 4e0143fb6..66fa0fee5 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -392,10 +392,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (!designationMode) { - while (ui->main.mode != Default) - { - sendKey(df::interface_key::LEAVESCREEN); - } + Gui::resetDwarfmodeView(); if (key == interface_key::NONE) key = get_default_query_mode(mpos);

      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