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
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
Last edited by lantian; 07-12-2017 at 03:13 PM.