Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ that repo.

## New Scripts

- `gui/workorder-details`: adjusts work orders' input item, material, traits
- `warn-stealers`: warn when creatures that may steal your food, drinks, or items become visible

## Fixes
Expand Down
190 changes: 190 additions & 0 deletions gui/workorder-details.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
-- adjust work orders' input item, material, traits
--[====[

gui/workorder-details
=====================
Adjust input items, material, or traits for work orders. Actual
jobs created for it will inherit the details.

This is the equivalent of `gui/workshop-job` for work orders,
with the additional possibility to set input items' traits.

It has to be run from a work order's detail screen
(:kbd:`j-m`, select work order, :kbd:`d`).

For best experience add the following to your ``dfhack*.init``::

keybinding add D@workquota_details gui/workorder-details

]====]

--[[
Credit goes to the author of `gui/workshop-job`, it showed
me the way. Also, a huge chunk of code could be reused.
]]

local utils = require 'utils'
local gui = require 'gui'
local guimat = require 'gui.materials'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'

local wsj = reqscript 'gui/workshop-job'

local JobDetails = defclass(JobDetails, gui.FramedScreen)

JobDetails.focus_path = 'workorder-details'

JobDetails.ATTRS {
job = DEFAULT_NIL,
frame_inset = 1,
frame_background = COLOR_BLACK,
}

function JobDetails:init(args)
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') },
{ key = 'CUSTOM_T', text = ': Traits',
enabled = self:callback('canChangeTrait'),
on_activate = self:callback('onChangeTrait') }
}
},
widgets.List{
view_id = 'list',
frame = { t = 6, b = 2 },
row_height = 4,
},
widgets.Label{
frame = { l = 0, b = 0 },
text = {
{ key = 'LEAVESCREEN', text = ': Back',
on_activate = self:callback('dismiss') }
}
},
}

self:initListChoices()
end

function JobDetails:onGetSelectedJob()
return self.job
end

local describe_item_type = wsj.describe_item_type
local is_caste_mat = wsj.is_caste_mat
local describe_material = wsj.describe_material
local list_flags = wsj.list_flags

local function describe_item_traits(iobj)
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, iobj.has_material_reaction_product .. '-producing')
end
if iobj.reaction_class ~= '' then
table.insert(line1, 'reaction 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 traits')
end
return table.concat(line1, ', ')
end

function JobDetails:initListChoices()
if not self.job.items then
self.subviews.list:setChoices({})
return
end

local choices = {}
for i,iobj in ipairs(self.job.items) do
local head = 'Item '..(i+1)..' x'..iobj.quantity
if iobj.min_dimension > 0 then
head = head .. '(size '..iobj.min_dimension..')'
end

table.insert(choices, {
index = i,
iobj = iobj,
text = {
head, NEWLINE,
' ', { text = curry(describe_item_type, iobj) }, NEWLINE,
' ', { text = curry(describe_material, iobj) }, NEWLINE,
' ', { text = curry(describe_item_traits, iobj) }, NEWLINE
}
})
end

self.subviews.list:setChoices(choices)
end

JobDetails.canChangeIType = wsj.JobDetails.canChangeIType
JobDetails.setItemType = wsj.JobDetails.setItemType
JobDetails.onChangeIType = wsj.JobDetails.onChangeIType
JobDetails.canChangeMat = wsj.JobDetails.canChangeMat
JobDetails.setMaterial = wsj.JobDetails.setMaterial
JobDetails.findUnambiguousItem = wsj.JobDetails.findUnambiguousItem
JobDetails.onChangeMat = wsj.JobDetails.onChangeMat

function JobDetails:onInput(keys)
JobDetails.super.onInput(self, keys)
end

function JobDetails:canChangeTrait()
local idx, obj = self.subviews.list:getSelected()
return obj ~= nil and not is_caste_mat(obj.iobj)
end

function JobDetails:onChangeTrait()
local idx, obj = self.subviews.list:getSelected()
guimat.ItemTraitsDialog{
job_item = obj.iobj,
prompt = 'Please select traits for input '..idx,
none_caption = 'no traits',
}:show()
end

local scr = dfhack.gui.getCurViewscreen()
if not df.viewscreen_workquota_detailsst:is_instance(scr) then
qerror("This script needs to be run from a work order details screen")
end

-- by opening the viewscreen_workquota_detailsst the
-- work order's .items array is initialized
JobDetails{ job = scr.order }:show()
7 changes: 7 additions & 0 deletions gui/workshop-job.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ and then try to change the input item type, now it won't let you select *plant*;
you have to unset the material first.

]====]

--@ module = true

local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
Expand Down Expand Up @@ -323,6 +326,10 @@ function JobDetails:onInput(keys)
end
end

if dfhack_flags.module then
return
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
Expand Down
152 changes: 152 additions & 0 deletions test/gui/workorder-details.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
-- test -dhack/scripts/devel/tests -tworkorder%-details

config.mode = 'fortress'

local gui = require('gui')
local function send_keys(...)
local keys = {...}
for _,key in ipairs(keys) do
gui.simulateInput(dfhack.gui.getCurViewscreen(true), key)
end
end

local xtest = {} -- use to temporarily disable tests (change `function test.somename` to `function xtest.somename`)
local wait = function(n)
--delay(n or 30) -- enable for debugging the tests
end

-- handle confirm plugin: we may need to additionally confirm order removal
local confirm = require 'plugins.confirm'
local confirmRemove = function() end
if confirm.isEnabled() then
for _, c in pairs(confirm.get_conf_data()) do
if c.id == 'order-remove' then
if c.enabled then
confirmRemove = function()
wait()
send_keys('CUSTOM_P', 'MANAGER_REMOVE')
end
end
break
end
end
end

function test.changeOrderDetails()
--[[ this is not needed because of how gui.simulateInput'D_JOBLIST' works
-- verify expected starting state
expect.eq(df.ui_sidebar_mode.Default, df.global.ui.main.mode)
expect.true_(df.viewscreen_dwarfmodest:is_instance(scr))
--]]

-- get into the orders screen
send_keys('D_JOBLIST', 'UNITJOB_MANAGER')
expect.true_(df.viewscreen_jobmanagementst:is_instance(dfhack.gui.getCurViewscreen(true)), "We need to be in the jobmanagement/Main screen")

local ordercount = #df.global.world.manager_orders

--- create an order
dfhack.run_command [[workorder "{ \"frequency\" : \"OneTime\", \"job\" : \"CutGems\", \"material\" : \"INORGANIC:SLADE\" }"]]
wait()
send_keys('STANDARDSCROLL_UP') -- move cursor to newly created CUT SLADE
wait()
send_keys('MANAGER_DETAILS')
expect.true_(df.viewscreen_workquota_detailsst:is_instance(dfhack.gui.getCurViewscreen(true)), "We need to be in the workquota_details screen")
local job = dfhack.gui.getCurViewscreen(true).order
local item = job.items[0]

dfhack.run_command 'gui/workorder-details'
--[[
input item: boulder
material: slade
traits: none
]]
expect.ne(-1, item.item_type, "Input should not be 'any item'")
expect.ne(-1, item.mat_type, "Material should not be 'any material'")
expect.false_(item.flags2.allow_artifact, "Trait allow_artifact should not be set")

wait()
send_keys('CUSTOM_I', 'SELECT') -- change input to 'any item'
wait()
send_keys('CUSTOM_M', 'SELECT') -- change material to 'any material'
wait()
send_keys('CUSTOM_T', 'STANDARDSCROLL_DOWN', 'STANDARDSCROLL_DOWN', 'SELECT', 'LEAVESCREEN') -- change traits to 'allow_artifact'
--[[
input item: any item
material: any material
traits: allow_artifact
]]
expect.eq(-1, item.item_type, "Input item should change to 'any item'")
expect.eq(-1, item.mat_type, "Material should change to 'any material'")
expect.true_(item.flags2.allow_artifact, "Trait allow_artifact should change to set")

-- cleanup
wait()
send_keys('LEAVESCREEN', 'LEAVESCREEN', 'MANAGER_REMOVE')
confirmRemove()
expect.eq(ordercount, #df.global.world.manager_orders, "Test order should've been removed")
-- go back to map screen
wait()
send_keys('LEAVESCREEN', 'LEAVESCREEN')
end

-- where flags is a table of key-boolean
local function any_flag(flags)
for f, v in pairs(flags) do
if v then return f end
end
return false
end

function test.unsetAllItemTraits()
-- get into the orders screen
send_keys('D_JOBLIST', 'UNITJOB_MANAGER')
expect.true_(df.viewscreen_jobmanagementst:is_instance(dfhack.gui.getCurViewscreen(true)), "We need to be in the jobmanagement/Main screen")

local ordercount = #df.global.world.manager_orders

--- create an order
dfhack.run_command [[workorder "{ \"frequency\" : \"OneTime\", \"job\" : \"CutGems\", \"material\" : \"INORGANIC:SLADE\" }"]]
wait()
send_keys('STANDARDSCROLL_UP') -- move cursor to newly created CUT SLADE
wait()
send_keys('MANAGER_DETAILS')
expect.true_(df.viewscreen_workquota_detailsst:is_instance(dfhack.gui.getCurViewscreen(true)), "We need to be in the workquota_details screen")
local job = dfhack.gui.getCurViewscreen(true).order
local item = job.items[0]

dfhack.run_command 'gui/workorder-details'

-- manually set some traits
item.flags1.improvable = true
item.flags2.allow_artifact = true
item.flags3.unimproved = true
item.has_tool_use = 0 -- LIQUID_COOKING
item.has_material_reaction_product = 'BAG_ITEM'
item.metal_ore = 0 -- iron
item.reaction_class = 'CALCIUM_CARBONATE'

wait()
send_keys('CUSTOM_T')
wait()
send_keys('SELECT') -- cursor is at 'no traits'
wait()
send_keys('LEAVESCREEN')

expect.false_(any_flag(item.flags1), "A flag in item.flags1 is set")
expect.false_(any_flag(item.flags2), "A flag in item.flags2 is set")
expect.false_(any_flag(item.flags3), "A flag in item.flags3 is set")
expect.eq(-1, item.has_tool_use, "Tool use is not reset")
expect.eq('', item.has_material_reaction_product, "Material reaction product is not reset")
expect.eq(-1, item.metal_ore, "Metal ore is not reset")
expect.eq('', item.reaction_class, "Reaction class is not reset")

-- cleanup
wait()
send_keys('LEAVESCREEN', 'LEAVESCREEN', 'MANAGER_REMOVE')
confirmRemove()
expect.eq(ordercount, #df.global.world.manager_orders, "Test order should've been removed")
-- go back to map screen
wait()
send_keys('LEAVESCREEN', 'LEAVESCREEN')
end