Restore the account sharing data layer, and resolve our own guild by its registered id - #14
Open
uga wants to merge 4 commits into
Open
Restore the account sharing data layer, and resolve our own guild by its registered id#14uga wants to merge 4 commits into
uga wants to merge 4 commits into
Conversation
Account sharing still called APIs that disappeared when Altoholic stopped being an AceAddon and DataStore moved off AceDB, so the very first step of a share failed with "Comm.lua:81: attempt to call a nil value". Comm: Whisper() called Altoholic:SendCommMessage(), an AceComm-3.0 mixin that no longer exists; it now goes through DataStore:SendChatMessage(). Nothing registered the "AltoShare" prefix either, so the receiving side was never reached even when a message got out; AccSharingHandler is now a plain callback registered with DataStore:OnGuildComm() at load. DataStore: GetModuleLastUpdate(), ImportData() and ImportCharacter() still indexed module.Characters[key] and addon.db.global, whereas GetCharacterTable() had already been ported to the id-based schema. They now resolve the character id and read/write the module tables listed in sharedTables, and ImportData() allocates an id for an unknown key so imported alts become visible. Faction and guild are no longer stored per character, so ImportCharacter() only takes the key and the transfer no longer tries to read them off the payload. CopyTable() recursed into the global CopyTable(), which returns a copy instead of filling the destination, so every nested table was silently dropped. Altoholic: the sharing UI and SetLastAccountSharingInfo() read Altoholic.db.global.Sharing, now Altoholic_Sharing_Options. The domain entry is created on demand instead of being assumed to exist, and the summary refresh is guarded since Altoholic_Summary is load on demand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sharedTables, the list of what account sharing transfers, mixed character tables with tables keyed by something else entirely. GetCharacterTable reads every entry as _G[tableName][charID], so those were sent as if they were the character's data, and ImportData wrote them back at the recipient's own char id: - DataStore_Containers_Guilds is a guild table, so a guild bank was overwritten whenever the recipient happened to have a guild with that id - DataStore_Currencies_Info and _Max are keyed by currency id, and DataStore_Crafts_RecipeCategories by category id, all small integers that collide with character ids. The last one holds a plain string, which also made CopyTable() fail on pairs() - DataStore_Spells_Tabs is keyed by class name, _Currencies_Catalog and _Headers are Set/List references, DataStore_Talents_SpecializationInfos is keyed by specialization id: never the character's data GetCharacterTable also assumed every listed table exists. On classic the talent covenant tables are not created at all, so sharing DataStore_Talents errored on indexing a nil global; it now skips missing tables. ImportData assumed every transferred value is a table, which is not true of the characterIdTables (a specialization id, a bank type); a plain value is now assigned directly instead of being fed to CopyTable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On a connected realm the guild may live on another realm of the group.
OnPlayerGuildUpdate() registers it under the guild's realm, as reported by
GetGuildInfo("player") and expanded by GetLongRealmName(), and stores that id in
DataStore_CharacterGuilds. GetGuild() however rebuilt the key from the
character's own realm, so for a character of Pyrewood Village in a guild homed
on Nethergarde Keep the two disagree:
Default.NethergardeKeep.ZERO = 1 <- what the writers use
Default.Pyrewood Village.ZERO = 2 <- what GetGuild() returned
Both keys end up in DataStore_GuildIDs, so the same guild has two ids. Every
module writes through GetCharacterGuildID() and therefore fills the tables at
the first id, while the guild members pane reads through GetGuild() and asks for
the second. The guild tables are dispatched by id, and that dispatch bails out
with a bare return when the table is missing, so GetGuildMemberAverageItemLevel
and GetGuildMemberInventoryItem were never even called: every member showed an
item level of 0.0 and no equipment at all.
GetGuild() with no name now answers with the key of the guild the current
character is registered in, which is by definition the id the writers use. The
call that passes a name, realm and account is untouched, the search tab relies
on it to resolve the guild of a bank result.
This only aligns the readers with the writers, it does not stop the duplicate
key from being created in the first place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same connected realm mismatch as GetGuild(), one layer down. GetAlts() rebuilt the guild key from the character's own realm: local guildKey = GetKey(guild) local guildID = addon:StoreToSetAndList(DataStore_GuildIDs, guildKey) then compared it against DataStore_CharacterGuilds[charID] of every alt. Those were written by OnPlayerGuildUpdate() using the guild's realm, so for a character of Pyrewood Village in a guild homed on Nethergarde Keep the two ids never matched and the list came back empty. The login broadcast therefore carried no alts, SaveAlts() skipped its non-empty branch, and expanding a member in the guild pane showed nothing - our own line included. That StoreToSetAndList() call is also what minted the duplicate key in the first place: it registers whatever key it is handed, so simply asking for the alts was enough to give an already known guild a second id. Resolving the id instead of building a key stops that too. The caller inside OnPlayerGuildUpdate() passes the id it has just resolved, since it is only written to DataStore_CharacterGuilds a few lines further down and would not be readable yet on a first login. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four fixes in
DataStore. Three of them restore account sharing, which has been non-functional since the addon stopped being an AceAddon and the DB moved to the character-id schema. The fourth fixes guild resolution on connected realms.Verified in game on Classic Era and TBC Anniversary.
1. Port the account sharing data layer to the id-based schema
GetCharacterTable()had already been ported to character ids, but its counterparts had not:GetModuleLastUpdate()still readmodule.Characters[key]ImportData()still readmodule.Characters[GetKey(...)]ImportCharacter()still readaddon.db.global.Characters, an object that no longer existsThey now resolve the character id and read/write the module tables listed in
sharedTables.ImportData()allocates an id for an unknown key, which is what makes an imported alt visible to client addons. Faction and guild are no longer stored per character, soImportCharacter()only takes the key.CopyTable()recursed into the globalCopyTable(), which returns a copy instead of filling the destination, so every nested table was silently dropped: an imported character arrived with itslastUpdatebut with empty bags, talents, and so on.2. Only share tables that are indexed by character id
sharedTablesmixed character tables with tables keyed by something else.GetCharacterTable()reads every entry as_G[tableName][charID], so those were sent as if they were the character's data and written back at the recipient's own character id:DataStore_Containers_GuildsDataStore_Currencies_Info/_MaxDataStore_Crafts_RecipeCategoriesCopyTable()failed onpairs()of a stringDataStore_Spells_TabsDataStore_Currencies_Catalog/_HeadersDataStore_Talents_SpecializationInfosGetCharacterTable()also assumed every listed table exists. On Classic the talent covenant tables are not created at all, so sharingDataStore_Talentserrored on indexing a nil global; missing tables are now skipped.ImportData()assumed every transferred value is a table, which is not true of thecharacterIdTables(a specialization id, a bank type); a plain value is now assigned directly instead of being fed toCopyTable().Note that the index of a table in
sharedTablesidentifies it in the payload, so both ends must run the same version. That was already true before this change.3 & 4. Resolve our own guild by the id it was registered under
On a connected realm the guild may be homed on another realm of the group.
OnPlayerGuildUpdate()registers it under the guild's realm, as reported byGetGuildInfo("player")and expanded byGetLongRealmName(), and stores that id inDataStore_CharacterGuilds. Two other places rebuilt the key from the character's own realm instead, so for a character of Pyrewood Village in a guild homed on Nethergarde Keep the two disagreed:Both keys end up in
DataStore_GuildIDs, so the same guild has two ids.GetGuild()returned the second one. Guild tables are dispatched by id and that dispatch bails out with a barereturnwhen the table is missing, soGetGuildMemberAverageItemLevel()andGetGuildMemberInventoryItem()were never even called: every member in the guild pane showed an item level of0.0and no equipment at all.GetGuild()with no name now answers with the key of the guild the current character is registered in. The call that passes a name, realm and account is untouched, the search tab relies on it.GetAlts()compared the second id againstDataStore_CharacterGuildsof every alt, so no alt ever matched and the login broadcast carried an empty list: expanding a member in the guild pane showed nothing, our own line included. It now resolves the id instead of building a key. That also stops the duplicate from being created in the first place, since theStoreToSetAndList()call it used registers whatever key it is handed: simply asking for the alts was enough to give an already known guild a second id.A duplicate created by an earlier version stays in the saved variables. It is inert once nothing reads or writes it, but it is not migrated.
Applicability
sharedTablesfilteringGetGuild()/GetAlts()guild idTesting
Related PRs
This is one change set spread over the repos it touches. Account sharing needs the first two together; the others are independent of each other.