That's pretty cool! Considered making it scrollable?
Printable View
Do you think it's possible to count necklaces like gill or prismatic to waste?
Anyway, good job :)
Great hud, but I have question. Can u add EXP left to lvl? Its complicated?
links are dead can someone upload it again here ?
I just have the loot
HUD:
init start
local UserConfig = {
titleColor = {90, 0, 0},
headerColor = {90, 0, 0},
leftColumnColor = {55, 55, 55},
rightColumnColor = {85, 85, 85},
buttonPressedColor = {0, 90, 0},
buttonNormalColor = {90, 0, 0},
hourlyColorChangeTreshold = 5000,
wasteColor = {130, 0, 0},
profitColor = {0, 130, 0},
neutralColor = {65, 65, 0},
fontSize = 7,
maxItems = 20,
}
setfontstyle("Tahoma", UserConfig.fontSize, 75, color(255, 255, 255, 0), 1, color(0, 0, 0, 50))
setfillstyle("gradient", "linear", 2, 0, 0, 0, UserConfig.unitHeight)
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")
Config.width = totalWidth
Config.unitHeight = unitHeight
Config.unityDiff = unitHeight + 0
Config.width = totalWidth
Config.itemWidth = leftWidth
Config.rightWidth = totalWidth - leftWidth
Config.insetx = Config.fontSize
Config.insety = (unitHeight - stringHeight)/2 + 2
Config.radius = 4
Config.header = {width = totalWidth, background = fixColor(unpack(Config.headerColor))}
Config.itemColumn = {width = leftWidth, background = fixColor(unpack(Config.leftColumnColor))}
Config.valueColumn = {width = totalWidth - leftWidth, background = fixColor(unpack(Config.rightColumnColor))}
Config.titleBackground = fixColor(unpack(Config.titleColor))
Config.profitBackground = fixColor(unpack(Config.profitColor))
Config.wasteBackground = fixColor(unpack(Config.wasteColor))
Config.neutralBackground = fixColor(unpack(Config.neutralColor))
Config.buttonPressedColor = fixColor(unpack(Config.buttonPressedColor))
Config.buttonNormalColor = fixColor(unpack(Config.buttonNormalColor))
Config.border = {1, color(0, 0, 0, 50)}
Config.imageWidth = unitHeight
Config.iteminsety = Config.insety
Config.imageZoom = 50
end
fixConfig()
local Buttons = {}
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
setposition($clientwin.right - Config.width - Config.rightMargin + x, $worldwin.top + y)
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,
-- Bolts
[itemid("bolt")] = true,
[itemid("drill bolt")] = true,
[itemid("infernal bolt")] = true,
[itemid("piercing bolt")] = true,
[itemid("power bolt")] = true,
[itemid("prismatic bolt")] = true,
[itemid("vortex bolt")] = true,
-- Throwing weapons
[itemid("enchanted spear")] = true,
[itemid("glooth spear")] = true,
[itemid("hunting spear")] = true,
[itemid("royal spear")] = true,
[itemid("spear")] = true,
[itemid("assassin star")] = true,
[itemid("snowball")] = true,
[itemid("small stone")] = true,
[itemid("throwing knife")] = true,
[itemid("throwing star")] = true,
[itemid("viper star")] = true,
-- Amulets
[itemid("gill necklace")] = true,
[itemid("prismatic necklace")] = true,
[itemid("protection amulet")] = true,
[itemid("bonfire amulet")] = true,
[itemid("bronze amulet")] = true,
[itemid("dragon necklace")] = true,
[itemid("elven amulet")] = true,
[itemid("garlic necklace")] = true,
[itemid("glooth amulet")] = true,
[itemid("glacier amulet")] = true,
[itemid("Leviathan's amulet")] = true,
[itemid("lightning pendant")] = true,
[itemid("magma amulet")] = true,
[itemid("necklace of the deep")] = true,
[itemid("sacred tree amulet")] = true,
[itemid("shockwave amulet")] = true,
[itemid("silver amulet")] = true,
[itemid("stone skin amulet")] = true,
[itemid("strange talisman")] = true,
[itemid("terra amulet")] = true,
[itemid("collar of blue plasma")] = 30 * 60,
[itemid("collar of green plasma")] = 30 * 60,
[itemid("collar of red plasma")] = 30 * 60,
-- Potions
[itemid("mana potion")] = true,
[itemid("strong mana potion")] = true,
[itemid("great mana potion")] = true,
[itemid("ultimate mana potion")] = true,
[itemid("small health potion")] = true,
[itemid("health potion")] = true,
[itemid("strong health potion")] = true,
[itemid("great health potion")] = true,
[itemid("ultimate health potion")] = true,
[itemid("supreme health potion")] = true,
[itemid("great spirit potion")] = true,
[itemid("ultimate spirit potion")] = true,
-- Runes
[itemid("animate dead rune")] = true,
[itemid("avalanche rune")] = true,
[itemid("chameleon rune")] = true,
[itemid("convince creature rune")] = true,
[itemid("cure poison rune")] = true,
[itemid("destroy field rune")] = true,
[itemid("energy bomb rune")] = true,
[itemid("energy field rune")] = true,
[itemid("energy wall rune")] = true,
[itemid("explosion rune")] = true,
[itemid("fire bomb rune")] = true,
[itemid("fire field rune")] = true,
[itemid("fire wall rune")] = true,
[itemid("fireball rune")] = true,
[itemid("great fireball rune")] = true,
[itemid("heavy magic missile rune")] = true,
[itemid("holy missile rune")] = true,
[itemid("icicle rune")] = true,
[itemid("intense healing rune")] = true,
[itemid("magic wall rune")] = true,
[itemid("poison bomb rune")] = true,
[itemid("poison field rune")] = true,
[itemid("poison wall rune")] = true,
[itemid("soulfire rune")] = true,
[itemid("stalagmite rune")] = true,
[itemid("stone shower rune")] = true,
[itemid("sudden death rune")] = true,
[itemid("thunderstorm rune")] = true,
[itemid("ultimate healing rune")] = true,
[itemid("wild growth rune")] = true,
-- Misc
[itemid("scarab coin")] = true,
[itemid("brown mushroom")] = true,
[itemid("might ring")] = true,
}
local function isSupply(id)
return supplies[id]
end
local function getValue(id)
return itemvalue(id)
end
local function getCost(id)
return itemcost(id)
end
local rings = {
"axe ring",
"club ring",
"death ring",
"dwarven ring",
"energy ring",
"life ring",
"power ring",
"prismatic ring",
"ring of healing",
"stealth ring",
"sword ring",
"time ring",
}
local durations = {}
for _, name in ipairs(rings) do
durations[itemid(name)] = iteminfo(name).durationtotalinmsecs / 1000
end
local passiveIDs = {
[3549] = 6529,
[6530] = 6529,
}
for passiveid, _ in pairs(durations) do
passiveIDs[ringinuse(passiveid)] = passiveid
end
-- Soft boots
durations[6529] = 14400
local function getDuration(id)
return durations[id]
end
local function getPassiveId(id)
return passiveIDs[id]
end
local invalidationIDs = {
itemid("shovel"),
itemid("light shovel"),
itemid("rope"),
itemid("elvenhair rope"),
itemid("fishing rod"),
itemid("soft boots")
}
return {
isSupply = isSupply,
getValue = getValue,
getCost = getCost,
getDuration = getDuration,
getPassiveId = getPassiveId,
invalidationIDs = invalidationIDs,
}
end)()
local Validation = (function()
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
return {
diffIsRealistic = diffIsRealistic,
isBackpack = isBackpack,
environmentIsValid = environmentIsValid,
}
end)()
local ItemData = (function()
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()
return {
updateItemData = updateItemData,
countAllItems = countAllItems,
}
end)()
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
totalSpent = totalSpent + ($moneyspent - lastSpent)
lastSpent = $moneyspent
end
local function getStats()
return {
time = totalTime,
loot = loot,
waste = waste,
activeItems = activeItems,
sumLooted = totalLooted,
sumWasted = totalWasted,
sumSpent = totalSpent,
profit = totalLooted - totalWasted - totalSpent
}
end
local function getValue(id)
return customValues[id] or itemvalue(id)
end
local function getCost(id)
return customPrices[id] or itemcost(id)
end
local function getCostPerS(id)
local duration = Utility.getDuration(id)
return duration and getCost(id) / duration or 0
end
return {
getStats = getStats,
getValue = getValue,
getCost = getCost,
getCostPerS = getCostPerS,
updateStats = updateStats,
reset = reset,
}
end)()
listas("shAdOwHUDData")
init end
shAdOwHUD.updateStats()
auto(200)
Thanks, @Cisco
I actually wanted just the loot .. because the count stealth rings thing :D
thanks
any idea how to edit the price of the ring ?
edit
" If you want to change the value or the price of an item you have to add it to the looting or the supplies section and change it there."
nvm ty