Update time! It's now possible to specify a max number of items to display in the loot list! All remaining items will be summarized under an "other items" category. This should make the hud look much better on small screens. Per default up to 20 items are displayed but you can change it by changing the last line in the config:
local Config = UserConfig
local function fixConfig()
local function fixColor(r, g, b)
return {0, color(r, g, b, 20), 1, color(r/1.7, g/1.7, b/1.7, 20)}
end
Config.version = "4.1.5"
Config.rightMargin = 185
local leftWidth = measurestring(" Crest of the Deep Seas ")
local rightWidth = measurestring("10:10:10 (1000 gp)")
local totalWidth = rightWidth + leftWidth
local unitHeight = Config.fontSize * 2.5
local _, stringHeight = measurestring("Foo")
local HUD = (function ()
local isMoving = false
local x, y, lastX, lastY = 0, 0, 0, 0
local function move(mousedown)
if mousedown and not isMoving then
lastX, lastY = $cursor.x, $cursor.y
end
isMoving = mousedown
end
local row = 0
local function currentRow(inc)
local oldRow = row
if inc then row = row + 1 end
return oldRow
end
local function center(s, width)
local sw = measurestring(s)
return (width - sw) / 2
end
local function drect(ox, oy, width)
return drawroundrect(ox, oy, width, Config.unitHeight, Config.radius, Config.radius)
end
local function dtext(s, ox, oy)
drawtext(s, ox, oy + Config.insety)
end
local function dinsetText(s, x, background, width, incRow, id)
setfillstyle("gradient", "linear", 2, 0, 0, 0, Config.unitHeight)
addgradcolors(unpack(background))
drect(x, currentRow() * Config.unityDiff, width)
if id then
setcompositionmode(CompositionMode_SourceOver)
drawitem(id, Config.insetx - 4, currentRow() * Config.unityDiff + Config.iteminsety - 4, Config.imageZoom, 100)
setcompositionmode(CompositionMode_Automatic)
end
dtext(s, x + Config.insetx + Config.imageWidth, currentRow(incRow) * Config.unityDiff)
end
local function dcenteredText(s, x, background, width, inc)
local background = background or Config.header.background
local width = width or Config.header.width
local x = x or 0
setfillstyle("gradient", "linear", 2, 0, 0, 0, Config.unitHeight)
addgradcolors(unpack(background))
drect(x, currentRow() * Config.unityDiff, width)
local cx = x + center(s, width)
dtext(s, cx, currentRow(inc) * Config.unityDiff)
end
local function drawButton(id, text, background, rightEnd, callback)
local width = measurestring(text)
setfillstyle("gradient", "linear", 2, 0, 0, 0, Config.unitHeight)
addgradcolors(unpack(background))
local rect = drect(rightEnd - width - 2 * Config.insetx, currentRow() * Config.unityDiff, width + 2 * Config.insetx)
Buttons[id] = {elementid = rect, callback = callback}
dtext(text, rightEnd - width - Config.insetx, currentRow(true) * Config.unityDiff)
end
local function formatMoney(total)
if math.abs(total) < 500 then
return string.format("%.0f gp", total)
else
return string.format("%.1f k", total / 1000)
end
end
local function drawTable(tbl, keyMap, valMap)
-- Some kind of sorting maybe?
local keys = {}
for key, _ in pairs(tbl) do
table.insert(keys, key)
end
local function moreValue(id1, id2)
local _, v1 = valMap(id1, tbl[id1])
local _, v2 = valMap(id2, tbl[id2])
return v1 > v2
end
table.sort(keys, moreValue)
local items = 0
local others = 0
for _, key in ipairs(keys) do
local val = tbl[key]
if items < Config.maxItems then
local keyText = tostring(keyMap and keyMap(key, val) or key)
local valText = tostring(valMap and valMap(key, val) or val)
dinsetText(keyText, 0, Config.itemColumn.background, Config.itemColumn.width, nil, key)
dcenteredText(valText, Config.itemColumn.width, Config.valueColumn.background, Config.valueColumn.width, true)
else
local _, totalValueOfThisItem = valMap(key, val)
others = others + totalValueOfThisItem
end
items = items + 1
end
if others > 0 then
dinsetText("other items", 0, Config.itemColumn.background, Config.itemColumn.width, nil, key)
dcenteredText(formatMoney(others), Config.itemColumn.width, Config.valueColumn.background, Config.valueColumn.width, true)
end
end
local defaults = {
time = 1,
loot = {},
waste = {},
activeItems = {},
sumLooted = 0,
sumWasted = 0,
sumSpent = 0,
profit = 0,
}
local data = defaults
local function valueTransformer(id, count)
local value = (shAdOwHUD and shAdOwHUD.getValue(id) or itemvalue(id)) * count
return string.format("%d (%s)", count, formatMoney(value)), value
end
local function wasteTransformer(id, count)
local cost = (shAdOwHUD and shAdOwHUD.getCost(id) or itemcost(id)) * count
return string.format("%d (%s)", count, formatMoney(cost)), cost
end
local function activeWasteTransformer(id, dur)
local cost = dur * (shAdOwHUD and shAdOwHUD.getCostPerS(id) or 0)
return string.format("%s (%s)", time(dur), formatMoney(cost)), cost
end
local function drawProfits()
local totalProfit = data.profit
local hourlyProfits = totalProfit * 3600 / data.time
local background = (math.abs(hourlyProfits) < Config.hourlyColorChangeTreshold) and Config.neutralBackground or
(totalProfit > 0 and Config.profitBackground or Config.wasteBackground)
dinsetText(string.format("%s: %s (%s/h)", totalProfit >= 0 and "Profit" or "Waste", formatMoney(totalProfit), formatMoney(hourlyProfits)), 0, background, Config.width, true)
end
local drawLoot, drawWaste = true, true
local function draw()
data = shAdOwHUD and shAdOwHUD.getStats() or defaults
if isMoving then
auto(10)
x, lastX = x + ($cursor.x - lastX), $cursor.x
y, lastY = y + ($cursor.y - lastY), $cursor.y
end
row = 0
local resetWidth = measurestring("Reset") + 2 * Config.insetx
dcenteredText("shAdOwHUD Profits v" .. Config.version, 0, Config.titleBackground, Config.width - resetWidth)
drawButton("Rest", "Reset", Config.buttonNormalColor, Config.width, function() if shAdOwHUD then shAdOwHUD.reset() end end)
dcenteredText("Looted")
drawButton("Minimize Loot", drawLoot and "-" or "+", drawLoot and Config.buttonNormalColor or Config.buttonPressedColor, Config.width, function() drawLoot = not drawLoot end)
if drawLoot then
drawTable(data.loot, itemname, valueTransformer)
end
dinsetText(string.format("Total: %d gp", data.sumLooted), 0, Config.itemColumn.background, Config.width, true)
dcenteredText("Wasted")
drawButton("Minimize Waste", drawWaste and "-" or "+", drawWaste and Config.buttonNormalColor or Config.buttonPressedColor, Config.width, function() drawWaste = not drawWaste end)
if drawWaste then
drawTable(data.waste, itemname, wasteTransformer)
drawTable(data.activeItems, itemname, activeWasteTransformer)
if data.sumSpent > 0 then
dinsetText("Money Spent", 0, Config.itemColumn.background, Config.itemColumn.width, nil, 3031)
dcenteredText(wasteTransformer(3031, data.sumSpent), Config.itemColumn.width, Config.valueColumn.background, Config.valueColumn.width, true)
end
end
dinsetText(string.format("Total: %d gp", data.sumWasted + data.sumSpent), 0, Config.itemColumn.background, Config.width, true)
drawProfits()
end
return {
draw = draw,
move = move,
}
end)()
listas("shAdOwHUDProfits")
filterinput(false, true, false, false)
function inputevents(e)
if e.type == IEVENT_LMOUSEDOWN then
for _, button in pairs(Buttons) do
if e.elementid == button.elementid then
button.callback()
end
end
end
if e.type == IEVENT_MMOUSEDOWN then
HUD.move(true)
end
if e.type == IEVENT_MMOUSEUP then
HUD.move(false)
end
end
init end
HUD.draw()
Persistent:
init start
local Utility = (function()
-- These are items whose count may decrease, and should then be counted as waste
local supplies = {
-- Arrows
[itemid("arrow")] = true,
[itemid("burst arrow")] = true,
[itemid("crystalline arrow")] = true,
[itemid("earth arrow")] = true,
[itemid("envenomed arrow")] = true,
[itemid("flaming arrow")] = true,
[itemid("flash arrow")] = true,
[itemid("onyx arrow")] = true,
[itemid("poison arrow")] = true,
[itemid("shiver arrow")] = true,
[itemid("simple arrow")] = true,
[itemid("sniper arrow")] = true,
[itemid("tarsal arrow")] = true,
local function isCorpse(contName)
local corpsePatterns = {"the", "demonic", "dead", "slain", "dissolved", "remains", "elemental", "split"}
for _, pattern in ipairs(corpsePatterns) do
if contName:find(pattern) then
return true
end
end
return false
end
local function isDepot(contName)
return contName:find("depot") or contName:find("your inbox") or contName:find("locker")
end
local function isBackpack(cont)
local contName = cont.name:lower()
return not (isCorpse(contName) or isDepot(contName) or contName:find("browse field"))
end
local function changeIsWithinLimits(id, change)
if change >= 0 then
if id == 3031 then
return change < 300
elseif id == 3035 then
return change < 15
elseif itemproperty(id, ITEM_STACKABLE) then
return change < 7
else
return change < 3
end
else
return change >= (id == 3725 and -5 or -1)
end
end
-- We only want to update the diff if it contains reasonable values
local totalNewItemLimit = ($level < 100 and 5 or ($level < 200 and 7 or 9))
local lastBpCount = 0
local function diffIsRealistic(diff, new)
local totalNewCount = 0
-- If we discovered too many of any one item we invalidate the diff
for id, count in pairs(diff) do
if (count > 0 and not changeIsWithinLimits(id, count)) or
(count < 0 and Utility.isSupply(id) and not changeIsWithinLimits(id, count)) or
(count < 0 and not Utility.isSupply(id) and new[id] and not changeIsWithinLimits(id, new[id])) then
return false
end
if count > 0 then
totalNewCount = totalNewCount + 1
end
end
-- If we discovered too many new items in total we invalidate the diff
if totalNewCount > totalNewItemLimit then
return false
end
-- If we discovered a new tool we invalidate the diff
for _, id in ipairs(Utility.invalidationIDs) do
local change = diff[id]
if change and change ~= 0 then
return false
end
end
-- If we found a crystal coin we invalidate the diff
local crystalDiff = diff[itemid("crystal coin")]
if crystalDiff and crystalDiff > 0 then return false end
-- If the number of open backpacks have changed we invalidate
local foundDepot = false
local bpCount = 0
for i = 0, 16 do
local cont = getcontainer(i)
local name = cont.name:lower()
-- Invalidate if theres an open depot
if cont.isopen and isDepot(name) then
foundDepot = true
elseif cont.isopen and isBackpack(cont) then
bpCount = bpCount + 1
end
end
if bpCount ~= lastBpCount then
lastBpCount = bpCount
return false
end
-- We're returning from this condition late so that we'll be able to finish counting the backpacks first
if foundDepot then return false end
-- Otherwise we're valid
return true
end
local function environmentIsValid()
-- Dont update loot with npcs on screen
foreach creature c 'ns' do
return false
end
-- Dont update loot in pz
return not $pzone
end
local function countAllItems()
local counts, activeItems = {}, {}
-- foreach container
for contIndex = 0, 15 do
local cont = getcontainer(contIndex)
if cont.isopen and Validation.isBackpack(cont) then
-- foreach item
for itemIndex = 1, cont.itemcount do
local item = cont.item[itemIndex]
local name = itemname(item.id):lower()
-- Ignore gold in the mainbackpack since that's most likely from trading
local specialGoldCondition = contIndex == 0 and (item.id == 3031 or item.id == 3035)
if not name:find("backpack") and not name:find("chessbox") and not specialGoldCondition then
counts[item.id] = (counts[item.id] or 0) + item.count
end
end
end
end
-- Count in equipment
local slots = {$head, $chest, $legs, $feet, $neck, $lhand, $finger, $rhand, $belt}
for _, slot in ipairs(slots) do
local passiveid = Utility.getPassiveId(slot.id)
-- Active items should be counted by their passive id
local id = passiveid or slot.id
counts[id] = (counts[id] or 0) + slot.count
if passiveid then activeItems[id] = true end
end
-- Remove the counts for empty flasks and unknown items
counts[0] = nil
counts[283] = nil
counts[284] = nil
counts[285] = nil
return counts, activeItems
end
local oldCounts = {}
local function updateItemData()
local counts, activeItems = countAllItems()
local diff = {}
-- Calculate the diff between the old and the new counts
for id, newCount in pairs(counts) do
diff[id] = newCount
end
for id, oldCount in pairs(oldCounts) do
diff[id] = (diff[id] or 0) - oldCount
end
-- 0 diffs are not interesting
for id, count in pairs(diff) do
if diff[id] == 0 then
diff[id] = nil
end
end
-- Remember the new counts for the next invocation
oldCounts = counts
-- Filter the diff into loot and waste
local loot, waste = {}, {}
for id, count in pairs(diff) do
if count > 0 then
loot[id] = count
-- Only accept waste of valid supplies
elseif Utility.isSupply(id) then
waste[id] = 0 - count
end
end
-- If the diff might have been corupted we return no diff for count based items
if not Validation.diffIsRealistic(diff, counts) or $tradeopen then
loot, waste = {}, {}
end
-- Dont count loot in pz
if not Validation.environmentIsValid() then
loot = {}
end
return loot, waste, activeItems
end
-- Call once to ignore item that were had on start-up
updateItemData()
shAdOwHUD = (function ()
local customValues, customPrices = {}, {[6529] = 10000}
local function updateCustomValuesAndPrices()
customValues, customPrices = {}, {[6529] = 10000}
foreach lootingitem item do
customValues[item.id] = item.sellprice
end
foreach supplyitem item do
customPrices[item.id] = item.buyprice
end
end
local totalLooted, totalWasted = 0, 0
local loot, waste, activeItems = {}, {}, {}
local totalTime, lastInvocation = 0, os.clock()
local totalSpent = 0
local lastSpent = $moneyspent
local function reset()
totalLooted, totalWasted, totalSpent, totalTime = 0, 0, 0, 0
loot, waste, activeItems = {}, {}, {}
end
local function updateStats()
updateCustomValuesAndPrices()
local now = os.clock()
local diff = (now - lastInvocation)
diff = (diff < 10 and $connected) and diff or 0
totalTime = totalTime + diff
lastInvocation = now
local newLoot, newWaste, newActiveItems = ItemData.updateItemData()
for id, newCount in pairs(newLoot) do
loot[id] = (loot[id] or 0) + newCount
end
for id, newCount in pairs(newWaste) do
waste[id] = (waste[id] or 0) + newCount
end
for id, _ in pairs(newActiveItems) do
activeItems[id] = (activeItems[id] or 0) + diff
end
totalLooted, totalWasted = 0, 0
for id, count in pairs(loot) do
totalLooted = totalLooted + (customValues[id] or Utility.getValue(id)) * count
end
for id, count in pairs(waste) do
totalWasted = totalWasted + (customPrices[id] or Utility.getCost(id)) * count
end
for id, dur in pairs(activeItems) do
totalWasted = totalWasted + (customPrices[id] or Utility.getCost(id)) / Utility.getDuration(id) * dur
end