From be17239077e7089c94324d6aa9079b8360473baf Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 11 Mar 2024 11:40:44 -0700 Subject: [PATCH] implement skill level restriction overlay --- docs/changelog.txt | 2 +- docs/plugins/orders.rst | 53 ++++++++++----- plugins/lua/orders.lua | 139 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 176 insertions(+), 18 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a12727901f..c412c0d636 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,7 +57,7 @@ Template for new versions: - `stocks`: add button/hotkey for removing empty categories from the stocks list - `sort`: updated and reinstated military status/squad membership/burrow membership filter for work animal assignment screen - `logistics`: ``autoretrain`` will automatically assign trainers to your partially-trained (but not yet domesticated) livestock. this prevents children of partially-trained parents from reverting to wild if you don't notice they were born -- `orders`: add overlay for configuring labor restrictions for workshops +- `orders`: add overlay for configuring labor and skill level restrictions for workshops ## Fixes - `autochop`: fix underestimation of log yield for cavern mushrooms diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index a018779975..8da9fecfe1 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -51,10 +51,13 @@ Examples Overlay ------- +Fort-wide work orders screen +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Orders plugin functionality is directly available via an `overlay` widget when -the manager orders screen is open. There are hotkeys assigned to export, import, -sort, clear, and recheck conditions. You can also click on the hotkey hints as -if they were buttons. Clearing will ask for confirmation before acting. +the fort-wide work orders screen is open. There are hotkeys assigned to export, +import, sort, clear, and recheck conditions. You can also click on the hotkey +hints as if they were buttons. Clearing will ask for confirmation before acting. When you open the conditions screen for a manager order, there is also a small overlay that allows you to recheck conditions for just that order. This is @@ -62,26 +65,44 @@ useful for when the conditions were true when the order started, but they have become false and now you're just getting repeated cancellation spam as the order cannot be fulfilled. -Finally, an overlay is added to the "Workers" tab for workshop and furnace -buildings that support orders that can be categorized by labor type. On the -overlay, you can configure the workshop to only accept general work orders that -pertain to specific labors (the list of allowed labors is different for every -workshop). +Workshop Workers tab +~~~~~~~~~~~~~~~~~~~~ + +For workshops that do *not* have a workshop master assigned, there is a slider +you can use to restrict the units that perform jobs at that workshop by their +skill level. + +Due to space constraints, some skill levels are combined with the adjacent +higher rank on the slider: + +- "Competent" includes "Adequate" workers +- "Proficient" includes "Skilled" workers +- "Expert" includes "Adept" workers +- "Accomplished" includes "Professional" workers +- "Master" includes "Great" workers +- "Grand Master" includes "High Master" workers + +Finally, a list is shown for workshops that service manager orders of multiple +labor types. You can toggle the listed labors so the workshop only accepts +general work orders that match the enabled labors (the list of allowed labors +is different for every workshop). For example, by default, all weapon, armor, and blacksmithing general manager -orders get sent to all forges. With labor restrictions, you can designate -specific forges to handle just weapons, just armor, or just metalsmithing. -Then, you can assign appropriate legendary masters to each forge, and they will -only receive orders for appropriate products. +orders get sent to all forges that can take general work orders. With labor +restrictions, you can designate specific forges to handle just weapons, just +armor, or just metalsmithing. Then, you can assign appropriate legendary +masters to each forge, and they will only receive orders for appropriate +products. Simiarly, you can set up Craftsdwarf's workshops to specialize in stone, wood, or bone. -Regardless of these settings, you can manually assign any task to the workshop -and it will still be completed. The labor restrictions only apply to general -manager work orders scheduled from the fort-wide work orders screen. +Regardless of the labor restriction settings, you can manually assign any task +to the workshop and it will still be completed. The labor restrictions only +apply to general manager work orders scheduled from the fort-wide work orders +screen. -Veteran players may remember this as a vanilla feature in pre-v50 Dwarf +Veteran players may remember these overlays as vanilla features in pre-v50 Dwarf Fortress. This is actually still the case. The DFHack overlay simply provides a UI for the vanilla feature hiding beneath the surface. diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 909a4e289c..1fd7efaab7 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -4,6 +4,7 @@ local dialogs = require('gui.dialogs') local gui = require('gui') local overlay = require('plugins.overlay') local textures = require('gui.textures') +local utils = require('utils') local widgets = require('gui.widgets') -- @@ -256,6 +257,141 @@ function RecheckOverlay:onRenderBody(dc) RecheckOverlay.super.onRenderBody(self, dc) end +-- +-- SkillRestrictionOverlay +-- + +SkillRestrictionOverlay = defclass(SkillRestrictionOverlay, overlay.OverlayWidget) +SkillRestrictionOverlay.ATTRS{ + desc='Adds a UI to the Workers tab for vanilla workshop labor restrictions.', + default_pos={x=-40, y=16}, + default_enabled=true, + viewscreens={ + 'dwarfmode/ViewSheets/BUILDING/Furnace', + 'dwarfmode/ViewSheets/BUILDING/Workshop', + }, + frame={w=54, h=7}, +} + +local function can_set_skill_level() + for _,fs in ipairs(dfhack.gui.getFocusStrings(dfhack.gui.getDFViewscreen(true))) do + if fs:endswith('/Workers') then + local bld = dfhack.gui.getSelectedBuilding(true) + if not bld then return false end + return #bld.profile.permitted_workers == 0 + end + end + return false +end + +local function set_skill_level(which, val, bld) + bld = bld or dfhack.gui.getSelectedBuilding(true) + if not bld then return end + bld.profile[which] = val +end + +-- the UI isn't wide enough to accomodate all the skills. select a few to combine +-- into the adjacent tier. +local SKIP_RATINGS = utils.invert{ + df.skill_rating.Adequate, + df.skill_rating.Skilled, + df.skill_rating.Adept, + df.skill_rating.Professional, + df.skill_rating.Great, + df.skill_rating.HighMaster, + df.skill_rating.Legendary1, + df.skill_rating.Legendary2, + df.skill_rating.Legendary3, + df.skill_rating.Legendary4, + df.skill_rating.Legendary5, +} + +function SkillRestrictionOverlay:init() + local options = {} + local min_rating, max_rating = {}, {} + for ridx in ipairs(df.skill_rating) do + if SKIP_RATINGS[ridx] then goto continue end + local idx = #options + 1 + table.insert(options, {label=df.skill_rating.attrs[ridx].caption, value=idx}) + min_rating[idx] = max_rating[idx-1] and (max_rating[idx-1]+1) or 0 + max_rating[idx] = ridx + ::continue:: + end + max_rating[#max_rating] = 3000 -- DF value for upper cap + + local panel = widgets.Panel{ + frame_style=gui.FRAME_MEDIUM, + frame_background=gui.CLEAR_PEN, + } + panel:addviews{ + widgets.CycleHotkeyLabel{ + view_id='min_skill', + frame={l=0, t=0, w=16}, + label='Min skill:', + label_below=true, + key_back='CUSTOM_SHIFT_C', + key='CUSTOM_SHIFT_V', + options=options, + initial_option=options[1].value, + on_change=function(val) + local bld = dfhack.gui.getSelectedBuilding(true) + if self.subviews.max_skill:getOptionValue() < val then + self.subviews.max_skill:setOption(val) + set_skill_level('max_level', max_rating[val], bld) + end + set_skill_level('min_level', min_rating[val], bld) + end, + }, + widgets.CycleHotkeyLabel{ + view_id='max_skill', + frame={r=1, t=0, w=16}, + label='Max skill:', + label_below=true, + key_back='CUSTOM_SHIFT_E', + key='CUSTOM_SHIFT_R', + options=options, + initial_option=options[#options].value, + on_change=function(val) + local bld = dfhack.gui.getSelectedBuilding(true) + if self.subviews.min_skill:getOptionValue() > val then + self.subviews.min_skill:setOption(val) + set_skill_level('min_level', min_rating[val], bld) + end + set_skill_level('max_level', max_rating[val], bld) + end, + }, + widgets.RangeSlider{ + frame={l=0, t=3}, + num_stops=#options, + get_left_idx_fn=function() + return self.subviews.min_skill:getOptionValue() + end, + get_right_idx_fn=function() + return self.subviews.max_skill:getOptionValue() + end, + on_left_change=function(idx) self.subviews.min_skill:setOption(idx, true) end, + on_right_change=function(idx) self.subviews.max_skill:setOption(idx, true) end, + }, + } + + self:addviews{ + panel, + widgets.HelpButton{command='orders'}, + } +end + +function SkillRestrictionOverlay:render(dc) + if can_set_skill_level() then + SkillRestrictionOverlay.super.render(self, dc) + end +end + +function SkillRestrictionOverlay:onInput(keys) + if can_set_skill_level() then + return SkillRestrictionOverlay.super.onInput(self, keys) + end +end + -- -- LaborRestrictionsOverlay -- @@ -263,7 +399,7 @@ end LaborRestrictionsOverlay = defclass(LaborRestrictionsOverlay, overlay.OverlayWidget) LaborRestrictionsOverlay.ATTRS{ desc='Adds a UI to the Workers tab for vanilla workshop labor restrictions.', - default_pos={x=-40,y=24}, + default_pos={x=-40, y=24}, default_enabled=true, viewscreens={ 'dwarfmode/ViewSheets/BUILDING/Furnace/Kiln/Workers', @@ -431,6 +567,7 @@ end OVERLAY_WIDGETS = { recheck=RecheckOverlay, importexport=OrdersOverlay, + skillrestrictions=SkillRestrictionOverlay, laborrestrictions=LaborRestrictionsOverlay, }