@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.
08-17-2016, 02:26 AM
Jesseh
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.
08-17-2016, 05:40 PM
hamiltonx
Is it really that hard to remember what monsters you looted? :P
08-17-2016, 06:04 PM
Jesseh
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.
08-30-2016, 05:24 PM
Jesseh
Bump. Any ideas?
08-30-2016, 06:17 PM
shAdOwArt
Quote:
Originally Posted by Jesseh
Bump. Any ideas?
This seemed like an interesting problem, one which cannot be solved on Xeno where I normally hang out. Does Windbot's API reveal which corpse that is bound to which loot message?
08-30-2016, 06:35 PM
Jesseh
Quote:
Originally Posted by shAdOwArt
This seemed like an interesting problem, one which cannot be solved on Xeno where I normally hang out. Does Windbot's API reveal which corpse that is bound to which loot message?
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.
08-31-2016, 07:12 AM
shAdOwArt
Quote:
Originally Posted by Jesseh
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:
-- 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
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
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
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.
08-31-2016, 12:44 PM
Jesseh
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.
08-31-2016, 03:07 PM
Raphael
Quote:
Originally Posted by shAdOwArt
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:
...
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.
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...