Пытаясь программировать ...иногда даже получается

Как я Loot Appraiser 1.9.3 чинил

Loot Appraiser — это аддон, который позволяет учитывать доход от фарма. Штука, несомненно, хорошая и полезная, но с двумя неприятными недочетами (как минимум, в версии 1.9.3), которые не позволяют разгуляться на полную.

Fast fix

Для тех, кто по ссылке под роликом Полный мониторинг фарма. Исправления и настройка Loot Appraiser и не сильно горит желанием разбираться в истоках описанных там проблем:

  1. Откройте папку c файлами World of Warcraft.
  2. Перейдите в _retail_\Interface\AddOns\LootAppraiser.
  3. Замените файл LootAppraiser.lua на этот.
  4. Перейдите в _retail_\Interface\AddOns\LootAppraiser\Core.
  5. Замените файл Util.lua на этот.
  6. Запустите игру, или сделайте /restart, если она уже запущена.

Более любознательных приглашаю к дальнейшему чтению!

Баг №1. Неправильный парсинг полученного золота

При фарме попадаются как предметы, так и золото. Каждый аспект учитывается отдельно. Количество золота аддон получает из сообщений чата — они конвертируются в число функцией Util.StringToMoney из файла Core\Util.lua:

-- parse currency text from loot window and covert the result to copper
-- e.g. 2 silve, 2 copper -> 202 copper
function Util.StringToMoney(lootedCurrencyAsText)
    local digits = {}
    local digitsCounter = 0;
    lootedCurrencyAsText:gsub("%d+",
        function(i)
            table.insert(digits, i)
            digitsCounter = digitsCounter + 1
        end
    )
    local copper = 0
    if digitsCounter == 3 then
        -- gold + silber + copper
        copper = (digits[1]*10000)+(digits[2]*100)+(digits[3])
    elseif digitsCounter == 2 then
        -- silber + copper
        copper = (digits[1]*100)+(digits[2])
    else
        -- copper
        copper = digits[1]
    end

    return copper
end

Как видно, строка преобразуется в таблицу чисел — по 1 столбцу для каждого порядка — через регулярное выражение %d+. По задумке авторов, строка вида 2 silve, 2 copper станет таблицей 1 1, а после, числом 202. Но для русской локализации, сообщение имеет следующий вид:

 Ваша добыча: 2 |4серебряная:серебряные:серебряных;, 2 |4медная монета:медные монеты:медных монет;.

Не вникал, как это разбирает клиент WoW, но вышеприведенная функция выберет все цифры, что даст таблицу 2 4 2 4, а после, так как столбцов аж 4, применит последнее условие и получится число 2. Для того, чтобы разбор велся верно, подкорректировать ругулярку:

-- Выбираем только последовательности цифр, после которых есть один пробел
lootedCurrencyAsText:gsub("%d+%s", -- Строка 86

Теперь, сообщение будет преобразовано в 2 4, применится второе условие, а на выходе будет 2 * 100 + 2 = 202.

Баг №2. Неверные данные о цене от TSM, если предмет еще не в кэше

Еще один must have аддон — Trade Skill Master. Он добавляется множество нужного функционала, но в нашем случае интересны источники цен. Loot Appraiser использует API от Trade Skill Master, чтобы оценить потенциальную стоимость предмета. Беда лишь в том, что для впервые встреченных вещей, TSM возвращает 0, а потом таки достает реальную стоимость в фоне и далее будет возвращать искомые данные в полном объеме.

Здесь пришлось пошаманить немного больше. Есть в файле LootAppraiser.lua фукнция private.HandleItemLooted, которая вызывается каждый раз, когда предмет попадает в сумку. В ней в, строке 513, вычисляется конечная стоимость набора:

local itemValue = singleItemValue * quantity

Я решил делать рекурсивный вызов private.HandleItemLooted каждые 300 мс до 5 раз, в надежде, что за это время TSM обновит данные и вернет правильную стоимость. Спойлеры, обычно правильная цена возвращается в ответ на уже второй запрос.

-- resolve price timer - это линия 512
if 0 == singleItemValue then -- Если цена таки не определилась
    if not ticker then -- И тикер не обьявлен
        LA.Debug.Log("Single item value of "  .. tostring(itemID) .. ": " .. tostring(itemLink) .. " was not resolved. Trying to repeat")
        -- Стартуем тикер со всеми нужными параметрами
        ticker = C_Timer.NewTicker(0.3, private.resolvePriceTimerClosure, 5)
        ticker.params = { itemLink = itemLink, itemID = itemID, quantity = quantity, source = source, priceSource = priceSource }
    end
    -- Если тикер не отменен и количество оставшихся итераций больше 1, то прерываем выполнение
    if ticker and not ticker._cancelled and ticker._remainingIterations > 1 then
        LA.Debug.Log("Tiker has been started. Going to next iteration for "  .. tostring(itemID) .. ": " .. tostring(itemLink))

        return
    end
    -- Сюда мы попадем, если прошло 5 итераций, но цена так и  не была определена
    LA.Debug.Log("Price of "  .. tostring(itemID) .. ": " .. tostring(itemLink) .. " was not resolved. Timer aborted")
end

Сама функция private.resolvePriceTimerClosure может быть помещена в конец файла:

-- retry to fetch item price
function private.resolvePriceTimerClosure(ticker)
    local params = ticker.params
    local tsmPrice = LA.TSM.GetItemValue(params.itemID, params.priceSource) or 0 -- Пытаемся получить цену
    -- Если получили
    if tsmPrice > 0 then
        LA.Debug.Log("Price of "  .. tostring(params.itemID) .. ": " .. tostring(params.itemLink) .. " has been resolved: " .. tostring(tsmPrice))
        ticker:Cancel() -- Отменяем тикер, что позволит добавить вещь в список и правильно сложить его цену
    else
        LA.Debug.Log("Timer for "  .. tostring(params.itemID) .. ": " .. tostring(params.itemLink) .. " will be runned " .. tostring(ticker._remainingIterations) .. " more times")
    end
    -- Делаем вызов private.HandleItemLooted
    private.HandleItemLooted(params.itemLink, params.itemID, params.quantity, params.source, ticker)
end

Внимательный читатель заметит, что сигнатура фукнции private.HandleItemLooted не содержит последнего аргумента ticker и будет прав. Нужно обновить сигнатуру в строке 441 на:

function private.HandleItemLooted(itemLink, itemID, quantity, source, ticker)

Теперь все должно работать. Не закэшированные вещи будут добавлены в список позже на 300 миллисекунд, зато с правильной ценой.

Бонус. Как увидеть debug log в игре?

Если есть желание поработать с кодом самостоятельно, подскажу, как включить отображение логов, отправленных через LA.Debug.Log. Необходимо создать окно чата с именем LADebug. Это все.