diff --git a/DataStore/API/Core.lua b/DataStore/API/Core.lua index 5259457..4978b29 100644 --- a/DataStore/API/Core.lua +++ b/DataStore/API/Core.lua @@ -325,30 +325,39 @@ local function GetModuleTable(module) end +-- Tables that account sharing is allowed to transfer, per module. +-- Only list tables that are indexed by character id (ie: registered as characterTables or characterIdTables). +-- Guild tables are indexed by guild id, and reference tables by a currency id, a category id, a class name, etc.. +-- an entry of theirs at [charID] belongs to something else entirely, and sending it would overwrite +-- unrelated data on the receiving side. +-- The index of a table in this list is what identifies it in the transferred payload, so both ends must +-- run the same version. Append new tables at the end of a module rather than inserting them. local sharedTables = { - DataStore = { - -- "DataStore_GuildIDs", - -- "DataStore_GuildFactions", - -- "DataStore_CharacterIDs", - "DataStore_CharacterGUIDs", - -- "DataStore_CharacterGuilds", + DataStore = { + -- "DataStore_GuildIDs", + -- "DataStore_GuildFactions", + -- "DataStore_CharacterIDs", + "DataStore_CharacterGUIDs", + -- "DataStore_CharacterGuilds", -- "DataStore_AltGroups", "DataStore_ConnectedRealms", "DataStore_RealmNames" }, DataStore_Achievements = { "DataStore_Achievements_Characters" }, DataStore_Auctions = { "DataStore_Auctions_Characters", "DataStore_Auctions_AuctionsList", "DataStore_Auctions_BidsList" }, DataStore_Characters = { "DataStore_Characters_Info" }, - DataStore_Containers = { - "DataStore_Containers_Characters", "DataStore_Containers_Banks", "DataStore_Containers_Guilds", "DataStore_Containers_Reagents", + DataStore_Containers = { + -- "DataStore_Containers_Guilds" is a guild table + "DataStore_Containers_Characters", "DataStore_Containers_Banks", "DataStore_Containers_Reagents", "DataStore_Containers_VoidStorage", "DataStore_Containers_Keystones", "DataStore_Containers_BankTypes" }, - DataStore_Crafts = { "DataStore_Crafts_Characters", "DataStore_Crafts_ArcheologyItems", "DataStore_Crafts_RecipeCategories" }, - DataStore_Currencies = { - "DataStore_Currencies_Characters", "DataStore_Currencies_Catalog", "DataStore_Currencies_Info", "DataStore_Currencies_Max", - "DataStore_Currencies_Headers", "DataStore_Currencies_Archeology" + -- "DataStore_Crafts_RecipeCategories" is indexed by category id + DataStore_Crafts = { "DataStore_Crafts_Characters", "DataStore_Crafts_ArcheologyItems" }, + DataStore_Currencies = { + -- "DataStore_Currencies_Catalog" & "_Headers" are Set/List references, "_Info" & "_Max" are indexed by currency id + "DataStore_Currencies_Characters", "DataStore_Currencies_Archeology" }, - DataStore_Garrisons = { - "DataStore_Garrisons_Characters", "DataStore_Garrisons_Missions", "DataStore_Garrisons_MissionInfos", - "DataStore_Garrisons_Followers", "DataStore_Garrisons_FollowerNamesToID", "DataStore_Garrisons_Buildings", + DataStore_Garrisons = { + "DataStore_Garrisons_Characters", "DataStore_Garrisons_Missions", "DataStore_Garrisons_MissionInfos", + "DataStore_Garrisons_Followers", "DataStore_Garrisons_FollowerNamesToID", "DataStore_Garrisons_Buildings", "DataStore_Garrisons_CovenantSanctum", "DataStore_Garrisons_CypherEquipment", "DataStore_Garrisons_Shipments" }, DataStore_Inventory = { "DataStore_Inventory_Characters" }, @@ -357,50 +366,58 @@ local sharedTables = { --, "DataStore_Quests_History", "DataStore_Quests_Progress", "DataStore_Quests_Dailies", "DataStore_Quests_Weeklies", "DataStore_Quests_Colors", "DataStore_Quests_Infos" }, DataStore_Reputations = { "DataStore_Reputations_Characters" }, - DataStore_Spells = { "DataStore_Spells_Characters", "DataStore_Spells_Tabs" }, + -- "DataStore_Spells_Tabs" is indexed by class name + DataStore_Spells = { "DataStore_Spells_Characters" }, DataStore_Stats = { "DataStore_Stats_Characters" --, "DataStore_Stats_Weekly", "DataStore_Stats_Dungeons" }, - DataStore_Talents = { - "DataStore_Talents_Characters", "DataStore_Talents_Specializations", "DataStore_Talents_SpecializationInfos", - "DataStore_Talents_Covenant", "DataStore_Talents_Conduits", "DataStore_Talents_ConduitSpecs", + DataStore_Talents = { + -- "DataStore_Talents_SpecializationInfos" is indexed by specialization id + "DataStore_Talents_Characters", "DataStore_Talents_Specializations", + "DataStore_Talents_Covenant", "DataStore_Talents_Conduits", "DataStore_Talents_ConduitSpecs", "DataStore_Talents_Soulbinds", "DataStore_Talents_Reasons" }, } function addon:GetCharacterTable(module, name, realm, account) -- Can the module be shared? - if not sharedTables[module] then return {} end - + local moduleTables = sharedTables[module] + if not moduleTables then return {} end + local charTable = {} local key = GetKey(name, realm, account) local charID = addon:GetCharacterID(key) - + -- Iterate tables in the current module - local moduleTables = sharedTables[module] - for moduleIndex, tableName in ipairs(moduleTables) do + -- the table may not exist at all, several of them are only created on retail + local sourceTable = _G[tableName] + -- do we have data for this character in the current table ? - if _G[tableName][charID] then - - -- we do, link it + if sourceTable and sourceTable[charID] then + + -- we do, link it charTable[module] = charTable[module] or {} - charTable[module][moduleIndex] = _G[tableName][charID] + charTable[module][moduleIndex] = sourceTable[charID] end end - + return charTable - -- return module.Characters[GetKey(name, realm, account)] - end function addon:GetModuleLastUpdate(module, name, realm, account) - module = GetModuleTable(module) + -- Can the module be shared? + local moduleTables = sharedTables[module] + if not moduleTables then return end - local key = GetKey(name, realm, account) - if key then - return module.Characters[key].lastUpdate - end + -- the first table of a module is the one holding its character data + local characterTable = _G[moduleTables[1]] + if not characterTable then return end + + local charID = addon:GetCharacterID(GetKey(name, realm, account)) + if not charID then return end + + return characterTable[charID] and characterTable[charID].lastUpdate end function addon:GetModuleLastUpdateByKey(moduleName, key) @@ -419,31 +436,42 @@ function addon:GetModuleLastUpdateByKey(moduleName, key) end function addon:ImportData(module, data, name, realm, account) - module = GetModuleTable(module) - - -- CopyTable is necessary rather than assignment, without it, ace DB wildcards are not applied. - addon:CopyTable(data, module.Characters[GetKey(name, realm, account)]) + -- Can the module be shared? + local moduleTables = sharedTables[module] + if not moduleTables then return end + + -- data was packed by GetCharacterTable: data[module][moduleIndex] = the module table of that character + local importedTables = data and data[module] + if not importedTables then return end + + -- be sure an id exists for the imported character, every module table is indexed by it + local charID = addon:StoreToSetAndList(DataStore_CharacterIDs, GetKey(name, realm, account)) + + for moduleIndex, importedData in pairs(importedTables) do + local tableName = moduleTables[moduleIndex] + local destination = tableName and _G[tableName] + + if destination then + if type(importedData) == "table" then + -- CopyTable is necessary rather than assignment, the source table belongs to the serializer. + destination[charID] = {} + addon:CopyTable(importedData, destination[charID]) + else + -- character id tables may hold a plain value (ex: a specialization id) + destination[charID] = importedData + end + end + end end -function addon:ImportCharacter(key, faction, guild) +function addon:ImportCharacter(key) -- after data has been imported, add a player entry to the DB, so that it becomes "visible" to the outside world. -- in other words, the correct sequence of operations should be something like: -- DataStore:ImportData(DataStore_Talents) -- DataStore:ImportData(DataStore_Spells) - -- DataStore:ImportCharacter(key, faction, guild) + -- DataStore:ImportCharacter(key) - local characters = addon.db.global.Characters - - characters[key].faction = faction - characters[key].guildName = guild - - -- Ensure a key is created for every module, even those which were not imported. - -- Required for proper UI support without extra validation of every method - addon:IterateDBModules(function(moduleDB) - if moduleDB.Characters then - moduleDB.Characters[key].lastUpdate = time() - end - end) + return addon:StoreToSetAndList(DataStore_CharacterIDs, key) end diff --git a/DataStore/API/GuildComm.lua b/DataStore/API/GuildComm.lua index 60c6fe5..b054576 100644 --- a/DataStore/API/GuildComm.lua +++ b/DataStore/API/GuildComm.lua @@ -29,14 +29,16 @@ local function GetKey(name, realm, account) return format("%s.%s.%s", account, realm, name) end -local function GetAlts() +local function GetAlts(guildID) + -- guildID = the guild of the current character. Callers that have just resolved it pass it in, + -- the others ask for the one this character is registered under. + -- Do not rebuild a key from ThisRealm here: on connected realms the guild may be homed on another + -- realm of the group, and OnPlayerGuildUpdate() registered it under that one. Rebuilding it would + -- compare our alts against the wrong id, so none of them would ever match, and it would also + -- register a second id in DataStore_GuildIDs for a guild that already has one. + guildID = guildID or addon:GetCharacterGuildID(addon.ThisCharKey) + if not guildID then return end - local guild = GetGuildInfo("player") - if not guild then return end - - local guildKey = GetKey(guild) - local guildID = addon:StoreToSetAndList(DataStore_GuildIDs, guildKey) - local out = {} for k, charID in pairs(DataStore_CharacterIDs.Set) do local account, realm, char = strsplit(".", k) @@ -131,7 +133,8 @@ local function OnPlayerGuildUpdate() DataStore_GuildFactions[guildID] = addon.ThisFaction -- the first time a valid value is found, broadcast to guild, it must happen here for a standard login, but won't work here after a reloadui since this event is not triggered - addon:GuildBroadcast(commPrefix, MSG_ANNOUNCELOGIN, GetAlts()) + -- pass the id we just resolved, it is only written to DataStore_CharacterGuilds below + addon:GuildBroadcast(commPrefix, MSG_ANNOUNCELOGIN, GetAlts(guildID)) AddonFactory:Broadcast("DATASTORE_ANNOUNCELOGIN", currentGuildName) end diff --git a/DataStore/API/Utility.lua b/DataStore/API/Utility.lua index 2c3431a..796cd28 100644 --- a/DataStore/API/Utility.lua +++ b/DataStore/API/Utility.lua @@ -47,18 +47,24 @@ function addon:SortedArrayClone(array) return clone end -function addon:CopyTable(source, destination) +local function CopyTableInto(source, destination) for k, v in pairs(source) do - + if type(v) == "table" then destination[k] = {} - CopyTable(v, destination[k]) + CopyTableInto(v, destination[k]) else destination[k] = v end end end +function addon:CopyTable(source, destination) + -- note: do not call the global CopyTable() for nested tables, it returns a new table + -- instead of filling the destination, which silently dropped every sub-table. + CopyTableInto(source, destination) +end + function addon:ArrayInsertUnique(array, value) -- Check if the element already exists in the array for _, v in ipairs(array) do diff --git a/DataStore/DataStore.lua b/DataStore/DataStore.lua index ced59db..37a7b7d 100644 --- a/DataStore/DataStore.lua +++ b/DataStore/DataStore.lua @@ -261,9 +261,21 @@ end -- *** Guild functions *** function addon:GetGuild(name, realm, account) + -- No name ? the caller wants the guild of the current character, so answer with the guild it is + -- actually registered in, instead of rebuilding a key from its own realm. On connected realms the + -- guild may live on another realm of the group, and OnPlayerGuildUpdate() registered it under that + -- realm. Rebuilding the key from ThisRealm then creates a second key for the same guild, with a + -- second id, and every module that stored its data under the registered one looks empty. + if not name then + local guildID = _GetCharacterGuildID(addon.ThisCharKey) + local guildKey = guildID and allGuilds.List[guildID] + + if guildKey then return guildKey end + end + name = name or GetGuildInfo("player") local key = GetKey(name, realm, account) - + if allGuilds.Set[key] then -- if the key is known, return it to caller, it can be passed to other modules return key else -- if the key is not known, try checking the connected realm info