Thread Tools Display Modes
01/07/16, 10:14 AM   #1
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
EVENT_EFFECT_CHANGED and unitIDs

Hey,

I have 2 questions:
  • The unitId and unitName of the mentioned event is always from the same unit, right?
  • Does anyone know when unitIDs change?


Edit: Ok, they definately change, just don't know on what condition(s) yet.

Last edited by coolmodi : 01/07/16 at 01:17 PM.
  Reply With Quote
01/07/16, 06:04 PM   #2
circonian
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 613
Originally Posted by coolmodi View Post
Hey,

I have 2 questions:
  • The unitId and unitName of the mentioned event is always from the same unit, right?
  • Does anyone know when unitIDs change?


Edit: Ok, they definately change, just don't know on what condition(s) yet.
Yes they are from the same unit and:
Quote from: http://www.esoui.com/forums/showthread.php?p=21696#post21696
Originally Posted by ZOS_ChipHilseberg View Post
We've added a new function called GetAbilityIcon which takes the ability id and returns its icon path. We've also modified COMBAT_EVENT to pass numerical identifiers for the source and target so units with the same name can be separated. This ids are good for as long as the unit exists on the client. The same is true of EFFECT_UPDATED and its unit.
Although I have no idea what effect reloading the UI or zoning would have on the existence of a unit. Unfortunately there is no function to check and see if a unitId still exists.
  Reply With Quote
01/08/16, 10:09 AM   #3
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
Thanks! So they just change when going out of visual range I guess, and probably when zoning.

I'll just migrate the data from one unit ID to the other when it changes then, and delete the old entry. That way it should be possible to track players even in cyrodiil reliably.
  Reply With Quote
01/08/16, 06:50 PM   #4
circonian
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 613
Originally Posted by coolmodi View Post
Thanks! So they just change when going out of visual range I guess, and probably when zoning.

I'll just migrate the data from one unit ID to the other when it changes then, and delete the old entry. That way it should be possible to track players even in cyrodiil reliably.
It sounds like you are going to run into one of the problems I ran into with BuffTracker.

Despite the wording of this post:
Originally Posted by ZOS_ChipHilseberg View Post
For the unique ID we're looking at providing a number to identify each instance with the same name. Players will always be 0 (since their name should be unique anyway) and non-players will have an arbitrary number that will be the same as long as that unit exists.
Players do not have a unitId of 0 and the unitTag is nil unless the event is firing for a unit you are currently targeting. Although the player names are unique that does us no good because there is no way to distinguish between players & other targets (except the users character).

Since other targets/mobs do not have unique names you can not use the units name as a key for the data (not even for players because we can't distinguish between players & mobs to separate the two). It also means that if the unitId changes you can't migrate the data because you have no way of knowing what the original unitId was to find the correct data to migrate. Also migrating the data would probably be a bad idea. As an example: If a player went out of range & his unitId changed (or even if it didn't change) he was probably far enough out of range that when his effects changed the event did not fire for us so that effect data you have saved is probably no longer valid.

This also means that if you want to track effects on mobs you have no choice but to also track effects on players (and vice-versa) since we can't tell them apart.

My best thought would be to save everything by UnitId and if a targets unitId changes just let it change and start saving new data for that target. Also record a timestamp in the saved effects data table each time the data is updated for any given unitId. Then set a RegisterForUpdate(...) and every once in a while check the timestamps for all of your saved effect data & if its xxx old (meaning it hasn't been updated in a while) assume its no longer valid and delete the data to keep the table clean. Although its not a good solution its the best I've come up with.

I added some things that might help a little to the wish list a while back. Maybe someday we'll get some functions to help with things like this: http://www.esoui.com/forums/showthread.php?t=5298
  Reply With Quote
01/08/16, 08:11 PM   #5
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
Originally Posted by circonian View Post
Since other targets/mobs do not have unique names you can not use the units name as a key for the data (not even for players because we can't distinguish between players & mobs to separate the two). It also means that if the unitId changes you can't migrate the data because you have no way of knowing what the original unitId was to find the correct data to migrate.
Oh yes I can, at least if I'm not totally wrong and miss something really obvious, my two addons here are the first thing I ever programmed that goes over the hello world crap I had to do for an uni course

Basic arrays needed, just to show the data structure
Lua Code:
  1. --to have name and the info whether it's a player (or named mob but that's ok for migration purposes) and in group for each unitId
  2. unitList = {                    
  3.    [unitId] = {name,player,group}
  4. }
  5. --this is the important thing, you need names as key with id as value too!!!
  6. nameIdPairs = {          
  7.    [name] = Id
  8. }
  9. --data saved to each unitId
  10. dataArray = {
  11.    [unitId] = dataForUnit
  12. }

Then some changed wip code from my addon:

Lua Code:
  1. --on every combat and effect event this is done, data collection into the "dataArray" is done somewhere else
  2. function GroupDamage:UpdateUnitList(unitId, unitName, unitType)
  3.     if unitName == "" and (unitType ~= 3 or unitType ~= 1) then return end
  4.     if self.unitList[unitId] == nil then
  5.         self.unitList[unitId] = {
  6.             ["name"] = "<placeholder>" .. unitId,
  7.             ["player"] = false,
  8.             ["group"] = false
  9.         }
  10.     end
  11.     if targetName ~= "" then
  12.         self.unitList[unitId]["name"] = zo_strformat("<<C:1>>", unitName)
  13.        
  14.         --magic starts here
  15.         --if a name contains a "^" it isn't a general mob, and in any case(?) at least unique, or unique enough to not make problems
  16.         if unitName:match("%^")~=nil then
  17.             self.unitList[unitId]["player"] = true --this is bull**** because of that, but it helps filtering out general mobs in my addon (do mobs have an x after f/m too?)
  18.            
  19.             --so now check if you already had that name with an id before and if the id for the name changed
  20.             if self.nameIdPairs[unitName] ~= nil and self.nameIdPairs[unitName] ~= unitId then
  21.                 --yes? you now have the old and new ID!
  22.                 self:MigrateToNewId(self.nameIdPairs[unitName], unitId)
  23.             end
  24.             self.nameIdPairs[unitName] = unitId --assign new id to the name
  25.            
  26.         end
  27.    
  28.    
  29.     end
  30.     if unitType == 3 or unitType == 1 then
  31.         self.unitList[unitId]["group"] = true
  32.     end
  33. end
  34.  
  35. function GroupDamage:MigrateToNewId(oldId, newId) --WIP
  36.     d("Id for player or named mob changed!")
  37.     d("Old name: " .. self.unitList[oldId]["name"] .. " with ID: " .. oldId)
  38.     d("New name: " .. self.unitList[newId]["name"] .. " with ID: " .. newId)
  39.     --copy data from one unitId to the new unitId in "dataArray", or add it if it already exists, which is highly probable with combat event...
  40.     self.unitList[oldId] = nil   --also remove old from unitList
  41.     self.currentFightData["units"][oldId] = nil   --and also old data in the "dataArray"
  42. end

This should work, I even tested it ingame, but with a much worse solution in terms of performance, hence the extra array with [name] = ID pairs, so I can just use ~= two times and don't have to loop through the unitList

Thanks to effect changed ALWAYS giving name and ID this should work pretty reliable too, as soon as someone comes in range it will fire even if they only have the ESO PLUS buff, or a food buff, really anything. In that moment I will have their new id, and the old one from the name+id pairs.

Originally Posted by circonian View Post
Also migrating the data would probably be a bad idea. As an example: If a player went out of range & his unitId changed (or even if it didn't change) he was probably far enough out of range that when his effects changed the event did not fire for us so that effect data you have saved is probably no longer valid.
Doesn't affect me, I just need name and ID from it, my addon tracks damage and heal for every unit
But anyways, why would the effect data not be valid anymore? Doesn't it just provide begin and end time of buffs? That won't change only because they were out of range, so what isn't valid anymore?

Edit: And why are the code containers not as wide as quote containers here??

Last edited by coolmodi : 01/08/16 at 09:00 PM.
  Reply With Quote
01/08/16, 08:59 PM   #6
circonian
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 613
Originally Posted by coolmodi View Post
Oh yes I can, at least if I'm not totally wrong and miss something really obvious
...
--if a name contains a "^" it isn't a general mob, and in any case(?) at least unique, or unique enough to not make problems
Nice, if that's true then you are correct. I did not know that mob names do not contain a ^. If mobs don't have ^ then that would fix that problem and yes as long as its a player the names are unique.

Just fyi so you don't end up with a bug:
Lua Code:
  1. if unitName == "" and (unitType ~= 3 or unitType ~= 1) then return end
  2.  
  3. -- this doesn't do anything, its always true:
  4. -- and (unitType ~= 3 or unitType ~= 1)

Last edited by circonian : 01/08/16 at 09:04 PM.
  Reply With Quote
01/08/16, 11:52 PM   #7
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
Originally Posted by circonian View Post
I did not know that mob names do not contain a ^.
All those generic mobs like "Skeleton Archer" don't, but special ones, like most bosses, or generally the ones with "unique" names also have their gender appended to the name with ^F or ^M. But in any case it should filter duplicate names I guess, which is the important thing.

Every player should have ^Fx or ^Mx, out of hundreds not a single one didn't have an x, no clue what that x is for though. The few world bosses I checked didn't have it, only ^F or ^M, but can't say if that is a general pattern or not. The boss in the screen on my addon's page also has only ^M though. This needs some investigation


Originally Posted by circonian View Post
-- this doesn't do anything, its always true:
-- and (unitType ~= 3 or unitType ~= 1)
All according to plan...no seriously, thx, just way to tired, even took me a time to see whats wrong now that you mentioned it
  Reply With Quote
01/09/16, 07:10 AM   #8
sirinsidiator
 
sirinsidiator's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 1,567
Originally Posted by coolmodi View Post
All those generic mobs like "Skeleton Archer" don't, but special ones, like most bosses, or generally the ones with "unique" names also have their gender appended to the name with ^F or ^M. But in any case it should filter duplicate names I guess, which is the important thing.

Every player should have ^Fx or ^Mx, out of hundreds not a single one didn't have an x, no clue what that x is for though. The few world bosses I checked didn't have it, only ^F or ^M, but can't say if that is a general pattern or not. The boss in the screen on my addon's page also has only ^M though. This needs some investigation
Like you said, only players seem to have the 'x', but I wouldn't count on normal mobs not having a gender tag. For example the generic resource guards in Cyrodiil have names like "Covenant Honor Guard^M".

The question is, why do you even need the function you posted?
You can easily get that information without keeping a list.

Lua Code:
  1. isPlayer = (unitName:find("%^.x") ~= nil)
  2. isGrouped = IsPlayerInGroup(zo_strformat("<<1>>", unitName))

Last edited by sirinsidiator : 01/09/16 at 07:13 AM.
  Reply With Quote
01/09/16, 11:27 AM   #9
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
Originally Posted by sirinsidiator View Post
Like you said, only players seem to have the 'x', but I wouldn't count on normal mobs not having a gender tag. For example the generic resource guards in Cyrodiil have names like "Covenant Honor Guard^M".
Mmh, then I'll filter for ^ and x to make it less suscebtible to errors.

Originally Posted by sirinsidiator View Post
The question is, why do you even need the function you posted?
You can easily get that information without keeping a list.
The COMBAT_EVENT doesn't give names or anything if you aren't source or target, so I can only collect information by unitId, and therefore need to collect names and group state (and why not player too) independent of it, and then assign names and group/player flags to the Ids if they become available. The extra unitList is needed because currentFightData is cleared for every new fight, and now I can just assign info to known IDs immediately with:

Lua Code:
  1. if self.unitList[unit] ~= nil then
  2.             values["name"] = self.unitList[unit]["name"]
  3.             values["player"] = self.unitList[unit]["player"]
  4.             values["group"] = self.unitList[unit]["group"]
  5.         end

It would ofc be possible to get rid of unitList, but then I'd need to handle everything in currentFightData and keep every unit info in for every new fight, effectively having useless info in the combatHistory, and it's also easier to handle changing IDs that way, as it only needs to be updated in unitList and every new fight will just use correct info.

Those are the event handlers if that helps to understand my thought process:

Lua Code:
  1. function GroupDamage.onCombatEvent(_, result, isError, _, _, _, sourceName, sourceType, targetName, targetType, hitValue, _, _, _, sourceUnitId, targetUnitId, abilityId)      
  2.     if isError then return end
  3.    
  4.     GroupDamage:UpdateUnitList(sourceUnitId, sourceName, sourceType)
  5.     GroupDamage:UpdateUnitList(targetUnitId, targetName, targetType)
  6.    
  7.     local isHeal = result==ACTION_RESULT_HEAL or result==ACTION_RESULT_CRITICAL_HEAL or result==ACTION_RESULT_HOT_TICK or result==ACTION_RESULT_HOT_TICK_CRITICAL
  8.     local isDamage = result==ACTION_RESULT_BLOCKED_DAMAGE or result==ACTION_RESULT_DAMAGE or result==ACTION_RESULT_CRITICAL_DAMAGE or result==ACTION_RESULT_DOT_TICK or result==ACTION_RESULT_DOT_TICK_CRITICAL or result==ACTION_RESULT_DAMAGE_SHIELDED   
  9.    
  10.     --only go on if it's an actual hit event
  11.     if (not isDamage and not isHeal) or abilityId == 41467 or abilityId == 57466 or abilityId == 57468 or hitValue < 2 then return end
  12.     local isCrit = result==ACTION_RESULT_CRITICAL_DAMAGE or result==ACTION_RESULT_CRITICAL_HEAL or result==ACTION_RESULT_DOT_TICK_CRITICAL or result==ACTION_RESULT_HOT_TICK_CRITICAL  
  13.     local hit = {
  14.         ["sourceId"]    = sourceUnitId,
  15.         ["targetId"]    = targetUnitId,
  16.         ["hitValue"]    = hitValue,
  17.         ["abilityId"]   = abilityId,
  18.         ["crit"]        = isCrit,
  19.         ["heal"]        = isHeal,
  20.         ["timeMs"]      = GetGameTimeMilliseconds()
  21.     }
  22.  
  23.     GroupDamage:Proces****Event(hit)
  24. end
  25.  
  26. function GroupDamage.onEffectChanged(_, _, _, _, unitTag, _, _, _, _, _, _, _, _, unitName, unitId, _) 
  27.     if unitTag:match("group") ~= nil then
  28.         unitTag = 3
  29.     end
  30.     GroupDamage:UpdateUnitList(unitId, unitName, unitTag)
  31. end

Edit: Also, is there any difference between using match and find to see if a string contains something? Both are not nil in that case, but find returns start and end while match returns the word(s), right?

Last edited by coolmodi : 01/09/16 at 11:57 AM.
  Reply With Quote
01/09/16, 02:04 PM   #10
sirinsidiator
 
sirinsidiator's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 1,567
That makes more sense.

Btw instead of checking if the combat event is an error and which actionResult it has inside lua, you should use AddFilterForEvent and let the game handle filtering. You may need to split the handling over multiple functions, but it's much faster that way. See this wiki page for what I mean.
  Reply With Quote
01/10/16, 08:34 AM   #11
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
Filters were a thing I wanted to do for a long time, never figuered out how to add multiple results though, but that part got updated as it seems, or I was blind...

EVENT_MANAGER:AddFilterForEvent(string eventNamespace, number eventId[, RegisterForEventFilterType filterType, varying parameter]+)

Multiple filters can be added in one call by supplying a filter type followed by a parameter.
Have to look into it again.
  Reply With Quote
01/10/16, 09:29 AM   #12
sirinsidiator
 
sirinsidiator's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 1,567
It was there all along. You can also just call AddFilterForEvent multiple times for the same event and it will also work. Just updated the wiki to reflect this.
  Reply With Quote
01/10/16, 10:28 AM   #13
coolmodi
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 47
Originally Posted by sirinsidiator View Post
You can also just call AddFilterForEvent multiple times for the same event and it will also work.
That's what I tried, but only the last added result would work, don't know what I did wrong then if it should work.

Edit: If anyone ever finds this with the same question, then here's my current solution to multiple results with multiple handlers:

Lua Code:
  1. local filters = {
  2.         [self.onCombatEventDmg] = {
  3.             ACTION_RESULT_DAMAGE,
  4.             ACTION_RESULT_DOT_TICK,    
  5.             ACTION_RESULT_BLOCKED_DAMAGE,
  6.             ACTION_RESULT_DAMAGE_SHIELDED
  7.         },
  8.         [self.onCombatEventDmgCrit] = {
  9.             ACTION_RESULT_CRITICAL_DAMAGE, 
  10.             ACTION_RESULT_DOT_TICK_CRITICAL
  11.         },
  12.         [self.onCombatEventHeal] = {
  13.             ACTION_RESULT_HOT_TICK,
  14.             ACTION_RESULT_HEAL
  15.         },
  16.         [self.onCombatEventHealCrit] = {
  17.             ACTION_RESULT_CRITICAL_HEAL,
  18.             ACTION_RESULT_HOT_TICK_CRITICAL
  19.         }
  20.     }
  21.     local nsInc = 1
  22.     for k,v in pairs(filters) do
  23.         for i=1, #v do
  24.             EVENT_MANAGER:RegisterForEvent(self.name..nsInc, EVENT_COMBAT_EVENT, k)
  25.             EVENT_MANAGER:AddFilterForEvent(self.name..nsInc, EVENT_COMBAT_EVENT, REGISTER_FILTER_COMBAT_RESULT , v[i], REGISTER_FILTER_IS_ERROR, false)
  26.             nsInc = nsInc+1
  27.         end
  28.     end

I could reduce the need for IFs in LUA to checking whether I need to create arrays or not, which should be a major improvement I guess.

Last edited by coolmodi : 01/11/16 at 07:24 PM.
  Reply With Quote

ESOUI » Developer Discussions » General Authoring Discussion » EVENT_EFFECT_CHANGED and unitIDs

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