Thread Tools Display Modes
05-18-17, 11:16 PM   #1
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Logging the items my character acquired

Hi,

I am working on a simple addon, which is supposed to log (i.e. save in a text/lua file) the items that I acquire. This is for my own personal use and to keep track of which items I have acquired in the game so far. These items include anything my character can receive via loot, mail, trade, vendor, quest reward, crafting, containers etc.

I have the following code so far:

Lua Code:
  1. LootLog = {}
  2.  
  3. local f = CreateFrame("Frame")
  4. f:RegisterEvent("BAG_UPDATE")
  5.  
  6. local function Log_Loot()
  7.     for bag = 0, NUM_BAG_SLOTS do
  8.         for slot = 1, GetContainerNumSlots(bag) do
  9.             local item = GetContainerItemLink(bag, slot)
  10.             local itemName = select(1, GetItemInfo(item))
  11.            
  12.             table.insert(LootLog, itemName)
  13.         end
  14.     end
  15. end
  16.  
  17. f:SetScript("OnEvent", Log_Loot)

It gets the item name correctly, it stores the item name correctly in the LootLog.lua file in savedvariables folder, but it stores it 183 times. When I acquire an item, it creates 183 entries with that item's name. When I acquire a second item, it creates another 183 entries with those two items' names. What is wrong with my code? I heard that to do it efficiently, I should register BAG_UPDATE after PLAYER_ENTERING_WORLD. How do I do that?

Thank you very much.

Last edited by Eommus : 05-20-17 at 12:37 AM.
  Reply With Quote
05-19-17, 01:00 AM   #2
Fizzlemizz
I did that?
 
Fizzlemizz's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Dec 2011
Posts: 1,871
You are continually adding to an already existing table. At the very least, LootLog = {} should be performed on each parse ("BAG_UPDATE").
__________________
Fizzlemizz
Maintainer of Discord Unit Frames and Discord Art.
Author of FauxMazzle, FauxMazzleHUD and Move Pad Plus.
  Reply With Quote
05-19-17, 02:18 AM   #3
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Originally Posted by Fizzlemizz View Post
You are continually adding to an already existing table. At the very least, LootLog = {} should be performed on each parse ("BAG_UPDATE").
Thanks. I moved LootLog = {} to just above table.insert(LootLog, itemName), but it saves the items only for the current session. The next time I log in, the items log is empty. I want it to be saved between sessions.

Let's say, I acquired 100 items in a play session, I want them all to be logged in the LootLog.lua savedvariable file. In my next play session, say, I acquired 60 items, I want them to be added to the same file. A growing log of items in other words.

Last edited by Eommus : 05-19-17 at 02:51 AM.
  Reply With Quote
05-19-17, 08:26 AM   #4
Kakjens
A Cliff Giant
Join Date: Apr 2017
Posts: 75
Originally Posted by Eommus View Post
Thanks. I moved LootLog = {} to just above table.insert(LootLog, itemName), but it saves the items only for the current session. The next time I log in, the items log is empty. I want it to be saved between sessions.

Let's say, I acquired 100 items in a play session, I want them all to be logged in the LootLog.lua savedvariable file. In my next play session, say, I acquired 60 items, I want them to be added to the same file. A growing log of items in other words.
I don't see how moving LootLog = {} to just above table.insert(LootLog, itemName) would save anything but the last entry.
Also, I don't see how you are loading items from previous session.
Instead of table.insert(LootLog, itemName) I would suggest using LootLog[itemName] = {}, assuming you don't want to track how many items you obtained.
Edit. Also, there might be better events to watch, for example, "BAG_UPDATE_DELAYED" or events dealing with loot. Additionally, I think long time ago there was an addon that tracked what previously dropped from a mob. Due to AoE looting, it might be abandoned.

Last edited by Kakjens : 05-19-17 at 08:46 AM.
  Reply With Quote
05-19-17, 09:48 AM   #5
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 666
The LootLog table initialization was fine where it was. The problem is that you're looking through your entire inventory every bag update, adding an item 183 times because you have 183 total slots. What I find weird is that it's adding the same item, but your code should add every item the way it's written.

In order to log a new item, you need to maintain a separate memory table that keeps track of what's in your bags, then log any difference. You can either log both additions and removals or just additions, like logging only if the bag/slot was empty and an item name was returned.

I would write up a working example, but I'm on my phone. I'll see if I can help later today.
  Reply With Quote
05-19-17, 10:06 AM   #6
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Originally Posted by Kanegasi View Post
The LootLog table initialization was fine where it was.
Yes, I read that it is done like that to save data between sessions. Though, I am just a newbie to LUA, so, just trying to stitch together bits of info here or there.

Originally Posted by Kanegasi View Post
The problem is that you're looking through your entire inventory every bag update, adding an item 183 times because you have 183 total slots.
How do you calculate 183? I have backpack (16), 4 x 30 slot bags, shouldn't it be 136 in total?

Originally Posted by Kanegasi View Post
What I find weird is that it's adding the same item, but your code should add every item the way it's written.
I also found that weird

Originally Posted by Kakjens View Post
I don't see how moving LootLog = {} to just above table.insert(LootLog, itemName) would save anything but the last entry.
Also, I don't see how you are loading items from previous session.
Instead of table.insert(LootLog, itemName) I would suggest using LootLog[itemName] = {}, assuming you don't want to track how many items you obtained.
Edit. Also, there might be better events to watch, for example, "BAG_UPDATE_DELAYED" or events dealing with loot. Additionally, I think long time ago there was an addon that tracked what previously dropped from a mob. Due to AoE looting, it might be abandoned.
I started with LOOT_OPENED event which worked just fine for item acquisition from mobs, gathering, containers etc. (things that open a loot window) but I want to log every item I acquire in every possible way as mentioned in my first post (loot, quest reward, trade, vendor, crafting etc.). Anything that enters into my inventory from an external source. I was recommended to use BAG_UPDATE as it seemed the best candidate.

With your tips and comments, I was finally able to do it, using the code below:

Lua Code:
  1. LootLog = {}
  2.  
  3. local f = CreateFrame("Frame")
  4. f:RegisterEvent("PLAYER_ENTERING_WORLD")
  5. f:RegisterEvent("BAG_UPDATE")
  6.  
  7. local function Log_Loot()
  8.     for bag = 0, NUM_BAG_SLOTS do
  9.         for slot = 1, GetContainerNumSlots(bag) do
  10.             local item = GetContainerItemLink(bag, slot)
  11.             local itemName = select(1, GetItemInfo(item))
  12.            
  13.             LootLog[itemName] = {}
  14.         end
  15.     end
  16. end
  17.  
  18. f:SetScript("OnEvent", Log_Loot)

My last questions: Is my use of PLAYER_ENTERING_WORLD correct (I used it for efficiency, not sure). Is the code I have efficient?

Thanks.

Last edited by Eommus : 05-19-17 at 01:13 PM.
  Reply With Quote
05-19-17, 12:56 PM   #7
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
You can use C_NewItems to query whether an item is new or not, then clear it manually.

Lua Code:
  1. isNewItem = C_NewItems.IsNewItem(bag, slot)
  2. C_NewItems.RemoveNewItem(bag, slot)
  3. C_NewItems.ClearAll()

Note that in the case of stacks, your stack will not be flagged as new when you loot another item of that type.
The default UI uses this class to highlight new items in your bags, and it also clears all new items whenever you close the container they're in. You'll have to do the lookup on BAG_UPDATE and clear them yourself before the container frame gets a chance to do it. This way you can track any new item that you acquire.
__________________

Last edited by MunkDev : 05-19-17 at 12:59 PM.
  Reply With Quote
05-19-17, 12:59 PM   #8
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
You should try CHAT_MSG_LOOT. I don't know that it will fire for every conceivable situation, but I don't think BAG_UPDATE will work very well. First of all you'd need to keep track of what's actually changed between every update, and not just store everything in your bags. BAG_UPDATE also fires when items are removed, stacks are split, etc. Anything that requires the bag UI to update. What if you swap two items with each other, or put an equipped item in your bags?
__________________
Grab your sword and fight the Horde!
  Reply With Quote
05-19-17, 01:02 PM   #9
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Originally Posted by Lombra View Post
You should try CHAT_MSG_LOOT.
All in all, I don't think you'll find a single event that can account for the entire loot log.
You probably need to work with multiple events and draw information from different sources.
__________________
  Reply With Quote
05-19-17, 01:22 PM   #10
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Originally Posted by Lombra View Post
First of all you'd need to keep track of what's actually changed between every update, and not just store everything in your bags.
Interestingly, the code I have in post #6 above stores only items I acquire. It does not store everything in my bags. It looks it is not working as it should, but it is doing what I need it to do.

Currently, my addon works just fine. My only concern is whether it is efficient or not. From a general programming perspective, using BAG_UPDATE is not efficient for identifying newly acquired items. I currently don't know how to use CHAT_MSG_LOOT, but I will check. If there was a simple ITEM_RECIEVED event, that would be perfect.

Is there a way to check or compare two lua scripts in terms of efficiency? For example, using BAG_UPDATE vs using CHAT_MSG_LOOT.

@MunkDev, the current code stores only the newly acquired items, so, I will note your suggestion about C_NewItems, but I will not use it for the moment.

Thanks again.

Last edited by Eommus : 05-20-17 at 12:19 AM.
  Reply With Quote
05-19-17, 03:09 PM   #11
Kakjens
A Cliff Giant
Join Date: Apr 2017
Posts: 75
Link in gamepedia about CHAT_MSG_LOOT.
When comparing OnEvent scripts, both the frequency of the event firing, and the complexity of it (for example, getting info from inventory is slower than doing arithmetical operations with local variables).
The frequency of different events can be estimated by assigning an integer to number of calls of particular event. The complexity of code can be evaluated by having it repeated CRAZY but the same number of times (say, 10000), and recording execution time.
With BAG_UPDATE in this particular case it's looping through inventory many times per session despite (potentially) just rearranging items in bag/splitting a stack.
With CHAT_MSG_LOOT the corresponding script of getting itemlink from the text, or saving the text as is. Moreover, it's supposed to run just when you receive an item.
  Reply With Quote
05-19-17, 06:33 PM   #12
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Originally Posted by Eommus View Post
@MunkDev, the current code stores only the newly acquired items, so, I will note your suggestion about C_NewItems, but I will not use it for the moment.
It seemingly only stores the items you acquire because you're overwriting the table entry for each existing item on every BAG_UPDATE. This isn't the same as tracking the items you acquire, you are simply tracking the items you have. What this also means is that since you're not wiping the log on every update, you'll also keep any items that you already have logged but no longer possess.

It's unclear whether you want your addon to track items that you have acquired but no longer possess or just the items you currently have. Logging the items you acquire can mean both of these things and using C_NewItems is certainly not necessary in the latter case. If you, as an example, want to track how many of a particular item you've acquired over time then your current solution would not work.
__________________

Last edited by MunkDev : 05-19-17 at 06:36 PM.
  Reply With Quote
05-19-17, 11:00 PM   #13
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 666
Originally Posted by Eommus View Post
Yes, I read that it is done like that to save data between sessions. Though, I am just a newbie to LUA, so, just trying to stitch together bits of info here or there.
That is called SavedVariables. They need to be declared in your .toc file in order to save them. Otherwise, what you have there is just a global table that will be discarded upon logout.

I have drycoded (as in, not tested) something that should log everything new. The first time you run this, it will assume everything in your bags is new. You are safe to immediately logout, open the saved variable file, and delete "MyLootLog" after this first run. Leave the other table, "MyCurrentBags", alone. The second table is important to ensure what you log is new.

First step, open your .toc file for this addon and add the following line to the header:

Code:
## SavedVariables: MyLootLog,MyCurrentBags

Secondly, replace all the code you have with the following:

lua Code:
  1. local a,e=...
  2. local LL=CreateFrame('frame',a)
  3. LL:RegisterEvent('ADDON_LOADED')
  4. LL:RegisterEvent('PLAYER_LOGIN')
  5. local llog,bags={},{} -- local log and image tables
  6. MyLootLog,MyCurrentBags={},{} -- initialize SavedVariables
  7.  
  8. function e.ADDON_LOADED(addon)
  9.     if addon==a then -- load saved data into local tables, data is automatically linked between tables
  10.         bags=MyCurrentBags
  11.         llog=MyLootLog
  12.     end
  13. end
  14.  
  15. function e.PLAYER_LOGIN()
  16.     LL:RegisterEvent('BAG_UPDATE') -- delays BAG_UPDATE until full login in attempt to minimize processing impact
  17. end
  18.  
  19. function e.BAG_UPDATE()
  20.     local move,new={} -- temp tables for moved and new items
  21.     for b=0,NUM_BAG_SLOTS do for s=1,GetContainerNumSlots(b) do -- loop through bags
  22.         local _,item=strsplit('[]',GetContainerItemLink(b,s) or '') -- split item link into three parts, second part being the name
  23.         if bags[b..s] and bags[b..s]~=item then -- if something was there and current doesn't match...
  24.             move[bags[b..s]]=1 -- ...assume item was moved...
  25.             new[bags[b..s]]=nil -- ...and remove from new if there
  26.         elseif item and item~=bags[b..s] then -- there's an item there that wasn't there...
  27.             if not move[item] then new[item]=1 end -- ...so add to new if not moved
  28.         end
  29.         bags[b..s]=item -- update bag image
  30.     end end
  31.     for k in next,new do tinsert(llog,k) end -- loop through new and add to log, should only be one item to add each event, except for first run
  32. end
  33.  
  34. LL:SetScript('OnEvent',function(_,event)e[event]()end) -- calls registered events


Final note, you can use /dump MyLootLog ingame to see the last 30 items you looted.

Last edited by Kanegasi : 05-19-17 at 11:28 PM.
  Reply With Quote
05-20-17, 12:36 AM   #14
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Originally Posted by Kakjens View Post
With CHAT_MSG_LOOT the corresponding script of getting itemlink from the text, or saving the text as is. Moreover, it's supposed to run just when you receive an item.
Thank you. It looks using CHAT_MSG_LOOT is more efficient than using BAG_UPDATE. I would assume, CHAT_MSG_LOOT is fired for any type of item acquisition. I will study how CHAT_MSG_LOOT works and try to use it instead.

Originally Posted by MunkDev View Post
It's unclear whether you want your addon to track items that you have acquired but no longer possess or just the items you currently have.
Sorry if my first post was unclear: I want to keep track of items I acquire from any source. This is for building my own item database. The item log will be a growing log of all items I have collected (since the start of the addon), it will have no duplicates, it will not have item counts. Currently, I am copying that data and using it in an Excel file for final storage. I do that like once a few days or a week and I delete the log, in order not to make it huge (i.e. thousands of items).

Originally Posted by Kanegasi View Post
That is called SavedVariables. They need to be declared in your .toc file in order to save them. Otherwise, what you have there is just a global table that will be discarded upon logout.
Yes, I know that and I had it declared. I didn't think you would go ahead and code something after my last reply where I mentioned I had it working, but thank you very much for the effort.

You see, as a newbie, I don't know how something should be done, or what's the best approach for something in WoW LUA. So, I am following tips and advice given here or there. Yesterday, I was thinking BAG_UPDATE is the way to go, today, it became obvious that I need to use CHAT_MSG_LOOT as it will be more efficient for what I am trying to accomplish, which is simply logging what items I acquire.

Now, I need to figure out how to get the itemlink from the CHAT_MSG_LOOT event.

Last edited by Eommus : 05-20-17 at 02:05 AM.
  Reply With Quote
05-20-17, 03:36 AM   #15
Kakjens
A Cliff Giant
Join Date: Apr 2017
Posts: 75
Originally Posted by Eommus View Post
Thank you. It looks using CHAT_MSG_LOOT is more efficient than using BAG_UPDATE. I would assume, CHAT_MSG_LOOT is fired for any type of item acquisition. I will study how CHAT_MSG_LOOT works and try to use it instead.
You'll need to verify that the one due to whom is the event was your character (not party member - gamepedia article has info how to do it), and it's a item receive event (not, for example, selecting need/greed/pass/DE - gamepedia article doesn't disclose it but checking the OnEvent variables should make it possible).
Test it, with, for example, this:
Lua Code:
  1. local my_event = "CHAT_MSG_LOOT"
  2. local f = CreateFrame('Frame')
  3. f:RegisterEvent(my_event)
  4. f:SetScript('OnEvent', function(self, event, arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11, ...)
  5.     if event == my_event then
  6.         print("arg1=",arg1,"arg2=",arg2,"arg3=",arg3,"arg4=",arg4,"arg5=",arg5)
  7.         print("arg6=",arg6,"arg7=",arg7,"arg8=",arg8,"arg9=",arg9,"arg10=",arg10)
  8.         print("arg11=",arg11)
  9.     end
  10. end)
  Reply With Quote
05-20-17, 06:03 AM   #16
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Originally Posted by Kakjens View Post
You'll need to verify that the one due to whom is the event was your character (not party member - gamepedia article has info how to do it), and it's a item receive event (not, for example, selecting need/greed/pass/DE - gamepedia article doesn't disclose it but checking the OnEvent variables should make it possible).
Test it, with, for example, this:
Lua Code:
  1. local my_event = "CHAT_MSG_LOOT"
  2. local f = CreateFrame('Frame')
  3. f:RegisterEvent(my_event)
  4. f:SetScript('OnEvent', function(self, event, arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11, ...)
  5.     if event == my_event then
  6.         print("arg1=",arg1,"arg2=",arg2,"arg3=",arg3,"arg4=",arg4,"arg5=",arg5)
  7.         print("arg6=",arg6,"arg7=",arg7,"arg8=",arg8,"arg9=",arg9,"arg10=",arg10)
  8.         print("arg11=",arg11)
  9.     end
  10. end)
Thank you, I will continue from this and see how to get itemlink.

Isn't there a way to directly trigger at CHAT_MSG_LOOT event, rather than checking all events and triggering IF the event is CHAT_MSG_LOOT? I mean your use of if event == my_event.

I checked the eventtrace and noticed that hundreds of events are taking place in a minute, even when your character is just standing still.
  Reply With Quote
05-20-17, 06:48 AM   #17
Kakjens
A Cliff Giant
Join Date: Apr 2017
Posts: 75
In this example check "if event == my_event" then can be omitted - the frame f is registered to listen to only "CHAT_MSG_LOOT". Forgot to delete. You can test by inserting after line 8 "else print(event)"
You might want to use string.find(where_to_search,what_to_search).
  Reply With Quote
05-20-17, 07:30 AM   #18
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Thanks to all the tips and help on this thread, I was finally able to finish my addon using the following. If you think it can be improved (i.e. made more efficient), please let me know.

Lua Code:
  1. LootLog = {}
  2.  
  3. local f = CreateFrame("Frame")
  4. f:RegisterEvent("CHAT_MSG_LOOT")
  5.  
  6. function Log_Loot(self, event, message, _, _, _, player, _, _, _, _, _, _, ...)
  7.     if player == "Eommus" then
  8.         local itemId = message:match("item:(%d+):")
  9.         LootLog[itemId] = {}
  10.     end
  11. end
  12.  
  13. f:SetScript('OnEvent', Log_Loot)

Such a simple thing, taking so long to come up with.

Food for thought: What if I was in a group with a player from another realm with the same name? The (if player == "Eommus") check won't be enough then.

Last edited by Eommus : 05-20-17 at 08:07 AM.
  Reply With Quote
05-20-17, 09:53 AM   #19
Kakjens
A Cliff Giant
Join Date: Apr 2017
Posts: 75
Using itemID-s instead of names is a good idea. Saw that could be done but didn't want to overwhelm.
While still debugging, you might want to print itemId.
Don't know much of regular expressions but does string:match("item%d+):") recognize that instead of [item]x2 you want to record [item]? I think it does but you might need to test. Does it detect that if you roll for item but loose, and doesn't record?
Function Log_Loot isn't local.
You are not using variables after player, so they can be omitted.
I think players from another realm have "-realmname". You can test it by putting "else print(player)" after line 9, and running a random heroic/mythic without premade group.
  Reply With Quote
05-20-17, 11:25 AM   #20
Eommus
An Aku'mai Servant
Join Date: Apr 2017
Posts: 34
Originally Posted by Kakjens View Post
Using itemID-s instead of names is a good idea. Saw that could be done but didn't want to overwhelm.
While still debugging, you might want to print itemId.
Don't know much of regular expressions but does string:match("item%d+):") recognize that instead of [item]x2 you want to record [item]? I think it does but you might need to test. Does it detect that if you roll for item but loose, and doesn't record?
Function Log_Loot isn't local.
You are not using variables after player, so they can be omitted.
I think players from another realm have "-realmname". You can test it by putting "else print(player)" after line 9, and running a random heroic/mythic without premade group.
Actually, I have a little more details in my actual addon which were not relevant to what I was asking here, so I omitted them. For example, I store other item info too (itemId, itemName, itemLevel, itemRequiredLevel, itemPrice, etc.). Also, I have an array of my own characters and I check if the player is in that array, so that it does the logging while playing with all my characters. What I have at the moment seems to work just fine. I will keep an eye on it though and see if it will continue to work correctly for various cases.

The string:match I used gets the item ID from the chat message. LootLog[itemId] = {} ensures no duplicate entries are made. LootLog = {} ensures the entries are saved between sessions, in other words, my log keeps growing as I play.

No idea about rolls yet, but the purpose of this addon is to log only items I acquire. There is one thing I am not sure if it will work, need to check it, some quests provide invisible reward items, that do not create a chat message, but their appearances are added to your Appearances. It won't be an issue though as I can manually add them to my database eventually, since they are recorded in the Appearances section.

Should I make the function local? How? and why?

Do you mean to remove other variables like this?

Lua Code:
  1. function Log_Loot(self, event, message, _, _, _, player)

I will test cross realm player names next time I do a dungeon.

Thanks so much.

Last edited by Eommus : 05-20-17 at 12:38 PM.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Logging the item my character acquired

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off