forked from DFHack/scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkitchen-info.lua
More file actions
446 lines (374 loc) · 15.5 KB
/
kitchen-info.lua
File metadata and controls
446 lines (374 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
-- Adds more info to the Kitchen screen.
--@ enable = true
--[====[
gui/kitchen-info
================
Adds more info to the Kitchen screen.
Usage:
gui/kitchen-info enable|disable|help
enable|disable gui/kitchen-info
]====]
--[[
Possible future improvements:
* add option on Vegetables/fruit/leaves tab to show all processable plants and growths, not just cookable or brewable ones
* add column settings sub-window for selecting which columns are visible
--]]
gui = require 'gui'
utils = require 'utils'
gps = df.global.gps
kitchen_overlay = defclass(kitchen_overlay, gui.Screen)
kitchen_overlay.ATTRS {
focus_path = 'kitchen_overlay'
}
local show_processing = true
local COLUMNS = {
{
label = "Extract",
maxValueLength = 6,
minValueLeftPadding = 0,
emptyValueLeftPadding = 1,
activeOnPages = { 'VegetablesFruitLeaves' }
},
{
label = "ToBag",
maxValueLength = 5, -- actually 6 but rarely appears
minValueLeftPadding = 0,
emptyValueLeftPadding = 0,
activeOnPages = { 'VegetablesFruitLeaves' },
hidden = true -- This column is hidden because the only plant with BAG_ITEM reaction is Quarry Bush, which is neither cookable nor brewable so it never appears in the Kitchen screen
},
{
label = "Thread",
maxValueLength = 6,
minValueLeftPadding = 0,
emptyValueLeftPadding = 1,
activeOnPages = { 'VegetablesFruitLeaves' }
},
{
label = "Mill",
maxValueLength = 5,
minValueLeftPadding = 0,
activeOnPages = { 'VegetablesFruitLeaves', 'Seeds' }
},
--[[
-- Unimplemented
{
label = "Press",
maxValueLength = 5,
minValueLeftPadding = 0,
activeOnPages = { 'VegetablesFruitLeaves', 'Seeds' }
},
--]]
{
label = "EatRaw",
maxValueLength = 4,
minValueLeftPadding = 1,
emptyValueLeftPadding = 1,
activeOnPages = { 'VegetablesFruitLeaves', 'Seeds' }
}
}
local COLUMNS_BY_LABEL = {}
for _, column in ipairs(COLUMNS) do
column.activeOnPages = utils.invert(column.activeOnPages)
column.minLength = math.max(#column.label, column.minValueLeftPadding + column.maxValueLength)
COLUMNS_BY_LABEL[column.label] = column
end
function COLUMNS_BY_LABEL.Extract.getColumnValue(item_type, material, plant_raw)
if (item_type == df.item_type.PLANT) then
if (plant_raw.flags.EXTRACT_BARREL) then
return "Barrel"
elseif (plant_raw.flags.EXTRACT_VIAL) then
return " Vial"
elseif (plant_raw.flags.EXTRACT_STILL_VIAL) then
return " Still"
end
end
return false
end
-- This column is hidden because the only plant with BAG_ITEM reaction is Quarry Bush, which is neither cookable nor brewable so it never appears in the Kitchen screen
function COLUMNS_BY_LABEL.ToBag.getColumnValue(item_type, material, plant_raw)
if (item_type == df.item_type.PLANT) then
local reaction_product = material.reaction_product
for i, reaction_product_id in ipairs(reaction_product.id) do
if (reaction_product_id.value == "BAG_ITEM") then
local bag_item_type = reaction_product.item_type[i]
if (bag_item_type == df.item_type.PLANT_GROWTH) then
local bag_item_subtype = reaction_product.item_subtype[i]
local bag_mat_type = reaction_product.material.mat_type[i]
local bag_mat_index = reaction_product.material.mat_index[i]
local bag_matinfo = dfhack.matinfo.decode(bag_mat_type, bag_mat_index)
local bag_mat_plant_raw = bag_matinfo.plant
local bag_mat_plant_growth = bag_mat_plant_raw.growths[bag_item_subtype]
if (bag_mat_plant_growth.name_plural:sub(-6) == "leaves") then
return "Leaves"
end
end
return "Other"
end
end
end
return false
end
function COLUMNS_BY_LABEL.Thread.getColumnValue(item_type, material, plant_raw)
if (item_type == df.item_type.PLANT and plant_raw.flags.THREAD) then
return "Thread"
end
return false
end
function COLUMNS_BY_LABEL.Mill.getColumnValue(item_type, material, plant_raw)
if (item_type == df.item_type.PLANT and plant_raw.flags.MILL) then
local mill_mat_type = plant_raw.material_defs.type.mill
local mill_mat_index = plant_raw.material_defs.idx.mill
local mill_matinfo = dfhack.matinfo.decode(mill_mat_type, mill_mat_index)
local mill_material = mill_matinfo.material
if (mill_material.flags.IS_DYE) then
return "Dye"
elseif (mill_material.state_name.Powder:sub(-5) == "flour") then
return "Flour"
else
return "Other"
end
elseif (item_type == df.item_type.SEEDS) then
for _, reaction_product_id in ipairs(material.reaction_product.id) do
if (reaction_product_id.value == "PRESS_LIQUID_MAT") then
return "Paste"
end
end
end
return false
end
--[[
-- TODO?
function COLUMNS_BY_LABEL.Press.getColumnValue(item_type, material, plant_raw)
return false
end
--]]
function COLUMNS_BY_LABEL.EatRaw.getColumnValue(item_type, material, plant_raw)
if (material.flags.EDIBLE_RAW) then
return "Eat"
end
return false
end
-- In TrueType mode the string can be found as a series of TrueType tiles of the same length as the expected string
-- Column labels "Ingredient Type", "Number" and "Permissions" all have different lengths, which allows this method to find distinct positions of them
local function findTrueTypeSeriesInLine(x1, x2, y, length)
local seriesStartX = x1
local seriesLength = 1 -- assumes first tile is TrueType
for x = x1 + 1, x2 do
if (dfhack.screen.readTile(x, y) == nil) then -- series started or continues
if (seriesLength == 0) then -- series started
seriesStartX = x
end
seriesLength = seriesLength + 1
elseif (seriesLength == length) then -- series of expected length ended
break
else -- series of other length ended, reset counter for next series
seriesLength = 0
end
end
if (seriesLength == length) then -- recheck the length of the series at the end, in case the loop ended normally, not because of break
return seriesStartX, seriesStartX + length - 1
end
return -1, -1
end
local function findStringInLine(x1, x2, y, str)
local length = #str
local firstByte = str:byte(1)
local xEnd = x2 - (length - 1)
for x = x1, xEnd do
local tile = dfhack.screen.readTile(x, y)
if (tile == nil) then -- TrueType text detected
return findTrueTypeSeriesInLine(x, x2, y, length)
elseif (tile.ch == firstByte) then
local matches = true
for i = 2, length do
tile = dfhack.screen.readTile(x + i - 1, y)
if (not tile) or (tile.ch ~= str:byte(i)) then
matches = false
break
end
end
if (matches) then
return x, x + length - 1
end
end
end
return -1, -1
end
local function pluginCompatibility_search_isInputActive()
return true -- FIXME: Implement when API is available
end
local function pluginCompatibility_tweak_kitchen_prefs_color_isEnabled()
return false -- FIXME: Implement when API is available
end
local function drawNumberColumn(x, kitchen, firstVisibleIndex, numDisplayedItems)
dfhack.screen.paintString({ fg = COLOR_WHITE }, x, 4, "Number")
local p = gui.Painter.new_xy(x, 6, x + 10 - 1, 6 + numDisplayedItems - 1)
local count = kitchen.count[kitchen.page]
for i = 0, numDisplayedItems - 1 do
local index = firstVisibleIndex + i
p:seek(0, i)
local color = (index == kitchen.cursor) and COLOR_YELLOW or COLOR_BROWN
p:pen(color):string(tostring(count[index]))
end
end
local function drawPermissionsColumn(x, kitchen, firstVisibleIndex, numDisplayedItems)
dfhack.screen.paintString({ fg = COLOR_WHITE }, x, 4, "Permissions")
local p = gui.Painter.new_xy(x, 6, x + string.len("Permissions") - 1, 6 + numDisplayedItems - 1)
local possible = kitchen.possible[kitchen.page]
local forbidden = kitchen.forbidden[kitchen.page]
local unforbiddenColor = pluginCompatibility_tweak_kitchen_prefs_color_isEnabled() and COLOR_LIGHTGREEN or COLOR_LIGHTBLUE
for i = 0, numDisplayedItems - 1 do
local index = firstVisibleIndex + i
p:seek(1, i)
if (possible[index].Cook) then
local color = forbidden[index].Cook and COLOR_LIGHTRED or unforbiddenColor
p:pen(color):string("Cook")
else
p:pen(COLOR_DARKGREY):string("----")
end
p:seek(6, i)
if (possible[index].Brew) then
local color = forbidden[index].Brew and COLOR_LIGHTRED or unforbiddenColor
p:pen(color):string("Brew")
else
p:pen(COLOR_DARKGREY):string("----")
end
end
end
function kitchen_overlay:onRender()
local kitchen = self._native.parent
kitchen:render()
if (not enabled) then
self:dismiss()
return
end
local origNumberStart, origNumberEnd = findStringInLine(2, gps.dimx - 3, 4, "Number")
local origPermissionsStart, origPermissionsEnd = findStringInLine(math.max(2, origNumberEnd + 1), gps.dimx - 3, 4, "Permissions")
local numberStart = origNumberStart
local permissionsStart, permissionsEnd = origPermissionsStart, origPermissionsEnd
local numItems = #kitchen.item_str[kitchen.page]
local maxItemsOnPage = 1 + (gps.dimy - 5) - 6
local cursorPage = math.floor(kitchen.cursor / maxItemsOnPage)
local firstVisibleIndex = cursorPage * maxItemsOnPage
local numDisplayedItems = math.min(numItems - firstVisibleIndex, maxItemsOnPage)
if show_processing then
local pageName = df.viewscreen_kitchenpref_page[kitchen.page]
local visibleColumns = {}
for _, column in ipairs(COLUMNS) do
if (not column.hidden) and column.activeOnPages[pageName] then
table.insert(visibleColumns, column)
end
end
if (#visibleColumns >= 1 and origPermissionsEnd ~= -1) then
local columnsPos = origPermissionsEnd + 3
local currentOffset = 0
local columnOffset = {}
local prevColumn = nil
for i, column in ipairs(visibleColumns) do
if (prevColumn) then
currentOffset = currentOffset + math.max(#prevColumn.label, prevColumn.minValueLeftPadding + prevColumn.maxValueLength - column.minValueLeftPadding) + 2
end
columnOffset[i] = currentOffset
prevColumn = column
end
local allColumnsWidth = currentOffset + prevColumn.minLength
-- Check whether to redraw Permissions and Number columns
if (columnsPos + allColumnsWidth > gps.dimx - 2) then
columnsPos = gps.dimx - 2 - allColumnsWidth
permissionsEnd = columnsPos - 3
permissionsStart = permissionsEnd - (origPermissionsEnd - origPermissionsStart)
dfhack.screen.fillRect(gui.CLEAR_PEN, origPermissionsStart, 4, origPermissionsEnd, 6 + numDisplayedItems - 1)
if (numberStart + 9 >= permissionsStart) then
numberStart = permissionsStart - 9
dfhack.screen.fillRect(gui.CLEAR_PEN, numberStart, 4, origNumberStart + 10 - 1, 6 + numDisplayedItems - 1)
drawNumberColumn(numberStart, kitchen, firstVisibleIndex, numDisplayedItems)
end
drawPermissionsColumn(permissionsStart, kitchen, firstVisibleIndex, numDisplayedItems)
end
-- Column labels
local labels_p = gui.Painter.new_xy(columnsPos, 4, gps.dimx - 2, 4)
labels_p:pen(COLOR_WHITE)
for i, column in ipairs(visibleColumns) do
labels_p:seek(columnOffset[i], 0):string(column.label)
end
-- Columns data
local p = gui.Painter.new_xy(columnsPos, 6, gps.dimx - 3, 6 + numDisplayedItems - 1)
local item_type = kitchen.item_type[kitchen.page]
local item_subtype = kitchen.item_subtype[kitchen.page]
local mat_type = kitchen.mat_type[kitchen.page]
local mat_index = kitchen.mat_index[kitchen.page]
for i = 0, numDisplayedItems - 1 do
local index = firstVisibleIndex + i
local matinfo = dfhack.matinfo.decode(mat_type[index], mat_index[index])
local material = matinfo.material
local plant_raw = df.plant_raw.find( mat_index[index] )
for c, column in ipairs(visibleColumns) do
local columnValue = column.getColumnValue(item_type[index], material, plant_raw)
p:seek(columnOffset[c], i)
if (columnValue) then
p:advance(column.minValueLeftPadding)
p:pen(COLOR_LIGHTGREEN):string(columnValue)
else
p:advance(column.emptyValueLeftPadding)
p:pen(COLOR_DARKGREY):string("----")
end
end
end
end
end
-- Cursor
local cursorY = kitchen.cursor - firstVisibleIndex
if (numDisplayedItems >= 1 and cursorY < numDisplayedItems) then
local cursor_p = gui.Painter.new_xy(0, 6 + cursorY, gps.dimx - 1, 6 + cursorY)
cursor_p:pen(COLOR_LIGHTGREEN)
--cursor_p:seek(1, 0):char('>')
cursor_p:seek(permissionsStart, 0):char('[')
cursor_p:seek(permissionsEnd, 0):char(']')
if show_processing then
cursor_p:seek(gps.dimx - 2, 0):char('<')
end
end
-- Hotkey
--local keys_p = gui.Painter.new_xy(1, gps.dimy - 3, gps.dimx - 2, gps.dimy - 2)
--local keys_x = math.min(gps.dimx - 3 - string.len("p: Hide processing info"), 62)
--keys_p:key_pen(COLOR_LIGHTRED):pen(COLOR_WHITE)
--keys_p:seek(keys_x, 1):key('CUSTOM_P'):string(show_processing and ": Hide processing info" or ": Show processing info")
end
function kitchen_overlay:onInput(keys)
if not pluginCompatibility_search_isInputActive() then
if keys.CUSTOM_P then
show_processing = not show_processing
end
end
self:sendInputToParent(keys)
local parent = self._native.parent
if dfhack.screen.isDismissed(parent) then
self:dismiss()
end
end
local stateChangeHandler = function(eventCode)
if (eventCode == SC_VIEWSCREEN_CHANGED) then
local curFocus = dfhack.gui.getCurFocus()
local curScreen = dfhack.gui.getCurViewscreen()
if curFocus == 'kitchenpref' and (not dfhack.screen.isDismissed(curScreen)) then
kitchen_overlay():show()
end
end
end
args = {...}
if dfhack_flags and dfhack_flags.enable then
args = {dfhack_flags.enable_state and 'enable' or 'disable'}
end
if args[1] == 'enable' then
enabled = true
dfhack.onStateChange['gui/kitchen-info'] = stateChangeHandler
if dfhack.gui.getCurFocus() == 'kitchenpref' then
kitchen_overlay():show()
end
elseif args[1] == 'disable' then
enabled = false -- dismissed in next onRender()
dfhack.onStateChange['gui/kitchen-info'] = nil
else
print(dfhack.script_help() .. '\n')
end