I got sick of scrolling through my entire unit list to see which ones were naked or wearing worn-out clothes. I also have no idea how fast the average dwarf goes through socks, so I didn't want to make a standing manager order. At any rate, here's the solution: The check-laundry script.
--Checks the status of worn clothing in your fortress.
local help = [====[
check-laundry
=============
A simple script to check the demand for clothing in your fortress. The default mode displays the total amounts of upper-body clothing, lower-body clothing, and footwear currently worn by your citizens, color-coded by status:
Green:
like new
pig tail sock
Cyan:
slightly worn
xpig tail sock
Yellow:
moderately worn
Xpig tail sockX
Red:
heavily worn
XXpig tail sockXX
Dark grey:
missing
(dwarf has no suitable clothing for this slot)
The verbose mode further displays a list of all citizens with worn or missing clothing. Run the verbose mode with "check-laundry -v" or "check-laundry -verbose."
The script displays only the article of clothing in the best condition for each slot. That means that if Urist McSnazzyDresser is wearing a XXpig tail sockXX, a xlamma wool sockx, and two pairs of Xforgotten beast leather sandalsX, check-laundry will list Urist as only having "slightly worn" footwear, on the basis that even if most of the footwear degrades, Urist won't be going barefoot until the last pair of socks rots off. Citizens with fresh clothes in all four slots (upper body, lower body, foot, and other foot) are not displayed in the verbose list but still count towards the total at the top of the window.
"check-laundry -h" or "check-laundry -help" to view this message.
]====]
local gui = require 'gui'
local utils=require('utils')
validArgs = utils.invert({
'help',
'h',
'verbose',
'v'
})
local args = utils.processArgs({...}, validArgs)
local function getCitizenList()
local citizenTable={}
for k,u in ipairs(df.global.world.units.active) do
if dfhack.units.isCitizen(u) and dfhack.units.isVisible(u) then
table.insert(citizenTable,u)
end
end
return citizenTable
end
local function getReadableTitle(unit)
return dfhack.TranslateName(dfhack.units.getVisibleName(unit)) ..", " .. dfhack.units.getProfessionName(unit)
end
local function getReadableName(unit)
return dfhack.TranslateName(dfhack.units.getVisibleName(unit))
end
laundryPanel = defclass(laundryPanel, gui.FramedScreen)
local width
local height
local garmentStatus = {"fine", "slight wear", "moderate wear", "heavy wear", "missing!"}
local statusColor = {COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, COLOR_LIGHTRED, COLOR_DARKGREY}
local totalShirts = {0, 0, 0, 0, 0}
local totalPants = {0, 0, 0, 0, 0}
local totalSocks = {0, 0, 0, 0, 0}
local function countNaked()
for k, u in ipairs(getCitizenList()) do
local torso = 5
local legs = 5
local foot = 5
local otherFoot = 5
-- 1: clothed; 2-4: worn; 5: naked
for k,i in ipairs(u.inventory) do
if i.mode == 2 then
if i.body_part_id == 0 and torso > i.item.wear then
torso = i.item.wear + 1
elseif i.body_part_id == 1 and legs > i.item.wear then
legs = i.item.wear + 1
elseif i.body_part_id == 14 and foot > i.item.wear then
foot = i.item.wear + 1
elseif i.body_part_id == 15 and otherFoot > i.item.wear then
otherFoot = i.item.wear + 1
end
end
end
totalShirts[torso]=totalShirts[torso]+1
totalPants[legs]=totalPants[legs]+1
totalSocks[foot]=totalSocks[foot]+1
totalSocks[otherFoot]=totalSocks[otherFoot]+1
end
end
local function checkNaked(unit)
local torso = 5
local legs = 5
local foot = 5
local otherFoot = 5
local overview = {}
-- 1: clothed; 2-4: worn; 5: naked
for k,i in ipairs(unit.inventory) do
if i.mode == 2 then
if i.body_part_id == 0 and torso > i.item.wear then
torso = i.item.wear + 1
elseif i.body_part_id == 1 and legs > i.item.wear then
legs = i.item.wear + 1
elseif i.body_part_id == 14 and foot > i.item.wear then
foot = i.item.wear + 1
elseif i.body_part_id == 15 and otherFoot > i.item.wear then
otherFoot = i.item.wear + 1
end
end
end
if torso == 1 and legs == 1 and foot == 1 and otherFoot == 1 then
return nil
else
overview["Upper body"]=torso
overview["Lower body"]=legs
overview["Foot"]=foot
overview["Other foot"]=otherFoot
return overview
end
end
local function verboseNaked(unit)
local output = {}
check = checkNaked(unit)
if check then
table.insert(output, {dfhack.units.getProfessionColor(unit), getReadableTitle(unit)..":"})
if check["Upper body"] > 1 then
table.insert(output, {statusColor[check["Upper body"]], " Upper body: "..garmentStatus[check["Upper body"]] })
end
if check["Lower body"] > 1 then
table.insert(output, {statusColor[check["Lower body"]], " Lower body: "..garmentStatus[check["Lower body"]] })
end
if check["Foot"] > 1 then
table.insert(output, {statusColor[check["Foot"]], " Foot: "..garmentStatus[check["Foot"]] })
end
if check["Other foot"] > 1 then
table.insert(output, {statusColor[check["Other foot"]], " Other foot: "..garmentStatus[check["Other foot"]] })
end
return output
else
return nil
end
end
if args.v or args.verbose then
width = 60
height = 20
else
width = 30
height = 3
end
laundryPanel.ATTRS = {
frame_style = gui.GREY_FRAME,
frame_title = 'check-laundry',
frame_width = width,
frame_height = height,
frame_inset = 1,
}
function laundryPanel:init()
self.text = {}
for k, d in ipairs(getCitizenList()) do
dorf = checkNaked(d)
if dorf then
for k, l in ipairs(verboseNaked(d)) do
table.insert(self.text, l)
end
end
end
self.start = 1
self.start_min = 1
self.start_max = #self.text - self.frame_height + 4
end
function laundryPanel:onRenderBody(dc)
dc:pen(COLOR_WHITE):string("Upper body: ")
for k, w in ipairs(totalShirts) do
dc:pen(statusColor[k]):string(tostring(w).." ")
end
dc:newline()
dc:pen(COLOR_WHITE):string("Lower body: ")
for k, w in ipairs(totalPants) do
dc:pen(statusColor[k]):string(tostring(w).." ")
end
dc:newline()
dc:pen(COLOR_WHITE):string("Footwear: ")
for k, w in ipairs(totalSocks) do
dc:pen(statusColor[k]):string(tostring(w).." ")
end
if args.v or args.verbose then
dc:newline()
for k, line in ipairs(self.text) do
if k >= self.start then
dc:pen(line[1])
dc:string(line[2]):newline()
end
end
if self.start > self.start_min then
dc:pen(COLOR_WHITE)
else
dc:pen(COLOR_DARKGREY)
end
dc:seek(self.frame_width - 1, 0):char(30)
if self.start < self.start_max then
dc:pen(COLOR_WHITE)
else
dc:pen(COLOR_DARKGREY)
end
dc:seek(self.frame_width - 1, self.frame_height - 1):char(31)
dc:pen(COLOR_WHITE)
dc:seek(self.frame_width-1, math.floor((self.frame_height-3)*(self.start/self.start_max))+1):char(219)
end
end
function laundryPanel: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 args.help or args.h then
print(help)
return
else
countNaked()
if not scr then
scr = laundryPanel()
scr:show()
else
scr:dismiss()
scr = nil
end
end
--body_part_id:
-- 0: Upper Body
-- 1: Legs (lower body)
-- 3: Hat
-- 8: Hand
-- 9: Other Hand
-- 14: Foot
-- 15: Other Foot
--mode:
-- 2 (Worn)
Running check-laundry in DFHack will display a small pop-up window with some minimal information: It tells you how many dwarves are wearing new clothing, xslightly worn clothingx, Xmoderately worn clothingX, XXheavily worn clothingXX, and no clothing in each of the important slots: Upper body, lower body, and feet, any of which will lead to bad thoughts if left uncovered. Running the verbose mode with check-laundry -v will give you a more detailed list of which dwarves are lacking what clothes.
Copy the code into a file called check-laundry.lua in the hack/scripts directory to install. Works in .44.10 and presumably a few other similar versions.
Now v1.1 with slightly cleaned-up code.