It would be very useful to have a coloured tile like on "show waypoint/special area" showing which tiles has a not yet looted dead body. This would be amazing when manual hunting many monsters, as we often miss some loots.
Printable View
It would be very useful to have a coloured tile like on "show waypoint/special area" showing which tiles has a not yet looted dead body. This would be amazing when manual hunting many monsters, as we often miss some loots.
Not really doable right now.
Unfortunately =(
But it shouldn't be that hard to code it into bot core, right? The bot identifies the bodies already.
I've been thinking about this today @Raphael, isn't it doable using "foreach deadcreature c do..." that gives us the coordinates and using $lootbodies, then implementing it into a HUD?
You're right =[
Would be a nice addition to have this info,as there are even some more useless variables comparing to having this one, like $lootsaround, it should be almost the same as $lootbodies right
Any changes on this? Maybe on a to do list to be possible to implement for future versions, like windbot 3?
Raphael, as this feature would really be good (I'm hunting more or less manually on oramond wests and sometimes I miss good loot), I still think on ways to implement it.
When I said about foreach deadcreature you said "How do you know which ones were looted, though?". What if we color the tile of the recently dead monster, combined with the loot message existing on many HUD's. That way it should be possible to know which valuable creature died on a specific SQM. Instead of caring if that body was already looted we could maybe set a X minutes timer, that would be reseted as soon as we reach X minutes. After that time the tile get reseted and we don't get any color there anymore.
Is it even possible?
Bump @Raphael
@Jesseh, sorry for the long delay to reply, life has been rough lately. Yes, this does (partially) solve the problem of not knowing if it's looted or not but still means I have to redo a lot of the work the bot already does. Seeing as time has been short and I hate reinventing the wheel, I'll not be implementing this for now. @mistgun might wanna tackle it though, not sure.
Thanks @Raphael, no problem.
@mistgun: I saw a HUD from you, with fire wall timers. I don't know how to implement it, but I think it has similar things, like the time counter. I thought of the timer way, reseting the tile after some time, but also thought on using the ID of the ground, for example, if the monster is recently dead it got an ID, after some time the corpse decay and the ID changes, resetting the tile, but this option would have the problem, at first, of other monsters dieing on the same SQM.
The bot should sinchronize the recent deadcreature with it's coordinates, set a timer there, to keep it colored, and reset after the timer ends.
Is it really that hard to remember what monsters you looted? :P
When you Hunt luring like 15+ monsters at a time and many of them die at the same time, and you keep walking luring more, it becomes harder to track which SQMs got a good loot, so you must walk near all dead monsters and wait a bit until bot identifies which one is the right one.
Bump. Any ideas?
I don't know exactly how, or if it's possible. There is a function with "foreach deadcreature" that can store the coordinates of that dead body. Something must be done in order to concatenate that SQM to a recent loot msg, one that shows a valuable body. I don't know if that binding exists or if we need to try to figure it via a sloppy workaround, like one time based.
So I'm pretty sure this isn't possible now. There are two problems that prevent you from tying loot messages to dead creatures;
- foreach deadcreature does not iterate through the corpses in the same order as the message iterator iterates through the loot messages.
- foreach deadcreature is not really tied to creatures dying at all, but rather it looks for corpses on the floor. Whenever a creature dies offscreen you're in trouble.
I have some ideas that may be able to work around the latter restriction but the former prevents the script from working under all circumstances where it's actually useful.
Anyway, here's what I ended up with before I quit:
init start
local DELAY = 1000
setfontstyle('Tahoma', 7, 75, 0xFFFFFF, 1, 0x000000)
-- Assumes no overlap between the keysets
local function mergeTables(a, b)
local a = a or {}
local b = b or {}
local res = {}
for key, val in pairs(a) do
res[key] = val
end
for key, val in pairs(b) do
res[key] = val
end
return res
end
-- Consume current dead creatures
foreach deadcreature c do end
local unlootedCorpses = {}
local lastCorpseIndex = 1
local function checkCorpses()
foreach deadcreature c do
local corpseData = {x = c.posx, y = c.posy, z = c.posz}
unlootedCorpses[lastCorpseIndex] = mergeTables(unlootedCorpses[lastCorpseIndex], corpseData)
lastCorpseIndex = lastCorpseIndex + 1
end
end
-- Lifted from Rydan
local pluralExceptions = {
{"knives", "knife"},
{"pieces of *", "piece of "},
{"*pieces of", "piece of "},
{"bunches of *", "bunch of "},
{"haunches of *", "haunch of "},
{"flasks of *", "flask of "},
{"veins of *", "vein of "},
{"bowls of *", "bowl of "},
{"sandwiches", "sandwich"}
}
-- Lifted from Rydan
local pluralEndings = {
{"che", "ch"},
{"she", "sh"},
{"ie", "y"},
{"ve", "fe"},
{"oe", "o"},
{"ze", "z"}
}
-- Lifted from Rydan
function string:explode(div)
if (div == '') then
return false
end
local pos, arr = 0, {}
for st, sp in function() return string.find(self, div, pos, true) end do
table.insert(arr, string.sub(self, pos, st - 1))
pos = sp + 1
end
table.insert(arr, string.sub(self, pos))
for i = 1, #arr do
arr[i] = arr[i]:trim()
end
return arr
end
-- Mostly lifed from Rydan
local function getLootAsTable(msg)
local tbl = {}
local parts = msg:explode(", ")
for i = 1, #parts do
local name = parts[i]
local count = tonumber(name:match("^(%d+)"))
if count then
name = name:gsub("^%d+ ", "")
for j = 1, #pluralExceptions do
local pluralException = pluralExceptions[j]
if name:find(pluralException[1]) then
name = name:gsub(pluralException[1], pluralException[2])
break
end
end
if name:find("s$") then
name = name:sub(1, -2)
for j = 1, #pluralEndings do
local pluralEnding = pluralEndings[j]
if name:find(pluralEnding[1].."$") then
name = name:gsub(pluralEnding[1].."$", pluralEnding[2])
break
end
end
end
end
count = count or 1
name = name:gsub("^a ", "")
name = name:gsub("^an ", "")
table.insert(tbl, {id = itemid(name), count = count})
end
return tbl
end
-- Consume all previous messages
local lastInfoMsgId = 1
foreach newmessage m do
if m.type == MSG_INFO then
lastInfoMsgId = lastInfoMsgId + 1
end
end
local lastLootMsgIndex = 1
local function checkLootMsgs()
local id = 1
foreach newmessage m do
if m.type == MSG_INFO then
-- Ignore old messages
if id >= lastInfoMsgId then
local monster, loot = m.content:match('Loot of a?n? (.+): (.+)')
-- Ignore non loot messages
if monster then
print(loot)
local loot = loot:lower()
local lootData = {loot = {}}
if loot ~= 'nothing' then
-- Figure out if the loot message contains an interesting item
local containsInteresting = false
foreach lootingitem item do
if loot:find(item.name) then
containsInteresting = true
break
end
end
-- Only add loot data for messages containing relevant loot
if containsInteresting then
lootData.msg = loot
lootData.loot = getLootAsTable(loot)
end
end
unlootedCorpses[lastLootMsgIndex] = mergeTables(unlootedCorpses[lastLootMsgIndex], lootData)
lastLootMsgIndex = lastLootMsgIndex + 1
end
end
id = id + 1
end
end
lastInfoMsgId = id
end
-- Liften from the waypoint hud
local function gettilepos(x, y, z)
local tile = getobjectarea(x, y, z)
if tile == nil then
local xDiff, yDiff = x - $posx, y - $posy
if math.abs($posx - x) <= 7 then
tile = getobjectarea(x, $posy, $posz)
xDiff = 0
elseif math.abs($posy - y) <= 5 then
tile = getobjectarea($posx, y, $posz)
yDiff = 0
else
tile = getobjectarea($posx, $posy, $posz)
end
-- Some strange stuff happens when you go from 0 to -1, so I'm
-- adding this as a precaution.
if tile ~= nil then
local width, height = $worldwin.width, $worldwin.height
tile.left = tile.left + (width * xDiff)
tile.right = tile.right + (width * xDiff)
tile.centerx = tile.centerx + (width * xDiff)
tile.top = tile.top + (height * yDiff)
tile.bottom = tile.bottom + (height * yDiff)
tile.centery = tile.centery + (height * yDiff)
end
end
return tile
end
useworldhud()
local function drawCorpses()
local function isOnScreen(corpse)
return corpse.z == $posz and math.abs(corpse.y - $posy) <= 5 and math.abs(corpse.x - $posx) <= 7
end
for _, corpse in pairs(unlootedCorpses) do
if corpse.msg and isOnScreen(corpse) then
local tile = gettilepos(corpse.x, corpse.y, corpse.z)
drawtext(corpse.msg, tile.left, tile.top)
end
end
end
local function updateLooted()
local function isCorpse(cont)
local corpseWords = {"the", "demonic", "dead", "slain", "dissolved", "remains", "elemental", "split"}
local name = cont.name:lower()
for _, word in ipairs(corpseWords) do
if name:find(word) then
return true
end
end
return false
end
local function contentsMatch(cont, corpse)
if not corpse.loot or cont.itemcount ~= #corpse.loot then return false end
for i = 1, cont.itemcount do
local contItem = cont.item[i]
local corpseItem = corpse.loot[i]
if contItem.id ~= corpseItem.id or contItem.count ~= corpseItem.count then return false end
end
return true
end
local function isAdjecent(corpse)
return corpse.z == $posz and math.abs(corpse.y - $posy) <= 1 and math.abs(corpse.x - $posx) <= 1
end
-- foreach container
for i = 0, 15 do
local cont = getcontainer(i)
if cont.isopen and isCorpse(cont) then
for index, corpse in pairs(unlootedCorpses) do
-- Remove corpses whose loot messages exactly matches some open corpse
if isAdjecent(corpse) and contentsMatch(cont, corpse) then
unlootedCorpses[index] = nil
break
end
end
end
end
end
local lastc, lastm = 1, 1
local function debugPrint()
if lastCorpseIndex > lastc or lastLootMsgIndex > lastm then
print(string.format("CorpseIndex: %d, MsgIndex: %d", lastCorpseIndex, lastLootMsgIndex))
lastc = lastCorpseIndex
lastm = lastLootMsgIndex
end
end
init end
auto(DELAY)
checkCorpses()
checkLootMsgs()
drawCorpses()
updateLooted()
debugPrint()
Note that checking whether a corpse has been looted or not can be done with good accuracy by compating the contents of open corpses the contents of adjecent corpses, if you manage to correctly tie loot messages to corpses. Tying loot messages to corpses really is the crux of it all, it's the same problem I ran into when I tried to write a very similar script in Xeno a long time ago.
Great effort @shAdOwArt! But as you said, it may not work correctly and we may need to wait for an update, if this feature catch developers' eyes.
By now, what I do is,I set looting range to like 15 SQM or lower, set to loot 101+ gp value bodies, and after killing monsters manually, I start cavebot with no waypoints, so it starts running and collecting useful loot, but sometimes I get into trouble with the bot running into many monsters and not stopping even if cavebot is already set to off.
Number 1 really isn't that much of a problem, all you gotta do is keep a "cache" of the currently dead monsters and when you read a "Loot of..." message, check which new corpse was added. That's the one that just died. I'd argue you don't even need foreach deadcreature, you should just loop over all alive creatures (foreach creature) and if one disappears within a certain timespan of a "Loot of..." message, then that's the creature. Well, both are the same approach on a different direction.
Number 2 doesn't really have any real solution, there's not much that can be done, unfortunately.
About your code, a few possible improvements:
- WindBot has string.explode and table.marge (equivalent of your mergeTable) baked in one of its standard libs (Mine, actually)
- WindBot has a few useful regexes like REGEX_LOOT baked in one of its standard libs (Mine, actually)
Other than that, pretty good stuff. The part about checking for looted bodies was really well thought...
I always dream with something like that when hunting catacombs, so it can highlight sqm of rare loot bodies
Isn't that exactly what I am doing? Even if I check for corpses from inside the function that reads the loot messages I will still run into problems in situations where multiple monsters die between two invocations of the message handler (which frequently will happen regardless of how small we make the delay given to auto). Then there will be multiple new loot messages, and multiple new corpses but no real way to connect them to each others.
Thanks, it's the first wb script I've ever written.Quote:
About your code, a few possible improvements:
- WindBot has string.explode and table.marge (equivalent of your mergeTable) baked in one of its standard libs (Mine, actually)
- WindBot has a few useful regexes like REGEX_LOOT baked in one of its standard libs (Mine, actually)
Other than that, pretty good stuff. The part about checking for looted bodies was really well thought...
I haven't really had that much of a look at your code, so I'm sorry if that's how you did that. That should work really well for the vast majority of the cases for the vast majority of the hunting places. When it doesn't, simply assuming both dropped the most valuable stuff seems like a valid option.