From 74464f3b61e7e9734b4da22b5f944e974237c950 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 3 Nov 2023 11:02:43 -0700 Subject: [PATCH 1/3] mock out potential UI --- plugins/lua/buildingplan.lua | 3 ++ plugins/lua/buildingplan/mechanisms.lua | 69 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 plugins/lua/buildingplan/mechanisms.lua diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index d64317eb0..7e034b988 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -14,6 +14,7 @@ local _ENV = mkmodule('plugins.buildingplan') local argparse = require('argparse') local inspector = require('plugins.buildingplan.inspectoroverlay') +local mechanisms = require('plugins.buildingplan.mechanisms') local pens = require('plugins.buildingplan.pens') local planner = require('plugins.buildingplan.planneroverlay') require('dfhack.buildings') @@ -134,12 +135,14 @@ function reload_modules() reload('plugins.buildingplan.itemselection') reload('plugins.buildingplan.planneroverlay') reload('plugins.buildingplan.inspectoroverlay') + reload('plugins.buildingplan.mechanisms') reload('plugins.buildingplan') end OVERLAY_WIDGETS = { planner=planner.PlannerOverlay, inspector=inspector.InspectorOverlay, + mechanisms=mechanisms.MechanismOverlay, } return _ENV diff --git a/plugins/lua/buildingplan/mechanisms.lua b/plugins/lua/buildingplan/mechanisms.lua new file mode 100644 index 000000000..b3475be2b --- /dev/null +++ b/plugins/lua/buildingplan/mechanisms.lua @@ -0,0 +1,69 @@ +local _ENV = mkmodule('plugins.buildingplan.mechanisms') + +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +-------------------------------- +-- MechanismOverlay +-- + +MechanismOverlay = defclass(MechanismOverlay, overlay.OverlayWidget) +MechanismOverlay.ATTRS{ + default_pos={x=5,y=5}, + default_enabled=true, + viewscreens='dwarfmode/LinkingLever', + frame={w=57, h=13}, +} + +function MechanismOverlay:init() + self:addviews{ + widgets.CycleHotkeyLabel{ + view_id='safety1', + frame={t=4, l=4, w=40}, + key='CUSTOM_G', + label='Lever mechanism safety:', + options={ + {label='Any', value=0}, + {label='Magma', value=2, pen=COLOR_RED}, + {label='Fire', value=1, pen=COLOR_LIGHTRED}, + }, + initial_option=0, + }, + widgets.CycleHotkeyLabel{ + view_id='safety2', + frame={t=5, l=4, w=40}, + key='CUSTOM_SHIFT_G', + label='Target mechanism safety:', + options={ + {label='Any', value=0}, + {label='Magma', value=2, pen=COLOR_RED}, + {label='Fire', value=1, pen=COLOR_LIGHTRED}, + }, + initial_option=0, + }, + widgets.HotkeyLabel{ + frame={t=6, l=9, w=48, h=3}, + key='CUSTOM_M', + label='Choose', + auto_height=false, + on_activate=function() end, + }, + widgets.HotkeyLabel{ + frame={t=9, l=9, w=48, h=3}, + key='CUSTOM_SHIFT_M', + label='Choose', + auto_height=false, + on_activate=function() end, + }, + } +end + +function MechanismOverlay:onInput(keys) + if keys._MOUSE_L and self:getMousePos() then + MechanismOverlay.super.onInput(self, keys) + -- don't let clicks bleed through the panel + return true + end +end + +return _ENV From f0badc63e4ca62b2fd6f50511dcd8d3f39a2f9ac Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 3 Nov 2023 16:33:08 -0700 Subject: [PATCH 2/3] implement mechanism selection for lever linking --- docs/changelog.txt | 1 + plugins/buildingplan/buildingplan.cpp | 36 +++++- plugins/lua/buildingplan/itemselection.lua | 40 ++++-- plugins/lua/buildingplan/mechanisms.lua | 128 +++++++++++++++++--- plugins/lua/buildingplan/planneroverlay.lua | 6 +- 5 files changed, 180 insertions(+), 31 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index f4860efc0..7a13ba6f4 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,7 @@ Template for new versions: ## New Features - `prospect`: can now give you an estimate of resources from the embark screen. hover the mouse over a potential embark area and run `prospect`. - `burrow`: integrated 3d box fill and 2d/3d flood fill extensions for burrow painting mode +- `buildingplan`: allow specific mechanisms to be selected when linking levers ## Fixes - `stockpiles`: hide configure and help buttons when the overlay panel is minimized diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 9fb5641bb..7a1416855 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -674,13 +674,14 @@ static void scheduleCycle(color_ostream &out) { } static int scanAvailableItems(color_ostream &out, df::building_type type, int16_t subtype, - int32_t custom, int index, bool ignore_filters, vector *item_ids = NULL, - map *counts = NULL) { + int32_t custom, int index, bool ignore_filters, bool ignore_quality, HeatSafety *heat_override = NULL, + vector *item_ids = NULL, map *counts = NULL) +{ DEBUG(status,out).print( "entering scanAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); BuildingTypeKey key(type, subtype, custom); - HeatSafety heat = get_heat_safety_filter(key); + HeatSafety heat = heat_override ? *heat_override : get_heat_safety_filter(key); auto &job_items = get_job_items(out, key); if (index < 0 || job_items.size() <= (size_t)index) return 0; @@ -703,6 +704,10 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_ filter.setMaterials(set()); special.clear(); } + if (ignore_quality) { + filter.setMinQuality(df::item_quality::Ordinary); + filter.setMaxQuality(df::item_quality::Artifact); + } if (itemPassesScreen(out, item) && matchesFilters(item, jitem, heat, filter, special)) { if (item_ids) item_ids->emplace_back(item->id); @@ -732,7 +737,25 @@ static int getAvailableItems(lua_State *L) { "entering getAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); vector item_ids; - scanAvailableItems(*out, type, subtype, custom, index, true, &item_ids); + scanAvailableItems(*out, type, subtype, custom, index, true, false, NULL, &item_ids); + Lua::PushVector(L, item_ids); + return 1; +} + +static int getAvailableItemsByHeat(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + df::building_type type = (df::building_type)luaL_checkint(L, 1); + int16_t subtype = luaL_checkint(L, 2); + int32_t custom = luaL_checkint(L, 3); + int index = luaL_checkint(L, 4); + HeatSafety heat = (HeatSafety)luaL_checkint(L, 5); + DEBUG(status,*out).print( + "entering getAvailableItemsByHeat building_type=%d subtype=%d custom=%d index=%d\n", + type, subtype, custom, index); + vector item_ids; + scanAvailableItems(*out, type, subtype, custom, index, true, true, &heat, &item_ids); Lua::PushVector(L, item_ids); return 1; } @@ -755,7 +778,7 @@ static int countAvailableItems(color_ostream &out, df::building_type type, int16 DEBUG(status,out).print( "entering countAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); - int count = scanAvailableItems(out, type, subtype, custom, index, false); + int count = scanAvailableItems(out, type, subtype, custom, index, false, false); if (count) return count; @@ -968,7 +991,7 @@ static int getMaterialFilter(lua_State *L) { return 0; const auto &mat_filter = filters[index].getMaterials(); map counts; - scanAvailableItems(*out, type, subtype, custom, index, false, NULL, &counts); + scanAvailableItems(*out, type, subtype, custom, index, false, false, NULL, NULL, &counts); HeatSafety heat = get_heat_safety_filter(key); const df::job_item *jitem = get_job_items(*out, key)[index]; // name -> {count=int, enabled=bool, category=string, heat=string} @@ -1232,6 +1255,7 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getGlobalSettings), DFHACK_LUA_COMMAND(getAvailableItems), + DFHACK_LUA_COMMAND(getAvailableItemsByHeat), DFHACK_LUA_COMMAND(setMaterialMaskFilter), DFHACK_LUA_COMMAND(getMaterialMaskFilter), DFHACK_LUA_COMMAND(setMaterialFilter), diff --git a/plugins/lua/buildingplan/itemselection.lua b/plugins/lua/buildingplan/itemselection.lua index 4b8ee73d8..bc836bc33 100644 --- a/plugins/lua/buildingplan/itemselection.lua +++ b/plugins/lua/buildingplan/itemselection.lua @@ -21,6 +21,29 @@ function get_automaterial_selection(building_type) return tracker.list[#tracker.list] end +local function get_artifact_name(item) + local gref = dfhack.items.getGeneralRef(item, df.general_ref_type.IS_ARTIFACT) + if not gref then return end + local artifact = df.artifact_record.find(gref.artifact_id) + if not artifact then return end + return dfhack.TranslateName(artifact.name) +end + +function get_item_description(item_id, item, safety_label) + item = item or df.item.find(item_id) + if not item then + return ('No %s safe mechanisms available'):format(safety_label:lower()) + end + local desc = item.flags.artifact and get_artifact_name(item) or + dfhack.items.getDescription(item, 0, true) + local wear_level = item:getWear() + if wear_level == 1 then desc = ('x%sx'):format(desc) + elseif wear_level == 2 then desc = ('X%sX'):format(desc) + elseif wear_level == 3 then desc = ('XX%sXX'):format(desc) + end + return desc +end + local function sort_by_type(a, b) local ad, bd = a.data, b.data return ad.item_type < bd.item_type or @@ -57,7 +80,7 @@ ItemSelection.ATTRS{ frame_title='Choose items', frame={w=56, h=24, l=4, t=7}, resizable=true, - index=DEFAULT_NIL, + get_available_items_fn=DEFAULT_NIL, desc=DEFAULT_NIL, quantity=DEFAULT_NIL, autoselect=DEFAULT_NIL, @@ -99,9 +122,9 @@ function ItemSelection:init() text_pen=BUILD_TEXT_PEN, text_hpen=BUILD_TEXT_HPEN, text={ - ' Use filter ', NEWLINE, - ' for remaining ', NEWLINE, - ' items ', + ' ', NEWLINE, + ' Autoselect ', NEWLINE, + ' ', }, on_click=self:callback('submit'), visible=function() return self.num_selected < self.quantity end, @@ -238,13 +261,12 @@ local function make_search_key(str) end function ItemSelection:get_choices(sort_fn) - local item_ids = require('plugins.buildingplan').getAvailableItems(uibs.building_type, - uibs.building_subtype, uibs.custom_type, self.index-1) + local item_ids = self.get_available_items_fn() local buckets = {} for _,item_id in ipairs(item_ids) do local item = df.item.find(item_id) if not item then goto continue end - local desc = dfhack.items.getDescription(item, 0, true) + local desc = get_item_description(item_id, item) if buckets[desc] then local bucket = buckets[desc] table.insert(bucket.data.item_ids, item_id) @@ -396,7 +418,7 @@ ItemSelectionScreen.ATTRS { pass_pause=false, pass_mouse_clicks=false, defocusable=false, - index=DEFAULT_NIL, + get_available_items_fn=DEFAULT_NIL, desc=DEFAULT_NIL, quantity=DEFAULT_NIL, autoselect=DEFAULT_NIL, @@ -407,7 +429,7 @@ ItemSelectionScreen.ATTRS { function ItemSelectionScreen:init() self:addviews{ ItemSelection{ - index=self.index, + get_available_items_fn=self.get_available_items_fn, desc=self.desc, quantity=self.quantity, autoselect=self.autoselect, diff --git a/plugins/lua/buildingplan/mechanisms.lua b/plugins/lua/buildingplan/mechanisms.lua index b3475be2b..0993953cd 100644 --- a/plugins/lua/buildingplan/mechanisms.lua +++ b/plugins/lua/buildingplan/mechanisms.lua @@ -1,8 +1,11 @@ local _ENV = mkmodule('plugins.buildingplan.mechanisms') +local itemselection = require('plugins.buildingplan.itemselection') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') +local view_sheets = df.global.game.main_interface.view_sheets + -------------------------------- -- MechanismOverlay -- @@ -17,53 +20,148 @@ MechanismOverlay.ATTRS{ function MechanismOverlay:init() self:addviews{ + widgets.Label{ + frame={t=4, l=2}, + text='Mechanism safety:' + }, widgets.CycleHotkeyLabel{ - view_id='safety1', - frame={t=4, l=4, w=40}, + view_id='safety_lever', + frame={t=4, l=21, w=15}, key='CUSTOM_G', - label='Lever mechanism safety:', + label='Lever:', options={ {label='Any', value=0}, {label='Magma', value=2, pen=COLOR_RED}, {label='Fire', value=1, pen=COLOR_LIGHTRED}, }, initial_option=0, + on_change=self:callback('choose_mechanism', 'lever', true), }, widgets.CycleHotkeyLabel{ - view_id='safety2', - frame={t=5, l=4, w=40}, + view_id='safety_target', + frame={t=4, l=38, w=16}, key='CUSTOM_SHIFT_G', - label='Target mechanism safety:', + label='Target:', options={ {label='Any', value=0}, {label='Magma', value=2, pen=COLOR_RED}, {label='Fire', value=1, pen=COLOR_LIGHTRED}, }, initial_option=0, + on_change=self:callback('choose_mechanism', 'target', true), }, widgets.HotkeyLabel{ - frame={t=6, l=9, w=48, h=3}, + frame={t=7, l=8, w=49, h=2}, key='CUSTOM_M', - label='Choose', + label=function() + return itemselection.get_item_description(view_sheets.linking_lever_mech_lever_id, + nil, + self.subviews.safety_lever:getOptionLabel()) + end, auto_height=false, - on_activate=function() end, + enabled=function() return view_sheets.linking_lever_mech_lever_id ~= -1 end, + on_activate=self:callback('choose_mechanism', 'lever', false), }, widgets.HotkeyLabel{ - frame={t=9, l=9, w=48, h=3}, + frame={t=10, l=8, w=49, h=2}, key='CUSTOM_SHIFT_M', - label='Choose', + label=function() + return itemselection.get_item_description(view_sheets.linking_lever_mech_target_id, + nil, + self.subviews.safety_target:getOptionLabel()) + end, auto_height=false, - on_activate=function() end, + enabled=function() return view_sheets.linking_lever_mech_target_id ~= -1 end, + on_activate=self:callback('choose_mechanism', 'target', false), }, } end +local item_selection_dlg +local function reset_dlg() + if item_selection_dlg then + if item_selection_dlg:isActive() then + item_selection_dlg:dismiss() + end + item_selection_dlg = nil + end +end + +local function get_available_items(safety, other_mechanism) + local item_ids = require('plugins.buildingplan').getAvailableItemsByHeat( + df.building_type.Trap, df.trap_type.Lever, -1, 0, safety) + for idx,item_id in ipairs(item_ids) do + if item_id == other_mechanism then + table.remove(item_ids, idx) + break + end + end + return item_ids +end + +function MechanismOverlay:save_id(which, item_id) + local saved_id = ('saved_%s_id'):format(which) + local ui_id = ('linking_lever_mech_%s_id'):format(which) + view_sheets[ui_id] = item_id + self[saved_id] = item_id +end + +function MechanismOverlay:choose_mechanism(which, autoselect) + local widget_id = 'safety_' .. which + local safety = self.subviews[widget_id]:getOptionValue() + local ui_other_id = ('linking_lever_mech_%s_id'):format(which == 'lever' and 'target' or 'lever') + local available_item_ids = get_available_items(safety, view_sheets[ui_other_id]) + + if autoselect then + self:save_id(which, available_item_ids[1] or -1) + return + end + + -- to integrate with ItemSelection's last used sorting + df.global.buildreq.building_type = df.building_type.Trap + + local desc = self.subviews[widget_id]:getOptionLabel() + if desc ~= 'Any' then + desc = desc .. ' safe' + end + desc = desc .. ' mechanism' + + item_selection_dlg = item_selection_dlg or itemselection.ItemSelectionScreen{ + get_available_items_fn=function() return available_item_ids end, + desc=desc, + quantity=1, + autoselect=false, + on_cancel=reset_dlg, + on_submit=function(chosen_ids) + self:save_id(which, chosen_ids[1] or available_item_ids[1] -1) + reset_dlg() + end, + }:show() +end + function MechanismOverlay:onInput(keys) - if keys._MOUSE_L and self:getMousePos() then - MechanismOverlay.super.onInput(self, keys) - -- don't let clicks bleed through the panel + if MechanismOverlay.super.onInput(self, keys) then return true end + if keys._MOUSE_L then + if self:getMousePos() then + -- don't let clicks bleed through the panel + return true + end + -- don't allow the lever to be linked if mechanisms are not set + return view_sheets.linking_lever_mech_lever_id == -1 or + view_sheets.linking_lever_mech_target_id == -1 + end +end + +function MechanismOverlay:onRenderFrame(dc, rect) + MechanismOverlay.super.onRenderFrame(self, dc, rect) + if self.saved_lever_id ~= view_sheets.linking_lever_mech_lever_id then + self:choose_mechanism('lever', true) + end + if self.saved_target_id ~= view_sheets.linking_lever_mech_target_id then + self:choose_mechanism('target', true) + end end return _ENV diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index 688d3bf74..2f150710f 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -809,8 +809,12 @@ function PlannerOverlay:onInput(keys) for idx = num_filters,1,-1 do chosen_items[idx] = {} local filter = filters[idx] + local get_available_items_fn = function() + return require('plugins.buildingplan').getAvailableItems( + uibs.building_type, uibs.building_subtype, uibs.custom_type, idx-1) + end local selection_screen = itemselection.ItemSelectionScreen{ - index=idx, + get_available_items_fn=get_available_items_fn, desc=require('plugins.buildingplan').get_desc(filter), quantity=get_quantity(filter, is_hollow, bounds), autoselect=autoselect, From 7873ff8b5aa91723277b860d41342f6fb5784b8c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 3 Nov 2023 16:41:58 -0700 Subject: [PATCH 3/3] docs and branding --- docs/plugins/buildingplan.rst | 7 +++ plugins/lua/buildingplan/mechanisms.lua | 63 +++++++++++++------------ 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index db04a97f7..b1e77a80d 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -190,3 +190,10 @@ bump the items for this building to the front of their respective queues. Note that each item type and filter configuration has its own queue, so even if an item is in queue position 1, there may be other queues that snag the needed item first. + +Lever linking +------------- + +When linking levers, `buildingplan` extends the vanilla panel by offering +control over which mechanisms are chosen for installation at the lever and at +the target. Heat safety filters are provided for convenience. diff --git a/plugins/lua/buildingplan/mechanisms.lua b/plugins/lua/buildingplan/mechanisms.lua index 0993953cd..4b6e42885 100644 --- a/plugins/lua/buildingplan/mechanisms.lua +++ b/plugins/lua/buildingplan/mechanisms.lua @@ -20,35 +20,40 @@ MechanismOverlay.ATTRS{ function MechanismOverlay:init() self:addviews{ - widgets.Label{ - frame={t=4, l=2}, - text='Mechanism safety:' - }, - widgets.CycleHotkeyLabel{ - view_id='safety_lever', - frame={t=4, l=21, w=15}, - key='CUSTOM_G', - label='Lever:', - options={ - {label='Any', value=0}, - {label='Magma', value=2, pen=COLOR_RED}, - {label='Fire', value=1, pen=COLOR_LIGHTRED}, - }, - initial_option=0, - on_change=self:callback('choose_mechanism', 'lever', true), - }, - widgets.CycleHotkeyLabel{ - view_id='safety_target', - frame={t=4, l=38, w=16}, - key='CUSTOM_SHIFT_G', - label='Target:', - options={ - {label='Any', value=0}, - {label='Magma', value=2, pen=COLOR_RED}, - {label='Fire', value=1, pen=COLOR_LIGHTRED}, - }, - initial_option=0, - on_change=self:callback('choose_mechanism', 'target', true), + widgets.BannerPanel{ + frame={t=4, l=1, r=1, h=1}, + subviews={ + widgets.Label{ + frame={t=0, l=1}, + text='Mechanism safety:' + }, + widgets.CycleHotkeyLabel{ + view_id='safety_lever', + frame={t=0, l=20, w=15}, + key='CUSTOM_G', + label='Lever:', + options={ + {label='Any', value=0}, + {label='Magma', value=2, pen=COLOR_RED}, + {label='Fire', value=1, pen=COLOR_LIGHTRED}, + }, + initial_option=0, + on_change=self:callback('choose_mechanism', 'lever', true), + }, + widgets.CycleHotkeyLabel{ + view_id='safety_target', + frame={t=0, l=38, w=16}, + key='CUSTOM_SHIFT_G', + label='Target:', + options={ + {label='Any', value=0}, + {label='Magma', value=2, pen=COLOR_RED}, + {label='Fire', value=1, pen=COLOR_LIGHTRED}, + }, + initial_option=0, + on_change=self:callback('choose_mechanism', 'target', true), + }, + } }, widgets.HotkeyLabel{ frame={t=7, l=8, w=49, h=2},