diff --git a/functions/pack.lua b/functions/pack.lua index e527cc7e..47b16f30 100644 --- a/functions/pack.lua +++ b/functions/pack.lua @@ -1 +1,778 @@ -local Details = Details \ No newline at end of file + +local Details = _G.Details +local bit = _G.bit +local DETAILS_ATTRIBUTE_DAMAGE = _G.DETAILS_ATTRIBUTE_DAMAGE +local DF = _G.DetailsFramework +local tonumber = _G.tonumber +local select = _G.select +local strsplit = _G.strsplit +local floor = _G.floor +local tremove = _G.tremove + +Details.packFunctions = {} + +--lookup actor information tables +local actorInformation = {} +local actorInformationIndexes = {} +local actorDamageInfo = {} +local actorHealInformation = {} + +--flags +local REACTION_HOSTILE = 0x00000040 +local IS_GROUP_OBJECT = 0x00000007 +local REACTION_FRIENDLY = 0x00000010 +local OBJECT_TYPE_MASK = 0x0000FC00 +local OBJECT_TYPE_OBJECT = 0x00004000 +local OBJECT_TYPE_PETGUARDIAN = 0x00003000 +local OBJECT_TYPE_GUARDIAN = 0x00002000 +local OBJECT_TYPE_PET = 0x00001000 +local OBJECT_TYPE_NPC = 0x00000800 +local OBJECT_TYPE_PLAYER = 0x00000400 + +local INDEX_EXPORT_FLAG = 1 +local INDEX_COMBAT_START_TIME = 2 +local INDEX_COMBAT_END_TIME = 3 +local INDEX_COMBAT_START_DATE = 4 +local INDEX_COMBAT_END_DATE = 5 +local INDEX_COMBAT_NAME = 6 + +local TOTAL_INDEXES_FOR_COMBAT_INFORMATION = 6 + +local entitySerialCounter = 0 + +function Dexport() --test case + local combat = Details:GetCurrentCombat() + local readyToSendData = Details.packFunctions.PackCombatData(combat, 0x1) + local newCombatWithData = Details.packFunctions.UnPackCombatData(readyToSendData) +end + +--pack the combat +function Details.packFunctions.PackCombatData(combatObject, flags) + + --0x1 damage + --0x2 healing + --0x4 energy + --0x8 misc + + table.wipe(actorInformation) + table.wipe(actorInformationIndexes) + table.wipe(actorDamageInfo) + table.wipe(actorHealInformation) + + --reset the serial counter + entitySerialCounter = 0 + + local isBossEncouter = combatObject.is_boss + local startDate, endDate = combatObject:GetDate() + + local startCombatTime = combatObject:GetStartTime() or 0 + local endCombatTime = combatObject:GetEndTime() or 0 + local combatInfo = { + floor(startCombatTime), --1 + floor(endCombatTime), --2 + startDate, --3 + endDate, --4 + isBossEncouter and isBossEncouter.encounter or "Unknown Enemy" --5 + } + + if (bit.band(flags, 0x1) ~= 0) then + Details.packFunctions.PackDamage(combatObject) + end + + if (bit.band(flags, 0x2) ~= 0) then + Details.packFunctions.PackHeal(combatObject) + end + + --> prepare data to send over network + local exportedString = flags .. "," + + --add the combat info + for index, data in ipairs(combatInfo) do + exportedString = exportedString .. data .. "," + end + + --add the actor references table + for index, data in ipairs(actorInformation) do + exportedString = exportedString .. data .. "," + end + + --add the damage actors data + if (bit.band(flags, 0x1) ~= 0) then + exportedString = exportedString .. "!D" .. "," + for index, data in ipairs(actorDamageInfo) do + exportedString = exportedString .. data .. "," + end + end + + --add the heal actors data + if (bit.band(flags, 0x2) ~= 0) then + exportedString = exportedString .. "!H" .. "," + for index, data in ipairs(actorHealInformation) do + exportedString = exportedString .. data .. "," + end + end + + --print("finished export", exportedString) --debug + + --compress + local LibDeflate = _G.LibStub:GetLibrary("LibDeflate") + local dataCompressed = LibDeflate:CompressDeflate(exportedString, {level = 9}) + local dataEncoded = LibDeflate:EncodeForWoWAddonChannel(dataCompressed) + + return dataEncoded +end + +function Details.packFunctions.GenerateSerialNumber() + local serialNumber = entitySerialCounter + entitySerialCounter = entitySerialCounter + 1 + return serialNumber +end + +--[[ + actor flag IDs + 1: player friendly + 2: player enemy + 3: enemy npc pet + 4: enemy npc non-pet + 5: friendly npc pet + 6: friendly non-npc + 7: unknown entity type +]] + +local packActorFlag = function(actor) + if (actor.grupo) then + --it's a player in the group + return 1 + end + + local flag = actor.flag_original or 0 + + --check hostility + if (bit.band(flag, REACTION_HOSTILE) ~= 0) then + --is hostile + if (bit.band(flag, OBJECT_TYPE_PLAYER) == 0) then + --isn't a player + if (bit.band(flag, OBJECT_TYPE_PETGUARDIAN) ~= 0) then + --is pet + return 3 + else + --is enemy npc + return 4 + end + else + --is enemy player + return 2 + end + else + --is friendly + if (bit.band(flag, OBJECT_TYPE_PLAYER) == 0) then + --is player + return 1 + else + --isn't a player + if (bit.band(flag, OBJECT_TYPE_PETGUARDIAN) ~= 0) then + --is friendly pet + return 5 + else + --is a friendly entity, most likely a npc + return 6 + end + end + end + + return 7 +end + +local unpackActorFlag = function(flag) + --convert to integer + flag = tonumber(flag) + + if (flag == 1) then --player + return 0x511 + + elseif (flag == 2) then --enemy player + return 0x548 + + elseif (flag == 3) then --enemy pet with player or AI controller + return 0x1A48 + + elseif (flag == 4) then --enemy npc + return 0xA48 + + elseif (flag == 5) then --friendly pet + return 0x1914 + + elseif (flag == 6) then --friendly npc + return 0xA14 + + elseif (flag == 7) then --unknown entity + return 0x4A28 + end +end + +local isActorInGroup = function(class, flag) + if (bit.band (flag, IS_GROUP_OBJECT) ~= 0 and class ~= "UNKNOW" and class ~= "UNGROUPPLAYER" and class ~= "PET") then + return true + end + return false +end + +--[[ + actor class IDs + 1-12: player class Id + 20: "PET" + 21: "UNKNOW" + 22: "UNGROUPPLAYER" + 23: "ENEMY" + 24: "MONSTER" +]] + +local detailsClassIndexToFileName = { + [20] = "PET", + [21] = "UNKNOW", + [22] = "UNGROUPPLAYER", + [23] = "ENEMY", + [24] = "MONSTER", +} + +local packActorClass = function(actor) + local classId = DF.ClassFileNameToIndex[actor.classe] + if (classId) then + return classId + elseif (classId == "PET") then + return 20 + elseif (classId == "UNKNOW") then + return 21 + elseif (classId == "UNGROUPPLAYER") then + return 22 + elseif (classId == "ENEMY") then + return 23 + elseif (classId == "MONSTER") then + return 24 + end + + return 21 +end + +local unpackActorClass = function(classId) + --convert to integer + classId = tonumber(classId) + + local classFileName = DF.ClassIndexToFileName[classId] + if (not classFileName) then + classFileName = detailsClassIndexToFileName[classId] + end + + return classFileName +end + +--[[ + actor serial + creature: C12345 (numbers are the npcId) + player: P +--]] + +local packActorSerial = function(actor) + local serial = actor.serial + if (serial:match("^C") == "C") then + local npcId = tonumber(select(6, strsplit("-", serial)) or 0) + return "C" .. npcId + + elseif (serial:match("^P") == "P") then + return "P" + + elseif (serial == "") then + return "C12345" + end +end + +local unpackActorSerial = function(serialNumber) + --player serial + if (serialNumber:match("^P")) then + return "Player-1-" .. Details.packFunctions.GenerateSerialNumber() + + elseif (serialNumber:match("^C")) then + return "Creature-0-0-0-0-" .. serialNumber:gsub("C", "") .."-" .. Details.packFunctions.GenerateSerialNumber() + end +end + +function Details.packFunctions.AddActorInformation(actor) + --the next index to use on the actor info table + local currentIndex = #actorInformation + 1 + + --calculate where this actor will be placed on the combatData table + local indexOnCombatDataTable = TOTAL_INDEXES_FOR_COMBAT_INFORMATION + currentIndex + + --add the actor start information index + actorInformationIndexes[actor.nome] = indexOnCombatDataTable + + --index 1: actor name + actorInformation[currentIndex] = actor.nome or "unnamed" --[1] + + --index 2: actor flag + actorInformation[currentIndex + 1] = packActorFlag(actor) --[2] + + --index 3: actor serial + actorInformation[currentIndex + 2] = packActorSerial(actor) or "" --[3] + + --index 4: actor class + actorInformation[currentIndex + 3] = packActorClass(actor) --[4] + + --index 5: actor spec + actorInformation[currentIndex + 4] = actor.spec or 0 --[5] + + return indexOnCombatDataTable +end + +function Details.packFunctions.RetriveActorInformation(combatData, index) + --name [1] + local actorName = combatData[index] + if (not actorName) then + return + end + + --flag [2] + local actorFlag = combatData[index + 1] + actorFlag = unpackActorFlag(actorFlag) + + --serial [3] + local serialNumber = combatData[index + 2] + serialNumber = unpackActorSerial(serialNumber) + + --class [4] + local class = combatData[index + 3] + class = unpackActorClass(class) + + --spec [5] + local spec = tonumber(combatData[index + 4]) + + --return the values + return actorName, actorFlag, serialNumber, class, spec +end + +function Details.packFunctions.PackDamage(combatObject) + local allActors = combatObject[DETAILS_ATTRIBUTE_DAMAGE]._ActorTable + + --fill the actor information table + for i = 1, #allActors do + --get the actor object + local actor = allActors[i] + --check if already has the actor information + local indexToActorInfo = actorInformationIndexes[actor.nome] --actor name + if (not indexToActorInfo) then + --need to add the actor general information into the actor information table + indexToActorInfo = Details.packFunctions.AddActorInformation(actor) + end + end + + local spellSize = 0 + + for i = 1, #allActors do + --get the actor object + local actor = allActors[i] + local indexToActorInfo = actorInformationIndexes[actor.nome] + + --where the information of this actor starts + local currentIndex = #actorDamageInfo + 1 + + --[1] index where is stored the this actor info like name, class, spec, etc + actorDamageInfo[currentIndex] = indexToActorInfo --[1] + + --[2 - 6] + actorDamageInfo [currentIndex + 1] = floor(actor.total) --[2] + actorDamageInfo [currentIndex + 2] = floor(actor.totalabsorbed) --[3] + actorDamageInfo [currentIndex + 3] = floor(actor.damage_taken) --[4] + actorDamageInfo [currentIndex + 4] = floor(actor.friendlyfire_total) --[5] + actorDamageInfo [currentIndex + 5] = floor(actor.total_without_pet) --[6] + + --[=[]] + --damage taken from (not passing, this list can be rebuilt with spell targets) + local damageTakenFrom = actor.damage_from + actorDamageInfo[#actorDamageInfo+1] = Details.packFunctions.CountTableEntriesValid(damageTakenFrom) + + for actorName, _ in pairs(damageTakenFrom) do + local actorInfoIndex = actorInformationIndexes[actorName] + if (actorInfoIndex) then + actorDamageInfo[#actorDamageInfo+1] = actorInfoIndex + end + end + --]=] + + local spellContainer = actor.spells._ActorTable + + --reserve an index to tell the length of spells + actorDamageInfo [currentIndex + 1] = 0 + local reservedSpellSizeIndex = #actorDamageInfo + local totalSpellIndexes = 0 + + for spellId, spellInfo in pairs(spellContainer) do + local spellDamage = spellInfo.total + local spellHits = spellInfo.counter + local spellTargets = spellInfo.targets + local spellCasts = 0 + + actorDamageInfo [currentIndex + 1] = floor(spellId) + actorDamageInfo [currentIndex + 1] = floor(spellDamage) + actorDamageInfo [currentIndex + 1] = floor(spellHits) + + --build targets + local targetsSize = Details.packFunctions.CountTableEntriesValid(spellTargets) + actorDamageInfo[#actorDamageInfo + 1] = targetsSize + totalSpellIndexes = totalSpellIndexes + 3 + targetsSize + + for actorName, damageDone in pairs(spellTargets) do + local actorInfoIndex = actorInformationIndexes[actorName] + if (actorInfoIndex) then + actorDamageInfo[#actorDamageInfo + 1] = actorInfoIndex + actorDamageInfo[#actorDamageInfo + 1] = floor(damageDone) + spellSize = spellSize + 2 + end + end + + spellSize = spellSize + 1 + end + + --amount of indexes spells are using + actorDamageInfo[reservedSpellSizeIndex] = totalSpellIndexes + + end + + print ("spellSize (debug):", spellSize, #actorDamageInfo) +end + +-------------------------------------------------------------------------------------------------------------------------------- +--> unpack + +--@currentCombat: details! combat object +--@combatData: array with strings with combat information +--@tablePosition: first index of the first damage actor +function Details.packFunctions.UnPackDamage(currentCombat, combatData, tablePosition) + + --get the damage container + local damageContainer = currentCombat[DETAILS_ATTRIBUTE_DAMAGE] + + --loop from 1 to 199, the amount of actors store is unknown + --todo: it's only unpacking the first actor from the table, e.g. theres izimode and eye of corruption, after export it only shows the eye of corruption + --table position does not move forward + for i = 1, 199 do + --actor information index in the combatData table + --this index gives the position where the actor name, class, spec are stored + local actorReference = tonumber(combatData[tablePosition]) --[1] + local actorName, actorFlag, serialNumber, class, spec = Details.packFunctions.RetriveActorInformation(combatData, actorReference) + + --check if all damage actors has been processed + --if there's no actor name it means it reached the end + if (not actorName) then + print("damage END index:", i, actorReference, "tablePosition:", tablePosition, "value:", combatData[tablePosition]) + break + end + + --creata the actor object + local actorObject = damageContainer:GetOrCreateActor(serialNumber, actorName, actorFlag, true) + --set the actor class, spec and group + actorObject.classe = class + actorObject.spec = spec + actorObject.grupo = isActorInGroup(class, actorFlag) + + --> copy back the base damage + actorObject.total = tonumber(combatData[tablePosition+1]) --[2] + actorObject.totalabsorbed = tonumber(combatData[tablePosition+2]) --[3] + actorObject.damage_taken = tonumber(combatData[tablePosition+3]) --[4] + actorObject.friendlyfire_total = tonumber(combatData[tablePosition+4]) --[5] + actorObject.total_without_pet = tonumber(combatData[tablePosition+5]) --[6] + + tablePosition = tablePosition + 6 --increase table position + + --[=[]] + --> copy back the damage_from + --this index stores the amount of indexes used to store the actor references + local damageFrom_Size = tonumber(combatData[tablePosition]) --[7] + tablePosition = tablePosition + 1 --increase table position [8] + + if (damageFrom_Size > 0) then + --restore data damage taken data, table position is on the first damage taken index + for o = tablePosition, (tablePosition + damageFrom_Size - 1) do + local actorReferenceIndex = tonumber(combatData[o]) + local actorName = Details.packFunctions.RetriveActorInformation(combatData, actorReferenceIndex) + actorObject.damage_from[actorName] = true + end + end + + tablePosition = tablePosition + damageFrom_Size --increase table position [?] + --]=] + + --> copy back the actor spells + --amount of indexes used to store spells for this actor + local spellsSize = tonumber(combatData[tablePosition]) --[7] + tablePosition = tablePosition + 1 + + local spellIndex = tablePosition + while(spellIndex < tablePosition + spellsSize) do + local spellId = combatData [spellIndex] --[1] + local spellDamage = combatData [spellIndex+1] --[2] + local spellHits = combatData [spellIndex+2] --[3] + + local targetsSize = combatData [spellIndex+3] --[4] + local targetTable = Details.packFunctions.UnpackTable(combatData, spellIndex+4, true) + + local spellObject = actorObject.spells:PegaHabilidade(spellId, true) --this one need some translation + spellObject.total = spellDamage + spellObject.counter = spellHits + spellObject.targets = targetTable + + spellIndex = spellIndex + targetsSize + 4 + end + + end + + return tablePosition +end + +function Details.packFunctions.PackHeal(combatObject) + local allActors = combatObject[DETAILS_ATTRIBUTE_HEAL]._ActorTable + + --fill the actor information table + for i = 1, #allActors do + --get the actor object + local actor = allActors[i] + --check if already has the actor information + local indexToActorInfo = actorInformationIndexes[actor.nome] --actor name + if (not indexToActorInfo) then + --need to add the actor general information into the actor information table + indexToActorInfo = Details.packFunctions.AddActorInformation(actor) + end + end + + for i = 1, #allActors do + --get the actor object + local actor = allActors[i] + + --check if already hs the actor information + local indexToActorInfo = actorInformationIndexes[actor.nome] --actor name + if (not indexToActorInfo) then + --need to add the actor general information into the actor information table + indexToActorInfo = Details.packFunctions.AddActorInformation(actor) + end + + --where the information of this actor starts + local currentIndex = #actorHealInformation + 1 + + --[1] index where is stored the this actor general information + actorHealInformation[currentIndex] = indexToActorInfo --[1] + + --[2 - 6] + actorHealInformation [currentIndex + 1] = floor(actor.total) --[2] + actorHealInformation [currentIndex + 2] = floor(actor.totalabsorb) --[3] + actorHealInformation [currentIndex + 3] = floor(actor.totalover) --[4] + actorHealInformation [currentIndex + 4] = floor(actor.healing_taken) --[5] + actorHealInformation [currentIndex + 5] = floor(actor.totalover_without_pet) --[6] + + end +end + +-------------------------------------------------------------------------------------------------------------------------------- +--> unpack + +function Details.packFunctions.UnPackHeal(currentCombat, combatData, tablePosition) + --get the damage container + local healContainer = currentCombat[DETAILS_ATTRIBUTE_HEAL] + + for i = 1, 199 do + --this is the same as damage, all comments for the code are there + local actorInfoIndex = tonumber(combatData[tablePosition]) --[1] + local actorName, actorFlag, serialNumber, class, spec = Details.packFunctions.RetriveActorInformation(combatData, actorInfoIndex) + + --if there's no actor name it means it reached the end + if (not actorName) then + print("Heal loop has been stopped", "index:", i, "tablePosition:", tablePosition, "value:", combatData[tablePosition]) + break + end + + --creata the actor object + local actorObject = healContainer:GetOrCreateActor(serialNumber, actorName, actorFlag, true) + --set the actor class, spec and group + actorObject.classe = class + actorObject.spec = spec + actorObject.grupo = isActorInGroup(class, actorFlag) + + --> copy the base healing + actorObject.total = tonumber(combatData[tablePosition+1]) --[2] + actorObject.totalabsorb = tonumber(combatData[tablePosition+2]) --[3] + actorObject.totalover = tonumber(combatData[tablePosition+3]) --[4] + actorObject.healing_taken = tonumber(combatData[tablePosition+4]) --[5] + actorObject.totalover_without_pet = tonumber(combatData[tablePosition+5]) --[6] + + --advance the table position + local indexesUsed = 6 + tablePosition = tablePosition + indexesUsed + end + + return tablePosition +end + +--what this function receives? +--@packedCombatData: packed combat, ready to be unpacked +function Details.packFunctions.UnPackCombatData(packedCombatData) + local LibDeflate = _G.LibStub:GetLibrary("LibDeflate") + local dataCompressed = LibDeflate:DecodeForWoWAddonChannel(packedCombatData) + local combatDataString = LibDeflate:DecompressDeflate(dataCompressed) + + --[= + local function count(text, pattern) + return select(2, text:gsub(pattern, "")) + end + --]=] + + local combatData = {} + local amountOfIndexes = count(combatDataString, ",") + 1 + print ("amountOfIndexes (debug):", amountOfIndexes) + + while (amountOfIndexes > 0) do + + local splitPart = {strsplit(",", combatDataString, 4000)} --strsplit(): Stack overflow, max allowed: 4000 + + if (#splitPart == 4000 and amountOfIndexes > 4000) then + + print ("#combatDataString (debug) must be > 4000:", amountOfIndexes) + for i = 1, 3999 do + combatData[#combatData+1] = splitPart[i] + end + + --get get part that couldn't be read this loop + combatDataString = splitPart[4000] + amountOfIndexes = amountOfIndexes - 3999 + + print ("#combatDataString (debug) left over:", amountOfIndexes) + else + for i = 1, #splitPart do + combatData[#combatData+1] = splitPart[i] + end + + amountOfIndexes = amountOfIndexes - #splitPart + end + end + + print("total indexes (debug):", #combatData) + + --if true then return end + + local flags = tonumber(combatData[INDEX_EXPORT_FLAG]) + + local tablePosition = TOTAL_INDEXES_FOR_COMBAT_INFORMATION + 1 --[[ +1 to jump to damage ]] + --tablePosition now have the first index of the actorInforTable + + --stop the combat if already in one + if (Details.in_combat) then + Details:EndCombat() + end + + --start a new combat + Details:StartCombat() + --get the current combat + local currentCombat = Details:GetCurrentCombat() + + --check if this export has include damage info + if (bit.band(flags, 0x1) ~= 0) then + --find the index where the damage information start + for i = tablePosition, #combatData do + if (combatData[i] == "!D") then + tablePosition = i + 1; + break + end + end + + --unpack damage + tablePosition = Details.packFunctions.UnPackDamage(currentCombat, combatData, tablePosition) + end + + if (bit.band(flags, 0x2) ~= 0) then + --find the index where the heal information start + for i = tablePosition, #combatData do + if (combatData[i] == "!H") then + tablePosition = i + 1; + break + end + end + + --unpack heal + Details.packFunctions.UnPackHeal(currentCombat, combatData, tablePosition) + end + + --all done, end combat + Details:EndCombat() + + --set the start and end of combat time and date + currentCombat:SetStartTime(combatData[INDEX_COMBAT_START_TIME]) + currentCombat:SetEndTime(combatData[INDEX_COMBAT_END_TIME]) + currentCombat:SetDate(combatData[INDEX_COMBAT_START_DATE], combatData[INDEX_COMBAT_END_DATE]) + currentCombat.enemy = combatData[INDEX_COMBAT_NAME] + + --debug: delete the segment just created (debug) + --[[ + local combat2 = _detalhes.tabela_historico.tabelas[2] + if (combat2) then + tremove (_detalhes.tabela_historico.tabelas, 1) + _detalhes.tabela_historico.tabelas[1] = combat2 + _detalhes.tabela_vigente = combat2 + end + --]] +end + + + +--get the amount of entries of a hash table +function Details.packFunctions.CountTableEntries(hasTable) + local amount = 0 + for _ in pairs(hasTable) do + amount = amount + 1 + end + return amount +end + +--get the amount of entries and check for validation +function Details.packFunctions.CountTableEntriesValid(hasTable) + local amount = 0 + for actorName, _ in pairs(hasTable) do + if (actorInformationIndexes[actorName]) then + amount = amount + 1 + end + end + return amount +end + +--stract some indexes of a table +local selectIndexes = function(table, startIndex, amountIndexes) + local values = {} + for i = startIndex, amountIndexes do + values[#values+1] = tonumber(table[i]) or 0 + end + return unpack(values) +end + +function Details.packFunctions.UnpackTable(table, index, isPair, valueAsTable, amountOfValues) + local result = {} + local reservedIndexes = table[index] + local indexStart = index+1 + local indexEnd = reservedIndexes+index + + if (isPair) then + amountOfValues = amountOfValues or 2 + for i = indexStart, indexEnd, amountOfValues do + if (valueAsTable) then + local key = tonumber(table[i]) + result[key] = {selectIndexes(table, i+1, amountOfValues-1)} + else + local key = tonumber(table[i]) + local value = tonumber(table[i+1]) + result[key] = value + end + end + else + for i = indexStart, indexEnd do + local value = tonumber(table[i]) + result[#result+1] = value + end + end + + return result +end \ No newline at end of file