-- A GUI front-end for the autobutcher plugin. --[[=begin gui/autobutcher =============== An in-game interface for `autobutcher`. =end]] local gui = require 'gui' local utils = require 'utils' local widgets = require 'gui.widgets' local dlg = require 'gui.dialogs' local plugin = require 'plugins.zone' WatchList = defclass(WatchList, gui.FramedScreen) WatchList.ATTRS { frame_title = 'Autobutcher Watchlist', frame_inset = 0, -- cover full DF window frame_background = COLOR_BLACK, frame_style = gui.BOUNDARY_FRAME, } -- width of the race name column in the UI local racewidth = 25 function nextAutowatchState() if(plugin.autowatch_isEnabled()) then return 'Stop ' end return 'Start' end function nextAutobutcherState() if(plugin.autobutcher_isEnabled()) then return 'Stop ' end return 'Start' end function getSleepTimer() return plugin.autobutcher_getSleep() end function setSleepTimer(ticks) plugin.autobutcher_setSleep(ticks) end function WatchList:init(args) local colwidth = 7 self:addviews{ widgets.Panel{ frame = { l = 0, r = 0 }, frame_inset = 1, subviews = { widgets.Label{ frame = { l = 0, t = 0 }, text_pen = COLOR_CYAN, text = { { text = 'Race', width = racewidth }, ' ', { text = 'female', width = colwidth }, ' ', { text = ' male', width = colwidth }, ' ', { text = 'Female', width = colwidth }, ' ', { text = ' Male', width = colwidth }, ' ', { text = 'watch? ' }, { text = ' butchering' }, NEWLINE, { text = '', width = racewidth }, ' ', { text = ' kids', width = colwidth }, ' ', { text = ' kids', width = colwidth }, ' ', { text = 'adults', width = colwidth }, ' ', { text = 'adults', width = colwidth }, ' ', { text = ' ' }, { text = ' ordered' }, } }, widgets.List{ view_id = 'list', frame = { t = 3, b = 5 }, not_found_label = 'Watchlist is empty.', edit_pen = COLOR_LIGHTCYAN, text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, --on_select = self:callback('onSelectEntry'), }, widgets.Label{ view_id = 'bottom_ui', frame = { b = 0, h = 1 }, text = 'filled by updateBottom()' } } }, } self:initListChoices() self:updateBottom() end -- change the viewmode for stock data displayed in left section of columns local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' } local viewmode = 1 function WatchList:onToggleView() if viewmode < #viewmodes then viewmode = viewmode + 1 else viewmode = 1 end self:initListChoices() self:updateBottom() end -- update the bottom part of the UI (after sleep timer changed etc) function WatchList:updateBottom() self.subviews.bottom_ui:setText( { { key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max', on_activate = self:callback('onToggleView') }, NEWLINE, { key = 'CUSTOM_F', text = ': f kids', on_activate = self:callback('onEditFK') }, ', ', { key = 'CUSTOM_M', text = ': m kids', on_activate = self:callback('onEditMK') }, ', ', { key = 'CUSTOM_SHIFT_F', text = ': f adults', on_activate = self:callback('onEditFA') }, ', ', { key = 'CUSTOM_SHIFT_M', text = ': m adults', on_activate = self:callback('onEditMA') }, '. ', { key = 'CUSTOM_W', text = ': Toggle watch', on_activate = self:callback('onToggleWatching') }, '. ', { key = 'CUSTOM_X', text = ': Delete', on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE, --{ key = 'CUSTOM_A', text = ': Add race', -- on_activate = self:callback('onAddRace') }, ', ', { key = 'CUSTOM_SHIFT_R', text = ': Set whole row', on_activate = self:callback('onSetRow') }, '. ', { key = 'CUSTOM_B', text = ': Remove butcher orders', on_activate = self:callback('onUnbutcherRace') }, '. ', { key = 'CUSTOM_SHIFT_B', text = ': Butcher race', on_activate = self:callback('onButcherRace') }, '. ', NEWLINE, { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', on_activate = self:callback('onToggleAutobutcher') }, '. ', { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', on_activate = self:callback('onToggleAutowatch') }, '. ', { key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)', on_activate = self:callback('onEditSleepTimer') }, '. ', }) end function stringify(number) -- cap displayed number to 3 digits -- after population of 50 per race is reached pets stop breeding anyways -- so probably this could safely be reduced to 99 local max = 999 if number > max then number = max end return tostring(number) end function WatchList:initListChoices() local choices = {} -- first two rows are for "edit all races" and "edit new races" local settings = plugin.autobutcher_getSettings() local fk = stringify(settings.fk) local fa = stringify(settings.fa) local mk = stringify(settings.mk) local ma = stringify(settings.ma) local watched = '' local colwidth = 7 table.insert (choices, { text = { { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = watched, width = 6, rjustify = true } } }) table.insert (choices, { text = { { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', { text = watched, width = 6, rjustify = true } } }) local watchlist = plugin.autobutcher_getWatchList() for i,entry in ipairs(watchlist) do fk = stringify(entry.fk) fa = stringify(entry.fa) mk = stringify(entry.mk) ma = stringify(entry.ma) if viewmode == 1 then fkc = stringify(entry.fk_total) fac = stringify(entry.fa_total) mkc = stringify(entry.mk_total) mac = stringify(entry.ma_total) end if viewmode == 2 then fkc = stringify(entry.fk_protected) fac = stringify(entry.fa_protected) mkc = stringify(entry.mk_protected) mac = stringify(entry.ma_protected) end if viewmode == 3 then fkc = stringify(entry.fk_butcherable) fac = stringify(entry.fa_butcherable) mkc = stringify(entry.mk_butcherable) mac = stringify(entry.ma_butcherable) end if viewmode == 4 then fkc = stringify(entry.fk_butcherflag) fac = stringify(entry.fa_butcherflag) mkc = stringify(entry.mk_butcherflag) mac = stringify(entry.ma_butcherflag) end local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag local bo = ' ' if butcher_ordered > 0 then bo = stringify(butcher_ordered) end local watched = 'no' if entry.watched then watched = 'yes' end local racestr = entry.name -- highlight entries where the target quota can't be met because too many are protected bad_pen = COLOR_LIGHTRED good_pen = NONE -- this is stupid, but it works. sue me fk_pen = good_pen fa_pen = good_pen mk_pen = good_pen ma_pen = good_pen if entry.fk_protected > entry.fk then fk_pen = bad_pen end if entry.fa_protected > entry.fa then fa_pen = bad_pen end if entry.mk_protected > entry.mk then mk_pen = bad_pen end if entry.ma_protected > entry.ma then ma_pen = bad_pen end table.insert (choices, { text = { { text = racestr, width = racewidth, pad_char = ' ' }, --' ', { text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/', { text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ', { text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/', { text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ', { text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/', { text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ', { text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/', { text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ', { text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ', { text = bo, width = 8, rjustify = true, pad_char = ' ' } }, obj = entry, }) end local list = self.subviews.list list:setChoices(choices) end function WatchList:onInput(keys) if keys.LEAVESCREEN then self:dismiss() else WatchList.super.onInput(self, keys) end end -- check the user input for target population values function WatchList:checkUserInput(count, text) if count == nil then dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) return false end if count < 0 then dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) return false end return true end -- check the user input for sleep timer function WatchList:checkUserInputSleep(count, text) if count == nil then dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) return false end if count < 1000 then dlg.showMessage('Invalid Number', 'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!', COLOR_LIGHTRED) return false end return true end function WatchList:onEditFK() local selidx,selobj = self.subviews.list:getSelected() local settings = plugin.autobutcher_getSettings() local fk = settings.fk local mk = settings.mk local fa = settings.fa local ma = settings.ma local race = 'ALL RACES PLUS NEW' local id = -1 local watched = false if selidx == 2 then race = 'ONLY NEW RACES' end if selidx > 2 then local entry = selobj.obj fk = entry.fk mk = entry.mk fa = entry.fa ma = entry.ma race = entry.name id = entry.id watched = entry.watched end dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of female kids:', COLOR_WHITE, ' '..fk, function(text) local count = tonumber(text) if self:checkUserInput(count, text) then fk = count if selidx == 1 then plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) end if selidx == 2 then plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) end if selidx > 2 then plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) end self:initListChoices() end end ) end function WatchList:onEditMK() local selidx,selobj = self.subviews.list:getSelected() local settings = plugin.autobutcher_getSettings() local fk = settings.fk local mk = settings.mk local fa = settings.fa local ma = settings.ma local race = 'ALL RACES PLUS NEW' local id = -1 local watched = false if selidx == 2 then race = 'ONLY NEW RACES' end if selidx > 2 then local entry = selobj.obj fk = entry.fk mk = entry.mk fa = entry.fa ma = entry.ma race = entry.name id = entry.id watched = entry.watched end dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of male kids:', COLOR_WHITE, ' '..mk, function(text) local count = tonumber(text) if self:checkUserInput(count, text) then mk = count if selidx == 1 then plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) end if selidx == 2 then plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) end if selidx > 2 then plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) end self:initListChoices() end end ) end function WatchList:onEditFA() local selidx,selobj = self.subviews.list:getSelected() local settings = plugin.autobutcher_getSettings() local fk = settings.fk local mk = settings.mk local fa = settings.fa local ma = settings.ma local race = 'ALL RACES PLUS NEW' local id = -1 local watched = false if selidx == 2 then race = 'ONLY NEW RACES' end if selidx > 2 then local entry = selobj.obj fk = entry.fk mk = entry.mk fa = entry.fa ma = entry.ma race = entry.name id = entry.id watched = entry.watched end dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of female adults:', COLOR_WHITE, ' '..fa, function(text) local count = tonumber(text) if self:checkUserInput(count, text) then fa = count if selidx == 1 then plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) end if selidx == 2 then plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) end if selidx > 2 then plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) end self:initListChoices() end end ) end function WatchList:onEditMA() local selidx,selobj = self.subviews.list:getSelected() local settings = plugin.autobutcher_getSettings() local fk = settings.fk local mk = settings.mk local fa = settings.fa local ma = settings.ma local race = 'ALL RACES PLUS NEW' local id = -1 local watched = false if selidx == 2 then race = 'ONLY NEW RACES' end if selidx > 2 then local entry = selobj.obj fk = entry.fk mk = entry.mk fa = entry.fa ma = entry.ma race = entry.name id = entry.id watched = entry.watched end dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of male adults:', COLOR_WHITE, ' '..ma, function(text) local count = tonumber(text) if self:checkUserInput(count, text) then ma = count if selidx == 1 then plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) end if selidx == 2 then plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) end if selidx > 2 then plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) end self:initListChoices() end end ) end function WatchList:onEditSleepTimer() local sleep = getSleepTimer() dlg.showInputPrompt( 'Edit Sleep Timer', 'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)', COLOR_WHITE, ' '..sleep, function(text) local count = tonumber(text) if self:checkUserInputSleep(count, text) then sleep = count setSleepTimer(sleep) self:updateBottom() end end ) end function WatchList:onToggleWatching() local selidx,selobj = self.subviews.list:getSelected() if selidx > 2 then local entry = selobj.obj plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched) end self:initListChoices() end function WatchList:onDeleteEntry() local selidx,selobj = self.subviews.list:getSelected() if(selidx < 3 or selobj == nil) then return end dlg.showYesNoPrompt( 'Delete from Watchlist', 'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)', COLOR_YELLOW, function() plugin.autobutcher_removeFromWatchList(selobj.obj.id) self:initListChoices() end ) end function WatchList:onAddRace() print('onAddRace - not implemented yet') end function WatchList:onUnbutcherRace() local selidx,selobj = self.subviews.list:getSelected() if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end if selidx > 2 then local entry = selobj.obj local race = entry.name plugin.autobutcher_unbutcherRace(entry.id) self:initListChoices() self:updateBottom() end end function WatchList:onButcherRace() local selidx,selobj = self.subviews.list:getSelected() if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end if selidx > 2 then local entry = selobj.obj local race = entry.name plugin.autobutcher_butcherRace(entry.id) self:initListChoices() self:updateBottom() end end -- set whole row (fk, mk, fa, ma) to one value function WatchList:onSetRow() local selidx,selobj = self.subviews.list:getSelected() local race = 'ALL RACES PLUS NEW' local id = -1 local watched = false if selidx == 2 then race = 'ONLY NEW RACES' end local watchindex = selidx - 3 if selidx > 2 then local entry = selobj.obj race = entry.name id = entry.id watched = entry.watched end dlg.showInputPrompt( 'Set whole row for '..race, 'Enter desired maximum for all subtypes:', COLOR_WHITE, ' ', function(text) local count = tonumber(text) if self:checkUserInput(count, text) then if selidx == 1 then plugin.autobutcher_setDefaultTargetAll( count, count, count, count ) end if selidx == 2 then plugin.autobutcher_setDefaultTargetNew( count, count, count, count ) end if selidx > 2 then plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched) end self:initListChoices() end end ) end function WatchList:onToggleAutobutcher() if(plugin.autobutcher_isEnabled()) then plugin.autobutcher_setEnabled(false) plugin.autobutcher_sortWatchList() else plugin.autobutcher_setEnabled(true) end self:initListChoices() self:updateBottom() end function WatchList:onToggleAutowatch() if(plugin.autowatch_isEnabled()) then plugin.autowatch_setEnabled(false) else plugin.autowatch_setEnabled(true) end self:initListChoices() self:updateBottom() end if not dfhack.isMapLoaded() then qerror('Map is not loaded.') end if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then qerror("This script must not be called while other lua gui stuff is running.") end -- maybe this is too strict, there is not really a reason why it can only be called from the status screen -- (other than the hotkey might overlap with other scripts) if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then qerror("This script must either be called from the overall status screen or the animal list screen.") end local screen = WatchList{ } screen:show()