aux auto bid and price heuristics
this commit does two things 1. it implements auto bid just like auto buy 2. it has some minimum pricing heuristics when posting items. this behavoiur actiaves when the post bid price is set to 0, the buyout price behaves like a minimum
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
module 'aux.util.info'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local persistence = require 'aux.util.persistence'
|
||||
|
||||
local MIN_ITEM_ID = 1
|
||||
local MAX_ITEM_ID = 30000
|
||||
|
||||
local items_schema = {'tuple', '#', {name='string'}, {quality='number'}, {level='number'}, {class='string'}, {subclass='string'}, {slot='string'}, {max_stack='number'}, {texture='string'}}
|
||||
local merchant_buy_schema = {'tuple', '#', {unit_price='number'}, {limited='boolean'}}
|
||||
|
||||
function aux.handle.LOAD()
|
||||
scan_wdb()
|
||||
|
||||
aux.event_listener('MERCHANT_SHOW', on_merchant_show)
|
||||
aux.event_listener('MERCHANT_CLOSED', on_merchant_closed)
|
||||
aux.event_listener('MERCHANT_UPDATE', on_merchant_update)
|
||||
aux.event_listener('BAG_UPDATE', on_bag_update)
|
||||
|
||||
CreateFrame('Frame', nil, MerchantFrame):SetScript('OnUpdate', merchant_on_update)
|
||||
|
||||
aux.event_listener('NEW_AUCTION_UPDATE', function()
|
||||
local data = auction_sell_item()
|
||||
if data then
|
||||
local item_id = item_id(data.name)
|
||||
if item_id then
|
||||
aux.account_data.merchant_sell[item_id] = data.vendor_price / (max_item_charges(item_id) or data.count)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
do
|
||||
local characters = {}
|
||||
function M.is_player(name)
|
||||
return not not characters[name]
|
||||
end
|
||||
function aux.handle.LOAD()
|
||||
characters = aux.realm_data.characters
|
||||
for k, v in characters do
|
||||
if GetTime() > v + 60 * 60 * 24 * 30 then
|
||||
characters[k] = nil
|
||||
end
|
||||
end
|
||||
characters[UnitName'player'] = GetTime()
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local sell_scan_queued, incomplete_buy_data
|
||||
function on_merchant_show()
|
||||
merchant_sell_scan()
|
||||
incomplete_buy_data = not merchant_buy_scan()
|
||||
end
|
||||
function on_merchant_closed()
|
||||
sell_scan_queued = nil
|
||||
incomplete_buy_data = false
|
||||
end
|
||||
function on_merchant_update()
|
||||
if incomplete_buy_data then
|
||||
incomplete_buy_data = not merchant_buy_scan()
|
||||
end
|
||||
end
|
||||
function on_bag_update()
|
||||
if MerchantFrame:IsVisible() then
|
||||
sell_scan_queued = true
|
||||
end
|
||||
end
|
||||
function merchant_on_update()
|
||||
if sell_scan_queued then
|
||||
sell_scan_queued = nil
|
||||
merchant_sell_scan()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function merchant_loaded()
|
||||
for i = 1, GetMerchantNumItems() do
|
||||
if not GetMerchantItemLink(i) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function M.merchant_info(item_id)
|
||||
local buy_info
|
||||
if aux.account_data.merchant_buy[item_id] then
|
||||
buy_info = persistence.read(merchant_buy_schema, aux.account_data.merchant_buy[item_id])
|
||||
end
|
||||
return aux.account_data.merchant_sell[item_id], buy_info and buy_info.unit_price, buy_info and buy_info.limited
|
||||
end
|
||||
|
||||
function M.item_info(item_id)
|
||||
local data_string = aux.account_data.items[item_id]
|
||||
if data_string then
|
||||
local cached_data = persistence.read(items_schema, data_string)
|
||||
return T.map(
|
||||
'name', cached_data.name,
|
||||
'itemstring', 'item:' .. item_id .. ':0:0:0',
|
||||
'quality', cached_data.quality,
|
||||
'level', cached_data.level,
|
||||
'class', cached_data.class,
|
||||
'subclass', cached_data.subclass,
|
||||
'slot', cached_data.slot,
|
||||
'max_stack', cached_data.max_stack,
|
||||
'texture', cached_data.texture
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function M.item_id(item_name)
|
||||
return aux.account_data.item_ids[strlower(item_name)]
|
||||
end
|
||||
|
||||
function merchant_buy_scan()
|
||||
local incomplete_data
|
||||
for i = 1, GetMerchantNumItems() do
|
||||
local _, _, price, count, stock = GetMerchantItemInfo(i)
|
||||
local link = GetMerchantItemLink(i)
|
||||
if link then
|
||||
local item_id = parse_link(link)
|
||||
local new_unit_price, new_limited = price / count, stock >= 0
|
||||
if aux.account_data.merchant_buy[item_id] then
|
||||
local buy_info = persistence.read(merchant_buy_schema, aux.account_data.merchant_buy[item_id])
|
||||
|
||||
local unit_price
|
||||
if buy_info.limited and not new_limited then
|
||||
unit_price = new_unit_price
|
||||
elseif new_limited and not buy_info.limited then
|
||||
unit_price = buy_info.unit_price
|
||||
else
|
||||
unit_price = min(buy_info.unit_price, new_unit_price)
|
||||
end
|
||||
|
||||
aux.account_data.merchant_buy[item_id] = persistence.write(merchant_buy_schema, T.temp-T.map(
|
||||
'unit_price', unit_price,
|
||||
'limited', buy_info.limited and new_limited
|
||||
))
|
||||
else
|
||||
aux.account_data.merchant_buy[item_id] = persistence.write(merchant_buy_schema, T.temp-T.map(
|
||||
'unit_price', new_unit_price,
|
||||
'limited', new_limited
|
||||
))
|
||||
end
|
||||
else
|
||||
incomplete_data = true
|
||||
end
|
||||
end
|
||||
|
||||
return not incomplete_data
|
||||
end
|
||||
|
||||
function merchant_sell_scan()
|
||||
for slot in inventory() do
|
||||
T.temp(slot)
|
||||
local item_info = T.temp-container_item(unpack(slot))
|
||||
if item_info then
|
||||
aux.account_data.merchant_sell[item_info.item_id] = item_info.tooltip_money / item_info.aux_quantity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function scan_wdb(item_id)
|
||||
item_id = item_id or MIN_ITEM_ID
|
||||
|
||||
local processed = 0
|
||||
while processed <= 100 and item_id <= MAX_ITEM_ID do
|
||||
local itemstring = 'item:' .. item_id
|
||||
local name, _, quality, level, class, subclass, max_stack, slot, texture = GetItemInfo(itemstring)
|
||||
if name and not aux.account_data.item_ids[strlower(name)] then
|
||||
aux.account_data.item_ids[strlower(name)] = item_id
|
||||
aux.account_data.items[item_id] = persistence.write(items_schema, T.temp-T.map(
|
||||
'name', name,
|
||||
'quality', quality,
|
||||
'level', level,
|
||||
'class', class,
|
||||
'subclass', subclass,
|
||||
'slot', slot,
|
||||
'max_stack', max_stack,
|
||||
'texture', texture
|
||||
))
|
||||
local tooltip = tooltip('link', itemstring)
|
||||
if auctionable(tooltip, quality) then
|
||||
tinsert(aux.account_data.auctionable_items, strlower(name))
|
||||
end
|
||||
processed = processed + 1
|
||||
end
|
||||
item_id = item_id + 1
|
||||
end
|
||||
|
||||
if item_id <= MAX_ITEM_ID then
|
||||
aux.thread(aux.when, aux.later(.5), scan_wdb, item_id)
|
||||
else
|
||||
sort(aux.account_data.auctionable_items, function(a, b) return strlen(a) < strlen(b) or (strlen(a) == strlen(b) and a < b) end)
|
||||
end
|
||||
end
|
||||
|
||||
function M.populate_wdb(item_id)
|
||||
item_id = item_id or MIN_ITEM_ID
|
||||
if item_id > MAX_ITEM_ID then
|
||||
aux.print('Cache populated.')
|
||||
return
|
||||
end
|
||||
if not GetItemInfo('item:' .. item_id) then
|
||||
aux.print('Fetching item ' .. item_id .. '.')
|
||||
AuxTooltip:SetHyperlink('item:' .. item_id)
|
||||
end
|
||||
aux.thread(populate_wdb, item_id + 1)
|
||||
end
|
||||
@@ -0,0 +1,99 @@
|
||||
module 'aux.core.crafting'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
local money = require 'aux.util.money'
|
||||
local history = require 'aux.core.history'
|
||||
local search_tab = require 'aux.tabs.search'
|
||||
|
||||
function aux.handle.LOAD()
|
||||
if not aux.account_data.crafting_cost then
|
||||
return
|
||||
end
|
||||
aux.event_listener('ADDON_LOADED', function()
|
||||
if arg1 == 'Blizzard_CraftUI' then
|
||||
craft_ui_loaded()
|
||||
elseif arg1 == 'Blizzard_TradeSkillUI' then
|
||||
trade_skill_ui_loaded()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
do
|
||||
local function cost_label(cost)
|
||||
local label = LIGHTYELLOW_FONT_COLOR_CODE .. '(Total Cost: ' .. FONT_COLOR_CODE_CLOSE
|
||||
label = label .. (cost and money.to_string2(cost, nil, LIGHTYELLOW_FONT_COLOR_CODE) or GRAY_FONT_COLOR_CODE .. '?' .. FONT_COLOR_CODE_CLOSE)
|
||||
label = label .. LIGHTYELLOW_FONT_COLOR_CODE .. ')' .. FONT_COLOR_CODE_CLOSE
|
||||
return label
|
||||
end
|
||||
local function hook_quest_item(f)
|
||||
f:SetScript('OnMouseUp', function()
|
||||
if arg1 == 'RightButton' then
|
||||
if aux.get_tab() then
|
||||
aux.set_tab(1)
|
||||
search_tab.set_filter(_G[this:GetName() .. 'Name']:GetText() .. '/exact')
|
||||
search_tab.execute(nil, false)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
function craft_ui_loaded()
|
||||
aux.hook('CraftFrame_SetSelection', T.vararg-function(arg)
|
||||
local ret = T.temp-T.list(aux.orig.CraftFrame_SetSelection(unpack(arg)))
|
||||
local id = GetCraftSelectionIndex()
|
||||
local total_cost = 0
|
||||
for i = 1, GetCraftNumReagents(id) do
|
||||
local link = GetCraftReagentItemLink(id, i)
|
||||
if not link then
|
||||
total_cost = nil
|
||||
break
|
||||
end
|
||||
local item_id, suffix_id = info.parse_link(link)
|
||||
local count = aux.select(3, GetCraftReagentInfo(id, i))
|
||||
local _, price, limited = info.merchant_info(item_id)
|
||||
local value = price and not limited and price or history.value(item_id .. ':' .. suffix_id)
|
||||
if not value then
|
||||
total_cost = nil
|
||||
break
|
||||
else
|
||||
total_cost = total_cost + value * count
|
||||
end
|
||||
end
|
||||
CraftReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost))
|
||||
return unpack(ret)
|
||||
end)
|
||||
for i = 1, 8 do
|
||||
hook_quest_item(_G['CraftReagent' .. i])
|
||||
end
|
||||
end
|
||||
function trade_skill_ui_loaded()
|
||||
aux.hook('TradeSkillFrame_SetSelection', T.vararg-function(arg)
|
||||
local ret = T.temp-T.list(aux.orig.TradeSkillFrame_SetSelection(unpack(arg)))
|
||||
local id = GetTradeSkillSelectionIndex()
|
||||
local total_cost = 0
|
||||
for i = 1, GetTradeSkillNumReagents(id) do
|
||||
local link = GetTradeSkillReagentItemLink(id, i)
|
||||
if not link then
|
||||
total_cost = nil
|
||||
break
|
||||
end
|
||||
local item_id, suffix_id = info.parse_link(link)
|
||||
local count = aux.select(3, GetTradeSkillReagentInfo(id, i))
|
||||
local _, price, limited = info.merchant_info(item_id)
|
||||
local value = price and not limited and price or history.value(item_id .. ':' .. suffix_id)
|
||||
if not value then
|
||||
total_cost = nil
|
||||
break
|
||||
else
|
||||
total_cost = total_cost + value * count
|
||||
end
|
||||
end
|
||||
TradeSkillReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost))
|
||||
return unpack(ret)
|
||||
end)
|
||||
for i = 1, 8 do
|
||||
hook_quest_item(_G['TradeSkillReagent' .. i])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,163 @@
|
||||
module 'aux.core.disenchant'
|
||||
|
||||
local T = require 'T'
|
||||
local history = require 'aux.core.history'
|
||||
|
||||
local UNCOMMON, RARE, EPIC = 2, 3, 4
|
||||
|
||||
local ARMOR = T.set(
|
||||
'INVTYPE_HEAD',
|
||||
'INVTYPE_NECK',
|
||||
'INVTYPE_SHOULDER',
|
||||
'INVTYPE_BODY',
|
||||
'INVTYPE_CHEST',
|
||||
'INVTYPE_ROBE',
|
||||
'INVTYPE_WAIST',
|
||||
'INVTYPE_LEGS',
|
||||
'INVTYPE_FEET',
|
||||
'INVTYPE_WRIST',
|
||||
'INVTYPE_HAND',
|
||||
'INVTYPE_FINGER',
|
||||
'INVTYPE_TRINKET',
|
||||
'INVTYPE_CLOAK',
|
||||
'INVTYPE_HOLDABLE'
|
||||
)
|
||||
|
||||
local WEAPON = T.set(
|
||||
'INVTYPE_2HWEAPON',
|
||||
'INVTYPE_WEAPONMAINHAND',
|
||||
'INVTYPE_WEAPON',
|
||||
'INVTYPE_WEAPONOFFHAND',
|
||||
'INVTYPE_SHIELD',
|
||||
'INVTYPE_RANGED',
|
||||
'INVTYPE_RANGEDRIGHT'
|
||||
)
|
||||
|
||||
function M.value(slot, quality, level)
|
||||
local expectation
|
||||
for _, event in distribution(slot, quality, level) do
|
||||
local value = history.value(event.item_id .. ':' .. 0)
|
||||
if not value then
|
||||
return
|
||||
else
|
||||
expectation = (expectation or 0) + event.probability * (event.min_quantity + event.max_quantity) / 2 * value
|
||||
end
|
||||
end
|
||||
return expectation
|
||||
end
|
||||
|
||||
function M.distribution(slot, quality, level)
|
||||
if not ARMOR[slot] and not WEAPON[slot] or level == 0 then
|
||||
return T.acquire()
|
||||
end
|
||||
|
||||
local function p(probability_armor, probability_weapon)
|
||||
if ARMOR[slot] then
|
||||
return probability_armor
|
||||
elseif WEAPON[slot] then
|
||||
return probability_weapon
|
||||
end
|
||||
end
|
||||
|
||||
if quality == UNCOMMON then
|
||||
if level <= 10 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 10940, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.8, .2)),
|
||||
T.temp-T.map('item_id', 10938, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .8))
|
||||
)
|
||||
elseif level <= 15 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 10940, 'min_quantity', 2, 'max_quantity', 3, 'probability', p(.75, .2)),
|
||||
T.temp-T.map('item_id', 10939, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05)
|
||||
)
|
||||
elseif level <= 20 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 10940, 'min_quantity', 4, 'max_quantity', 6, 'probability', p(.75, .15)),
|
||||
T.temp-T.map('item_id', 10998, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.15, .75)),
|
||||
T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', .10)
|
||||
)
|
||||
elseif level <= 25 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 11083, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)),
|
||||
T.temp-T.map('item_id', 11082, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 11084, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05)
|
||||
)
|
||||
elseif level <= 30 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 11083, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .2)),
|
||||
T.temp-T.map('item_id', 11134, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 11138, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05)
|
||||
)
|
||||
elseif level <= 35 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 11137, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)),
|
||||
T.temp-T.map('item_id', 11135, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 11139, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05)
|
||||
)
|
||||
elseif level <= 40 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 11137, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .2)),
|
||||
T.temp-T.map('item_id', 11174, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 11177, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05)
|
||||
)
|
||||
elseif level <= 45 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 11176, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)),
|
||||
T.temp-T.map('item_id', 11175, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 11178, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05)
|
||||
)
|
||||
elseif level <= 50 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 11176, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .22)),
|
||||
T.temp-T.map('item_id', 16202, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 14343, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03))
|
||||
)
|
||||
elseif level <= 55 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 16204, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .22)),
|
||||
T.temp-T.map('item_id', 16203, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03))
|
||||
)
|
||||
elseif level <= 60 then
|
||||
return T.temp-T.list(
|
||||
T.temp-T.map('item_id', 16204, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .22)),
|
||||
T.temp-T.map('item_id', 16203, 'min_quantity', 2, 'max_quantity', 3, 'probability', p(.2, .75)),
|
||||
T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03))
|
||||
)
|
||||
end
|
||||
elseif quality == RARE then
|
||||
if level <= 20 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 25 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11084, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 30 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11138, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 35 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11139, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 40 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11177, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 45 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11178, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 50 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 14343, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 55 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', .995), T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', .005))
|
||||
elseif level <= 60 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', .995), T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', .005))
|
||||
end
|
||||
elseif quality == EPIC then
|
||||
if level <= 40 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11177, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1))
|
||||
elseif level <= 45 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 11178, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1))
|
||||
elseif level <= 50 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 14343, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1))
|
||||
elseif level <= 55 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1))
|
||||
elseif level <= 60 then
|
||||
return T.temp-T.list(T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 2, 'probability', 1))
|
||||
end
|
||||
end
|
||||
return T.acquire()
|
||||
end
|
||||
@@ -0,0 +1,109 @@
|
||||
module 'aux.core.history'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
|
||||
local persistence = require 'aux.util.persistence'
|
||||
|
||||
local history_schema = {'tuple', '#', {next_push='number'}, {daily_min_buyout='number'}, {data_points={'list', ';', {'tuple', '@', {value='number'}, {time='number'}}}}}
|
||||
|
||||
local value_cache = {}
|
||||
|
||||
function aux.handle.LOAD2()
|
||||
data = aux.faction_data.history
|
||||
end
|
||||
|
||||
do
|
||||
local next_push = 0
|
||||
function get_next_push()
|
||||
if time() > next_push then
|
||||
local date = date('*t')
|
||||
date.hour, date.min, date.sec = 24, 0, 0
|
||||
next_push = time(date)
|
||||
end
|
||||
return next_push
|
||||
end
|
||||
end
|
||||
|
||||
function new_record()
|
||||
return T.temp-T.map('next_push', get_next_push(), 'data_points', T.acquire())
|
||||
end
|
||||
|
||||
function read_record(item_key)
|
||||
local record = data[item_key] and persistence.read(history_schema, data[item_key]) or new_record()
|
||||
if record.next_push <= time() then
|
||||
push_record(record)
|
||||
write_record(item_key, record)
|
||||
end
|
||||
return record
|
||||
end
|
||||
|
||||
function write_record(item_key, record)
|
||||
data[item_key] = persistence.write(history_schema, record)
|
||||
if value_cache[item_key] then
|
||||
T.release(value_cache[item_key])
|
||||
value_cache[item_key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function M.process_auction(auction_record)
|
||||
local item_record = read_record(auction_record.item_key)
|
||||
local unit_buyout_price = ceil(auction_record.buyout_price / auction_record.aux_quantity)
|
||||
if unit_buyout_price > 0 and unit_buyout_price < (item_record.daily_min_buyout or aux.huge) then
|
||||
item_record.daily_min_buyout = unit_buyout_price
|
||||
write_record(auction_record.item_key, item_record)
|
||||
end
|
||||
end
|
||||
|
||||
function M.data_points(item_key)
|
||||
return read_record(item_key).data_points
|
||||
end
|
||||
|
||||
function M.value(item_key)
|
||||
if not value_cache[item_key] or value_cache[item_key].next_push <= time() then
|
||||
local item_record, value
|
||||
item_record = read_record(item_key)
|
||||
if getn(item_record.data_points) > 0 then
|
||||
local total_weight, weighted_values = 0, T.temp-T.acquire()
|
||||
for _, data_point in item_record.data_points do
|
||||
local weight = .99 ^ aux.round((item_record.data_points[1].time - data_point.time) / (60 * 60 * 24))
|
||||
total_weight = total_weight + weight
|
||||
tinsert(weighted_values, T.map('value', data_point.value, 'weight', weight))
|
||||
end
|
||||
for _, weighted_value in weighted_values do
|
||||
weighted_value.weight = weighted_value.weight / total_weight
|
||||
end
|
||||
value = weighted_median(weighted_values)
|
||||
else
|
||||
value = item_record.daily_min_buyout
|
||||
end
|
||||
value_cache[item_key] = T.map('value', value, 'next_push', item_record.next_push)
|
||||
end
|
||||
return value_cache[item_key].value
|
||||
end
|
||||
|
||||
function M.market_value(item_key)
|
||||
return read_record(item_key).daily_min_buyout
|
||||
end
|
||||
|
||||
function weighted_median(list)
|
||||
sort(list, function(a,b) return a.value < b.value end)
|
||||
local weight = 0
|
||||
for _, v in ipairs(list) do
|
||||
weight = weight + v.weight
|
||||
if weight >= .5 then
|
||||
return v.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function push_record(item_record)
|
||||
if item_record.daily_min_buyout then
|
||||
tinsert(item_record.data_points, 1, T.map('value', item_record.daily_min_buyout, 'time', item_record.next_push))
|
||||
while getn(item_record.data_points) > 11 do
|
||||
T.release(item_record.data_points[getn(item_record.data_points)])
|
||||
tremove(item_record.data_points)
|
||||
end
|
||||
end
|
||||
item_record.next_push, item_record.daily_min_buyout = get_next_push(), nil
|
||||
end
|
||||
@@ -0,0 +1,165 @@
|
||||
module 'aux.core.post'
|
||||
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
local stack = require 'aux.core.stack'
|
||||
local history = require 'aux.core.history'
|
||||
local disenchant = require 'aux.core.disenchant'
|
||||
|
||||
local state
|
||||
|
||||
function aux.handle.CLOSE()
|
||||
stop()
|
||||
end
|
||||
|
||||
function process()
|
||||
if state.posted < state.count then
|
||||
|
||||
local stacking_complete
|
||||
|
||||
local send_signal, signal_received = aux.signal()
|
||||
aux.when(signal_received, function()
|
||||
local slot = signal_received()[1]
|
||||
if slot then
|
||||
return post_auction(slot, process)
|
||||
else
|
||||
return stop()
|
||||
end
|
||||
end)
|
||||
|
||||
return stack.start(state.item_key, state.stack_size, send_signal)
|
||||
end
|
||||
|
||||
return stop()
|
||||
end
|
||||
|
||||
function post_auction2(slot, k)
|
||||
local item_info = info.container_item(unpack(slot))
|
||||
if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then
|
||||
|
||||
ClearCursor()
|
||||
ClickAuctionSellItemButton()
|
||||
ClearCursor()
|
||||
PickupContainerItem(unpack(slot))
|
||||
ClickAuctionSellItemButton()
|
||||
ClearCursor()
|
||||
|
||||
StartAuction(max(1, aux.round(state.unit_start_price * item_info.aux_quantity)), aux.round(state.unit_buyout_price * item_info.aux_quantity), state.duration)
|
||||
|
||||
local send_signal, signal_received = aux.signal()
|
||||
aux.when(signal_received, function()
|
||||
state.posted = state.posted + 1
|
||||
return k()
|
||||
end)
|
||||
|
||||
local posted
|
||||
aux.event_listener('CHAT_MSG_SYSTEM', function(kill)
|
||||
if arg1 == ERR_AUCTION_STARTED then
|
||||
send_signal()
|
||||
kill()
|
||||
end
|
||||
end)
|
||||
else
|
||||
return stop()
|
||||
end
|
||||
end
|
||||
|
||||
function post_auction(slot, k)
|
||||
local item_info = info.container_item(unpack(slot))
|
||||
if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then
|
||||
|
||||
ClearCursor()
|
||||
ClickAuctionSellItemButton()
|
||||
ClearCursor()
|
||||
PickupContainerItem(unpack(slot))
|
||||
ClickAuctionSellItemButton()
|
||||
ClearCursor()
|
||||
|
||||
local start_price = state.unit_start_price
|
||||
local buyout_price = state.unit_buyout_price
|
||||
local kz_undercut = 0
|
||||
|
||||
if start_price == 0 then
|
||||
|
||||
if history.market_value(state.item_key) ~= nil then
|
||||
start_price = max(start_price,0.93*tonumber(history.market_value(state.item_key)))
|
||||
buyout_price = max(buyout_price,0.99*tonumber(history.market_value(state.item_key)))
|
||||
|
||||
kz_undercut = 1
|
||||
end
|
||||
|
||||
if disenchant.value(item_info.slot, item_info.quality, item_info.level) ~= nil then
|
||||
buyout_price = max(buyout_price,0.80* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level)))
|
||||
end
|
||||
|
||||
if aux.account_data.merchant_sell[item_info.item_id] ~= nil then
|
||||
start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * 1.35)
|
||||
buyout_price = max(buyout_price,start_price)
|
||||
|
||||
if kz_undercut == 0 then
|
||||
start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * (1.35+3.65*math.exp(-(1/4000)* tonumber(aux.account_data.merchant_sell[item_info.item_id] ))))
|
||||
buyout_price = max(buyout_price,start_price)
|
||||
end
|
||||
end
|
||||
|
||||
if history.value(state.item_key) ~= nil then
|
||||
start_price = max(start_price,0.89*tonumber(history.value(state.item_key)))
|
||||
buyout_price = max(buyout_price,0.89*tonumber(history.value(state.item_key)))
|
||||
|
||||
if kz_undercut == 0 then
|
||||
buyout_price = max(buyout_price,1.0* tonumber(history.value(state.item_key)))
|
||||
end
|
||||
end
|
||||
|
||||
start_price = max(start_price,0.75 * buyout_price)
|
||||
end
|
||||
|
||||
StartAuction(max(1, aux.round(start_price * item_info.aux_quantity)), aux.round(buyout_price * item_info.aux_quantity), state.duration)
|
||||
|
||||
local send_signal, signal_received = aux.signal()
|
||||
aux.when(signal_received, function()
|
||||
state.posted = state.posted + 1
|
||||
return k()
|
||||
end)
|
||||
|
||||
local posted
|
||||
aux.event_listener('CHAT_MSG_SYSTEM', function(kill)
|
||||
if arg1 == ERR_AUCTION_STARTED then
|
||||
send_signal()
|
||||
kill()
|
||||
end
|
||||
end)
|
||||
else
|
||||
return stop()
|
||||
end
|
||||
end
|
||||
|
||||
function M.stop()
|
||||
if state then
|
||||
aux.kill_thread(state.thread_id)
|
||||
|
||||
local callback = state.callback
|
||||
local posted = state.posted
|
||||
|
||||
state = nil
|
||||
|
||||
if callback then
|
||||
callback(posted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.start(item_key, stack_size, duration, unit_start_price, unit_buyout_price, count, callback)
|
||||
stop()
|
||||
state = {
|
||||
thread_id = aux.thread(process),
|
||||
item_key = item_key,
|
||||
stack_size = stack_size,
|
||||
duration = duration,
|
||||
unit_start_price = unit_start_price,
|
||||
unit_buyout_price = unit_buyout_price,
|
||||
count = count,
|
||||
posted = 0,
|
||||
callback = callback,
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,236 @@
|
||||
module 'aux.core.scan'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
local history = require 'aux.core.history'
|
||||
|
||||
local PAGE_SIZE = 50
|
||||
|
||||
function aux.handle.CLOSE()
|
||||
abort()
|
||||
end
|
||||
|
||||
do
|
||||
local scan_states = {}
|
||||
|
||||
function M.start(params)
|
||||
local old_state = scan_states[params.type]
|
||||
if old_state then
|
||||
abort(old_state.id)
|
||||
end
|
||||
do (params.on_scan_start or pass)() end
|
||||
local thread_id = aux.thread(scan)
|
||||
scan_states[params.type] = {
|
||||
id = thread_id,
|
||||
params = params,
|
||||
}
|
||||
return thread_id
|
||||
end
|
||||
|
||||
function M.abort(scan_id)
|
||||
local aborted = T.acquire()
|
||||
for type, state in scan_states do
|
||||
if not scan_id or state.id == scan_id then
|
||||
aux.kill_thread(state.id)
|
||||
scan_states[type] = nil
|
||||
tinsert(aborted, state)
|
||||
end
|
||||
end
|
||||
for _, state in aborted do
|
||||
do (state.params.on_abort or pass)() end
|
||||
end
|
||||
end
|
||||
|
||||
function M.stop()
|
||||
get_state().stopped = true
|
||||
end
|
||||
|
||||
function complete()
|
||||
local on_complete = get_state().params.on_complete
|
||||
scan_states[get_state().params.type] = nil
|
||||
do (on_complete or pass)() end
|
||||
end
|
||||
|
||||
function get_state()
|
||||
for _, state in scan_states do
|
||||
if state.id == aux.thread_id() then
|
||||
return state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function get_query()
|
||||
return get_state().params.queries[get_state().query_index]
|
||||
end
|
||||
|
||||
function total_pages(total_auctions)
|
||||
return ceil(total_auctions / PAGE_SIZE)
|
||||
end
|
||||
|
||||
function last_page(total_auctions)
|
||||
local last_page = max(total_pages(total_auctions) - 1, 0)
|
||||
local last_page_limit = get_query().blizzard_query.last_page or last_page
|
||||
return min(last_page_limit, last_page)
|
||||
end
|
||||
|
||||
function scan()
|
||||
get_state().query_index = get_state().query_index and get_state().query_index + 1 or 1
|
||||
if get_query() and not get_state().stopped then
|
||||
do (get_state().params.on_start_query or pass)(get_state().query_index) end
|
||||
if get_query().blizzard_query then
|
||||
if (get_query().blizzard_query.first_page or 0) <= (get_query().blizzard_query.last_page or aux.huge) then
|
||||
get_state().page = get_query().blizzard_query.first_page or 0
|
||||
return submit_query()
|
||||
end
|
||||
else
|
||||
get_state().page = nil
|
||||
return scan_page()
|
||||
end
|
||||
end
|
||||
return complete()
|
||||
end
|
||||
|
||||
do
|
||||
local function submit()
|
||||
if get_state().params.type == 'bidder' then
|
||||
GetBidderAuctionItems(get_state().page)
|
||||
elseif get_state().params.type == 'owner' then
|
||||
GetOwnerAuctionItems(get_state().page)
|
||||
else
|
||||
get_state().last_list_query = GetTime()
|
||||
local blizzard_query = get_query().blizzard_query or T.acquire()
|
||||
QueryAuctionItems(
|
||||
blizzard_query.name,
|
||||
blizzard_query.min_level,
|
||||
blizzard_query.max_level,
|
||||
blizzard_query.slot,
|
||||
blizzard_query.class,
|
||||
blizzard_query.subclass,
|
||||
get_state().page,
|
||||
blizzard_query.usable,
|
||||
blizzard_query.quality
|
||||
)
|
||||
end
|
||||
return wait_for_results()
|
||||
end
|
||||
function submit_query()
|
||||
if get_state().stopped then return end
|
||||
if get_state().params.type ~= 'list' then
|
||||
return submit()
|
||||
else
|
||||
return aux.when(CanSendAuctionQuery, submit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function scan_page(i)
|
||||
i = i or 1
|
||||
|
||||
if i > PAGE_SIZE then
|
||||
do (get_state().params.on_page_scanned or pass)() end
|
||||
if get_query().blizzard_query and get_state().page < last_page(get_state().total_auctions) then
|
||||
get_state().page = get_state().page + 1
|
||||
return submit_query()
|
||||
else
|
||||
return scan()
|
||||
end
|
||||
end
|
||||
|
||||
local auction_info = info.auction(i, get_state().params.type)
|
||||
if auction_info and (auction_info.owner or get_state().params.ignore_owner or aux.account_data.ignore_owner) then
|
||||
auction_info.index = i
|
||||
auction_info.page = get_state().page
|
||||
auction_info.blizzard_query = get_query().blizzard_query
|
||||
auction_info.query_type = get_state().params.type
|
||||
|
||||
history.process_auction(auction_info)
|
||||
|
||||
if (get_state().params.auto_bid_validator or pass)(auction_info) then
|
||||
local send_signal, signal_received = aux.signal()
|
||||
aux.when(signal_received, scan_page, i)
|
||||
return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.bid_price, send_signal)
|
||||
elseif (get_state().params.auto_buy_validator or pass)(auction_info) then
|
||||
local send_signal, signal_received = aux.signal()
|
||||
aux.when(signal_received, scan_page, i)
|
||||
return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.buyout_price, send_signal)
|
||||
elseif not get_query().validator or get_query().validator(auction_info) then
|
||||
do (get_state().params.on_auction or pass)(auction_info) end
|
||||
end
|
||||
end
|
||||
|
||||
return scan_page(i + 1)
|
||||
end
|
||||
|
||||
function wait_for_results()
|
||||
if get_state().params.type == 'bidder' then
|
||||
return aux.when(function() return aux.bids_loaded() end, accept_results)
|
||||
elseif get_state().params.type == 'owner' then
|
||||
return wait_for_owner_results()
|
||||
elseif get_state().params.type == 'list' then
|
||||
return wait_for_list_results()
|
||||
end
|
||||
end
|
||||
|
||||
function accept_results()
|
||||
_, get_state().total_auctions = GetNumAuctionItems(get_state().params.type)
|
||||
do
|
||||
(get_state().params.on_page_loaded or pass)(
|
||||
get_state().page - (get_query().blizzard_query.first_page or 0) + 1,
|
||||
last_page(get_state().total_auctions) - (get_query().blizzard_query.first_page or 0) + 1,
|
||||
total_pages(get_state().total_auctions) - 1
|
||||
)
|
||||
end
|
||||
return scan_page()
|
||||
end
|
||||
|
||||
function wait_for_owner_results()
|
||||
if get_state().page == aux.current_owner_page() then
|
||||
return accept_results()
|
||||
else
|
||||
local updated
|
||||
aux.on_next_event('AUCTION_OWNED_LIST_UPDATE', function() updated = true end)
|
||||
return aux.when(function() return updated end, accept_results)
|
||||
end
|
||||
end
|
||||
|
||||
function wait_for_list_results()
|
||||
local updated, last_update
|
||||
local listener_id = aux.event_listener('AUCTION_ITEM_LIST_UPDATE', function()
|
||||
last_update = GetTime()
|
||||
updated = true
|
||||
end)
|
||||
local timeout = aux.later(5, get_state().last_list_query)
|
||||
local ignore_owner = get_state().params.ignore_owner or aux.account_data.ignore_owner
|
||||
return aux.when(function()
|
||||
if not last_update and timeout() then
|
||||
return true
|
||||
end
|
||||
if last_update and GetTime() - last_update > 5 then
|
||||
return true
|
||||
end
|
||||
-- short circuiting order important, owner_data_complete must be called iif an update has happened.
|
||||
if updated and (ignore_owner or owner_data_complete()) then
|
||||
return true
|
||||
end
|
||||
updated = false
|
||||
end, function()
|
||||
aux.kill_listener(listener_id)
|
||||
if not last_update and timeout() then
|
||||
return submit_query()
|
||||
else
|
||||
return accept_results()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function owner_data_complete()
|
||||
for i = 1, PAGE_SIZE do
|
||||
local auction_info = info.auction(i, 'list')
|
||||
if auction_info and not auction_info.owner then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
module 'aux.core.shortcut'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
|
||||
do
|
||||
local orig = SetItemRef
|
||||
_G.SetItemRef = T.vararg-function(arg)
|
||||
if arg[3] ~= 'RightButton' or not aux.index(aux.get_tab(), 'CLICK_LINK') or not strfind(arg[1], '^item:%d+') then
|
||||
return orig(unpack(arg))
|
||||
end
|
||||
local item_info = info.item(tonumber(aux.select(3, strfind(arg[1], '^item:(%d+)'))))
|
||||
if item_info then
|
||||
return aux.get_tab().CLICK_LINK(item_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local orig = UseContainerItem
|
||||
_G.UseContainerItem = T.vararg-function(arg)
|
||||
if aux.modified() or not aux.get_tab() then
|
||||
return orig(unpack(arg))
|
||||
end
|
||||
local item_info = info.container_item(arg[1], arg[2])
|
||||
if item_info and aux.get_tab().USE_ITEM then
|
||||
aux.get_tab().USE_ITEM(item_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module 'aux.core.slash'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
local post = require 'aux.tabs.post'
|
||||
|
||||
function status(enabled)
|
||||
return (enabled and aux.color.green'on' or aux.color.red'off')
|
||||
end
|
||||
|
||||
_G.SLASH_AUX1 = '/aux'
|
||||
function SlashCmdList.AUX(command)
|
||||
if not command then return end
|
||||
local arguments = aux.tokenize(command)
|
||||
local tooltip_settings = aux.character_data.tooltip
|
||||
if arguments[1] == 'scale' and tonumber(arguments[2]) then
|
||||
local scale = tonumber(arguments[2])
|
||||
aux.frame:SetScale(scale)
|
||||
aux.account_data.scale = scale
|
||||
elseif arguments[1] == 'ignore' and arguments[2] == 'owner' then
|
||||
aux.account_data.ignore_owner = not aux.account_data.ignore_owner
|
||||
aux.print('ignore owner ' .. status(aux.account_data.ignore_owner))
|
||||
elseif arguments[1] == 'post' and arguments[2] == 'bid' then
|
||||
aux.account_data.post_bid = not aux.account_data.post_bid
|
||||
aux.print('post bid ' .. status(aux.account_data.post_bid))
|
||||
elseif arguments[1] == 'post' and arguments[2] == 'duration' and T.map('2', post.DURATION_2, '8', post.DURATION_8, '24', post.DURATION_24)[arguments[3]] then
|
||||
aux.account_data.post_duration = T.map('2', post.DURATION_2, '8', post.DURATION_8, '24', post.DURATION_24)[arguments[3]]
|
||||
aux.print('post duration ' .. aux.color.blue(aux.account_data.post_duration / 60 .. 'h'))
|
||||
elseif arguments[1] == 'crafting' and arguments[2] == 'cost' then
|
||||
aux.account_data.crafting_cost = not aux.account_data.crafting_cost
|
||||
aux.print('crafting cost ' .. status(aux.account_data.crafting_cost))
|
||||
elseif arguments[1] == 'tooltip' and arguments[2] == 'value' then
|
||||
tooltip_settings.value = not tooltip_settings.value
|
||||
aux.print('tooltip value ' .. status(tooltip_settings.value))
|
||||
elseif arguments[1] == 'tooltip' and arguments[2] == 'daily' then
|
||||
tooltip_settings.daily = not tooltip_settings.daily
|
||||
aux.print('tooltip daily ' .. status(tooltip_settings.daily))
|
||||
elseif arguments[1] == 'tooltip' and arguments[2] == 'merchant' and arguments[3] == 'buy' then
|
||||
tooltip_settings.merchant_buy = not tooltip_settings.merchant_buy
|
||||
aux.print('tooltip merchant buy ' .. status(tooltip_settings.merchant_buy))
|
||||
elseif arguments[1] == 'tooltip' and arguments[2] == 'merchant' and arguments[3] == 'sell' then
|
||||
tooltip_settings.merchant_sell = not tooltip_settings.merchant_sell
|
||||
aux.print('tooltip merchant sell ' .. status(tooltip_settings.merchant_sell))
|
||||
elseif arguments[1] == 'tooltip' and arguments[2] == 'disenchant' and arguments[3] == 'value' then
|
||||
tooltip_settings.disenchant_value = not tooltip_settings.disenchant_value
|
||||
aux.print('tooltip disenchant value ' .. status(tooltip_settings.disenchant_value))
|
||||
elseif arguments[1] == 'tooltip' and arguments[2] == 'disenchant' and arguments[3] == 'distribution' then
|
||||
tooltip_settings.disenchant_distribution = not tooltip_settings.disenchant_distribution
|
||||
aux.print('tooltip disenchant distribution ' .. status(tooltip_settings.disenchant_distribution))
|
||||
elseif arguments[1] == 'clear' and arguments[2] == 'item' and arguments[3] == 'cache' then
|
||||
aux.account_data.items = {}
|
||||
aux.account_data.item_ids = {}
|
||||
aux.account_data.auctionable_items = {}
|
||||
aux.print('Item cache cleared.')
|
||||
elseif arguments[1] == 'populate' and arguments[2] == 'wdb' then
|
||||
info.populate_wdb()
|
||||
else
|
||||
aux.print('Usage:')
|
||||
aux.print('- scale [' .. aux.color.blue(aux.account_data.scale) .. ']')
|
||||
aux.print('- ignore owner [' .. status(aux.account_data.ignore_owner) .. ']')
|
||||
aux.print('- post bid [' .. status(aux.account_data.post_bid) .. ']')
|
||||
aux.print('- post duration [' .. aux.color.blue(aux.account_data.post_duration / 60 .. 'h') .. ']')
|
||||
aux.print('- crafting cost [' .. status(aux.account_data.crafting_cost) .. ']')
|
||||
aux.print('- tooltip value [' .. status(tooltip_settings.value) .. ']')
|
||||
aux.print('- tooltip daily [' .. status(tooltip_settings.daily) .. ']')
|
||||
aux.print('- tooltip merchant buy [' .. status(tooltip_settings.merchant_buy) .. ']')
|
||||
aux.print('- tooltip merchant sell [' .. status(tooltip_settings.merchant_sell) .. ']')
|
||||
aux.print('- tooltip disenchant value [' .. status(tooltip_settings.disenchant_value) .. ']')
|
||||
aux.print('- tooltip disenchant distribution [' .. status(tooltip_settings.disenchant_distribution) .. ']')
|
||||
aux.print('- clear item cache')
|
||||
aux.print('- populate wdb')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,130 @@
|
||||
module 'aux.core.stack'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
|
||||
local state
|
||||
|
||||
function aux.handle.CLOSE()
|
||||
stop()
|
||||
end
|
||||
|
||||
function stack_size(slot)
|
||||
local container_item_info = T.temp-info.container_item(unpack(slot))
|
||||
return container_item_info and container_item_info.count or 0
|
||||
end
|
||||
|
||||
function charges(slot)
|
||||
local container_item_info = T.temp-info.container_item(unpack(slot))
|
||||
return container_item_info and container_item_info.charges
|
||||
end
|
||||
|
||||
function max_stack(slot)
|
||||
local container_item_info = T.temp-info.container_item(unpack(slot))
|
||||
return container_item_info and container_item_info.max_stack
|
||||
end
|
||||
|
||||
function locked(slot)
|
||||
local container_item_info = T.temp-info.container_item(unpack(slot))
|
||||
return container_item_info and container_item_info.locked
|
||||
end
|
||||
|
||||
function find_item_slot(partial)
|
||||
for slot in info.inventory() do
|
||||
if matching_item(slot, partial) and not aux.eq(slot, state.target_slot) then
|
||||
return slot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function matching_item(slot, partial)
|
||||
local item_info = T.temp-info.container_item(unpack(slot))
|
||||
return item_info and item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and (not partial or item_info.count < item_info.max_stack)
|
||||
end
|
||||
|
||||
function find_empty_slot()
|
||||
for slot, type in info.inventory() do
|
||||
if type == 1 and not GetContainerItemInfo(unpack(slot)) then
|
||||
return slot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function find_charge_item_slot()
|
||||
for slot in info.inventory() do
|
||||
if matching_item(slot) and charges(slot) == state.target_size then
|
||||
return slot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function move_item(from_slot, to_slot, amount, k)
|
||||
if locked(from_slot) or locked(to_slot) then
|
||||
return aux.wait(k)
|
||||
end
|
||||
|
||||
amount = min(max_stack(from_slot) - stack_size(to_slot), stack_size(from_slot), amount)
|
||||
local expected_size = stack_size(to_slot) + amount
|
||||
|
||||
ClearCursor()
|
||||
SplitContainerItem(from_slot[1], from_slot[2], amount)
|
||||
PickupContainerItem(unpack(to_slot))
|
||||
|
||||
return aux.when(function() return stack_size(to_slot) == expected_size end, k)
|
||||
end
|
||||
|
||||
function process()
|
||||
if not state.target_slot or not matching_item(state.target_slot) then
|
||||
state.target_slot = find_item_slot()
|
||||
if not state.target_slot then
|
||||
return stop()
|
||||
end
|
||||
end
|
||||
if charges(state.target_slot) then
|
||||
state.target_slot = find_charge_item_slot()
|
||||
return stop()
|
||||
end
|
||||
if stack_size(state.target_slot) > state.target_size then
|
||||
local slot = find_item_slot(true) or find_empty_slot()
|
||||
if slot then
|
||||
return move_item(
|
||||
state.target_slot,
|
||||
slot,
|
||||
stack_size(state.target_slot) - state.target_size,
|
||||
process
|
||||
)
|
||||
end
|
||||
elseif stack_size(state.target_slot) < state.target_size then
|
||||
local slot = find_item_slot()
|
||||
if slot then
|
||||
return move_item(
|
||||
slot,
|
||||
state.target_slot,
|
||||
state.target_size - stack_size(state.target_slot),
|
||||
process
|
||||
)
|
||||
end
|
||||
end
|
||||
return stop()
|
||||
end
|
||||
|
||||
function M.stop()
|
||||
if state then
|
||||
aux.kill_thread(state.thread_id)
|
||||
local callback, slot = state.callback, state.target_slot
|
||||
slot = slot and matching_item(slot) and slot or nil
|
||||
state = nil
|
||||
do (callback or pass)(slot) end
|
||||
end
|
||||
end
|
||||
|
||||
function M.start(item_key, size, callback)
|
||||
stop()
|
||||
state = {
|
||||
thread_id = aux.thread(process),
|
||||
item_key = item_key,
|
||||
target_size = size,
|
||||
callback = callback,
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,216 @@
|
||||
module 'aux.core.tooltip'
|
||||
|
||||
local T = require 'T'
|
||||
local aux = require 'aux'
|
||||
local info = require 'aux.util.info'
|
||||
local money = require 'aux.util.money'
|
||||
local disenchant = require 'aux.core.disenchant'
|
||||
local history = require 'aux.core.history'
|
||||
local gui = require 'aux.gui'
|
||||
|
||||
local UNKNOWN = GRAY_FONT_COLOR_CODE .. '?' .. FONT_COLOR_CODE_CLOSE
|
||||
|
||||
local game_tooltip_hooks, game_tooltip_money = {}, 0
|
||||
|
||||
function aux.handle.LOAD()
|
||||
settings = aux.character_data.tooltip
|
||||
do
|
||||
local inside_hook = false
|
||||
for name, f in game_tooltip_hooks do
|
||||
local name, f = name, f
|
||||
aux.hook(name, GameTooltip, T.vararg-function(arg)
|
||||
game_tooltip_money = 0
|
||||
inside_hook = true
|
||||
local tmp = T.list(aux.orig[GameTooltip][name](unpack(arg)))
|
||||
inside_hook = false
|
||||
f(unpack(arg))
|
||||
return T.unpack(tmp)
|
||||
end)
|
||||
end
|
||||
SetTooltipMoney = SetTooltipMoney
|
||||
_G.SetTooltipMoney = T.vararg-function(arg)
|
||||
if inside_hook then
|
||||
game_tooltip_money = arg[2]
|
||||
else
|
||||
return SetTooltipMoney(unpack(arg))
|
||||
end
|
||||
end
|
||||
end
|
||||
local orig = SetItemRef
|
||||
setglobal('SetItemRef', T.vararg-function(arg)
|
||||
local name, _, quality = GetItemInfo(arg[1])
|
||||
local tmp = T.list(orig(unpack(arg)))
|
||||
if not IsShiftKeyDown() and not IsControlKeyDown() and name then
|
||||
local color_code = aux.select(4, GetItemQualityColor(quality))
|
||||
local link = color_code .. '|H' .. arg[1] .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE
|
||||
extend_tooltip(ItemRefTooltip, link, 1)
|
||||
end
|
||||
return T.unpack(tmp)
|
||||
end)
|
||||
end
|
||||
|
||||
function M.extend_tooltip(tooltip, link, quantity)
|
||||
local item_id, suffix_id = info.parse_link(link)
|
||||
quantity = IsShiftKeyDown() and quantity or 1
|
||||
local item_info = T.temp-info.item(item_id)
|
||||
if item_info then
|
||||
local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level)
|
||||
if getn(distribution) > 0 then
|
||||
if settings.disenchant_distribution then
|
||||
tooltip:AddLine('Disenchants into:', aux.color.tooltip.disenchant.distribution())
|
||||
sort(distribution, function(a,b) return a.probability > b.probability end)
|
||||
for _, event in ipairs(distribution) do
|
||||
tooltip:AddLine(format(' %s%% %s (%s-%s)', event.probability * 100, info.display_name(event.item_id, true) or 'item:' .. event.item_id, event.min_quantity, event.max_quantity), aux.color.tooltip.disenchant.distribution())
|
||||
end
|
||||
end
|
||||
if settings.disenchant_value then
|
||||
local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level)
|
||||
tooltip:AddLine('Disenchant: ' .. (disenchant_value and money.to_string2(disenchant_value) or UNKNOWN), aux.color.tooltip.disenchant.value())
|
||||
end
|
||||
end
|
||||
end
|
||||
if settings.merchant_buy then
|
||||
local _, price, limited = info.merchant_info(item_id)
|
||||
if price then
|
||||
tooltip:AddLine('Vendor Buy ' .. (limited and '(limited): ' or ': ') .. money.to_string2(price * quantity), aux.color.tooltip.merchant())
|
||||
end
|
||||
end
|
||||
if settings.merchant_sell then
|
||||
local price = info.merchant_info(item_id)
|
||||
if price ~= 0 then
|
||||
tooltip:AddLine('Vendor: ' .. (price and money.to_string2(price * quantity) or UNKNOWN), aux.color.tooltip.merchant())
|
||||
end
|
||||
end
|
||||
local auctionable = not item_info or info.auctionable(T.temp-info.tooltip('link', item_info.itemstring), item_info.quality)
|
||||
local item_key = (item_id or 0) .. ':' .. (suffix_id or 0)
|
||||
local value = history.value(item_key)
|
||||
if auctionable then
|
||||
if settings.value then
|
||||
tooltip:AddLine('Value: ' .. (value and money.to_string2(value * quantity) or UNKNOWN), aux.color.tooltip.value())
|
||||
end
|
||||
if settings.daily then
|
||||
local market_value = history.market_value(item_key)
|
||||
tooltip:AddLine('Today: ' .. (market_value and money.to_string2(market_value * quantity) .. ' (' .. gui.percentage_historical(aux.round(market_value / value * 100)) .. ')' or UNKNOWN), aux.color.tooltip.value())
|
||||
end
|
||||
end
|
||||
|
||||
if tooltip == GameTooltip and game_tooltip_money > 0 then
|
||||
SetTooltipMoney(tooltip, game_tooltip_money)
|
||||
end
|
||||
tooltip:Show()
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetHyperlink(itemstring)
|
||||
local name, _, quality = GetItemInfo(itemstring)
|
||||
if name then
|
||||
local hex = aux.select(4, GetItemQualityColor(quality))
|
||||
local link = hex .. '|H' .. itemstring .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE
|
||||
extend_tooltip(GameTooltip, link, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetAuctionItem(type, index)
|
||||
local link = GetAuctionItemLink(type, index)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, aux.select(3, GetAuctionItemInfo(type, index)))
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetLootItem(slot)
|
||||
local link = GetLootSlotLink(slot)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, aux.select(3, GetLootSlotInfo(slot)))
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetQuestItem(qtype, slot)
|
||||
local link = GetQuestItemLink(qtype, slot)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, aux.select(3, GetQuestItemInfo(qtype, slot)))
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetQuestLogItem(qtype, slot)
|
||||
local link = GetQuestLogItemLink(qtype, slot)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, aux.select(3, GetQuestLogRewardInfo(slot)))
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetBagItem(bag, slot)
|
||||
local link = GetContainerItemLink(bag, slot)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, aux.select(2, GetContainerItemInfo(bag, slot)))
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetInboxItem(index)
|
||||
local name, _, quantity = GetInboxItem(index)
|
||||
local id = name and info.item_id(name)
|
||||
if id then
|
||||
local _, itemstring, quality = GetItemInfo(id)
|
||||
local hex = aux.select(4, GetItemQualityColor(tonumber(quality)))
|
||||
local link = hex .. '|H' .. itemstring .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE
|
||||
extend_tooltip(GameTooltip, link, quantity)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetInventoryItem(unit, slot)
|
||||
local link = GetInventoryItemLink(unit, slot)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetMerchantItem(slot)
|
||||
local link = GetMerchantItemLink(slot)
|
||||
if link then
|
||||
local quantity = aux.select(4, GetMerchantItemInfo(slot))
|
||||
extend_tooltip(GameTooltip, link, quantity)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetCraftItem(skill, slot)
|
||||
local link, quantity
|
||||
if slot then
|
||||
link, quantity = GetCraftReagentItemLink(skill, slot), aux.select(3, GetCraftReagentInfo(skill, slot))
|
||||
else
|
||||
link, quantity = GetCraftItemLink(skill), 1
|
||||
end
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, quantity)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetCraftSpell(slot)
|
||||
local link = GetCraftItemLink(slot)
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetTradeSkillItem(skill, slot)
|
||||
local link, quantity
|
||||
if slot then
|
||||
link, quantity = GetTradeSkillReagentItemLink(skill, slot), aux.select(3, GetTradeSkillReagentInfo(skill, slot))
|
||||
else
|
||||
link, quantity = GetTradeSkillItemLink(skill), 1
|
||||
end
|
||||
if link then
|
||||
extend_tooltip(GameTooltip, link, quantity)
|
||||
end
|
||||
end
|
||||
|
||||
function game_tooltip_hooks:SetAuctionSellItem()
|
||||
local name, _, quantity = GetAuctionSellItemInfo()
|
||||
if name then
|
||||
for slot in info.inventory() do
|
||||
T.temp(slot)
|
||||
local link = GetContainerItemLink(unpack(slot))
|
||||
if link and aux.select(5, info.parse_link(link)) == name then
|
||||
extend_tooltip(GameTooltip, link, quantity)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user