Skip to content

Restore the account sharing data layer, and resolve our own guild by its registered id - #14

Open
uga wants to merge 4 commits into
Thaoky:developmentfrom
uga:fix/account-sharing-and-guild-ids
Open

Restore the account sharing data layer, and resolve our own guild by its registered id#14
uga wants to merge 4 commits into
Thaoky:developmentfrom
uga:fix/account-sharing-and-guild-ids

Conversation

@uga

@uga uga commented Aug 1, 2026

Copy link
Copy Markdown

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.

Account sharing also needs the companion changes in Altoholic_Cata
(linked PR). Either half alone is not enough to make the feature work.


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 read module.Characters[key]
  • ImportData() still read module.Characters[GetKey(...)]
  • ImportCharacter() still read addon.db.global.Characters, an object that no longer exists

They 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, so ImportCharacter() only takes the key.

CopyTable() recursed into the global CopyTable(), which returns a copy instead of filling the destination, so every nested table was silently dropped: an imported character arrived with its lastUpdate but with empty bags, talents, and so on.

2. Only share tables that are indexed by character id

sharedTables mixed 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:

Table Actually keyed by Effect
DataStore_Containers_Guilds guild id overwrote a guild bank
DataStore_Currencies_Info / _Max currency id collides with character ids
DataStore_Crafts_RecipeCategories category id, value is a string collision, plus CopyTable() failed on pairs() of a string
DataStore_Spells_Tabs class name never character data
DataStore_Currencies_Catalog / _Headers Set/List references never character data
DataStore_Talents_SpecializationInfos specialization id never character 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; missing tables are now skipped.

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().

Note that the index of a table in sharedTables identifies 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 by GetGuildInfo("player") and expanded by GetLongRealmName(), and stores that id in DataStore_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:

Default.NethergardeKeep.ZERO  = 1   <- what the writers use
Default.Pyrewood Village.ZERO = 2   <- what the readers asked for

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 bare return when the table is missing, so GetGuildMemberAverageItemLevel() and GetGuildMemberInventoryItem() were never even called: every member in the guild pane 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. The call that passes a name, realm and account is untouched, the search tab relies on it.

  • GetAlts() compared the second id against DataStore_CharacterGuilds of 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 the StoreToSetAndList() 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

Fix Classic Era TBC Anniversary Notes
Account sharing data layer yes yes version independent
sharedTables filtering yes yes the missing-table guard matters most on Classic, where the talent covenant tables do not exist
GetGuild() / GetAlts() guild id yes yes only manifests when the guild is homed on another realm of a connected group. Observed on Classic Era; a same-realm guild, as on the TBC realm tested, is unaffected

Testing

  • Account sharing: transfer completed between two characters, imported alts visible in the summary with bags, talents and professions populated.
  • Guild pane on Classic Era, connected realm: item level and equipment appear for every guild member running the addon, and expanding a member lists their alts. Both were broken before, and both were already correct on the TBC realm because its guild is not on a connected realm.

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.

Uga and others added 4 commits August 1, 2026 20:08
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant