diff --git a/Aux-AddOn.toc b/Aux-AddOn.toc index c247ab5..97929e1 100644 --- a/Aux-AddOn.toc +++ b/Aux-AddOn.toc @@ -15,6 +15,7 @@ util\persistence.lua util\sort.lua util\money.lua util\info.lua +util\filter.lua util\scan_util.lua diff --git a/components/scan.lua b/components/scan.lua index eb3ef52..e1d5f22 100644 --- a/components/scan.lua +++ b/components/scan.lua @@ -23,7 +23,7 @@ function private.current_thread() end function public.start(params) - Aux.safe(m).abort(Aux.safe(m).threads[params.type].id) + m.abort(Aux.safe(m.threads[params.type]).id/0) local thread_id = Aux.control.new_thread(Aux.f(m.wait_for_callback, params.on_scan_start, m.scan)) @@ -189,11 +189,11 @@ function private.scan_auctions_helper(i, k) Aux.history.process_auction(auction_info) - if Aux.safe(m).current_thread().params.auto_buy_validator(auction_info)/false then + if Aux.safe(m.current_thread().params.auto_buy_validator)(auction_info)/false then local c = Aux.control.wait_for(recurse) Aux.place_bid(auction_info.query_type, auction_info.index, auction_info.buyout_price, Aux.f(c, true)) Aux.control.new_thread(Aux.control.sleep, 10, Aux.f(c, false)) - elseif Aux.safe(m).current_query().validator(auction_info)/true then + elseif Aux.safe(m.current_query().validator)(auction_info)/true then return m.wait_for_callback(m.current_thread().params.on_auction, auction_info, function(removed) if removed then return recurse(true) @@ -210,14 +210,14 @@ end function private.submit_query(k) Aux.control.when(function() return m.current_thread().params.type ~= 'list' or CanSendAuctionQuery() end, function() - Aux.safe(m).current_thread().params.on_submit_query() + Aux.safe(m.current_thread().params.on_submit_query)() if m.current_thread().params.type == 'bidder' then GetBidderAuctionItems(m.current_thread().page) elseif m.current_thread().params.type == 'owner' then GetOwnerAuctionItems(m.current_thread().page) else - local blizzard_query = Aux.safe(m).current_query().blizzard_query/{} + local blizzard_query = Aux.safe(m.current_query().blizzard_query)/{} QueryAuctionItems( blizzard_query.name, blizzard_query.min_level, diff --git a/core.lua b/core.lua index c202c37..da1a243 100644 --- a/core.lua +++ b/core.lua @@ -56,62 +56,33 @@ function public.f(func, ...) end end -do - local data = {} - local eq = function() return true end - local proto = setmetatable({}, { __eq = eq }) - local safe_nil - - local function unsafe(object) - if object == proto then - return -object - else - return object - end - end - - local safe_mt = { +function public.safe(value) + return setmetatable({},{ __index = function(self, key) - key = unsafe(key) - if data[self] == nil or key == nil then - return safe_nil + if value ~= nil and key ~= nil then + return m.safe(value[key]) else - return m.safe(data[self][key]) + return m.safe(nil) end end, __call = function(self, ...) - if data[self] == nil then - return safe_nil - end - for i=1,arg.n do - if arg[i] == proto and -arg[i] == nil then - return safe_nil - else - arg[i] = unsafe(arg[i]) + if value ~= nil then + if type(value) ~= 'function' then + error('', 2) end - end - return m.safe(data[self](unpack(arg))) - end, - __div = function(self, default) - if data[self] == nil then - return default + return m.safe(value(unpack(arg))) else - return data[self] + return m.safe(nil) end end, - __unm = function(self) - return data[self] + __div = function(self, alt) + if value ~= nil then + return value + else + return alt + end end, - __eq = eq, - } - - function public.safe(object) - local self = {} - data[self] = unsafe(object) - return setmetatable(self, safe_mt) - end - - safe_nil = m.safe() + }) end do @@ -124,7 +95,7 @@ do end function public.on_load() - public.version = '3.5.3' + public.version = '3.6.0' public.bids_loaded = false public.current_owner_page = nil public.last_owner_page_requested = nil @@ -476,39 +447,6 @@ function private.UseContainerItem(...) return m.orig.UseContainerItem(unpack(arg)) end -function public.item_class_index(item_class) - for i, class in ipairs({ GetAuctionItemClasses() }) do - if strupper(class) == strupper(item_class) then - return i - end - end -end - -function public.item_subclass_index(class_index, item_subclass) - for i, subclass in ipairs({ GetAuctionItemSubClasses(class_index) }) do - if strupper(subclass) == strupper(item_subclass) then - return i - end - end -end - -function public.item_slot_index(class_index, subclass_index, slot_name) - for i, slot in ipairs({ GetAuctionInvTypes(class_index, subclass_index) }) do - if strupper(getglobal(slot)) == strupper(slot_name) then - return i - end - end -end - -function public.item_quality_index(item_quality) - for i=0,4 do - local quality = getglobal('ITEM_QUALITY'..i..'_DESC') - if strupper(item_quality) == strupper(quality) then - return i - end - end -end - function public.is_player(name, current) local realm = GetCVar('realmName') return (not current and m.safe(aux_characters)[realm][name]/false) or UnitName('player') == name diff --git a/gui.lua b/gui.lua index 4680ef7..fa78a19 100644 --- a/gui.lua +++ b/gui.lua @@ -14,15 +14,6 @@ public.config = { off_color = {0.7, 0.3, 0.3}, } -do - local x = 0 - - function private.id() - x = x + 1 - return 'Aux_frame'..x - end -end - function public.inline_color(color) local r, g, b, a = unpack(color) return format('|c%02X%02X%02X%02X', a, r, g, b) @@ -329,7 +320,7 @@ function public.item(parent) local item = CreateFrame('Button', nil, parent) item:SetWidth(193) item:SetHeight(40) - local icon = CreateFrame('CheckButton', m.id(), item, 'ActionButtonTemplate') + local icon = CreateFrame('CheckButton', 'aux_frame'..Aux.unique(), item, 'ActionButtonTemplate') icon:SetPoint('LEFT', 2, 0.5) icon:SetHighlightTexture(nil) icon:RegisterForClicks() @@ -379,99 +370,53 @@ function public.vertical_line(parent, x_offset, top_offset, bottom_offset, inver return texture end -do - local id = 0 - function public.dropdown(parent) - id = id + 1 - local dropdown = CreateFrame('Frame', 'aux_dropdown'..id, parent, 'UIDropDownMenuTemplate') +function public.dropdown(parent) + local dropdown = CreateFrame('Frame', 'aux_frame'..Aux.unique(), parent, 'UIDropDownMenuTemplate') - dropdown:SetBackdrop{bgFile=[[Interface\Buttons\WHITE8X8]], edgeFile=[[Interface\Buttons\WHITE8X8]], edgeSize=m.config.edge_size, insets={top=5,bottom=5}} - dropdown:SetBackdropColor(unpack(m.config.content_color.backdrop)) - dropdown:SetBackdropBorderColor(unpack(m.config.content_color.border)) - local left = getglobal(dropdown:GetName()..'Left'):Hide() - local middle = getglobal(dropdown:GetName()..'Middle'):Hide() - local right = getglobal(dropdown:GetName()..'Right'):Hide() + dropdown:SetBackdrop{bgFile=[[Interface\Buttons\WHITE8X8]], edgeFile=[[Interface\Buttons\WHITE8X8]], edgeSize=m.config.edge_size, insets={top=5,bottom=5}} + dropdown:SetBackdropColor(unpack(m.config.content_color.backdrop)) + dropdown:SetBackdropBorderColor(unpack(m.config.content_color.border)) + local left = getglobal(dropdown:GetName()..'Left'):Hide() + local middle = getglobal(dropdown:GetName()..'Middle'):Hide() + local right = getglobal(dropdown:GetName()..'Right'):Hide() - local button = getglobal(dropdown:GetName()..'Button') - button:ClearAllPoints() - button:SetPoint('RIGHT', dropdown, 0, 0) + local button = getglobal(dropdown:GetName()..'Button') + button:ClearAllPoints() + button:SetPoint('RIGHT', dropdown, 0, 0) - local text = getglobal(dropdown:GetName()..'Text') - text:ClearAllPoints() - text:SetPoint('RIGHT', button, 'LEFT', -2, 0) - text:SetPoint('LEFT', dropdown, 'LEFT', 8, 0) - text:SetFont(m.config.content_font, 13) - text:SetShadowColor(0, 0, 0, 0) + local text = getglobal(dropdown:GetName()..'Text') + text:ClearAllPoints() + text:SetPoint('RIGHT', button, 'LEFT', -2, 0) + text:SetPoint('LEFT', dropdown, 'LEFT', 8, 0) + text:SetFont(m.config.content_font, 13) + text:SetShadowColor(0, 0, 0, 0) - -- - -- dropdown:ClearAllPoints() - -- dropdown:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -7, 0) - -- dropdown:SetScript("OnHide", nil) - -- dropdown:SetScript("OnEnter", Control_OnEnter) - -- dropdown:SetScript("OnLeave", Control_OnLeave) - -- dropdown:SetScript("OnMouseUp", function(self, button) Dropdown_TogglePullout(self.obj.button, button) end) - -- TSMAPI.Design:SetContentColor(dropdown) - -- - -- local left = _G[dropdown:GetName().."Left"] - -- local middle = _G[dropdown:GetName().."Middle"] - -- local right = _G[dropdown:GetName().."Right"] - -- - -- middle:ClearAllPoints() - -- right:ClearAllPoints() - -- - -- middle:SetPoint("LEFT", left, "RIGHT", 0, 0) - -- middle:SetPoint("RIGHT", right, "LEFT", 0, 0) - -- right:SetPoint("TOPRIGHT", dropdown, "TOPRIGHT", 0, 17) - -- - -- local button = _G[dropdown:GetName().."Button"] - -- button:RegisterForClicks("AnyUp") - -- button:SetScript("OnEnter", Control_OnEnter) - -- button:SetScript("OnLeave", Control_OnLeave) - -- button:SetScript("OnClick", Dropdown_TogglePullout) - -- button:ClearAllPoints() - -- button:SetPoint("RIGHT", dropdown, 0, 0) - -- - -- local text = _G[dropdown:GetName().."Text"] - -- text:ClearAllPoints() - -- text:SetPoint("RIGHT", button, "LEFT", -2, 0) - -- text:SetPoint("LEFT", dropdown, "LEFT", 8, 0) - -- text:SetFont(TSMAPI.Design:GetContentFont("normal")) - -- text:SetShadowColor(0, 0, 0, 0) - -- - -- local label = frame:CreateFontString(nil, "OVERLAY") - -- label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0) - -- label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0) - -- label:SetJustifyH("LEFT") - -- label:SetHeight(18) - -- label:SetFont(TSMAPI.Design:GetContentFont("small")) - -- label:SetShadowColor(0, 0, 0, 0) - -- label:Hide() - -- - -- left:Hide() - -- middle:Hide() - -- right:Hide() - -- - -- local widget = { - -- frame = frame, - -- label = label, - -- dropdown = dropdown, - -- text = text, - -- button = button, - -- count = count, - -- alignoffset = 30, - -- type = Type, - -- } - -- for method, func in pairs(methods) do - -- widget[method] = func - -- end - -- frame.obj = widget - -- dropdown.obj = widget - -- text.obj = widget - -- button.obj = widget +-- +-- dropdown:ClearAllPoints() +-- dropdown:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -7, 0) +-- dropdown:SetScript("OnHide", nil) +-- dropdown:SetScript("OnEnter", Control_OnEnter) +-- dropdown:SetScript("OnLeave", Control_OnLeave) +-- dropdown:SetScript("OnMouseUp", function(self, button) Dropdown_TogglePullout(self.obj.button, button) end) +-- TSMAPI.Design:SetContentColor(dropdown) +-- +-- middle:ClearAllPoints() +-- right:ClearAllPoints() +-- +-- middle:SetPoint("LEFT", left, "RIGHT", 0, 0) +-- middle:SetPoint("RIGHT", right, "LEFT", 0, 0) +-- right:SetPoint("TOPRIGHT", dropdown, "TOPRIGHT", 0, 17) +-- +-- local button = _G[dropdown:GetName().."Button"] +-- button:RegisterForClicks("AnyUp") +-- button:SetScript("OnEnter", Control_OnEnter) +-- button:SetScript("OnLeave", Control_OnLeave) +-- button:SetScript("OnClick", Dropdown_TogglePullout) +-- button:ClearAllPoints() +-- button:SetPoint("RIGHT", dropdown, 0, 0) - return dropdown - end + return dropdown end function public.slider(frame) diff --git a/tables/auction_listing.lua b/tables/auction_listing.lua index 7756cc0..0bfaf61 100644 --- a/tables/auction_listing.lua +++ b/tables/auction_listing.lua @@ -900,7 +900,7 @@ local methods = { end, RemoveAuctionRecord = function(self, record) - local index = Aux.util.index_of(record, self.records) + local index = Aux.util.key(record, self.records) if index then tremove(self.records, index) end @@ -910,7 +910,7 @@ local methods = { RemoveSelectedRecord = function(self, count) count = count or 1 for i=1, count do - local index = Aux.util.index_of(self.selected, self.records) + local index = Aux.util.key(self.selected, self.records) if index then tremove(self.records, index) end @@ -927,7 +927,7 @@ local methods = { end, ContainsRecord = function(rt, record) - if Aux.util.index_of(record, rt.records) then + if Aux.util.key(record, rt.records) then return true end end, diff --git a/tabs/post/frames.lua b/tabs/post/frames.lua index 93f24e5..72cafb8 100644 --- a/tabs/post/frames.lua +++ b/tabs/post/frames.lua @@ -75,7 +75,7 @@ Aux.post_tab.FRAMES(function(m, public, private) m.auction_listing:EnableSorting(false) m.auction_listing:DisableSelection(true) m.auction_listing:SetHandler('OnClick', function(table, row_data, column, button) - local column_index = Aux.util.index_of(column, column.row.cols) + local column_index = Aux.util.key(column, column.row.cols) local unit_start_price, unit_buyout_price = m.undercut(row_data.record, m.stack_size_slider:GetValue(), button == 'RightButton') if column_index == 3 then m.stack_size_slider:SetValue(row_data.record.stack_size) diff --git a/tabs/search/core.lua b/tabs/search/core.lua index a5f484b..697c7ff 100644 --- a/tabs/search/core.lua +++ b/tabs/search/core.lua @@ -133,26 +133,20 @@ function public.set_filter(filter_string) m.search_box:SetText(filter_string) end -function private.add_filter(filter_string, replace) - filter_string = filter_string or m.get_form_filter() +function public.add_filter(filter_string) + local old_filter_string = m.search_box:GetText() + old_filter_string = Aux.util.trim(old_filter_string) - - local old_filter_string - if not replace then - old_filter_string = m.search_box:GetText() - old_filter_string = Aux.util.trim(old_filter_string) - - if strlen(old_filter_string) > 0 then - old_filter_string = old_filter_string..';' - end + if old_filter_string ~= '' then + old_filter_string = old_filter_string..';' end - m.search_box:SetText((old_filter_string or '')..filter_string) + m.search_box:SetText(old_filter_string..filter_string) end function private.update_auto_buy_filter() if aux_auto_buy_filter ~= '' then - local queries = Aux.scan_util.parse_filter_string(aux_auto_buy_filter) + local queries = Aux.filter.queries(aux_auto_buy_filter) if queries then if getn(queries) > 1 then Aux.log('Error: The automatic buyout filter may contain only one query') @@ -177,9 +171,12 @@ end function private.clear_form() m.name_input:SetText('') + m.name_input:ClearFocus() m.exact_checkbox:SetChecked(nil) m.min_level_input:SetText('') + m.min_level_input:ClearFocus() m.max_level_input:SetText('') + m.max_level_input:ClearFocus() m.usable_checkbox:SetChecked(nil) UIDropDownMenu_ClearAll(m.class_dropdown) UIDropDownMenu_ClearAll(m.subclass_dropdown) @@ -187,56 +184,6 @@ function private.clear_form() UIDropDownMenu_ClearAll(m.quality_dropdown) end -function private.get_form_filter() - local filter_term = '' - - local function add(part) - filter_term = filter_term == '' and part or filter_term..'/'..part - end - - add(m.name_input:GetText()) - - if m.exact_checkbox:GetChecked() then - add('exact') - end - - local min_level = m.blizzard_level(m.min_level_input:GetText()) - if min_level then - add(min_level) - end - - local max_level = m.blizzard_level(m.min_level_input:GetText()) - if max_level then - add(max_level) - end - - if m.usable_checkbox:GetChecked() then - add('usable') - end - - local class = UIDropDownMenu_GetSelectedValue(m.class_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.class_dropdown) - if class then - local classes = { GetAuctionItemClasses() } - add(strlower(classes[class])) - local subclass = UIDropDownMenu_GetSelectedValue(m.subclass_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.subclass_dropdown) - if subclass then - local subclasses = {GetAuctionItemSubClasses(class)} - add(strlower(subclasses[subclass])) - local slot = UIDropDownMenu_GetSelectedValue(m.slot_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.slot_dropdown) - if slot then - add(strlower(getglobal(slot))) - end - end - end - - local quality = UIDropDownMenu_GetSelectedValue(m.quality_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.quality_dropdown) - if quality then - add(strlower(getglobal('ITEM_QUALITY'..quality..'_DESC'))) - end - - return filter_term -end - function private.discard_continuation() Aux.scan.abort(m.search_scan_id) m.current_search().continuation = nil @@ -431,7 +378,7 @@ function public.execute(resume, real_time) end local filter_string = m.search_box:GetText() - local queries = Aux.scan_util.parse_filter_string(filter_string) + local queries = Aux.filter.queries(filter_string) if not queries then return elseif real_time then @@ -765,7 +712,7 @@ function private.initialize_quality_dropdown() UIDropDownMenu_AddButton{ text = ALL, - value = 0, + value = -1, func = on_click, } for i=0,4 do @@ -777,3 +724,159 @@ function private.initialize_quality_dropdown() end end +function private.initialize_filter_dropdown() + local function on_click() + UIDropDownMenu_SetSelectedValue(m.filter_dropdown, this.value) + m.filter_button:SetText(this.value) + if Aux.safe(Aux.filter.filters[this.value]).input_type/'' == '' then + m.filter_input:Hide() + else + m.filter_input:Show() + m.filter_input:SetFocus() + end + end + + for _, filter in {'and', 'or', 'not', 'min-unit-buy', 'max-unit-bid', 'max-unit-bid', 'max-unit-buy', 'bid-profit', 'buy-profit', 'bid-vend-profit', 'buy-vend-profit', 'bid-dis-profit', 'buy-dis-profit', 'bid-pct', 'buy-pct', 'item', 'tooltip', 'min-lvl', 'max-lvl', 'rarity', 'left', 'utilizable', 'discard'} do + UIDropDownMenu_AddButton{ + text = filter, + value = filter, + func = on_click, + } + end +end + +function private.get_form() + local query_string = '' + + local function add(part) + query_string = query_string == '' and part or query_string..'/'..part + end + + add(m.name_input:GetText()) + + if m.exact_checkbox:GetChecked() then + add('exact') + end + + local min_level = m.blizzard_level(m.min_level_input:GetText()) + if min_level then + add(min_level) + end + + local max_level = m.blizzard_level(m.min_level_input:GetText()) + if max_level then + add(max_level) + end + + if m.usable_checkbox:GetChecked() then + add('usable') + end + + local class = UIDropDownMenu_GetSelectedValue(m.class_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.class_dropdown) + if class then + local classes = { GetAuctionItemClasses() } + add(strlower(classes[class])) + local subclass = UIDropDownMenu_GetSelectedValue(m.subclass_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.subclass_dropdown) + if subclass then + local subclasses = {GetAuctionItemSubClasses(class)} + add(strlower(subclasses[subclass])) + local slot = UIDropDownMenu_GetSelectedValue(m.slot_dropdown) ~= 0 and UIDropDownMenu_GetSelectedValue(m.slot_dropdown) + if slot then + add(strlower(getglobal(slot))) + end + end + end + + local quality = UIDropDownMenu_GetSelectedValue(m.quality_dropdown) + if quality and quality >= 0 then + add(strlower(getglobal('ITEM_QUALITY'..quality..'_DESC'))) + end + + return query_string +end + +function private.set_form(filters) + + local class_index, subclass_index + + for _, filter in filters do + if filter[1] == 'name' then + m.name_input:SetText(filter[2]) + elseif filter[1] == 'exact' then + m.exact_checkbox:SetChecked(true) + elseif filter[1] == 'min_level' then + m.min_level_input:SetText(tonumber(filter[2])) + elseif filter[1] == 'max_level' then + m.max_level_input:SetText(tonumber(filter[2])) + elseif filter[1] == 'usable' then + m.usable_checkbox:SetChecked(true) + elseif filter[1] == 'class' then + class_index = Aux.info.item_class_index(filter[2]) + UIDropDownMenu_Initialize(m.class_dropdown, m.initialize_class_dropdown) -- TODO, wtf, why is this needed + UIDropDownMenu_SetSelectedValue(m.class_dropdown, class_index) + elseif filter[1] == 'subclass' then + subclass_index = Aux.info.item_subclass_index(class_index, filter[2]) + UIDropDownMenu_Initialize(m.subclass_dropdown, m.initialize_subclass_dropdown) -- TODO, wtf, why is this needed + UIDropDownMenu_SetSelectedValue(m.subclass_dropdown, subclass_index) + elseif filter[1] == 'slot' then + UIDropDownMenu_Initialize(m.slot_dropdown, m.initialize_slot_dropdown) -- TODO, wtf, why is this needed + UIDropDownMenu_SetSelectedValue(m.slot_dropdown, Aux.info.item_slot_index(class_index, subclass_index, filter[2])) + elseif filter[1] == 'quality' then + UIDropDownMenu_Initialize(m.quality_dropdown, m.initialize_quality_dropdown) -- TODO, wtf, why is this needed + UIDropDownMenu_SetSelectedValue(m.quality_dropdown, Aux.info.item_quality_index(filter[2])) + end + end +end + +function private.import_query_string() + local components, error = Aux.filter.parse_query_string(m.search_box:GetText()) + if components then + m.set_form(components.blizzard) + m.post_components = components.post + m.filter_display:SetText(Aux.filter.indented_post_query_string(m.post_components)) + else + Aux.log(error) + end +end + +function private.export_query_string() + local components, error = Aux.filter.parse_query_string(m.get_form()) + if components then + m.search_box:SetText(Aux.filter.query_string({blizzard=components.blizzard, post=m.post_components})) + m.clear_form() + m.filter_input:ClearFocus() + m.post_components = {} + m.filter_display:SetText('') + else + Aux.log(error) + end +end + +private.post_components = {} + +function private.add_post_filter() + local name = UIDropDownMenu_GetSelectedValue(m.filter_dropdown) + if name and getn(Aux.util.filter(m.post_components, function(component) return component[1] == 'operator' end)) < 20 then + local filter = name + if Aux.filter.filters[name] and Aux.filter.filters[name].input_type ~= '' then + filter = filter..'/'..m.filter_input:GetText() + end + + local components, error, suggestions = Aux.filter.parse_query_string(filter) + + if components then + tinsert(m.post_components, components.post[1]) + m.filter_display:SetText(Aux.filter.indented_post_query_string(m.post_components)) + m.filter_input:SetText('') + m.filter_input:ClearFocus() + else + Aux.log(error) + end + end +end + +function private.remove_post_filter() + tremove(m.post_components) + m.filter_display:SetText(Aux.filter.indented_post_query_string(m.post_components)) +end + diff --git a/tabs/search/frames.lua b/tabs/search/frames.lua index 051a523..01dd62d 100644 --- a/tabs/search/frames.lua +++ b/tabs/search/frames.lua @@ -327,7 +327,7 @@ Aux.search_tab.FRAMES(function(m, public, private) btn:SetHeight(24) btn:SetText('Favorite') btn:SetScript('OnClick', function() - local filters = Aux.scan_util.parse_filter_string(m.search_box:GetText()) + local filters = Aux.filter.queries(m.search_box:GetText()) if filters then tinsert(aux_favorite_searches, 1, { filter_string = m.search_box:GetText(), @@ -344,9 +344,7 @@ Aux.search_tab.FRAMES(function(m, public, private) btn1:SetHeight(24) btn1:SetText('Search') btn1:SetScript('OnClick', function() - m.search_box:SetText('') - m.add_filter() - m.clear_form() + m.export_query_string() m.execute() end) @@ -354,21 +352,15 @@ Aux.search_tab.FRAMES(function(m, public, private) btn2:SetPoint('LEFT', btn1, 'RIGHT', 5, 0) btn2:SetWidth(80) btn2:SetHeight(24) - btn2:SetText('Add') - btn2:SetScript('OnClick', function() - m.add_filter() - m.clear_form() - end) + btn2:SetText('Export') + btn2:SetScript('OnClick', m.export_query_string) local btn3 = Aux.gui.button(m.frame.filter, 16) btn3:SetPoint('LEFT', btn2, 'RIGHT', 5, 0) btn3:SetWidth(80) btn3:SetHeight(24) - btn3:SetText('Replace') - btn3:SetScript('OnClick', function() - m.add_filter(nil, true) - m.clear_form() - end) + btn3:SetText('Import') + btn3:SetScript('OnClick', m.import_query_string) end do local editbox = Aux.gui.editbox(m.frame.filter) @@ -517,186 +509,54 @@ Aux.search_tab.FRAMES(function(m, public, private) private.quality_dropdown = dropdown end Aux.gui.vertical_line(m.frame.filter, 332) - local function add_modifier(...) - local current_filter_string = m.search_box:GetText() - for i=1,arg.n do - if current_filter_string ~= '' and strsub(current_filter_string, -1) ~= '/' then - current_filter_string = current_filter_string..'/' - end - current_filter_string = current_filter_string..arg[i] - end - m.search_box:SetText(current_filter_string) - end do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPRIGHT', -362, -10) - btn:SetWidth(50) - btn:SetHeight(19) - btn:SetText('and') - btn:SetScript('OnClick', function() - add_modifier('and') + local dropdown = Aux.gui.dropdown(m.frame.filter) + dropdown:SetPoint('TOPRIGHT', -210, -10) + dropdown:SetWidth(170) + dropdown:SetHeight(10) + UIDropDownMenu_Initialize(dropdown, m.initialize_filter_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, m.initialize_filter_dropdown) end) - private.and_operator_button = btn + getglobal(dropdown:GetName()..'Text'):Hide() + private.filter_dropdown = dropdown end do local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('LEFT', m.and_operator_button, 'RIGHT', 10, 0) - btn:SetWidth(50) - btn:SetHeight(19) - btn:SetText('or') + btn:SetWidth(170) + btn:SetHeight(25) + btn:SetPoint('CENTER', m.filter_dropdown, 'CENTER', 0, 0) + btn:RegisterForClicks('LeftButtonUp', 'RightButtonUp') btn:SetScript('OnClick', function() - add_modifier('or') - end) - private.or_operator_button = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('LEFT', m.or_operator_button, 'RIGHT', 10, 0) - btn:SetWidth(50) - btn:SetHeight(19) - btn:SetText('not') - btn:SetScript('OnClick', function() - add_modifier('not') - end) - private.not_operator_button = btn - end - private.modifier_buttons = {} - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.and_operator_button, 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['min-unit-bid'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['min-unit-bid'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['min-unit-buy'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['min-unit-buy'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['max-unit-bid'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['max-unit-bid'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['max-unit-buy'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['max-unit-buy'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['bid-profit'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['bid-profit'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['buy-profit'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['buy-profit'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['bid-vend-profit'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['bid-vend-profit'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['buy-vend-profit'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['buy-vend-profit'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['bid-dis-profit'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['bid-dis-profit'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['buy-dis-profit'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.and_operator_button, 'BOTTOMLEFT', 205, -10) - m.modifier_buttons['bid-pct'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['bid-pct'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['buy-pct'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['buy-pct'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['item'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['item'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['tooltip'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['tooltip'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['min-lvl'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['min-lvl'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['max-lvl'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['max-lvl'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['rarity'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['rarity'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['left'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['left'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['utilizable'] = btn - end - do - local btn = Aux.gui.button(m.frame.filter, 16) - btn:SetPoint('TOPLEFT', m.modifier_buttons['utilizable'], 'BOTTOMLEFT', 0, -10) - m.modifier_buttons['discard'] = btn - end - for modifier_name, btn in m.modifier_buttons do - local modifier_name = modifier_name - local btn = btn - - local filter = Aux.scan_util.filters[modifier_name] - - btn:SetWidth(100) - btn:SetHeight(19) - btn:SetText(modifier_name) - btn:SetScript('OnClick', function() - local args = Aux.util.map(btn.inputs, function(input) return input:GetText() end) - if filter.test(unpack(args)) then - add_modifier(modifier_name, unpack(args)) - for _, input in btn.inputs do - input:SetText('') - input:ClearFocus() - end + if arg1 == 'LeftButton' then + m.add_post_filter() + elseif arg1 == 'RightButton' then + m.remove_post_filter() end end) - btn.inputs = {} - if filter.arity > 0 then - local editbox = Aux.gui.editbox(m.frame.filter) - editbox.complete = Aux.completion.complete(function() return ({filter.test()})[2] end) - editbox:SetPoint('LEFT', btn, 'RIGHT', 10, 0) - editbox:SetWidth(80) - -- editbox:SetNumeric(true) - -- editbox:SetMaxLetters(2) - editbox:SetScript('OnChar', function() - this:complete() - end) - local on_click = btn:GetScript('OnClick') - editbox:SetScript('OnEnterPressed', function() - on_click() - end) - tinsert(btn.inputs, editbox) - end + private.filter_button = btn + end + do + local input = Aux.gui.editbox(m.frame.filter) + input:SetPoint('LEFT', m.filter_dropdown, 'RIGHT', 10, 0) + input:SetWidth(150) + input:SetHeight(25) + -- editbox:SetNumeric(true) + -- editbox:SetMaxLetters(2) + input:SetScript('OnChar', function() +-- local filter = Aux.filter.filters[UIDropDownMenu_GetSelectedValue(m.filter_dropdown) or ''] -- TODO +-- Aux.completion.complete(this, function() return filter and ({filter.validator()})[3] or {} end) + end) + input:SetScript('OnEnterPressed', m.add_post_filter) + input:Hide() + private.filter_input = input + end + do + local label = Aux.gui.label(m.frame.filter, 13) + label:SetPoint('TOPLEFT', 350, -53) + label:SetWidth(300) + label:SetJustifyH('LEFT') + private.filter_display = label end private.status_bars = {} diff --git a/util/completion.lua b/util/completion.lua index 6681fbf..c40b3d3 100644 --- a/util/completion.lua +++ b/util/completion.lua @@ -8,7 +8,7 @@ function public:complete_filter() local filter_string = this:GetText() local completed_filter_string = ({strfind(filter_string, '([^;]*)/[^/;]*$')})[3] - local _, suggestions = Aux.scan_util.query(completed_filter_string or '') + local _, suggestions = Aux.filter.query(completed_filter_string or '') local start_index, _, current_modifier = strfind(filter_string, '([^/;]*)$') current_modifier = current_modifier or '' diff --git a/util/core.lua b/util/core.lua index 1158ebe..b0a25db 100644 --- a/util/core.lua +++ b/util/core.lua @@ -3,6 +3,22 @@ local m, public, private = Aux.module'util' function public.pass() end +function public.sum(...) + local x = 0 + for i=1,arg.n do + x = x + arg[i] + end + return x +end + +function public.product(...) + local x = 0 + for i=1,arg.n do + x = x * arg[i] + end + return x +end + function public.id(value) return value end @@ -98,7 +114,7 @@ function public.bag_type(bag) if link then local item_id = Aux.info.parse_hyperlink(GetInventoryItemLink('player', ContainerIDToInventoryID(bag))) local item_info = Aux.info.item(item_id) - return Aux.item_subclass_index(3, item_info.subclass) + return Aux.info.item_subclass_index(3, item_info.subclass) end end @@ -179,10 +195,10 @@ function public.take(n, xs) return ys end -function public.index_of(value, array) - for i, item in ipairs(array) do - if item == value then - return i +function public.key(value, t) + for k, v in t do + if v == value then + return k end end end diff --git a/util/filter.lua b/util/filter.lua new file mode 100644 index 0000000..6153341 --- /dev/null +++ b/util/filter.lua @@ -0,0 +1,715 @@ +local m, public, private = Aux.module'filter' + +function private.default_filter(str) + return { + input_type = '', + validator = function() + return function(auction_record) + return Aux.util.any(auction_record.tooltip, function(entry) + return strfind(strupper(entry.left_text or ''), strupper(str or ''), 1, true) or strfind(strupper(entry.right_text or ''), strupper(str or ''), 1, true) + end) + end + end, + } +end + +public.filters = { + + ['utilizable'] = { + input_type = '', + validator = function() + return function(auction_record) + return auction_record.usable and not Aux.info.tooltip_match(ITEM_SPELL_KNOWN, auction_record.tooltip) + end + end, + }, + + ['tooltip'] = { + input_type = 'string', + validator = function(str) + return m.default_filter(str).validator() + end, + }, + + ['item'] = { + input_type = 'string', + validator = function(name) + return function(auction_record) + return strlower(Aux.info.item(auction_record.item_id).name) == name + end + end + }, + + ['left'] = { + input_type = {'30m', '2h', '8h', '24h'}, + validator = function(duration) + local code = Aux.util.key(duration, {'30m', '2h', '8h', '24h'}) + return function(auction_record) + return auction_record.duration == code + end + end + }, + + ['rarity'] = { + input_type = {'poor', 'common', 'uncommon', 'rare', 'epic'}, + validator = function(rarity) + local code = Aux.util.key(rarity, {'poor', 'common', 'uncommon', 'rare', 'epic'}) - 1 + return function(auction_record) + return auction_record.quality == code + end + end + }, + + ['min-lvl'] = { + input_type = 'number', + validator = function(level) + return function(auction_record) + return auction_record.level >= level + end + end + }, + + ['max-lvl'] = { + input_type = 'number', + validator = function(level) + return function(auction_record) + return auction_record.level <= level + end + end + }, + + ['min-unit-bid'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.unit_bid_price >= amount + end + end + }, + + ['min-unit-buy'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.unit_buyout_price >= amount + end + end + }, + + ['max-unit-bid'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.unit_bid_price <= amount + end + end + }, + + ['max-unit-buy'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.buyout_price > 0 and auction_record.unit_buyout_price <= amount + end + end + }, + + ['bid-pct'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return auction_record.unit_buyout_price > 0 + and Aux.history.value(auction_record.item_key) + and auction_record.unit_buyout_price / Aux.history.value(auction_record.item_key) * 100 <= pct + end + end + }, + + ['buy-pct'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return auction_record.unit_buyout_price > 0 + and Aux.history.value(auction_record.item_key) + and auction_record.unit_buyout_price / Aux.history.value(auction_record.item_key) * 100 <= pct + end + end + }, + + ['bid-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return Aux.history.value(auction_record.item_key) and Aux.history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.bid_price >= amount + end + end + }, + + ['buy-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.buyout_price > 0 and Aux.history.value(auction_record.item_key) and Aux.history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.buyout_price >= amount + end + end + }, + + ['bid-dis-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local disenchant_value = Aux.disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) + return disenchant_value and disenchant_value - auction_record.bid_price >= amount + end + end + }, + + ['buy-dis-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local disenchant_value = Aux.disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) + return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount + end + end + }, + + ['bid-vend-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local vendor_price = Aux.cache.merchant_info(auction_record.item_id) + return vendor_price and vendor_price * auction_record.aux_quantity - auction_record.bid_price >= amount + end + end + }, + + ['buy-vend-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local vendor_price = Aux.cache.merchant_info(auction_record.item_id) + return auction_record.buyout_price > 0 and vendor_price and vendor_price * auction_record.aux_quantity - auction_record.buyout_price >= amount + end + end + }, + + ['discard'] = { + input_type = '', + validator = function() + return function() + return false + end + end + }, +} + +function private.operator(str) + if str == 'not' then + return 'not', 1 + end + + local _, _, and_op, and_arity = strfind(str, '^(and)(%d*)$') + local _, _, or_op, or_arity = strfind(str, '^(or)(%d*)$') + + local op = and_op or or_op + local arity = and_arity or or_arity + + if op then + if arity == '' then + return op, 2 + elseif tonumber(arity) > 2 then + return op, tonumber(arity) + elseif arity == '*' then + return op + end + end +end + +function private.blizzard_filter_parser() + local class_index, subclass_index + local filters = {} + return function(str, first) + local filter + if tonumber(str) then + if tonumber(str) < 1 or tonumber(str) > 60 then + return nil, 'Erroneous level range modifier' + end + if not filters.min_level then + filter = {'min_level', str} + elseif not filters.max_level and tonumber(str) >= tonumber(filters.min_level) then + filter = {'max_level', str} + else + return nil, 'Erroneous level range modifier' + end + elseif Aux.info.item_class_index(str) and not (filters.class and not filters.subclass and str == strlower(({ GetAuctionItemClasses() })[10])) then + class_index = Aux.info.item_class_index(str) + if not filters.class then + filter = {'class', str} + else + return nil, 'Erroneous item class modifier' + end + elseif class_index and Aux.info.item_subclass_index(class_index, str) then + subclass_index = Aux.info.item_subclass_index(class_index, str) + if not filters.subclass then + filter = {'subclass', str} + else + return nil, 'Erroneous item subclass modifier' + end + elseif subclass_index and Aux.info.item_slot_index(class_index, subclass_index, str) then + if not filters.slot then + filter = {'slot', str} + else + return nil, 'Erroneous item slot modifier' + end + elseif Aux.info.item_quality_index(str) then + if not filters.quality then + filter = {'quality', str} + else + return nil, 'Erroneous rarity modifier' + end + elseif str == 'usable' then + if not filters.usable then + filter = {'usable'} + else + return nil, 'Erroneous usable only modifier' + end + elseif str == 'exact' then + if filters.name and not filters.exact then + filter = {'exact'} + else + return nil, 'Erroneous exact only modifier' + end + elseif first then + if strlen(str) <= 63 then + filter = {'name', str } + else + return nil, 'The name must not be longer than 63 characters' + end + end + + if filter then + filters[filter[1]] = filter[2] or true + end + + if filters.exact and + ( + filters.min_level + or filters.max_level + or filters.class + or filters.subclass + or filters.slot + or filters.quality + or filters.usable + ) + then + return false, 'Erroneous exact only modifier' + end + + return filter + end +end + +function private.parse_parameter(input_type, str) + if input_type == 'money' then + local money = Aux.money.from_string(str) + if money and money > 0 then + return money + end + elseif input_type == 'number' then + local number = tonumber(str) + if number then + return number + end + elseif input_type == 'string' then + if str ~= '' then + return str + end + elseif type(input_type) == 'table' then + local choice = Aux.util.key(str, input_type) + if choice then + return choice + end + end +end + +function public.parse_query_string(str) + local components = { blizzard = {}, post = {} } + local blizzard_filter_parser = m.blizzard_filter_parser() + local parts = Aux.util.map(Aux.util.split(str, '/'), function(part) return strlower(Aux.util.trim(part)) end) + + local i = 1 + while parts[i] do + local op, arity = m.operator(parts[i]) + if op then + tinsert(components.post, {'operator', op, arity}) + elseif m.filters[parts[i]] then + local input_type = m.filters[parts[i]].input_type + if input_type ~= '' then + if not parts[i + 1] or not m.parse_parameter(input_type, parts[i + 1]) then + if type(input_type) == 'table' then + return nil, 'Invalid input of type choice for '..parts[i], input_type + else + return nil, 'Invalid input of type '..input_type..' for '..parts[i] + end + end + tinsert(components.post, {'filter', parts[i], parts[i + 1]}) + i = i + 1 + else + tinsert(components.post, {'filter', parts[i]}) + end + else + local component, error = blizzard_filter_parser(parts[i], i == 1) + if component then + tinsert(components.blizzard, component) + elseif error then + return nil, error + elseif parts[i] ~= '' then + tinsert(components.post, {'filter', 'tooltip', parts[i]}) + else + return nil, 'Empty modifier' + end + end + i = i + 1 + end + + return components +end + +function public.query(query_string) + local components, error, suggestions = m.parse_query_string(query_string) + + if not components then + return nil, suggestions or {}, error + end + + local polish_notation_counter = 0 + for _, component in components.post do + if component[1] == 'operator' then + polish_notation_counter = max(polish_notation_counter, 1) + polish_notation_counter = polish_notation_counter + (tonumber(component[2]) or 1) - 1 + elseif component[1] == 'filter' then + polish_notation_counter = polish_notation_counter - 1 + end + end + + if polish_notation_counter > 0 then + local suggestions = {} + for filter, _ in m.filters do + tinsert(suggestions, strlower(filter)) + end + tinsert(suggestions, 'and') + tinsert(suggestions, 'or') + tinsert(suggestions, 'not') + return nil, {}, 'Malformed expression' --, i > getn(parts) and suggestions + end + + return { + blizzard_query = m.blizzard_query(components), + validator = m.validator(components), + prettified = m.prettified_query_string(components), + }, m.suggestions(components) +end + +function public.queries(query_string) + local parts = Aux.util.split(query_string, ';') + + local queries = {} + for _, str in ipairs(parts) do + str = Aux.util.trim(str) + + local query, _, error = m.query(str) + + if not query then + Aux.log('Invalid filter:', error) + return + else + tinsert(queries, query) + end + end + + return queries +end + +function private.suggestions(components) + + local blizzard_filters = {} + for _, filter in components.blizzard do + blizzard_filters[filter[1]] = filter[2] or true + end + + local suggestions = {} + + if blizzard_filters.name + and not blizzard_filters.min_level + and not blizzard_filters.max_level + and not blizzard_filters.class + and not blizzard_filters.subclass + and not blizzard_filters.slot + and not blizzard_filters.quality + and not blizzard_filters.usable + then + tinsert(suggestions, 'exact') + end + + tinsert(suggestions, 'and') + tinsert(suggestions, 'or') + tinsert(suggestions, 'not') + tinsert(suggestions, 'tt') + + for filter, _ in pairs(m.filters) do + tinsert(suggestions, strlower(filter)) + end + + -- classes + if not blizzard_filters.class then + for _, class in ipairs({ GetAuctionItemClasses() }) do + tinsert(suggestions, class) + end + end + + -- subclasses + local class_index = blizzard_filters.class and Aux.info.item_class_index(blizzard_filters.class) + if class_index and not blizzard_filters.subclass then + for _, subclass in ipairs({ GetAuctionItemSubClasses(class_index) }) do + tinsert(suggestions, subclass) + end + end + + -- slots + local subclass_index = class_index and blizzard_filters.subclass and Aux.info.item_subclass_index(class_index, blizzard_filters.subclass) + if subclass_index and not blizzard_filters.slot then + for _, invtype in ipairs({ GetAuctionInvTypes(class_index, Aux.info.item_subclass_index(class_index, blizzard_filters.subclass)) }) do + tinsert(suggestions, getglobal(invtype)) + end + end + + -- usable + if not blizzard_filters.usable then + tinsert(suggestions, 'usable') + end + + -- rarities + if not blizzard_filters.quality then + for i=0,4 do + tinsert(suggestions, getglobal('ITEM_QUALITY'..i..'_DESC')) + end + end + + -- item names + if getn(components.blizzard) + getn(components.post) == 1 and blizzard_filters.name == '' then + for _, name in aux_auctionable_items do + tinsert(suggestions, name..'/exact') + end + end + + return suggestions +end + +function public.query_string(components) + local prettified = m.query_builder() + + local blizzard_filters = {} + for _, filter in components.blizzard do + prettified.append((filter[2] or filter[1])) + end + + for _, component in components.post do + if component[1] == 'operator' then + local suffix = '' + if not component[3] then + suffix = '*' + elseif component[3] > 2 then + suffix = component[3] + end + prettified.append(component[2]..suffix) + elseif component[1] == 'filter' then + prettified.append(component[2]) + if component[3] then + prettified.append(component[3]) + end + end + end + + return prettified.get() +end + +function public.indented_post_query_string(components) + local no_line_break + local stack = {} + local str = '' + + for _, component in components do + + if str ~= '' then + str = str..(no_line_break and ' ' or '|n') + end + for _=1,getn(stack) do + str = str..' ' + end + + if component[1] == 'operator' and component[2] then + no_line_break = component[2] == 'not' + local suffix = '' + if not component[3] then + suffix = '*' + elseif component[3] > 2 then + suffix = component[3] + end + str = str..'|cffffff00'..component[2]..suffix..'|r' + tinsert(stack, component[3]) + elseif component[1] == 'filter' then + str = str..'|cffffff00'..component[2]..'|r' + if component[3] then + str = str..': '..'|cffff9218'..component[3]..'|r' + end + local top = tremove(stack) + if top and top > 1 then + tinsert(stack, top - 1) + end + end + end + + return str +end + +function private.prettified_query_string(components) + local prettified = m.query_builder() + + local blizzard_filters = {} + for _, filter in components.blizzard do + blizzard_filters[filter[1]] = filter[2] or true + if filter[1] == 'exact' then + prettified.prepend(Aux.info.display_name(Aux.cache.item_id(blizzard_filters.name)) or Aux.gui.inline_color({216, 225, 211, 1})..'['..blizzard_filters.name..']|r') + elseif filter[1] ~= 'name' then + prettified.append(Aux.gui.inline_color({216, 225, 211, 1})..(filter[2] or filter[1])..'|r') + end + end + + if not blizzard_filters.exact then + if blizzard_filters.name == '' then + prettified.prepend('|cffff0000'..'No Filter'..'|r') + elseif blizzard_filters.name then + prettified.prepend(Aux.gui.inline_color({216, 225, 211, 1})..blizzard_filters.name..'|r') + end + end + + for _, component in components.post do + if component[1] == 'operator' then + local suffix = '' + if not component[3] then + suffix = '*' + elseif component[3] > 2 then + suffix = component[3] + end + prettified.append('|cffffff00'..component[2]..suffix..'|r') + elseif component[1] == 'filter' then + if component[2] ~= 'tooltip' then + prettified.append('|cffffff00'..component[2]..'|r') + end + if component[3] then + prettified.append('|cffff9218'..component[3]..'|r') + end + end + end + + return prettified.get() +end + +function private.blizzard_query(components) + local filters = {} + for _, filter in components.blizzard do + filters[filter[1]] = filter[2] or true + end + + local query = { name=filters.name } + + local item_info, class_index, subclass_index, slot_index + if filters.exact then + local item_id = Aux.cache.item_id(filters.name) + item_info = Aux.info.item(item_id) + class_index = item_info and Aux.info.item_class_index(item_info.class) + subclass_index = class_index and item_info.subclass and Aux.info.item_subclass_index(class_index, item_info.subclass) + slot_index = subclass_index and item_info.slot and Aux.info.item_slot_index(class_index, subclass_index, item_info.slot) + end + + if item_info then + query.min_level = item_info.level + query.max_level = item_info.level + query.class = class_index + query.subclass = subclass_index + query.slot = item_info.class + query.usable = item_info.usable + query.quality = item_info.quality + else + query.min_level = tonumber(filters.min_level) + query.max_level = tonumber(filters.max_level) + query.class = filters.class and filters.class and Aux.info.item_class_index(filters.class) + query.subclass = query.class and filters.subclass and Aux.info.item_subclass_index(query.class, filters.subclass) + query.slot = query.subclass and filters.slot and Aux.info.item_slot_index(query.class, query.subclass, filters.slot) + query.usable = filters.usable and 1 + query.quality = filters.quality and Aux.info.item_quality_index(filters.quality) + end + + return query +end + +function private.validator(components) + + local validators = {} + for i, component in components.post do + if component[1] == 'filter' then + validators[i] = m.filters[component[2]].validator(m.parse_parameter(m.filters[component[2]].input_type, component[3])) + end + end + + return function(record) + for _, filter in components.blizzard do + if filter[1] == 'exact' and strlower(Aux.info.item(record.item_id).name) ~= components.blizzard[1][2] then + return false + end + end + local stack = {} + for i=getn(components.post),1,-1 do + local type, name, param = unpack(components.post[i]) + if type == 'operator' then + local args = {} + while (not param or param > 0) and getn(stack) > 0 do + tinsert(args, tremove(stack)) + param = param and param - 1 + end + if name == 'not' then + tinsert(stack, not args[1]) + elseif name == 'and' then + tinsert(stack, Aux.util.all(args, Aux.util.id)) + elseif name == 'or' then + tinsert(stack, Aux.util.any(args, Aux.util.id)) + end + elseif type == 'filter' then + tinsert(stack, validators[i](record) and true or false) + end + end + return Aux.util.all(stack, Aux.util.id) + end +end + +function public.query_builder(str) + local filter = str or '' + return { + appended = function(part) + return m.query_builder(filter == '' and part or filter..'/'..part) + end, + prepended = function(part) + return m.query_builder(filter == '' and part or part..'/'..filter) + end, + append = function(part) + filter = filter == '' and part or filter..'/'..part + end, + prepend = function(part) + filter = filter == '' and part or part..'/'..filter + end, + get = function() + return filter + end + } +end \ No newline at end of file diff --git a/util/info.lua b/util/info.lua index f14f6c0..04383de 100644 --- a/util/info.lua +++ b/util/info.lua @@ -384,4 +384,37 @@ function public.item(item_id, suffix_id) max_stack = max_stack, texture = texture, } or Aux.cache.item_info(item_id) +end + +function public.item_class_index(item_class) + for i, class in ipairs({ GetAuctionItemClasses() }) do + if strupper(class) == strupper(item_class) then + return i + end + end +end + +function public.item_subclass_index(class_index, item_subclass) + for i, subclass in ipairs({ GetAuctionItemSubClasses(class_index) }) do + if strupper(subclass) == strupper(item_subclass) then + return i + end + end +end + +function public.item_slot_index(class_index, subclass_index, slot_name) + for i, slot in ipairs({ GetAuctionInvTypes(class_index, subclass_index) }) do + if strupper(getglobal(slot)) == strupper(slot_name) then + return i + end + end +end + +function public.item_quality_index(item_quality) + for i=0,4 do + local quality = getglobal('ITEM_QUALITY'..i..'_DESC') + if strupper(item_quality) == strupper(quality) then + return i + end + end end \ No newline at end of file diff --git a/util/scan_util.lua b/util/scan_util.lua index bdef823..b47b378 100644 --- a/util/scan_util.lua +++ b/util/scan_util.lua @@ -1,315 +1,5 @@ local m, public, private = Aux.module'scan_util' -function public.default_filter(str) - return { - arity = 0, - test = function() - return function(auction_record) - return Aux.util.any(auction_record.tooltip, function(entry) - return strfind(strupper(entry.left_text or ''), strupper(str or ''), 1, true) or strfind(strupper(entry.right_text or ''), strupper(str or ''), 1, true) - end) - end - end, - } -end - -public.filters = { - - ['utilizable'] = { - arity = 0, - test = function() - return function(auction_record) - return auction_record.usable and not Aux.info.tooltip_match(ITEM_SPELL_KNOWN, auction_record.tooltip) - end - end, - }, - - ['tooltip'] = { - arity = 1, - test = function(str) - if str then - return m.default_filter(str).test() - else - return false, {}, 'Erroneous tooltip modifier' - end - end, - }, - - ['item'] = { - arity = 1, - test = function(name) - if not name then - return false, aux_auctionable_items, 'Erroneous item modifier' - end - - return function(auction_record) - return strlower(Aux.info.item(auction_record.item_id).name) == name - end - end - }, - - ['left'] = { - arity = 1, - test = function(duration) - if not duration then - return false, {'30m', '2h', '8h', '24h'}, 'Erroneous time left modifier' - end - - local code = ({ - ['30m'] = 1, - ['2h'] = 2, - ['8h'] = 3, - ['24h'] = 4, - })[duration or ''] - - if not code then - return false, {}, 'Erroneous time left modifier' - end - - return function(auction_record) - return auction_record.duration == code - end - end - }, - - ['rarity'] = { - arity = 1, - test = function(rarity) - if not rarity then - return false, {'poor', 'common', 'uncommon', 'rare', 'epic'}, 'Erroneous rarity modifier' - end - - local code = ({ - ['poor'] = 0, - ['common'] = 1, - ['uncommon'] = 2, - ['rare'] = 3, - ['epic'] = 4, - })[rarity or ''] - if not code then - return false, {}, 'Erroneous rarity modifier' - end - - return function(auction_record) - return auction_record.quality == code - end - end - }, - - ['min-lvl'] = { - arity = 1, - test = function(level) - level = tonumber(level or '') - if level then - return function(auction_record) - return auction_record.level >= level - end - else - return false, {}, 'Erroneous min level modifier' - end - end - }, - - ['max-lvl'] = { - arity = 1, - test = function(level) - level = tonumber(level or '') - if level then - return function(auction_record) - return auction_record.level <= level - end - else - return false, {}, 'Erroneous max level modifier' - end - end - }, - - ['min-unit-bid'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - return auction_record.unit_bid_price >= amount - end - else - return false, {}, 'Erroneous min bid modifier' - end - end - }, - - ['min-unit-buy'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - return auction_record.unit_buyout_price >= amount - end - else - return false, {}, 'Erroneous min buyout modifier' - end - end - }, - - ['max-unit-bid'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - return auction_record.unit_bid_price <= amount - end - else - return false, {}, 'Erroneous max bid modifier' - end - end - }, - - ['max-unit-buy'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - return auction_record.buyout_price > 0 and auction_record.unit_buyout_price <= amount - end - else - return false, {}, 'Erroneous max buyout modifier' - end - end - }, - - ['bid-pct'] = { - arity = 1, - test = function(pct) - pct = tonumber(pct) - if pct then - return function(auction_record) - return auction_record.unit_buyout_price > 0 - and Aux.history.value(auction_record.item_key) - and auction_record.unit_buyout_price / Aux.history.value(auction_record.item_key) * 100 <= pct - end - else - return false, {}, 'Erroneous bid percentage modifier' - end - end - }, - - ['buy-pct'] = { - arity = 1, - test = function(pct) - pct = tonumber(pct) - if pct then - return function(auction_record) - return auction_record.unit_buyout_price > 0 - and Aux.history.value(auction_record.item_key) - and auction_record.unit_buyout_price / Aux.history.value(auction_record.item_key) * 100 <= pct - end - else - return false, {}, 'Erroneous buyout percentage modifier' - end - end - }, - - ['bid-profit'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - return Aux.history.value(auction_record.item_key) and Aux.history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.bid_price >= amount - end - else - return false, {}, 'Erroneous bid profit modifier' - end - end - }, - - ['buy-profit'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - return auction_record.buyout_price > 0 and Aux.history.value(auction_record.item_key) and Aux.history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.buyout_price >= amount - end - else - return false, {}, 'Erroneous buyout profit modifier' - end - end - }, - - ['bid-dis-profit'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - local disenchant_value = Aux.disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) - return disenchant_value and disenchant_value - auction_record.bid_price >= amount - end - else - return false, {}, 'Erroneous bid disenchant profit modifier' - end - end - }, - - ['buy-dis-profit'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - local disenchant_value = Aux.disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) - return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount - end - else - return false, {}, 'Erroneous buyout disenchant profit modifier' - end - end - }, - - ['bid-vend-profit'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - local vendor_price = Aux.cache.merchant_info(auction_record.item_id) - return vendor_price and vendor_price * auction_record.aux_quantity - auction_record.bid_price >= amount - end - else - return false, {}, 'Erroneous bid vendor profit modifier' - end - end - }, - - ['buy-vend-profit'] = { - arity = 1, - test = function(amount) - amount = Aux.money.from_string(amount or '') or 0 - if amount > 0 then - return function(auction_record) - local vendor_price = Aux.cache.merchant_info(auction_record.item_id) - return auction_record.buyout_price > 0 and vendor_price and vendor_price * auction_record.aux_quantity - auction_record.buyout_price >= amount - end - else - return false, {}, 'Erroneous buyout vendor profit modifier' - end - end - }, - - ['discard'] = { - arity = 0, - test = function() - return function() - return false - end - end - }, -} - function public.find(auction_record, status_bar, on_abort, on_failure, on_success) local function test(index) @@ -380,368 +70,17 @@ function public.find(auction_record, status_bar, on_abort, on_failure, on_succes } end -function public.filter_builder() - local filter = '' - return { - append = function(self, modifier) - modifier = modifier - filter = filter == '' and modifier or filter..'/'..modifier - end, - prepend = function(self, modifier) - modifier = modifier - filter = filter == '' and modifier or modifier..'/'..filter - end, - get = function(self) - return filter - end - } -end - function public.item_query(item_id, first_page, last_page) local item_info = Aux.info.item(item_id) if item_info then - local filter = m.query(item_info.name..'/exact') - filter.blizzard_query.first_page = first_page - filter.blizzard_query.last_page = last_page + local query = Aux.filter.query(item_info.name..'/exact') + query.blizzard_query.first_page = first_page + query.blizzard_query.last_page = last_page return { - validator = filter.validator, - blizzard_query = filter.blizzard_query, + validator = query.validator, + blizzard_query = query.blizzard_query, } end -end - -function public.parse_filter_string(filter_string) - local parts = Aux.util.split(filter_string, ';') - - local filters = {} - for _, str in ipairs(parts) do - str = Aux.util.trim(str) - - local filter, _, error = m.query(str) - - if not filter then - Aux.log('Invalid filter: '..error) - return - elseif filter.name and strlen(filter.name) > 63 then - Aux.log('Invalid filter: The name may not longer than 63 characters') - return - else - tinsert(filters, filter) - end - end - - return filters -end - -function public.query(filter_term) - local parts = Aux.util.map(Aux.util.split(filter_term, '/'), function(part) return strlower(Aux.util.trim(part)) end) - - local blizzard_filters = {} - local post_filters = {} - local prettified = m.filter_builder() - local polish_notation_counter = 0 - local i = 1 - - local function post_filter(str) - local filter = m.filters[str] - if filter then - prettified:append('|cffffff00'..str..'|r') - else - filter = filter or m.default_filter(str) - prettified:append('|cffff9218'..str..'|r') - end - - local args = {} - for j=1, filter.arity do - local arg = parts[i - 1 + j] - if arg then - tinsert(args, arg) - prettified:append('|cffff9218'..arg..'|r') - end - end - i = i + filter.arity - - local test, suggestions, error = filter.test(unpack(args)) - if test then - tinsert(post_filters, test) - else - return error, i > getn(parts) and suggestions or {} - end - end - - while i <= getn(parts) do - local str = parts[i] - i = i + 1 - - if str == 'and*' or str == 'or*' then - if polish_notation_counter > 1 then - break - end - polish_notation_counter = 0 - tinsert(post_filters, str) - prettified:append('|cffffff00'..str..'|r') - elseif polish_notation_counter > 0 - or (function(_, _, m) return m == '' or m and tonumber(m) > 2 end)(strfind(str, '^and(%d*)$')) - or (function(_, _, m) return m == '' or m and tonumber(m) > 2 end)(strfind(str, '^or(%d*)$')) - or str == 'not' - then - polish_notation_counter = polish_notation_counter == 0 and polish_notation_counter + 1 or polish_notation_counter - if strfind(str, '^and%d*$') or strfind(str, '^or%d*$') then - local and_match, or_match = {strfind(str, '^(and)(%d*)$')}, {strfind(str, '^(or)(%d*)$') } - local op = and_match[3] or or_match[3] - local count = (tonumber(and_match[4]) or tonumber(or_match[4]) or 2) - 1 - polish_notation_counter = polish_notation_counter + count - for _=1,count do - tinsert(post_filters, op) - end - prettified:append('|cffffff00'..str..'|r') - elseif str == 'not' then - tinsert(post_filters, str) - prettified:append('|cffffff00'..str..'|r') - elseif str ~= '' then - polish_notation_counter = polish_notation_counter - 1 - local error, suggestions = post_filter(str) - if error then - return false, suggestions, error - end - end - elseif tonumber(str) then - if tonumber(str) < 1 or tonumber(str) > 60 then - return false, {}, 'Erroneous level range modifier' - end - if not blizzard_filters.min_level then - blizzard_filters.min_level = tonumber(str) - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - elseif not blizzard_filters.max_level and tonumber(str) >= blizzard_filters.min_level then - blizzard_filters.max_level = tonumber(str) - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - else - return false, {}, 'Erroneous level range modifier' - end - elseif Aux.item_class_index(str) and not (blizzard_filters.class and not blizzard_filters.subclass and str == strlower(({ GetAuctionItemClasses() })[10])) then - if not blizzard_filters.class then - blizzard_filters.class = Aux.item_class_index(str) - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - else - return false, {}, 'Erroneous item class modifier' - end - elseif blizzard_filters.class and Aux.item_subclass_index(blizzard_filters.class, str) then - if not blizzard_filters.subclass then - blizzard_filters.subclass = Aux.item_subclass_index(blizzard_filters.class, str) - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - else - return false, {}, 'Erroneous item subclass modifier' - end - elseif blizzard_filters.subclass and Aux.item_slot_index(blizzard_filters.class, blizzard_filters.subclass, str) then - if not blizzard_filters.slot then - blizzard_filters.slot = Aux.item_slot_index(blizzard_filters.class, blizzard_filters.subclass, str) - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - else - return false, {}, 'Erroneous item slot modifier' - end - elseif Aux.item_quality_index(str) then - if not blizzard_filters.quality then - blizzard_filters.quality = Aux.item_quality_index(str) - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - else - return false, {}, 'Erroneous rarity modifier' - end - elseif str == 'usable' then - if not blizzard_filters.usable then - blizzard_filters.usable = true - prettified:append(Aux.gui.inline_color({216, 225, 211, 1})..str..'|r') - else - return false, {}, 'Erroneous usable only modifier' - end - elseif str == 'exact' then - if not blizzard_filters.exact then - blizzard_filters.exact = true - else - return false, {}, 'Erroneous exact only modifier' - end - elseif i == 2 and not m.filters[str] then - blizzard_filters.name = str - elseif str ~= '' then - local error, suggestions = post_filter(str) - if error then - return false, suggestions, error - end - else - return false, {}, 'Empty modifier' - end - end - - if polish_notation_counter > 0 then - local suggestions = {} - for filter, _ in m.filters do - tinsert(suggestions, strlower(filter)) - end - tinsert(suggestions, 'and') - tinsert(suggestions, 'or') - tinsert(suggestions, 'not') - return false, i > getn(parts) and suggestions, 'Malformed expression' - end - - if blizzard_filters.exact then - if blizzard_filters.min_level - or blizzard_filters.max_level - or blizzard_filters.class - or blizzard_filters.subclass - or blizzard_filters.slot - or blizzard_filters.quality - or blizzard_filters.usable - or not blizzard_filters.name - then - return false, {}, 'Erroneous exact only modifier' - else - prettified:prepend(Aux.info.display_name(Aux.cache.item_id(blizzard_filters.name)) or Aux.gui.inline_color({216, 225, 211, 1})..'['..blizzard_filters.name..']|r') - end - elseif blizzard_filters.name then - if blizzard_filters.name == '' then - prettified:prepend('|cffff0000'..'No Filter'..'|r') - else - prettified:prepend(Aux.gui.inline_color({216, 225, 211, 1})..blizzard_filters.name..'|r') - end - end - - return { - blizzard_query = m.blizzard_query(blizzard_filters), - validator = m.validator(blizzard_filters, post_filters), - prettified = prettified:get(), - }, m.suggestions(blizzard_filters, getn(parts)) -end - -function public.suggestions(blizzard_filter, num_parts) - - local suggestions = {} - - if blizzard_filter.name - and not blizzard_filter.min_level - and not blizzard_filter.max_level - and not blizzard_filter.class - and not blizzard_filter.subclass - and not blizzard_filter.slot - and not blizzard_filter.quality - and not blizzard_filter.usable - then - tinsert(suggestions, 'exact') - end - - tinsert(suggestions, 'and') - tinsert(suggestions, 'or') - tinsert(suggestions, 'not') - tinsert(suggestions, 'tt') - - for filter, _ in pairs(m.filters) do - tinsert(suggestions, strlower(filter)) - end - - -- classes - if not blizzard_filter.class then - for _, class in ipairs({ GetAuctionItemClasses() }) do - tinsert(suggestions, class) - end - end - - -- subclasses - if blizzard_filter.class and not blizzard_filter.subclass then - for _, subclass in ipairs({ GetAuctionItemSubClasses(blizzard_filter.class) }) do - tinsert(suggestions, subclass) - end - end - - -- slots - if blizzard_filter.class and blizzard_filter.subclass and not blizzard_filter.slot then - for _, invtype in ipairs({ GetAuctionInvTypes(blizzard_filter.class, blizzard_filter.subclass) }) do - tinsert(suggestions, getglobal(invtype)) - end - end - - -- usable - if not blizzard_filter.usable then - tinsert(suggestions, 'usable') - end - - -- rarities - if not blizzard_filter.quality then - for i=0,4 do - tinsert(suggestions, getglobal('ITEM_QUALITY'..i..'_DESC')) - end - end - - -- item names - if num_parts == 1 and blizzard_filter.name == '' then - for _, name in aux_auctionable_items do - tinsert(suggestions, name..'/exact') - end - end - - return suggestions -end - -function public.blizzard_query(filter) - local query = { name=filter.name } - - local item_info, class_index, subclass_index, slot_index - if filter.exact then - local item_id = Aux.cache.item_id(filter.name) - item_info = Aux.info.item(item_id) - class_index = item_info and Aux.item_class_index(item_info.class) - subclass_index = class_index and item_info.subclass and Aux.item_subclass_index(class_index, item_info.subclass) - slot_index = subclass_index and item_info.slot and Aux.item_slot_index(class_index, subclass_index, item_info.slot) - end - - if item_info then - query.min_level = item_info.level - query.max_level = item_info.level - query.class = class_index - query.subclass = subclass_index - query.slot = item_info.class - query.usable = item_info.usable - query.quality = item_info.quality - else - query.min_level = filter.min_level - query.max_level = filter.max_level - query.class = filter.class - query.subclass = filter.subclass - query.slot = filter.slot - query.usable = filter.usable and 1 - query.quality = filter.quality - end - - return query -end - -function public.validator(blizzard_filter, post_filter) - - return function(record) - if blizzard_filter.exact and strlower(Aux.info.item(record.item_id).name) ~= blizzard_filter.name then - return false - end - if getn(post_filter) > 0 then - local stack = {} - for i=getn(post_filter),1,-1 do - local op = post_filter[i] - if op == 'and' then - local a, b = tremove(stack), tremove(stack) - tinsert(stack, a and b) - elseif op == 'or' then - local a, b = tremove(stack), tremove(stack) - tinsert(stack, a or b) - elseif op == 'not' then - tinsert(stack, not tremove(stack)) - elseif op == 'and*' then - stack = {Aux.util.all(stack, Aux.util.id)} - elseif op == 'or*' then - stack = {Aux.util.any(stack, Aux.util.id)} - else - tinsert(stack, op(record) and true or false) - end - end - return Aux.util.all(stack, Aux.util.id) - end - return true - end end \ No newline at end of file