-
-
Save manavortex/69e67e6e83a73efe259c7e13d4fccf43 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- WritWorthy UI window | |
-- | |
-- Do NOT put tooltip or settings UI code here. Just the big list-of-writs | |
-- window. | |
local WritWorthy = _G['WritWorthy'] -- defined in WritWorthy_Define.lua | |
WritWorthyInventoryList = ZO_SortFilterList:Subclass() | |
-- Inherits field "self.list" which is the scroll list control. | |
-- "WritWorthyInventoryList" is NOT the actual list control that has useful | |
-- "data members. Use WritWorthyInventoryList.singleton for that. | |
-- The header controls for each of our lists, recorded | |
-- during WritWorthyHeaderInit(). | |
-- [column_name] = control | |
WritWorthyInventoryList.list_header_controls = {} | |
-- The master list of row data for the inventory list | |
-- in no particular order. | |
WritWorthyInventoryList.inventory_data_list = {} | |
-- Dolgubon's LibLazyCrafting, which maintains | |
-- a queue of "stuff to automatically craft next | |
-- time you're at a appropriate station." Often | |
-- called "LLC" for a shorter abbreviation. | |
-- | |
-- version 0.3 has BS/CL/WW + Enchanting | |
-- version 0.4 has Alchemy and Provisioning. | |
-- version 1.2 has Alchemy and Provisioning, | |
-- released by Dolgubon in Writ/Set addons | |
-- had bugs that broke smithing and alchemy. DO NOT USE. | |
-- version 1.3 ZZ fixes to 1.2, works again | |
-- not released by Dolgubon." | |
WritWorthyInventoryList.LibLazyCrafting = nil | |
-- Live row_control used to lay out rows. Remembered | |
-- during SetupRowControl(). Used in | |
-- UpdateAllCellWidths(). | |
WritWorthyInventoryList.row_control_list = {} | |
local Log = WritWorthy.Log | |
local Util = WritWorthy.Util | |
-- Inventory List UI, "row type". | |
-- | |
-- We could choose to use different IDs for different types (consumables vs. | |
-- smithing) but that's more complexity than I want today. Sticking with | |
-- homogeneous data and a single data type. The list UI doesn't need to know or | |
-- care that some rows leave their cells blank because Provisioning writs lack | |
-- a "quality" field. | |
local TYPE_ID = 1 | |
WritWorthyInventoryList.SORT_KEYS = { | |
["ui_type" ] = {tiebreaker="ui_voucher_ct"} | |
, ["ui_voucher_ct" ] = {tiebreaker="ui_detail1", isNumeric=true } | |
, ["ui_detail1" ] = {tiebreaker="ui_detail2"} | |
, ["ui_detail2" ] = {tiebreaker="ui_detail3"} | |
, ["ui_detail3" ] = {tiebreaker="ui_detail4"} | |
, ["ui_detail4" ] = {tiebreaker="ui_detail5"} | |
, ["ui_detail5" ] = {tiebreaker="ui_is_queued"} | |
, ["ui_is_queued" ] = {tiebreaker="ui_can_queue"} | |
-- Not a visible columns, but still affect sort. | |
, ["ui_can_queue" ] = {} | |
, ["ui_station_sort"] = {tiebreaker="ui_voucher_ct"} | |
} | |
WritWorthyInventoryList.ROW_HEIGHT = 30 | |
-- Values written to savedChariables | |
WritWorthyInventoryList.STATE_QUEUED = "queued" | |
WritWorthyInventoryList.STATE_COMPLETED = "completed" | |
WritWorthyInventoryList.COLOR_TEXT_CANNOT_QUEUE = "CC3333" | |
WritWorthyInventoryList.COLOR_TEXT_CAN_QUEUE = "CCCCCC" | |
WritWorthyInventoryList.COLOR_TEXT_QUEUED = "FFFFFF" | |
WritWorthyInventoryList.COLOR_TEXT_COMPLETED = "33AA33" | |
WritWorthyInventoryList.COLOR_TEXT_WW = "E0FF93" | |
WritWorthyInventoryList.COLOR_TEXT_CL = "A8E0FF" | |
WritWorthyInventoryList.COLOR_TEXT_BS = "FFCE93" | |
-- The XML name suffixes for each of our columns. | |
-- NOT used for UI display (although they often match). | |
-- Useful when iterating through columns/cells. | |
WritWorthyInventoryList.CELL_TYPE = "Type" | |
WritWorthyInventoryList.CELL_VOUCHERCT = "VoucherCt" | |
WritWorthyInventoryList.CELL_DETAIL1 = "Detail1" | |
WritWorthyInventoryList.CELL_DETAIL2 = "Detail2" | |
WritWorthyInventoryList.CELL_DETAIL3 = "Detail3" | |
WritWorthyInventoryList.CELL_DETAIL4 = "Detail4" | |
WritWorthyInventoryList.CELL_DETAIL5 = "Detail5" | |
WritWorthyInventoryList.CELL_ENQUEUE = "Enqueue" | |
WritWorthyInventoryList.CELL_ENQUEUE_MASK = "EnqueueMask" -- not a cell on its own. | |
WritWorthyInventoryList.CELL_NAME_LIST = { | |
WritWorthyInventoryList.CELL_TYPE | |
, WritWorthyInventoryList.CELL_VOUCHERCT | |
, WritWorthyInventoryList.CELL_DETAIL1 | |
, WritWorthyInventoryList.CELL_DETAIL2 | |
, WritWorthyInventoryList.CELL_DETAIL3 | |
, WritWorthyInventoryList.CELL_DETAIL4 | |
, WritWorthyInventoryList.CELL_DETAIL5 | |
, WritWorthyInventoryList.CELL_ENQUEUE | |
} | |
-- Cells that are shown/hidden click buttons, not text data. | |
WritWorthyInventoryList.CELL_UNTEXT_LIST = { | |
[WritWorthyInventoryList.CELL_ENQUEUE] = true | |
} | |
WritWorthyInventoryList.HEADER_TOOLTIPS = { | |
[WritWorthyInventoryList.CELL_TYPE ] = nil | |
, [WritWorthyInventoryList.CELL_VOUCHERCT ] = "Voucher count" | |
, [WritWorthyInventoryList.CELL_DETAIL1 ] = nil | |
, [WritWorthyInventoryList.CELL_DETAIL2 ] = nil | |
, [WritWorthyInventoryList.CELL_DETAIL3 ] = nil | |
, [WritWorthyInventoryList.CELL_DETAIL4 ] = nil | |
, [WritWorthyInventoryList.CELL_DETAIL5 ] = nil | |
, [WritWorthyInventoryList.CELL_ENQUEUE ] = "Enqueued for crafting" | |
} | |
-- WritWorthyUI: The window around the inventory list ------------------------ | |
function WritWorthyUI_RestorePos() | |
local pos = WritWorthy.default.position | |
if WritWorthy | |
and WritWorthy.savedVariables | |
and WritWorthy.savedVariables.position then | |
pos = WritWorthy.savedVariables.position | |
end | |
if not WritWorthyUI then | |
-- Common crash that occurs when I've messed up | |
-- the XML somehow. Force it to crash here in this | |
-- if block rather than mysteriously on the | |
-- proper SetAnchor() line later. | |
d("Your XML probably did not load. Fix it.") | |
local _ = WritWorthyUI.SetAnchor | |
end | |
WritWorthyUI:ClearAnchors() | |
WritWorthyUI:SetAnchor( | |
TOPLEFT | |
, GuiRoot | |
, TOPLEFT | |
, pos[1] | |
, pos[2] | |
) | |
if pos[3] and pos[4] then | |
WritWorthyUI:SetWidth( pos[3] - pos[1]) | |
WritWorthyUI:SetHeight(pos[4] - pos[2]) | |
end | |
end | |
function WritWorthyUI_SavePos() | |
local l = WritWorthyUI:GetLeft() | |
local t = WritWorthyUI:GetTop() | |
local r = WritWorthyUI:GetRight() | |
local b = WritWorthyUI:GetBottom() | |
-- d("SavePos ltrb=".. l .. " " .. t .. " " .. r .. " " .. b) | |
local pos = { l, t, r, b } | |
WritWorthy.savedVariables.position = pos | |
end | |
function WritWorthyUI_OnMoveStop() | |
local l = WritWorthyUI:GetLeft() | |
local t = WritWorthyUI:GetTop() | |
local r = WritWorthyUI:GetRight() | |
local b = WritWorthyUI:GetBottom() | |
WritWorthyUI_SavePos() | |
end | |
function WritWorthyUI_OnResizeStop() | |
local l = WritWorthyUI:GetLeft() | |
local t = WritWorthyUI:GetTop() | |
local r = WritWorthyUI:GetRight() | |
local b = WritWorthyUI:GetBottom() | |
WritWorthy.InventoryList:UpdateAllCellWidths() | |
WritWorthyUI_SavePos() | |
-- Update vertical scrollbar and extents to | |
-- match new scrollpane height. | |
if WritWorthyInventoryList.singleton | |
and WritWorthyInventoryList.singleton.list then | |
local scroll_list = WritWorthyInventoryList.singleton.list | |
ZO_ScrollList_Commit(scroll_list) | |
end | |
end | |
function WritWorthyUI_ToggleUI() | |
local ui = WritWorthyUI | |
if not ui then | |
return | |
end | |
h = WritWorthyUI:IsHidden() | |
if h then | |
WritWorthyUI_RestorePos() | |
local t = WritWorthyUIInventoryListTitle | |
if t then | |
t:SetText("Writ Inventory: "..GetUnitName("player")) | |
end | |
WritWorthuUI_Refresh() | |
end | |
WritWorthyUI:SetHidden(not h) | |
end | |
function WritWorthyUI_RefreshUI() | |
WritWorthuUI_Refresh() | |
end | |
function WritWorthuUI_Refresh() | |
list = WritWorthyInventoryList.singleton | |
list:BuildMasterlist() | |
list:Refresh() | |
list:UpdateSummaryAndQButtons() | |
end | |
-- Inventory List ------------------------------------------------------------ | |
function WritWorthyInventoryList_HeaderInit(control, name, text, key) | |
ZO_SortHeader_Initialize( control -- control | |
, text -- name | |
, key or string.lower(text) -- key | |
, ZO_SORT_ORDER_DOWN -- initialDirection | |
, align or TEXT_ALIGN_LEFT -- alignment | |
, "ZoFontWinT1" -- font | |
, nil -- highlightTemplate | |
) | |
-- Remember this control! | |
-- | |
-- The header cell control that we get here, and which | |
-- ZO_SortHeader_Initialize() fills in is NOT the same | |
-- as the XML template control reachable from | |
-- WritWorthyUIInventoryListHeaders:GetNamedChild(). | |
-- We need this actual header cell control, which has | |
-- Text and alignment and live data, in addition to the | |
-- XML template control (which has dynamic width, | |
-- thanks to its two anchors). | |
WritWorthyInventoryList.list_header_controls[name] = control | |
local tooltip_text = WritWorthyInventoryList.HEADER_TOOLTIPS[name] | |
if tooltip_text then | |
ZO_SortHeader_SetTooltip(control, tooltip_text) | |
end | |
end | |
function WritWorthyInventoryList:New() | |
local o = ZO_SortFilterList.New(self, WritWorthyUIInventoryList) | |
WritWorthyInventoryList.singleton = o | |
return o | |
end | |
function WritWorthyInventoryList:Initialize(control) | |
ZO_SortFilterList.Initialize(self, control) | |
self.inventory_data_list = {} | |
self:SetEmptyText("This character has no sealed master writs in its inventory.") | |
-- Tell ZO_ScrollList how it can ask us to | |
-- create row controls. | |
ZO_ScrollList_AddDataType( | |
self.list -- scroll list control | |
, TYPE_ID -- row data type ID | |
, "WritWorthyInventoryListRow" -- template: virtual button defined in XML | |
, self.ROW_HEIGHT -- row height | |
-- setupCallback | |
, function(control, inventory_data) | |
self:SetupRowControl(control, inventory_data) | |
end | |
) | |
-- This call to ZO_ScrollList_EnableHighlight() seems | |
-- to do nothing. I have yet to get this working | |
-- correctly. Would be interesting to see row | |
-- highlighting during mouseover. | |
ZO_ScrollList_EnableHighlight(self.list, "ZO_ThinListHighlight") | |
-- How to order our table rows. Probably doesn't need | |
-- to be a specific data member with a specific name, | |
-- we just need to know how to find it and pass it to | |
-- table.sort() from within FilterScrollList() below. | |
self.sortFunction | |
= function(row_a, row_b) | |
return ZO_TableOrderingFunction( row_a.data | |
, row_b.data | |
, self.currentSortKey | |
, WritWorthyInventoryList.SORT_KEYS | |
, self.currentSortOrder | |
) | |
end | |
-- Set our initial sort key. Not sure this actually | |
-- works. And if it does, wouldn't it be polite to | |
-- save/restore the sort index in savedVariables? | |
-- | |
-- After ZO_SortFilterList:Initialize() we have a | |
-- sortHeaderGroup. At least, that's how it works in | |
-- ScrollListExample. | |
self.sortHeaderGroup:SelectHeaderByKey("detail1") | |
ZO_SortHeader_OnMouseExit(WritWorthyUIInventoryListHeadersType) | |
self:RefreshData() | |
-- Create the summary grid at the bottom of the window. | |
local OFFSET_X = { 0, 72, 100, 350, 350+72, 350+100, 700 } | |
local OFFSET_Y = { 5, 30, 55 } | |
local L = TEXT_ALIGN_LEFT -- for a LOT less typing | |
local R = TEXT_ALIGN_RIGHT | |
-- offsetX index into above table | |
-- offsetY index into above table | |
-- align | |
-- text | |
local GRID = { -- | |
["SummaryQueuedVoucherCt" ] = { 1, 1, R, "" } | |
, ["SummaryQueuedMatCost" ] = { 1, 2, R, "" } | |
, ["SummaryQueuedVoucherCost" ] = { 1, 3, R, "" } | |
, ["SummaryQueuedVoucherCtUnit" ] = { 2, 1, L, "v" } | |
, ["SummaryQueuedMatCostUnit" ] = { 2, 2, L, "g" } | |
, ["SummaryQueuedVoucherCostUnit" ] = { 2, 3, L, "g/v" } | |
, ["SummaryQueuedVoucherCtLabel" ] = { 3, 1, L, "total vouchers queued" } | |
, ["SummaryQueuedMatCostLabel" ] = { 3, 2, L, "total materials queued" } | |
, ["SummaryQueuedVoucherCostLabel" ] = { 3, 3, L, "average queued voucher cost" } | |
, ["SummaryCompletedVoucherCt" ] = { 4, 1, R, "" } | |
, ["SummaryCompletedMatCost" ] = { 4, 2, R, "" } | |
, ["SummaryCompletedVoucherCost" ] = { 4, 3, R, "" } | |
, ["SummaryCompletedVoucherCtUnit" ] = { 5, 1, L, "v" } | |
, ["SummaryCompletedMatCostUnit" ] = { 5, 2, L, "g" } | |
, ["SummaryCompletedVoucherCostUnit" ] = { 5, 3, L, "g/v" } | |
, ["SummaryCompletedVoucherCtLabel" ] = { 6, 1, L, "total vouchers completed" } | |
, ["SummaryCompletedMatCostLabel" ] = { 6, 2, L, "total materials completed" } | |
, ["SummaryCompletedVoucherCostLabel"] = { 6, 3, L, "average completed voucher cost" } | |
} | |
for name, def in pairs(GRID) do | |
local offset_x = OFFSET_X[def[1]] | |
local offset_y = OFFSET_Y[def[2]] | |
local text_align = def[3] | |
local text = def[4] | |
local width = OFFSET_X[def[1]+1] - OFFSET_X[def[1]] - 2 | |
-- local control = WritWorthyUI:GetNamedChild(name) | |
local control_name = "WritWorthyUI"..name | |
local control = WritWorthyUI:CreateControl(control_name, CT_LABEL) | |
control:SetHorizontalAlignment(text_align) | |
control:SetColor(255,255,255) | |
control:SetFont("ZoFontGame") | |
control:SetHeight(20) | |
control:SetWidth(width) | |
control:SetText(text) | |
control:ClearAnchors() | |
control:SetAnchor( TOPLEFT -- point | |
, WritWorthyUIInventoryList -- relativeTo | |
, BOTTOMLEFT -- relativePoint | |
, offset_x -- offsetX | |
, offset_y -- offsetY | |
) | |
end | |
end | |
-- Collect data that we'll eventually use to fill the inventory list UI. | |
-- Just data, no UI code here (that's FilterScrollList()'s job). | |
function WritWorthyInventoryList:BuildMasterlist() | |
self.inventory_data_list = WritWorthy:ScanInventoryForMasterWrits() | |
-- We need UI data before we can sort. | |
for _, inventory_data in pairs(self.inventory_data_list) do | |
self:PopulateUIFields(inventory_data) | |
end | |
-- This seems as good a place as any to | |
-- make this once-a-day-or-so call. | |
-- Certainly do not want it once-per-init(). | |
self:PurgeAncientSavedChariables() | |
end | |
-- Populate the ScrollList's rows, using our data model as a source. | |
function WritWorthyInventoryList:FilterScrollList() | |
local scroll_data = ZO_ScrollList_GetDataList(self.list) | |
ZO_ClearNumericallyIndexedTable(scroll_data) | |
for _, inventory_data in ipairs(self.inventory_data_list) do | |
table.insert( scroll_data | |
, ZO_ScrollList_CreateDataEntry(TYPE_ID, inventory_data)) | |
end | |
end | |
function WritWorthyInventoryList:SortScrollList() | |
-- Original boilerplate SortScrollList() implementation that works | |
-- perfectly with the usual sortFunction | |
-- | |
local scroll_data = ZO_ScrollList_GetDataList(self.list) | |
table.sort(scroll_data, self.sortFunction) | |
end | |
function WritWorthyInventoryList:Refresh() | |
Log:Add("WritWorthyInventoryList:Refresh") | |
self:RefreshData() | |
end | |
function WritWorthyInventoryList_Cell_OnMouseEnter(cell_control) | |
-- .tooltip_text is our own field, not ZOS'. | |
-- We set it in PopulateUIFields() with our | |
-- own "reasons why you cannot queue this row" | |
-- text (same red text that WritWorthy stuffs | |
-- into sealed writ tooltips). | |
if cell_control.tooltip_text then | |
ZO_Tooltips_ShowTextTooltip( | |
cell_control | |
, TOP | |
, cell_control.tooltip_text) | |
end | |
end | |
function WritWorthyInventoryList_Cell_OnMouseExit(cell_control) | |
ZO_Tooltips_HideTextTooltip() | |
end | |
function WritWorthyInventoryList_Cell_OnMouseDown(cell_control) | |
if not ( cell_control | |
and cell_control.inventory_data | |
and cell_control.inventory_data.item_link) then | |
return | |
end | |
-- Inject this item's unique_id so that our | |
-- WritWorthy.TooltipInsertOurText() code will see | |
-- it and display "Queued for crafting". Otherwise | |
-- an itemLink alone is insufficient to uniquely | |
-- identify a Sealed Master Writ within our inventory. | |
PopupTooltip.WritWorthy_UniqueId = cell_control.inventory_data.unique_id | |
ZO_PopupTooltip_SetLink(cell_control.inventory_data.item_link) | |
PopupTooltip.WritWorthy_UniqueId = nil | |
end | |
-- First time through a row's SetupRowControl(), programmatically create the | |
-- individual label controls that will hold cell text. Doing so | |
-- programmatically here is less maintenance work than trying to keep the XML | |
-- "virtual" row in sync with the XML headers. | |
-- | |
-- Do not fill labels with live text: that's SetupRowControl()'s job. | |
function WritWorthyInventoryList:CreateRowControlCells(row_control, header_control) | |
for i, cell_name in ipairs(self.CELL_NAME_LIST) do | |
local header_cell_control = header_control:GetNamedChild(cell_name) | |
local control_name = row_control:GetName() .. cell_name | |
local cell_control = nil | |
local is_text = true | |
local rel_to_left = header_control:GetLeft() | |
if self.CELL_UNTEXT_LIST[cell_name] then | |
-- Non-text cells (aka the "Enqueue" checkbox button | |
-- are not created programmatically, they are already | |
-- created for us via XML. Find and use the existing | |
-- control. | |
cell_control = row_control:GetNamedChild(cell_name) | |
is_text = false | |
else | |
-- Text cells are programmatically created here, not | |
-- created by XML. Create now. | |
cell_control = row_control:CreateControl(control_name, CT_LABEL) | |
end | |
row_control[cell_name] = cell_control | |
if i == 1 then | |
-- Leftmost column is flush up against | |
-- the left of the container | |
cell_control:SetAnchor( LEFT -- point | |
, row_control -- relativeTo | |
, LEFT -- relativePoint | |
, 0 -- offsetX | |
, 0 ) -- offsetY | |
else | |
local offsetX = header_cell_control:GetLeft() | |
- rel_to_left | |
cell_control:SetAnchor( LEFT -- point | |
, row_control -- relativeTo | |
, LEFT -- relativePoint | |
, offsetX -- offsetX | |
, 0 ) -- offsetY | |
end | |
cell_control:SetHidden(false) | |
if not is_text then | |
-- Lock our "Enqueue" checkbox to 20x20 | |
cell_control:SetWidth(20) | |
cell_control:SetHeight(20) | |
else | |
cell_control:SetWidth(header_cell_control:GetWidth()) | |
cell_control:SetHeight(self.ROW_HEIGHT) | |
cell_control:SetFont("ZoFontGame") | |
cell_control:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS) | |
-- Surprise! Headers:GetNamedChild() returns a control | |
-- instance that lacks a "Name" sub-control, which we | |
-- need if we want to match text alignment. Fall back | |
-- to the control we passed to | |
-- ZO_SortHeader_Initialize(). | |
local header_name_control = header_control:GetNamedChild("Name") | |
if not header_name_control then | |
local hc2 = self.list_header_controls[cell_name] | |
if hc2 then | |
header_name_control = hc2:GetNamedChild("Name") | |
end | |
end | |
local horiz_align = TEXT_ALIGN_LEFT | |
if header_name_control then | |
horiz_align = header_name_control:GetHorizontalAlignment() | |
end | |
cell_control:SetHorizontalAlignment(horiz_align) | |
-- Align all cells to top so that long/multiline | |
-- text still look acceptable. But hopefully we'll | |
-- never need this because TEXT_WRAP_MODE_ELLIPSIS | |
-- above should prevent multiline text. | |
cell_control:SetVerticalAlignment(TEXT_ALIGN_TOP) | |
-- Click to toggle item tooltip for row's | |
-- Sealed Master Writ. | |
cell_control:SetMouseEnabled(true) | |
cell_control:SetHandler("OnMouseDown", WritWorthyInventoryList_Cell_OnMouseDown) | |
end | |
end | |
local cb = row_control:GetNamedChild(self.CELL_ENQUEUE) | |
if cb then | |
ZO_CheckButton_SetToggleFunction(cb, function(checkbox, is_checked) | |
WritWorthyInventoryList_EnqueueToggled(checkbox, is_checked) | |
end) | |
end | |
cb:SetHandler("OnMouseEnter", WritWorthyInventoryList_Cell_OnMouseEnter) | |
cb:SetHandler("OnMouseExit", WritWorthyInventoryList_Cell_OnMouseExit) | |
-- Not a cell control, but a mask that floats above | |
-- one. Hook that up for fast access and tooltips. | |
local mask_control = row_control:GetNamedChild(self.CELL_ENQUEUE_MASK) | |
row_control[self.CELL_ENQUEUE_MASK] = mask_control | |
mask_control:SetHidden(false) | |
mask_control:SetHandler("OnMouseEnter", WritWorthyInventoryList_Cell_OnMouseEnter) | |
mask_control:SetHandler("OnMouseExit", WritWorthyInventoryList_Cell_OnMouseExit) | |
end | |
-- After a resize, widen our "detail1" column and nudge the others to its right. | |
function WritWorthyInventoryList:UpdateAllCellWidths() | |
for _, row_control in ipairs(self.row_control_list) do | |
self:UpdateColumnWidths(row_control) | |
end | |
end | |
-- Change column width/offsets after a window resize. NOP if nothing changed. | |
function WritWorthyInventoryList:UpdateColumnWidths(row_control) | |
-- Do nothing if we have not yet fully initialized. | |
local hc = WritWorthyUIInventoryListHeadersType | |
if not hc then return end | |
local rel_to_left = WritWorthyUIInventoryListHeaders:GetLeft() | |
-- Cache header cell controls from which we'll | |
-- gather column widths. We want the GetNamedChild() | |
-- controls (they have anchors and dynamic width) | |
-- not the ZO_SortHeader_Initialize() controls | |
-- (which appear to never change widths). | |
local hcl = {} | |
for cell_name, _ in pairs(self.list_header_controls) do | |
hcl[cell_name] = WritWorthyUIInventoryListHeaders:GetNamedChild(cell_name) | |
end | |
for cell_name, _ in pairs(self.list_header_controls) do | |
local cell_control = row_control:GetNamedChild(cell_name) | |
local header_cell_control = hcl[cell_name] | |
if header_cell_control then | |
local offsetX = header_cell_control:GetLeft() - rel_to_left | |
cell_control:SetAnchor( LEFT -- point | |
, row_control -- relativeTo | |
, LEFT -- relativePoint | |
, offsetX -- offsetX | |
, 0 ) -- offsetY | |
-- Resize text cells, but leave button cells locked | |
-- to whatever CreateRowControlCells() chose. | |
local is_text = not WritWorthyInventoryList.CELL_UNTEXT_LIST[cell_name] | |
if is_text then | |
cell_control:SetWidth(header_cell_control:GetWidth()) | |
end | |
end | |
end | |
-- I don't always have a background, but when I do, | |
-- I want it to stretch all the way across this row. | |
local background_control = GetControl(row_control, "BG") | |
if background_control then | |
background_control:SetWidth(row_control:GetWidth()) | |
end | |
end | |
local SHORTEN = { | |
["Alchemy" ] = "Alchemy" | |
, ["Enchanting" ] = "Enchant" | |
, ["Provisioning" ] = "Provis" | |
, ["Rubedite Axe" ] = "1h axe" | |
, ["Rubedite Mace" ] = "1h mace" | |
, ["Rubedite Sword" ] = "1h sword" | |
, ["Rubedite Greataxe" ] = "2h battle axe" | |
, ["Rubedite Greatsword" ] = "2h greatsword" | |
, ["Rubedite Maul" ] = "2h maul" | |
, ["Rubedite Dagger" ] = "dagger" | |
, ["Rubedite Cuirass" ] = "cuirass" | |
, ["Rubedite Sabatons" ] = "sabatons" | |
, ["Rubedite Gauntlets" ] = "gauntlets" | |
, ["Rubedite Helm" ] = "helm" | |
, ["Rubedite Greaves" ] = "greaves" | |
, ["Rubedite Pauldron" ] = "pauldron" | |
, ["Rubedite Girdle" ] = "girdle" | |
, ["Ancestor Silk Robe" ] = "robe" | |
, ["Ancestor Silk Jerkin" ] = "shirt" | |
, ["Ancestor Silk Shoes" ] = "shoes" | |
, ["Ancestor Silk Gloves" ] = "gloves" | |
, ["Ancestor Silk Hat" ] = "hat" | |
, ["Ancestor Silk Breeches" ] = "breeches" | |
, ["Ancestor Silk Epaulets" ] = "epaulets" | |
, ["Ancestor Silk Sash" ] = "sash" | |
, ["Rubedo Leather Jack" ] = "jack" | |
, ["Rubedo Leather Boots" ] = "boots" | |
, ["Rubedo Leather Bracers" ] = "bracers" | |
, ["Rubedo Leather Helmet" ] = "helmet" | |
, ["Rubedo Leather Guards" ] = "guards" | |
, ["Rubedo Leather Arm Cops" ] = "arm cops" | |
, ["Rubedo Leather Belt" ] = "belt" | |
, ["Ruby Ash Bow" ] = "bow" | |
, ["Ruby Ash Inferno Staff" ] = "flame" | |
, ["Ruby Ash Frost Staff" ] = "frost" | |
, ["Ruby Ash Lightning Staff" ] = "lightning" | |
, ["Ruby Ash Healing Staff" ] = "resto" | |
, ["Ruby Ash Shield" ] = "shield" | |
, ["Whitestrake's Retribution"] = "Whitestrake's" | |
, ["Armor of the Seducer" ] = "Seducer" | |
, ["Night Mother's Gaze" ] = "Night Mother's" | |
, ["Alessia's Bulwark" ] = "Alessia's" | |
, ["Law of Julianos" ] = "Julianos" | |
, ["Pelinal's Aptitude" ] = "Pelinal's" | |
, ["Epic" ] = "|c973dd8Epic|r" | |
, ["Legendary" ] = "|ce6c859Legendary|r" | |
} | |
-- Abbreviate strings so that they fit in narrow columns. | |
-- Increase data display density. | |
-- | |
-- Also applies purple/gold color to epic/legendary | |
-- | |
function WritWorthyInventoryList.Shorten(text) | |
if not text then return "" end | |
local s = SHORTEN[text] | |
if s then return s end | |
return text | |
end | |
function WritWorthyInventoryList:IsQueued(inventory_data) | |
local LLC = self:GetLLC() | |
local x = LLC:findItemByReference(inventory_data.unique_id) | |
if 0 < #x then | |
return true | |
end | |
return false | |
end | |
function WritWorthyInventoryList:IsCompleted(inventory_data) | |
if not ( inventory_data | |
and inventory_data.unique_id | |
and WritWorthy.savedChariables | |
and WritWorthy.savedChariables.writ_unique_id | |
and WritWorthy.savedChariables.writ_unique_id[inventory_data.unique_id] | |
) then | |
return false | |
end | |
return WritWorthy.savedChariables.writ_unique_id[inventory_data.unique_id].state | |
== WritWorthyInventoryList.STATE_COMPLETED | |
end | |
-- Can this row be queued in LibLazyCrafting? | |
-- | |
-- If so, return true, "". If not, return false, "why not". "Why not" here is | |
-- often the same red text that WritWorthy inserts into the sealed master | |
-- writ's tooltip. | |
function WritWorthyInventoryList:CanQueue(inventory_data) | |
if self:IsCompleted(inventory_data) then | |
return false, "completed" | |
end | |
if not inventory_data.llc_func then | |
return false, "WritWorthy bug: Missing LLC data" | |
end | |
local text_list = {} | |
if inventory_data.parser.ToKnowList then | |
for _, know in ipairs(inventory_data.parser:ToKnowList()) do | |
if not know.is_known then | |
table.insert(text_list, know:TooltipText()) | |
end | |
end | |
end | |
if 0 < #text_list then | |
return false, table.concat(text_list, "\n") | |
end | |
return true, "" | |
end | |
-- Fill in all inventory_data.ui_xxx fields. | |
-- Here is where our data gets translated into user-visible text. | |
function WritWorthyInventoryList:PopulateUIFields(inventory_data) | |
inventory_data.ui_voucher_ct = WritWorthy.ToVoucherCount(inventory_data.item_link) | |
inventory_data.ui_is_queued = self:IsQueued(inventory_data) | |
inventory_data.ui_is_completed = self:IsCompleted(inventory_data) | |
local can, why_not = self:CanQueue(inventory_data) | |
inventory_data.ui_can_queue = can | |
if can then | |
inventory_data.ui_can_queue_tooltip = nil | |
else | |
inventory_data.ui_can_queue_tooltip = why_not | |
end | |
-- For less typing. | |
local parser = inventory_data.parser | |
if parser.class == WritWorthy.Smithing.Parser.class then | |
local ri = parser.request_item -- For less typing. | |
if ri.school == WritWorthy.Smithing.SCHOOL_WOOD then | |
inventory_data.ui_type = "Wood" | |
else | |
inventory_data.ui_type = ri.school.armor_weight_name | |
end | |
inventory_data.ui_detail1 = parser.set_bonus.name | |
inventory_data.ui_detail2 = ri.item_name | |
inventory_data.ui_detail3 = parser.motif.motif_name | |
inventory_data.ui_detail4 = ri.trait_set[parser.trait_num].trait_name | |
inventory_data.ui_detail5 = parser.improve_level.name | |
elseif parser.class == WritWorthy.Alchemy.Parser.class then | |
inventory_data.ui_type = "Alchemy" | |
local mat_list = parser:ToMatList() | |
inventory_data.ui_detail1 = mat_list[1].name | |
inventory_data.ui_detail2 = mat_list[2].name | |
inventory_data.ui_detail3 = mat_list[3].name | |
inventory_data.ui_detail4 = mat_list[4].name | |
elseif parser.class == WritWorthy.Enchanting.Parser.class then | |
inventory_data.ui_type = "Enchanting" | |
if parser.level == 150 then | |
inventory_data.ui_detail1 = "Superb" | |
else | |
inventory_data.ui_detail1 = "Truly Superb" | |
end | |
inventory_data.ui_detail2 = parser.glyph.name | |
if parser.quality_num == 4 then | |
inventory_data.ui_detail5 = "Epic" | |
else | |
inventory_data.ui_detail5 = "Legendary" | |
end | |
elseif parser.class == WritWorthy.Provisioning.Parser.class then | |
inventory_data.ui_type = "Provisioning" | |
inventory_data.ui_detail1 = parser.recipe.fooddrink_name | |
end | |
-- Since the point of these UI fields is to drive the | |
-- UI, we might as well shorten them here, once, rather | |
-- than over and over again later during display and | |
-- sort. | |
-- | |
-- Shorten() also converts empty/nil to "" for safer | |
-- use later. | |
-- | |
-- leave ui_voucher_ct as an integer for better sorting. | |
inventory_data.ui_type = self.Shorten(inventory_data.ui_type ) | |
inventory_data.ui_detail1 = self.Shorten(inventory_data.ui_detail1 ) | |
inventory_data.ui_detail2 = self.Shorten(inventory_data.ui_detail2 ) | |
inventory_data.ui_detail3 = self.Shorten(inventory_data.ui_detail3 ) | |
inventory_data.ui_detail4 = self.Shorten(inventory_data.ui_detail4 ) | |
inventory_data.ui_detail5 = self.Shorten(inventory_data.ui_detail5 ) | |
-- "Sort by station" key. Consumables to the front, | |
-- then set bonus sites. | |
if parser.class == WritWorthy.Alchemy.Parser.class then | |
inventory_data.ui_station_sort = "01 alchemy" | |
elseif parser.class == WritWorthy.Enchanting.Parser.class then | |
inventory_data.ui_station_sort = "02 enchanting" | |
elseif parser.class == WritWorthy.Provisioning.Parser.class then | |
inventory_data.ui_station_sort = "03 provisioning" | |
elseif parser.class == WritWorthy.Smithing.Parser.class then | |
local set_name = inventory_data.ui_detail1 | |
local crafting_type = parser.request_item.school.trade_skill_type | |
local t = string.format( "04 %s %02d" | |
, set_name | |
, crafting_type | |
) | |
inventory_data.ui_station_sort = t | |
end | |
end | |
-- Called by ZOS code after user clicks in any of our "Enqueue" checkboxes. | |
function WritWorthyInventoryList_EnqueueToggled(cell_control, checked) | |
Log:StartNewEvent() | |
Log:Add("WritWorthyInventoryList_EnqueueToggled() checked:"..tostring(checked) | |
.." unique_id:"..tostring(cell_control.inventory_data.unique_id)) | |
self = WritWorthyInventoryList.singleton | |
if checked then | |
self:Enqueue(cell_control.inventory_data) | |
else | |
self:Dequeue(cell_control.inventory_data) | |
end | |
-- self.LogLLCQueue(self:GetLLC().personalQueue) | |
self:UpdateUISoon(cell_control.inventory_data) | |
end | |
-- Called by ZOS code after user clicks "Enqueue All" | |
function WritWorthyInventoryList_EnqueueAll() | |
Log:StartNewEvent() | |
self = WritWorthyInventoryList.singleton | |
self:EnqueueAll() | |
self:Refresh() | |
self:UpdateSummaryAndQButtons() | |
end | |
-- Called by ZOS code after user clicks "Dequeue All" | |
function WritWorthyInventoryList_DequeueAll() | |
Log:StartNewEvent() | |
self = WritWorthyInventoryList.singleton | |
self:DequeueAll() | |
self:Refresh() | |
self:UpdateSummaryAndQButtons() | |
end | |
-- No longer used, but boy howdy tihs was a fun way to get the skill IDs for | |
-- all the crafting passives I'm interested in. | |
local function DumpSkills() | |
Log:StartNewEvent() | |
local num_types = GetNumSkillTypes() | |
Log:Add("num_types:"..tostring(num_types)) | |
for skill_type = 1, num_types do | |
local num_lines = GetNumSkillLines(skill_type) | |
Log:Add("t:"..tostring(skill_type).." num_lines:"..tostring(num_lines)) | |
for skill_index = 1, num_lines do | |
local num_abilities = GetNumSkillAbilities(skill_type, skill_index) | |
Log:Add("t:"..tostring(skill_type).." i:"..tostring(skill_index) | |
.." num_abilities:"..tostring(num_abilities)) | |
for ability_index = 1, num_abilities do | |
local info = { GetSkillAbilityInfo(skill_type, skill_index, ability_index) } | |
local id = GetSkillAbilityId(skill_type, skill_index, ability_index, false) | |
Log:Add("t i a:"..tostring(skill_type).." "..tostring(skill_index) | |
.." "..tostring(ability_index) | |
.." id:"..tostring(id) | |
.." name:"..tostring(info[1]) | |
.." tex:" ..tostring(info[2]) | |
.." earnedRank:"..tostring(info[3]) | |
.." passive:"..tostring(info[4]) | |
.." ultimate:"..tostring(info[5]) | |
.." purchased:"..tostring(info[6]) | |
.." progression:"..tostring(info[7]) | |
) | |
end | |
end | |
end | |
end | |
-- Called by ZOS code after user clicks "Sort by Station" | |
function WritWorthyInventoryList_SortByStation() | |
Log:StartNewEvent() | |
Log:Add("SortByStation") | |
self = WritWorthyInventoryList.singleton | |
self.currentSortKey = "ui_station_sort" | |
self.currentSortOrder = ZO_SORT_ORDER_UP | |
self:RefreshData() | |
end | |
-- cache these, because with inline == the string will be created just to compare | |
-- it each time the fn runs | |
local stringWood = "Wood" | |
local stringHeavy = "Heavy" | |
local stringMedium = "Medium" | |
local stringLight = "Light" | |
-- ZO_ScrollFilterList will instantiate (or reuse!) a | |
-- WritWorthyInventoryListRow row_control to display some inventory_data. But | |
-- it's our job to fill in that control's nested labels with the appropriate | |
-- bits of data. | |
-- | |
-- Called as self.setupCallback from ZO_ScrollList_Commit() | |
-- | |
-- inventory_data is the instance passed to ZO_ScrollList_CreateDataEntry() by | |
-- FilterScrollList(), is an element of master list | |
-- WritWorthy.inventory_data_list. | |
function WritWorthyInventoryList:SetupRowControl(row_control, inventory_data) | |
-- Log:Add("SetupRowControl row_control:"..tostring(row_control) | |
-- .." inventory_data.unique_id:"..tostring(inventory_data.unique_id)) | |
row_control.inventory_data = inventory_data | |
-- ZO_SortList reuses row_control instances, so there | |
-- is a good chance we've already created these cell | |
-- controls. | |
local already_created = row_control[self.CELL_TYPE] | |
if not already_created then | |
local header_control = WritWorthyUIInventoryListHeaders | |
self:CreateRowControlCells(row_control, header_control) | |
-- Retain pointers to our row_control instances so that | |
-- we can update all their cell widths later upon | |
-- window resize. | |
table.insert(self.row_control_list, row_control) | |
end | |
-- Refresh mutable state (aka queued/completed) | |
self:PopulateUIFields(inventory_data) | |
-- For less typing. | |
local rc = row_control | |
local i_d = inventory_data | |
-- Apply text color to entire row. | |
local fn = Util.color | |
local c = self.COLOR_TEXT_CAN_QUEUE | |
local c2 = nil | |
if inventory_data.ui_is_completed then | |
c = self.COLOR_TEXT_COMPLETED | |
elseif not inventory_data.ui_can_queue then | |
c = self.COLOR_TEXT_CANNOT_QUEUE | |
elseif inventory_data.ui_is_queued then | |
c = self.COLOR_TEXT_QUEUED | |
if i_d.ui_type == stringWood then | |
c2 = self.COLOR_TEXT_WW | |
elseif (i_d.ui_type == stringLight) or (i_d.ui_type == stringMedium) then | |
c2 = self.COLOR_TEXT_CL | |
elseif i_d.ui_type == stringHeavy then | |
c2 = self.COLOR_TEXT_BS | |
end | |
end | |
if not c2 then c2 = c end | |
-- Allow each cell's OnMouseDown handler easy | |
-- access to this row's data. | |
for _, name in ipairs(self.CELL_NAME_LIST) do | |
rc[name].inventory_data = i_d | |
end | |
-- Fill in the cells with data for this row. | |
rc[self.CELL_TYPE ]:SetText(fn(c2, i_d.ui_type)) | |
rc[self.CELL_VOUCHERCT]:SetText(fn(c, tostring(i_d.ui_voucher_ct))) | |
rc[self.CELL_DETAIL1 ]:SetText(fn(c2, i_d.ui_detail1)) | |
rc[self.CELL_DETAIL2 ]:SetText(fn(c, i_d.ui_detail2)) | |
rc[self.CELL_DETAIL3 ]:SetText(fn(c, i_d.ui_detail3)) | |
rc[self.CELL_DETAIL4 ]:SetText(fn(c, i_d.ui_detail4)) | |
rc[self.CELL_DETAIL5 ]:SetText(fn(c, i_d.ui_detail5)) | |
-- The "Enqueue" checkbox and its mask that makes it | |
-- look dimmed out when we cannot enqueue this row | |
-- due to lack of knowledge or WritWorthy code: | |
-- | |
-- The mask does a lot for us: | |
-- 1. dims the checkbox | |
-- 2. intercepts mouse events | |
-- 3. provides tooltips | |
-- So there's no need to disable or hide the checkbox. | |
-- | |
local b = rc[self.CELL_ENQUEUE ] | |
local b_mask = rc[self.CELL_ENQUEUE_MASK] | |
b.inventory_data = inventory_data | |
b_mask.inventory_data = inventory_data | |
if i_d.ui_can_queue then | |
ZO_CheckButton_SetCheckState(b, i_d.ui_is_queued) | |
b_mask:SetHidden(true) | |
else | |
ZO_CheckButton_SetCheckState(b, false) | |
b_mask:SetHidden(false) | |
b_mask.tooltip_text = i_d.ui_can_queue_tooltip | |
end | |
end | |
-- savedChariables will slowly accumulate an ever-growing list of completed | |
-- sealed master writs. Discard any writ that has not been in inventory for | |
-- a while. | |
-- | |
-- There's no point in doing this every time we load UI, just do it once | |
-- a day, maybe once per window toggle. | |
function WritWorthyInventoryList:PurgeAncientSavedChariables() | |
-- Build a fast O(1) lookup table of | |
-- current sealed writs. | |
local inventory_data_list = WritWorthy:ScanInventoryForMasterWrits() | |
local current = {} | |
for _, inventory_data in pairs(inventory_data_list) do | |
current[inventory_data.unique_id] = inventory_data | |
end | |
local now = GetTimeStamp() | |
local DAY_SECS = 24 * 3600 | |
local too_old = now - 3 * DAY_SECS -- "a while" is "3 days" | |
local doomed = {} | |
for unique_id, sav in pairs(WritWorthy.savedChariables.writ_unique_id) do | |
-- Continue to update timestamps of any writs that we | |
-- still possess. | |
-- | |
-- Also update any ancient data that lacks any | |
-- timestamp at all (should no longer occur, such data | |
-- is leftovers from older unreleased code). | |
if current[unique_id] or not sav.last_seen_ts then | |
sav.last_seen_ts = now | |
-- Schedule for deletion any records whose writ | |
-- we've not seen in a long time. | |
elseif sav.last_seen_ts and sav.last_seen_ts < too_old then | |
table.insert(doomed, unique_id) | |
end | |
end | |
-- Delete the unworthy. | |
for _, unique_id in ipairs(doomed) do | |
WritWorthy.savedChariables.writ_unique_id[unique_id] = nil | |
end | |
if 0 < #doomed then | |
Log:Add("PurgeAncientSavedChariables() purged writ_unique_id count:" | |
..tostring(#doomed)) | |
end | |
end | |
-- Callback from LibLazyCrafting into our code upon completion of a single | |
-- queued request. | |
-- - event is "success" or "not enough mats" or some other string. | |
-- We COULD key off of "success" and display error redness if fail. | |
-- - llc_result is a table with bag/slot id of the crafted item and | |
-- its unique_id reference. | |
function WritWorthyInventoryList_LLCCompleted(event, station, llc_result) | |
Log:StartNewEvent() | |
-- Just finished crafting at this station. | |
-- Auto-exit the station so that we can move on. | |
if event == LLC_NO_FURTHER_CRAFT_POSSIBLE | |
and WritWorthyInventoryList.auto_exit_soon then | |
WritWorthyInventoryList.auto_exit_soon = false | |
SCENE_MANAGER:ShowBaseScene() | |
end | |
if event ~= LLC_CRAFT_SUCCESS then return end | |
-- Avoid auto-exiting immediately after connecting | |
-- to a station that LLC cannot craft anything for. | |
-- That would be super-annoying. | |
WritWorthyInventoryList.auto_exit_soon = true | |
local unique_id = nil | |
local request_index = nil | |
if llc_result then | |
unique_id = llc_result.reference | |
end | |
if not unique_id then return end | |
-- Log:Add("LibLazyCrafting completed" | |
-- .." unique_id:"..tostring(unique_id) | |
-- .." event:"..tostring(event) | |
-- .." station:"..tostring(station) | |
-- .." llc_result:"..tostring(llc_result)) | |
-- for k,v in pairs(llc_result) do | |
-- Log:Add("llc_result k:"..tostring(k).." v:"..tostring(v)) | |
-- end | |
local self = WritWorthyInventoryList.singleton | |
if not self then return end | |
-- Remember that this writ is noe "completed", no | |
-- longer "queued". | |
self.SaveChariableState( | |
unique_id | |
, WritWorthyInventoryList.STATE_COMPLETED ) | |
-- Upate UI to display new "completed" state that we | |
-- just recorded. | |
inventory_data = self:UniqueIDToInventoryData(unique_id) | |
if inventory_data then | |
self:UpdateUISoon(inventory_data) | |
end | |
end | |
-- O(n) scan for an inventory_data with a matching unique_id | |
function WritWorthyInventoryList:UniqueIDToInventoryData(unique_id) | |
for _, inventory_data in pairs(self.inventory_data_list) do | |
if inventory_data.unique_id == unique_id then | |
return inventory_data | |
end | |
end | |
return nil | |
end | |
-- Queued state for one or more rows has changed. Propagate that change through | |
-- our .inventory_data_list and into the UI. | |
-- | |
-- Eventually this may move to a zo_callLater() function that NOPs for 0.1 | |
-- seconds and then updates only after we've stopped calling it for 0.1 | |
-- seconds. | |
-- | |
function WritWorthyInventoryList:UpdateUISoon(inventory_data) | |
Log:Add("WritWorthyInventoryList:UpdateUISoon unique_id:" | |
..tostring(inventory_data.unique_id)) | |
self.LogLLCQueue(self:GetLLC().personalQueue) | |
self:UpdateSummaryAndQButtons() | |
self:Refresh() | |
end | |
-- Return our LibLazyCrafting API. | |
-- | |
-- The returned API is a table with members set to the public API functions and | |
-- values. It is NOT the same as LibLazyCrafting's internal instance: that's | |
-- private to LLC. | |
-- | |
-- LibLazyCrafting API k:addonName v:WritWorthy | |
-- LibLazyCrafting API k:version v:0.4 | |
-- LibLazyCrafting API k:autocraft v:true | |
-- LibLazyCrafting API k:personalQueue v:table: 00000093E1DF47C8 | |
-- LibLazyCrafting API k:cancelItem | |
-- LibLazyCrafting API k:cancelItemByReference | |
-- LibLazyCrafting API k:findItemByReference | |
-- LibLazyCrafting API k:findItemLocationById | |
-- LibLazyCrafting API k:craftItem | |
-- LibLazyCrafting API k:GetCurrentSetInteractionIndex | |
-- LibLazyCrafting API k:CraftAllItems | |
-- LibLazyCrafting API k:CraftSmithingItem | |
-- LibLazyCrafting API k:CraftSmithingItemByLevel | |
-- LibLazyCrafting API k:ImproveSmithingItem | |
-- LibLazyCrafting API k:CraftEnchantingItemId | |
-- LibLazyCrafting API k:CraftEnchantingGlyph | |
-- LibLazyCrafting API k:CraftAlchemyItem | |
-- LibLazyCrafting API k:CraftAlchemyItemByItemId | |
-- LibLazyCrafting API k:CraftProvisioningItemByRecipeId | |
-- | |
function WritWorthyInventoryList:GetLLC() | |
if self.LibLazyCrafting then | |
return self.LibLazyCrafting | |
end | |
local lib = LibStub:GetLibrary("LibLazyCrafting") | |
self.LibLazyCrafting = lib:AddRequestingAddon( | |
WritWorthy.name -- name | |
, true -- autocraft | |
, WritWorthyInventoryList_LLCCompleted -- functionCallback | |
) | |
Log:StartNewEvent() | |
if not self.LibLazyCrafting then | |
d("WritWorthy: Unable to load LibLazyCrafting") | |
Log:Add("Unable to load LibLazyCrafting") | |
end | |
Log:Add("LibLazyCrafting LLC:"..tostring(self.LibLazyCrafting)) | |
-- Record API names to log so that I have them handy | |
-- rather than spending any time asking "is Xxx() | |
-- available?" | |
-- No need to log .personalQueue contents here: the | |
-- LLC queue is always initially empty. It has no | |
-- savedVariables of its own; we control that. | |
for k,v in pairs(self.LibLazyCrafting) do | |
Log:Add("LibLazyCrafting API k:"..tostring(k).." v:"..tostring(v)) | |
end | |
-- Install our Alchemy and Provisioning pre-craft | |
-- hooks. LibLazyCrafting 1.3 lacks these checks and | |
-- erroneously attempts (and fails) to craft items with | |
-- insufficient materials, causing failure or in | |
-- Provisioning's case, an infinite loop followed by | |
-- client disconnect (!). Hopefully 1.4 will add them. | |
-- But until then, here, have our own hooks. | |
local llc_global = LibStub("LibLazyCrafting") | |
if llc_global | |
and llc_global.craftInteractionTables | |
and llc_global.craftInteractionTables[CRAFTING_TYPE_ALCHEMY ] | |
and llc_global.craftInteractionTables[CRAFTING_TYPE_PROVISIONING] then | |
llc_global.craftInteractionTables[CRAFTING_TYPE_ALCHEMY ]["isItemCraftable"] = WritWorthy_LLC_IsItemCraftable_Alchemy | |
llc_global.craftInteractionTables[CRAFTING_TYPE_PROVISIONING]["isItemCraftable"] = WritWorthy_LLC_IsItemCraftable_Provisioning | |
else | |
d("WritWorthy warning: unable to install code to check" | |
.." provisioning/alchemy materials. Auto-crafting these types" | |
.." will hang if you lack required materials.") | |
end | |
return self.LibLazyCrafting | |
end | |
-- Copied from LLC internals | |
local function getItemLinkFromItemId(itemId) local name = GetItemLinkName(ZO_LinkHandler_CreateLink("Test Trash", nil, ITEM_LINK_TYPE,itemId, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 10000, 0)) | |
return ZO_LinkHandler_CreateLink(zo_strformat("<<t:1>>",name), nil, ITEM_LINK_TYPE,itemId, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
end | |
local function HaveMaterials(mat_list) | |
for _, mat in ipairs(mat_list) do | |
local item_link = mat.item_link | |
if (not item_link) and mat.item_id then | |
item_link = getItemLinkFromItemId(mat.item_id) | |
end | |
if item_link then | |
local bag_ct, bank_ct, craft_bag_ct = GetItemLinkStacks(item_link) | |
local have_ct = bag_ct + bank_ct + craft_bag_ct | |
Log:Add("HaveMaterials: "..tostring(mat.required_ct) | |
.." <=? "..tostring(have_ct).." "..tostring(item_link)) | |
if have_ct < mat.required_ct then | |
d("WritWorthy: insufficient materials: "..tostring(item_link) | |
..": require "..tostring(mat.required_ct) | |
.." have "..tostring(have_ct)) | |
return false | |
end | |
else | |
Log:Add("HaveMaterials: nil link") | |
end | |
end | |
return true | |
end | |
-- Hook called by LibLazyCrafting before attempting to craft each request. | |
-- Return true if it's okay to start crafting it, false if not. | |
function WritWorthy_LLC_IsItemCraftable_Alchemy(self, station_crafting_type, request) | |
if station_crafting_type ~= CRAFTING_TYPE_ALCHEMY then return false end | |
local mat_list | |
= { { item_id = request.solventId , required_ct = request.timesToMake } | |
, { item_id = request.reagentId1 , required_ct = request.timesToMake } | |
, { item_id = request.reagentId2 , required_ct = request.timesToMake } | |
, { item_id = request.reagentId3 , required_ct = request.timesToMake } | |
} | |
return HaveMaterials(mat_list) | |
end | |
function WritWorthy_LLC_IsItemCraftable_Provisioning(self, station_crafting_type, request) | |
if station_crafting_type ~= CRAFTING_TYPE_PROVISIONING then return false end | |
local mat_list = {} | |
local recipe_link = getItemLinkFromItemId(request.recipeId) | |
local mat_ct = GetItemLinkRecipeNumIngredients(recipe_link) | |
for ingr_index = 1,mat_ct do | |
local _, _, ingr_ct = GetItemLinkRecipeIngredientInfo( | |
recipe_link | |
, ingr_index) | |
local ingr_link = GetItemLinkRecipeIngredientItemLink( | |
recipe_link | |
, ingr_index | |
, LINK_STYLE_DEFAULT) | |
if ingr_ct | |
and (0 < ingr_ct) | |
and ingr_link | |
and (ingr_link ~= "") then | |
local mat = { item_link = ingr_link | |
, required_ct = ingr_ct * request.timesToMake | |
} | |
table.insert(mat_list, mat) | |
end | |
end | |
return HaveMaterials(mat_list) | |
end | |
-- Record a "queued" or "completed" state to per-character savedVariables. If | |
-- we do not yet have a savedChariable entry for this unique_id, force one into | |
-- existence. | |
-- | |
-- Return the savedChariable record for this unique_id, guaranteed to be non-nil. | |
-- | |
function WritWorthyInventoryList.SaveChariableState(unique_id, state) | |
-- Force-create the outer writ_unique_id collection to | |
-- house all our per-writ records. | |
if not WritWorthy.savedChariables.writ_unique_id then | |
WritWorthy.savedChariables.writ_unique_id = {} | |
end | |
-- Force-create the record itself. | |
if not WritWorthy.savedChariables.writ_unique_id[unique_id] then | |
WritWorthy.savedChariables.writ_unique_id[unique_id] = {} | |
end | |
-- Now that we know that we have an existing record, | |
-- fill it with data. | |
WritWorthy.savedChariables.writ_unique_id[unique_id].state = state | |
return WritWorthy.savedChariables.writ_unique_id[unique_id] | |
end | |
-- Add the given item to LibLazyCrafting's queue of stuff | |
-- to be automatically crafted later. | |
function WritWorthyInventoryList:Enqueue(inventory_data) | |
-- Use the ZOS-assigned GetItemUniqueId() for | |
-- this sealed writ. | |
local unique_id = inventory_data.unique_id | |
Log:Add("Enqueue "..tostring(unique_id)) | |
-- Avoid enqueing the same writ twice: If we already | |
-- enqueued this specific writ, do nothing. | |
if self:IsQueued(inventory_data) then | |
d("WritWorthy bug: Already enqueued. UI incorrectly showed item as" | |
.. " not enqueued. Sealed writ unique_id:"..tostring(unique_id)) | |
return | |
end | |
self.EnqueueLLC(unique_id, inventory_data) | |
-- Remember this in savedChariables so that | |
-- we can restore checkbox state after /reloadui. | |
self.SaveChariableState( | |
unique_id | |
, WritWorthyInventoryList.STATE_QUEUED) | |
-- nur zum Testen | |
-- self.LogLLCQueue(self:GetLLC().personalQueue) | |
end | |
-- The LazyLibCrafting-only portion of enqueing a request, no list UI work | |
-- here because this is also called during initialization time, from | |
-- RestoreFromSavedChariables(). Also called after the user selects a checkbox. | |
-- | |
-- Enqueues one or more copies of inventory_data's request. | |
-- | |
function WritWorthyInventoryList.EnqueueLLC(unique_id, inventory_data) | |
self = WritWorthyInventoryList.singleton | |
if not inventory_data.llc_func then | |
-- Either this row should not have had its | |
-- "Enqueue" checkbox enabled, or this row | |
-- should have stored its LibLazyCrafting data | |
-- in its inventory_data slots before calling us. | |
d("WritWorthy bug: cannot enqueue, lacks LibLazyCrafting values:") | |
Log:Add("WritWorthy:Enqueue() Cannot enqueue, lacks LibLazyCrafting values:") | |
for k,v in pairs(inventory_data) do | |
Log:Add("i_d k:"..tostring(k).." v:"..tostring(v)) | |
end | |
return | |
end | |
-- Make sure this version of LibLazyCrafting | |
-- supports the required API. We might get stuck | |
-- with some other add-on's older version. | |
local i_d = inventory_data | |
local LLC = self:GetLLC() | |
if not LLC[i_d.llc_func] then | |
d("LibLazyCrafting function missing:"..tostring(i_d.llc_func)) | |
d("LibLazyCrafting version:"..tostring(LLC.version)) | |
return | |
-- else | |
-- d("LibLazyCrafting function found:"..tostring(i_d.llc_func)) | |
-- d("LibLazyCrafting version:"..tostring(LLC.version)) | |
end | |
-- Call LibLazyCrafting to queue it up for later. | |
if LLC[i_d.llc_func] then | |
LLC[i_d.llc_func](LLC, unpack(i_d.llc_args)) | |
else | |
-- Oops! This version of LibLazyCrafting lacks the | |
-- required function. Should not happen, but did | |
-- while Zig was developing WritWorthy with | |
-- unpublished versions of LibLazyCrafting. | |
d("LibLazyCrafting function missing:"..tostring(i_d.llc_func)) | |
d("LibLazyCrafting version:"..tostring(LLC.version)) | |
end | |
end | |
function WritWorthyInventoryList:Dequeue(inventory_data) | |
local unique_id = inventory_data.unique_id | |
Log:Add("Dequeue "..tostring(unique_id)) | |
local LLC = self:GetLLC() | |
LLC:cancelItemByReference(inventory_data.unique_id) | |
-- Remove from savedChariables so that we do not | |
-- re-queue this row upon /reloadui. | |
if WritWorthy.savedChariables.writ_unique_id then | |
WritWorthy.savedChariables.writ_unique_id[unique_id] = nil | |
end | |
end | |
-- Reload the LibLazyCrafting queue from savedChariables | |
function WritWorthyInventoryList.RestoreFromSavedChariables() | |
-- Do nothing if nothing to restore. | |
savedChariables = WritWorthy.savedChariables | |
if not ( savedChariables | |
and savedChariables.writ_unique_id) then | |
return | |
end | |
local inventory_data_list = WritWorthy:ScanInventoryForMasterWrits() | |
for _, inventory_data in pairs(inventory_data_list) do | |
local unique_id = inventory_data.unique_id | |
local sav = savedChariables.writ_unique_id[unique_id] | |
if sav and sav.state == WritWorthyInventoryList.STATE_QUEUED then | |
WritWorthyInventoryList.EnqueueLLC(unique_id, inventory_data) | |
end | |
end | |
end | |
-- Dump LibLazyCrafting's entire queue to log file. | |
-- This can be HUGE if you have dozens of sealed writs in inventory, so | |
-- comment this out before shipping. | |
function WritWorthyInventoryList.LogLLCQueue(queue) | |
if not queue then return end | |
for kk,vv in pairs(queue) do | |
local vstr = tostring(vv) | |
if type(vv) == "table" then | |
vstr = vstr.." ct:"..tostring(#vv) | |
end | |
Log:Add("LibLazyCrafting queue k:"..tostring(kk) | |
.." v:"..vstr) | |
if type(vv) == "table" then | |
for k,v in pairs(vv) do | |
Log:Add("LibLazyCrafting queue k:"..tostring(kk) | |
..","..tostring(k) | |
.." v:"..tostring(v)) | |
if type(v) == "table" then | |
for k3, v3 in pairs(v) do | |
Log:Add("LibLazyCrafting queue k:"..tostring(kk) | |
..","..tostring(k) | |
..","..tostring(k3) | |
.." v:"..tostring(v3)) | |
end | |
end | |
end | |
end | |
end | |
end | |
-- O(n) scan to collect a hash of unique item ids of items actually | |
-- in LibLazyCrafting's queue. | |
function WritWorthyInventoryList:QueuedReferenceList() | |
local queued_ids = {} | |
for station, queued in ipairs(self:GetLLC().personalQueue) do | |
if type(queued) == "table" then | |
for i, request in ipairs(queued) do | |
if request.reference then | |
local unique_id = request.reference | |
queued_ids[unique_id] = true | |
end | |
end | |
end | |
end | |
return queued_ids | |
end | |
-- Fill our summary with actual data from our enqueued items. | |
-- Enable/disable "Enqueue All"/"Dequeue All" buttons depending on | |
-- whether we've any more to enqueue or dequeue. | |
function WritWorthyInventoryList:UpdateSummaryAndQButtons() | |
-- Collect hashtable of all queued unique_ids | |
-- so that we can use it later in an O(n) loop | |
-- for O(1) lookups. | |
local queued_ids = self:QueuedReferenceList() | |
-- Accumulators | |
local can_enqueue_any = false | |
local can_dequeue_any = false | |
local total_queued_voucher_ct = 0 | |
local total_queued_mat_gold = 0 | |
local total_completed_voucher_ct = 0 | |
local total_completed_mat_gold = 0 | |
-- Scan our master request list, accumulate voucher | |
-- and mat totals for each request in LLC's queue. | |
-- While scanning, also notice if any of these can | |
-- be enqueued. | |
for _, inventory_data in ipairs(self.inventory_data_list) do | |
if inventory_data.unique_id then | |
if queued_ids[inventory_data.unique_id] then | |
local voucher_ct = WritWorthy.ToVoucherCount(inventory_data.item_link) | |
total_queued_voucher_ct = total_queued_voucher_ct + voucher_ct | |
local mat_list = inventory_data.parser:ToMatList() | |
local mat_gold = WritWorthy.MatRow.ListTotal(mat_list) or 0 | |
total_queued_mat_gold = total_queued_mat_gold + mat_gold | |
can_dequeue_any = true | |
elseif inventory_data.ui_can_queue | |
and not inventory_data.ui_is_queued then | |
can_enqueue_any = true | |
elseif self:IsCompleted(inventory_data) then | |
local voucher_ct = WritWorthy.ToVoucherCount(inventory_data.item_link) | |
total_completed_voucher_ct = total_completed_voucher_ct + voucher_ct | |
local mat_list = inventory_data.parser:ToMatList() | |
local mat_gold = WritWorthy.MatRow.ListTotal(mat_list) or 0 | |
total_completed_mat_gold = total_completed_mat_gold + mat_gold | |
end | |
end | |
end | |
local queued_mat_per_v = 0 | |
local completed_mat_per_v = 0 | |
if total_queued_voucher_ct then | |
queued_mat_per_v = total_queued_mat_gold / total_queued_voucher_ct | |
end | |
if total_completed_voucher_ct then | |
completed_mat_per_v = total_completed_mat_gold / total_completed_voucher_ct | |
end | |
local queued_voucher_string = Util.ToMoney(total_queued_voucher_ct) | |
local queued_mat_string = Util.ToMoney(total_queued_mat_gold) | |
local queued_mat_per_string = Util.ToMoney(queued_mat_per_v) | |
WritWorthyUISummaryQueuedVoucherCt:SetText(queued_voucher_string) | |
WritWorthyUISummaryQueuedMatCost:SetText(queued_mat_string) | |
WritWorthyUISummaryQueuedVoucherCost:SetText(queued_mat_per_string) | |
local completed_voucher_string = Util.ToMoney(total_completed_voucher_ct) | |
local completed_mat_string = Util.ToMoney(total_completed_mat_gold) | |
local completed_mat_per_string = Util.ToMoney(completed_mat_per_v) | |
WritWorthyUISummaryCompletedVoucherCt:SetText(completed_voucher_string) | |
WritWorthyUISummaryCompletedMatCost:SetText(completed_mat_string) | |
WritWorthyUISummaryCompletedVoucherCost:SetText(completed_mat_per_string) | |
WritWorthyUIEnqueueAll:SetEnabled(can_enqueue_any) | |
WritWorthyUIDequeueAll:SetEnabled(can_dequeue_any) | |
end | |
function WritWorthyInventoryList:EnqueueAll() | |
for _, inventory_data in ipairs(self.inventory_data_list) do | |
-- Use CanQueue()/IsQueued() for dynamic/current | |
-- reality instead of inventory.can_queue/ui_is_queued | |
-- because that might not be filled in yet for rows | |
-- scrolled offscreen. | |
if self:CanQueue(inventory_data) | |
and not self:IsQueued(inventory_data) then | |
self:Enqueue(inventory_data) | |
end | |
end | |
end | |
function WritWorthyInventoryList:DequeueAll() | |
for _, inventory_data in ipairs(self.inventory_data_list) do | |
-- Use IsQueued() for dynamic/current reality | |
-- instead of inventory.ui_is_queued because | |
-- that might not be filled in yet for rows | |
-- scrolled offscreen. | |
if self:IsQueued(inventory_data) then | |
self:Dequeue(inventory_data) | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment