-------- -- setup -------- -- toto, make cancelling a login clear LoginManager.auto_char_button_pressed = true local _G = _G or getfenv(0) local PASSWORD_MASK = "***************" local L = {} L["enUS"] = { -- localize the Left value class = { ["Druid"] = "Druid", ["Hunter"] = "Hunter", ["Mage"] = "Mage", ["Paladin"] = "Paladin", ["Priest"] = "Priest", ["Rogue"] = "Rogue", ["Shaman"] = "Shaman", ["Warrior"] = "Warrior", ["Warlock"] = "Warlock", }, -- localize the Right value ["SelectAccount"] = "Select account", ["RemoveAccount"] = "Remove account", ["LockAccounts"] = "Lock", ["StreamerMode"] = "Streamer", ["EncryptMode"] = "Encrypt", ["LockCharacters"] = "Lock Characters", ["NoDeps"] = "|cff77ff00Turtle AutoLogin|r requires Nampower 3.2.0+ or SuperWoW 1.4+ to operate.", } L["ruRU"] = { -- localize the Left value class = { ["Друид"] = "Druid", ["Охотник"] = "Hunter", ["Маг"] = "Mage", ["Паладин"] = "Paladin", ["Жрец"] = "Priest", ["Разбойник"] = "Rogue", ["Шаман"] = "Shaman", ["Воин"] = "Warrior", ["Чернокнижник"] = "Warlock", }, -- localize the Right value ["SelectAccount"] = "Выберите аккаунт", ["RemoveAccount"] = "Удалить аккаунт", ["LockAccounts"] = "Блок.", ["StreamerMode"] = "Стример", ["EncryptMode"] = "Шифр.", ["LockCharacters"] = "Заблокировать персонажей", ["NoDeps"] = "|cff77ff00Turtle AutoLogin|r требует Nampower 3.2.0+ или SuperWoW 1.4+ для работы.", } L = L[GetLocale()] or L["enUS"] local has_nampower = EncryptPassword and EncryptedServerLogin local has_superwow = (ImportFile and ExportFile) or (SUPERWOW_VERSION and tonumber(SUPERWOW_VERSION) >= 1.4) local has_encryption = false local encryption_missing_key = false if has_nampower then local ok, err = pcall(EncryptPassword, "test") if ok then has_encryption = true elseif string.find(err or "", "WOW_ENCRYPTION_KEY is not set") then encryption_missing_key = true end end if not has_nampower and not has_superwow then GlueDialogTypes["AL_NO_DEPS"] = { text = L["NoDeps"], button1 = TEXT(OKAY), showAlert = 1, } GlueDialog_Show("AL_NO_DEPS") return end GlueDialogTypes["AL_CANT_DECRYPT"] = { text = "This account's password is encrypted but decryption is not available.\nPlease re-enter your password to save it again.", button1 = TEXT(OKAY), showAlert = 1, } local CLASS_COLORS = { ["Druid"] = { r = 1.00, g = 0.49, b = 0.04, colorStr = "ffFF7C0A" }, ["Mage"] = { r = 0.25, g = 0.78, b = 0.92, colorStr = "ff3FC7EB" }, ["Hunter"] = { r = 0.67, g = 0.83, b = 0.45, colorStr = "ffAAD372" }, ["Paladin"] = { r = 0.96, g = 0.55, b = 0.73, colorStr = "ffF48CBA" }, ["Priest"] = { r = 1.00, g = 1.00, b = 1.00, colorStr = "ffFFFFFF" }, ["Rogue"] = { r = 1.00, g = 0.96, b = 0.41, colorStr = "ffFFF468" }, ["Shaman"] = { r = 0.00, g = 0.44, b = 0.87, colorStr = "ff0070DD" }, ["Warlock"] = { r = 0.53, g = 0.53, b = 0.93, colorStr = "ff8788EE" }, ["Warrior"] = { r = 0.78, g = 0.61, b = 0.43, colorStr = "ffC69B6D" }, } LoginManager = {} LoginManager.State = {} LoginManager.SelectedAcct = nil LoginManager.CurrentPage = 0 LoginManager.PageSize = 9 LoginManager.LimitReached = false LoginManager.from_login_screen = false LoginManager.has_nampower = has_nampower LoginManager.has_superwow = has_superwow -------- ------- -- util ------- -- Move the element at 'index' upward by one position. local function moveIndexUp(list, index) if index > 1 then local item = table.remove(list, index) table.insert(list, index - 1, item) end end -- Move the element at 'index' downward by one position. local function moveIndexDown(list, index) if list[index] and list[index+1] then local item = table.remove(list, index) table.insert(list, index + 1, item) end end ------- --------- -- serial --------- local Serialize = { serializeValue = function (self,val,indent) local t = type(val) if t == "string" then return string.format("%q", val) elseif t == "number" or t == "boolean" then return tostring(val) elseif t == "table" then return self:serializeTable(val,indent) else return '""' end end, serializeTable = function (self,tbl,indent) indent = indent or "" local nextIndent = indent .. " " local result = {} table.insert(result, "{\n") for k, v in pairs(tbl) do local key if type(k) == "string" and string.find(k, "^[_%a][_%w]*$") then key = k else key = "[" .. self:serializeValue(k, nextIndent) .. "]" end table.insert(result, nextIndent .. key .. " = " .. self:serializeValue(v, nextIndent) .. ",\n") end table.insert(result, indent .. "}") return table.concat(result) end, } --------- --------------- -- LoginManager --------------- function LoginManager:ToFile(file,value) local v = Serialize:serializeValue(value) if v ~= '""' then ExportFile(file, Serialize:serializeValue(value," ")) return true end return false end function LoginManager:FromFile(file) local contents = ImportFile(file) if contents then local chunk,err = loadstring("return " .. contents) if not err then return chunk() end end return false end function LoginManager:HideSideButtons() local hide_frames = { "AccountLoginTurtleWebsite", "AccountLoginTurtleArmory", "AccountLoginTurtleKnowledgeDatabase", "AccountLoginTurtleCommunityForum", "AccountLoginTurtleDiscord", "AccountLoginTurtleReddit", "AccountLoginCinematicsButton", -- "AccountLoginCreditsButton", } for _,frame in ipairs(hide_frames) do if _G[frame] then _G[frame]:Hide() end end end function LoginManager:MakeExtraAccountButtons() -- make account up/down/lock buttons local button_size = 26 for i=1,self.PageSize do local acctButton = _G["AutologinAccountButton"..i] if not acctButton.up then -- Create the Up button local upButton = CreateFrame("Button", acctButton:GetName().."Up", acctButton) upButton:SetWidth(button_size) upButton:SetHeight(button_size) upButton:SetPoint("TOPRIGHT", acctButton, "TOPRIGHT", -30, 1) upButton:SetHitRectInsets(1, 1, 1, 1) upButton:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollUp-Up") upButton:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollUp-Down") upButton:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD") upButton:SetScript("OnClick", function() moveIndexUp(self.State.accounts, this:GetParent():GetID()) LoginManager:UpdateUI() end) upButton:SetScript("OnEnter", function() this:GetParent():LockHighlight() end) upButton:SetScript("OnLeave", function() if this:GetParent():GetID() ~= self.SelectedAcct then this:GetParent():UnlockHighlight() end end) acctButton.up = upButton end if not acctButton.down then -- Create the Down button local downButton = CreateFrame("Button", acctButton:GetName().."Down", acctButton) downButton:SetWidth(button_size) downButton:SetHeight(button_size) downButton:SetPoint("TOP", acctButton.up, "BOTTOM", 0, 8) downButton:SetHitRectInsets(1, 1, 1, 1) downButton:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up") downButton:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down") downButton:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD") downButton:SetScript("OnClick", function() moveIndexDown(self.State.accounts, this:GetParent():GetID()) LoginManager:UpdateUI() end) downButton:SetScript("OnEnter", function() this:GetParent():LockHighlight() end) downButton:SetScript("OnLeave", function() if this:GetParent():GetID() ~= self.SelectedAcct then this:GetParent():UnlockHighlight() end end) acctButton.down = downButton end if not acctButton.char then -- Create the Char button local charButton = CreateFrame("Button", acctButton:GetName().."Char", acctButton, "GlueButtonSmallTemplate") charButton:SetWidth(40) charButton:SetHeight(35) charButton:SetPoint("RIGHT", acctButton, "RIGHT", -50, 8) charButton:SetHitRectInsets(1, 1, 1, 1) charButton:SetScript("OnClick", function() LoginManager:SelectAccount(this:GetParent():GetID()) LoginManager.auto_char_button_pressed = true AccountLogin_Login() end) charButton:SetScript("OnEnter", function() this:GetParent():LockHighlight() end) charButton:SetScript("OnLeave", function() if this:GetParent():GetID() ~= self.SelectedAcct then this:GetParent():UnlockHighlight() end end) acctButton.char = charButton end end local quitButton = _G["AccountLoginExitButton"] if has_nampower and not quitButton.encrypt then local encryptButton = CreateFrame("Button", "ButtonAccountButtonsEncrypt", quitButton, "GlueButtonSmallTemplate") encryptButton:SetWidth(150) encryptButton:SetHeight(35) encryptButton:SetPoint("RIGHT", quitButton, "LEFT", 4, 0) encryptButton:SetText(L["EncryptMode"]) encryptButton:SetWidth(encryptButton:GetFontString():GetStringWidth() + 50) encryptButton:SetScript("OnClick", function() if encryption_missing_key then return end LoginManager.State.encrypt_passwords = not LoginManager.State.encrypt_passwords this:Show() LoginManager:SaveAccounts() LoginManager:UpdateUI() end) if encryption_missing_key then encryptButton:SetScript("OnEnter", function() GlueTooltip_SetOwner(nil, encryptButton, 0, 0) GlueTooltip:ClearAllPoints() GlueTooltip:SetPoint("BOTTOM", encryptButton, "TOP", 0, 4) GlueTooltip_SetText("Set environment variable\n|cffffd100WOW_ENCRYPTION_KEY|r to enable.", nil, 1, 1, 1) end) encryptButton:SetScript("OnLeave", function() GlueTooltip:Hide() end) end quitButton.encrypt = encryptButton end if not quitButton.lock then local lockButton = CreateFrame("Button", "ButtonAccountButtonsLock", quitButton, "GlueButtonSmallTemplate") lockButton:SetWidth(150) lockButton:SetHeight(35) lockButton:SetPoint("RIGHT", (quitButton.encrypt or quitButton), "LEFT", 4, 0) lockButton:SetText(L["LockAccounts"]) lockButton:SetWidth(lockButton:GetFontString():GetStringWidth() + 50) lockButton:SetScript("OnClick", function() LoginManager.State.account_buttons_locked = not LoginManager.State.account_buttons_locked this:Show() LoginManager:SaveAccounts() LoginManager:UpdateUI() end) quitButton.lock = lockButton end if not quitButton.streamer then local streamerButton = CreateFrame("Button", "ButtonAccountButtonsStreamer", quitButton, "GlueButtonSmallTemplate") streamerButton:SetWidth(150) streamerButton:SetHeight(35) streamerButton:SetPoint("RIGHT", quitButton.lock, "LEFT", 4, 0) streamerButton:SetText(L["StreamerMode"]) streamerButton:SetWidth(streamerButton:GetFontString():GetStringWidth() + 50) streamerButton:SetScript("OnClick", function() LoginManager.State.account_buttons_streamer = not LoginManager.State.account_buttons_streamer this:Show() LoginManager:UpdateUI() end) quitButton.streamer = streamerButton end if not AccountLoginAccountEdit.cover then -- Create the account input cover for streaming mode local cover = CreateFrame("Frame", nil, AccountLoginAccountEdit) cover:SetFrameLevel(AccountLoginAccountEdit:GetFrameLevel() + 10) cover:SetWidth(AccountLoginAccountEdit:GetWidth()-15) cover:SetHeight(AccountLoginAccountEdit:GetHeight()-15) cover:SetPoint("TOPLEFT",AccountLoginAccountEdit,"TOPLEFT", 10, -6) local tex = cover:CreateTexture() tex:SetAllPoints(cover) tex:SetTexture(0, 0, 0, 1) -- RGBA: Black, 80% opaque AccountLoginAccountEdit.cover = cover end end -- Strangely when you login and cancel the program scrubs the string you used for the password from _anywhere_ it finds it. -- Essentially it deletes the immutable string itself. We avoid this by storing the password string in memory with a -- leading prefix and clip it at use-time. : = plaintext, :encrypted: = encrypted (nampower). function LoginManager:LoadAccounts() self.State.accounts = {} self.State.last = nil local login_data = ImportFile("logins") local file = self:FromFile("logins") if file then if file.accounts then self.State.accounts = file.accounts end if file.last then self.State.last = file.last end if file.account_buttons_locked then self.State.account_buttons_locked = file.account_buttons_locked end if file.account_buttons_streamer then self.State.account_buttons_streamer = file.account_buttons_streamer end if has_encryption and file.encrypt_passwords ~= nil then self.State.encrypt_passwords = file.encrypt_passwords elseif has_encryption then self.State.encrypt_passwords = true end return true elseif login_data then -- old format for label,account,password,character,auto,last in string.gfind(login_data, "label:(%S*) account:(%S+) password(:%S+) character:(%S*) auto:(%S*) last:(%S*)\n") do if auto == "true" then auto = true else auto = false end table.insert(self.State.accounts, { --[[ label = label, ]] account = account, password = password, character = character, auto = auto }) end return true end -- no saved passwords? check the old style account string storage if not next(self.State.accounts) then local val = GetSavedAccountName() for n, p, c in string.gfind(val, "(%S+) (%S+) *(%d*);") do if (c == "") then c = "-" end -- Decompress duplicate passwords if (string.find(p, "~%d") == 1) then p = self.State.accounts[tonumber(string.sub(p, 2, 3))].password end table.insert(self.State.accounts, { account = n, password = p }) end return true end return false end function LoginManager:SaveAccounts(by_login) if by_login then local account = AccountLoginAccountEdit:GetText() local password = AccountLoginPasswordEdit:GetText() -- only try to overwrite a password when there is one to overwrite! if (account and account ~= "" and password and password ~= "" and password ~= PASSWORD_MASK) then local stored = ":"..password if self.State.encrypt_passwords then local ok, encrypted = pcall(EncryptPassword, password) if ok then stored = encrypted end end local exists = false for i = 1, table.getn(self.State.accounts) do if (self.State.accounts[i].account == account) then exists = true self.State.accounts[i].password = stored break end end if (not exists) then self.pending_account = { account = account, password = stored } end end end return self:ToFile("logins", self.State) end function LoginManager:SaveLabel() self:SaveAccounts(true) self:UpdateUI() end function LoginManager:SelectAccount(idx) local i = self.CurrentPage * self.PageSize + idx; local act = self.State.accounts[i].account local pwd = self.State.accounts[i].password AccountLoginAccountEdit:SetText(act) if string.find(pwd, "^:encrypted:") then AccountLoginPasswordEdit:SetText(PASSWORD_MASK) else AccountLoginPasswordEdit:SetText(string.sub(pwd, 2)) end LoginManager:OnNameUpdate(act) end function LoginManager:OnNameUpdate(name) self.SelectedAcct = nil for i = 1, table.getn(self.State.accounts) do if (self.State.accounts[i].account == name) then self.SelectedAcct = i end end if (self.SelectedAcct) then self.CurrentPage = math.floor((self.SelectedAcct - 1) / self.PageSize) end self:UpdateUI() end function LoginManager:UpdateUI() if AccountLoginUI:IsVisible() then self:MakeExtraAccountButtons() self:HideSideButtons() AccountLoginSaveAccountName:Hide() self:UpdateLoginUI() elseif CharacterSelectUI:IsVisible() then self:UpdateCharacterUI() end end function LoginManager:UpdateLoginUI() local total = table.getn(self.State.accounts) local pageIndices = {} local skip = self.CurrentPage * self.PageSize; AutologinSelectAccountText:SetText(L["SelectAccount"]) AutologinRemoveAccountButton:SetText(L["RemoveAccount"]) local streamer = self.State.account_buttons_streamer if AccountLoginAccountEdit.cover then if streamer then AccountLoginAccountEdit.cover:Show() else AccountLoginAccountEdit.cover:Hide() end end for i = 1, self.PageSize do local button = _G["AutologinAccountButton" .. i] local acct_id = skip + i button:UnlockHighlight() if (acct_id > total) then button:Hide() else local r = self.State.accounts[acct_id] local selected = self.SelectedAcct == acct_id local acc_str = r.account if streamer then acc_str = string.sub(acc_str,1,1) .. string.rep('*',5) .. string.sub(acc_str,-2) end _G["AutologinAccountButton" .. i .. "ButtonTextName"]:SetText(acc_str) -- get save class, show character login name using class info and whether we're in streamer mode local class_str = r.class or "-----" local chr_str = streamer and class_str or r.character local class_clr = r.class and L.class[class_str] and CLASS_COLORS[L.class[class_str]].colorStr if class_clr then chr_str = class_clr and ("|c" .. class_clr .. chr_str .. "|r") end button.char:SetText(chr_str or "") button.char:SetWidth(button.char:GetFontString():GetStringWidth() + 20) if self.State.account_buttons_locked or acct_id == table.getn(self.State.accounts) then button.down:Hide() else button.down:Show() end if self.State.account_buttons_locked or acct_id == 1 then button.up:Hide() else button.up:Show() end if not r.character or r.character == "" then button.char:Hide() else button.char:Show() end if selected then button:LockHighlight() end button:Show() end end local lockButton = _G["ButtonAccountButtonsLock"] if lockButton then if self.State.account_buttons_locked then lockButton:GetNormalTexture():SetVertexColor(0.5,0.5,0.5) lockButton:SetTextColor(0.5,0.5,0.5) else lockButton:GetNormalTexture():SetVertexColor(1,1,1) lockButton:SetTextColor(1,0.78,0) end end local streamerButton = _G["ButtonAccountButtonsStreamer"] if streamerButton then if self.State.account_buttons_streamer then streamerButton:GetNormalTexture():SetVertexColor(0.5,0.5,0.5) streamerButton:SetTextColor(0.5,0.5,0.5) else streamerButton:GetNormalTexture():SetVertexColor(1,1,1) streamerButton:SetTextColor(1,0.78,0) end end local encryptButton = _G["ButtonAccountButtonsEncrypt"] if encryptButton then if encryption_missing_key then encryptButton:GetNormalTexture():SetVertexColor(0.3,0.3,0.3) encryptButton:SetTextColor(0.7,0.15,0.15) elseif self.State.encrypt_passwords then encryptButton:GetNormalTexture():SetVertexColor(0.5,0.5,0.5) encryptButton:SetTextColor(0.5,0.5,0.5) else encryptButton:GetNormalTexture():SetVertexColor(1,1,1) encryptButton:SetTextColor(1,0.78,0) end end local removeButton = _G["AutologinRemoveAccountButton"] if removeButton then if self.State.account_buttons_locked then removeButton:Hide() else removeButton:Show() end end end function LoginManager:UpdateCharacterUI() local numChars = GetNumCharacters() local index = 1 for _,char in ipairs(LoginManager.realm_chars) do local zone = char.zone local button = _G["CharSelectCharacterButton"..index] if ( not char.name ) then button:SetText("ERROR - Tell Jeremy") else button:UnlockHighlight() if ( not char.zone ) then zone = "" end local classColor = L.class[char.class] and CLASS_COLORS[L.class[char.class]].colorStr or "ffFFFFFF" _G["CharSelectCharacterButton"..index.."ButtonTextName"]:SetText(char.name) _G["CharSelectCharacterButton"..index.."ButtonTextInfo"]:SetText( format(TEXT(char.ghost and CHARACTER_SELECT_INFO_GHOST or CHARACTER_SELECT_INFO), char.level, "|c" .. classColor .. char.class .. "|r") ) _G["CharSelectCharacterButton"..index.."ButtonTextLocation"]:SetText(zone) end if (char.id == CharacterSelect.selectedIndex) then button:LockHighlight() end if LoginManager.char_buttons_locked then button.up:Hide() button.down:Hide() else button.up:Show() button.down:Show() end index = index + 1 if ( index > MAX_CHARACTERS_DISPLAYED ) then break end end local lockButton = _G["ButtonCharButtonsLock"] if lockButton then if self.char_buttons_locked then lockButton:GetNormalTexture():SetVertexColor(0.5,0.5,0.5) lockButton:SetTextColor(0.5,0.5,0.5) else lockButton:GetNormalTexture():SetVertexColor(1,1,1) lockButton:SetTextColor(1,0.78,0) end end end function LoginManager:OnLogin() local name = AccountLoginAccountEdit:GetText() local password = AccountLoginPasswordEdit:GetText() self.State.last = self.SelectedAcct self:SaveAccounts(true) self:OnNameUpdate(name) self.from_login_screen = true if self.SelectedAcct and self.State.accounts[self.SelectedAcct] then local stored = self.State.accounts[self.SelectedAcct].password if string.find(stored, "^:encrypted:") then if not has_encryption then GlueDialog_Show("AL_CANT_DECRYPT") return end EncryptedServerLogin(name, stored) return end end DefaultServerLogin(name, password) end function LoginManager:RemoveAccount() if not next(LoginManager.State.accounts) or not LoginManager.SelectedAcct then return end table.remove(LoginManager.State.accounts, LoginManager.SelectedAcct) LoginManager:SaveAccounts() AccountLoginAccountEdit:SetText("") AccountLoginPasswordEdit:SetText("") if (LoginManager.CurrentPage > 0 and LoginManager.CurrentPage * LoginManager.PageSize > table.getn(LoginManager.State) - 1) then LoginManager.CurrentPage = LoginManager.CurrentPage - 1 end LoginManager:UpdateUI() end function LoginManager:NextPage() if ((self.CurrentPage + 1) * self.PageSize > table.getn(self.State.accounts) - 1) then return end self.CurrentPage = self.CurrentPage + 1 self:UpdateUI() end function LoginManager:PrevPage() if (self.CurrentPage == 0) then return end self.CurrentPage = self.CurrentPage - 1 self:UpdateUI() end function LoginManager:OnCharactersLoad() self:LoadAccounts() if self.pending_account and self.pending_account.account and self.pending_account.account ~= "" and self.pending_account.password and self.pending_account.password ~= "" then table.insert(self.State.accounts, self.pending_account) self:SaveAccounts() end self.pending_account = nil -- make up/down/auto buttons for i=1,10 do local charButton = _G["CharSelectCharacterButton"..i] if not charButton.up then -- Create the Up button local upButton = CreateFrame("Button", charButton:GetName().."Up", charButton) upButton:SetWidth(32) upButton:SetHeight(32) upButton:SetPoint("TOPRIGHT", charButton, "TOPRIGHT", -30, 0) upButton:SetHitRectInsets(1, 1, 1, 1) upButton:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollUp-Up") upButton:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollUp-Down") upButton:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD") upButton:SetScript("OnClick", function() moveIndexUp(LoginManager.realm_chars, this:GetParent():GetID()) LoginManager:UpdateUI() end) upButton:SetScript("OnEnter", function() this:GetParent():LockHighlight() end) upButton:SetScript("OnLeave", function() if this:GetParent():GetID() ~= CharacterSelect.selectedIndex then this:GetParent():UnlockHighlight() end end) charButton.up = upButton end if not charButton.down then -- Create the Down button local downButton = CreateFrame("Button", charButton:GetName().."Down", charButton) downButton:SetWidth(32) downButton:SetHeight(32) downButton:SetPoint("TOP", charButton.up, "BOTTOM", 0, 8) downButton:SetHitRectInsets(1, 1, 1, 1) downButton:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up") downButton:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down") downButton:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD") downButton:SetScript("OnClick", function() moveIndexDown(LoginManager.realm_chars, this:GetParent():GetID()) LoginManager:UpdateUI() end) downButton:SetScript("OnEnter", function() this:GetParent():LockHighlight() end) downButton:SetScript("OnLeave", function() if this:GetParent():GetID() ~= CharacterSelect.selectedIndex then this:GetParent():UnlockHighlight() end end) charButton.down = downButton end end local addonsButton = _G["CharacterSelectAddonsButton"] if not addonsButton.lock then -- Create the Lock button local lockButton = CreateFrame("Button", "ButtonCharButtonsLock", addonsButton, "GlueButtonSmallTemplate") lockButton:SetWidth(150) lockButton:SetHeight(35) lockButton:SetPoint("LEFT", addonsButton, "RIGHT", 4, 0) lockButton:SetText(L["LockCharacters"]) lockButton:SetWidth(lockButton:GetFontString():GetStringWidth() + 50) lockButton:SetScript("OnClick", function() LoginManager.char_buttons_locked = not LoginManager.char_buttons_locked this:Show() LoginManager:SaveAccounts() LoginManager:UpdateUI() end) addonsButton.lock = lockButton end self.realm = GetServerName() self.realm_chars = {} for c = 1, GetNumCharacters() do local name, race, class, level, zone, fileString, gender, ghost = GetCharacterInfo(c) self.realm_chars[c] = { id = c, name = name, race = race, class = class, level = level, zone = zone, fileString = fileString, gender = gender, ghost = ghost } end if not self.from_login_screen then -- handle case that we came from a char logout, iow determine what account we're using based on characters -- this matters when we're multiboxing or have auto-login enabled local function SetIdx() for i,account in ipairs(self.State.accounts) do for saved_realm,saved_chars in pairs(account.characters or {}) do if saved_realm == self.realm then for _,schar in pairs(saved_chars.order) do for _,rchar in pairs(self.realm_chars) do if schar.name == rchar.name then self.SelectedAcct = i return end end end end end end end SetIdx() end -- reconcile given chars with saved chars local acct = self.State.accounts[self.SelectedAcct] if acct and acct.characters then for saved_realm,saved_chars in pairs(acct.characters or {}) do if saved_realm == self.realm then self.char_buttons_locked = saved_chars.char_buttons_locked local orderRank = {} local rank = 1 for _, item in ipairs(saved_chars.order or {}) do orderRank[item.id] = rank rank = rank + 1 end -- Reorder primaryList in place using table.sort with a custom comparator. table.sort(LoginManager.realm_chars, function(a, b) local ra = orderRank[a.id] or 10000 local rb = orderRank[b.id] or 10000 return ra < rb end) local numChars = GetNumCharacters() if numChars > 0 then CharacterSelect_SelectCharacter(math.min(saved_chars.last or 1, numChars)) if self.auto_char_button_pressed then self.auto_char_button_pressed = false EnterWorld() end end break end end end self:UpdateUI() end function LoginManager:EnterWorld() -- save live state if self.realm and self.SelectedAcct and self.State.accounts[self.SelectedAcct] then local acct = self.State.accounts[self.SelectedAcct] acct.characters = acct.characters or {} acct.characters[self.realm] = acct.characters[self.realm] or {} local chars = acct.characters[self.realm] chars.last = CharacterSelect.selectedIndex or 1 chars.order = {} for i,char in ipairs(LoginManager.realm_chars) do table.insert(chars.order, { id = char.id, name = char.name }) end chars.char_buttons_locked = self.char_buttons_locked or nil local name, race, class, _level, zone, _race2, _gender, _isGhost = GetCharacterInfo(chars.last) acct.character = name acct.class = class acct.race = race acct.zone = zone end self:SaveAccounts() EnterWorld() end --------------- ---------- -- buttons ---------- function AutologinAccountButton_OnClick(button) if button == "LeftButton" then LoginManager:SelectAccount(this:GetID()) end end function AutologinAccountButton_OnDoubleClick() LoginManager:SelectAccount(this:GetID()) AccountLogin_Login() end function AutologinRemoveAccountButton_OnClick() LoginManager:RemoveAccount() end ---------- -------- -- hooks -------- local orig_AccountLogin_OnLoad = AccountLogin_OnLoad AccountLogin_OnLoad = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) if orig_AccountLogin_OnLoad then orig_AccountLogin_OnLoad(a1,a2,a3,a4,a5,a6,a7,a8,a9) end LoginManager:LoadAccounts() end local orig_AccountLogin_OnShow = AccountLogin_OnShow AccountLogin_OnShow = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) if orig_AccountLogin_OnShow then orig_AccountLogin_OnShow(a1,a2,a3,a4,a5,a6,a7,a8,a9) end if not LoginManager.State.accounts then LoginManager:LoadAccounts() end if LoginManager.State.last and LoginManager.State.accounts[LoginManager.State.last] then LoginManager:SelectAccount(LoginManager.State.last) end LoginManager:UpdateUI() end local orig_AccountLoginAccountEdit_OnTextChanged = AccountLoginAccountEdit:GetScript("OnTextChanged") AccountLoginAccountEdit:SetScript("OnTextChanged", function (a1,a2,a3,a4,a5,a6,a7,a8,a9) orig_AccountLoginAccountEdit_OnTextChanged(a1,a2,a3,a4,a5,a6,a7,a8,a9) LoginManager:OnNameUpdate(this:GetText()) end) local orig_AccountLogin_Login = AccountLogin_Login AccountLogin_Login = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) LoginManager:OnLogin() -- if we got this far it's our show, take over -- if orig_AccountLogin_Login then orig_AccountLogin_Login(a1,a2,a3,a4,a5,a6,a7,a8,a9) end end -------- local orig_CharacterSelectButton_OnClick = CharacterSelectButton_OnClick CharacterSelectButton_OnClick = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) -- orig_CharacterSelectButton_OnClick(a1,a2,a3,a4,a5,a6,a7,a8,a9) local id = this:GetID() local sorted_id = LoginManager.realm_chars[id].id if ( sorted_id ~= CharacterSelect.selectedIndex ) then CharacterSelect_SelectCharacter(sorted_id) end LoginManager:UpdateUI() end local orig_CharacterSelectButton_OnDoubleClick = CharacterSelectButton_OnDoubleClick CharacterSelectButton_OnDoubleClick = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) -- orig_CharacterSelectButton_OnDoubleClick(a1,a2,a3,a4,a5,a6,a7,a8,a9) local id = this:GetID() local sorted_id = LoginManager.realm_chars[id].id if ( sorted_id ~= CharacterSelect.selectedIndex ) then CharacterSelect_SelectCharacter(sorted_id) end CharacterSelect_EnterWorld() end local orig_CharacterSelect_OnKeyDown = CharacterSelect_OnKeyDown CharacterSelect_OnKeyDown = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) local numChars = GetNumCharacters() local button_index for i,char in ipairs(LoginManager.realm_chars) do if char.id == this.selectedIndex then button_index = i break end end if ( arg1 == "UP" or arg1 == "LEFT" ) then if ( numChars > 1 ) then if ( button_index > 1 ) then _G["CharSelectCharacterButton" .. (button_index - 1)]:Click() else _G["CharSelectCharacterButton" .. numChars]:Click() end end elseif ( arg1 == "DOWN" or arg1 == "RIGHT" ) then if ( numChars > 1 ) then if ( button_index < GetNumCharacters() ) then _G["CharSelectCharacterButton" .. (button_index + 1)]:Click() else _G["CharSelectCharacterButton" .. 1]:Click() end end else orig_CharacterSelect_OnKeyDown(a1,a2,a3,a4,a5,a6,a7,a8,a9) end end local orig_CharacterSelect_OnEvent = CharacterSelect_OnEvent CharacterSelect_OnEvent = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) if orig_CharacterSelect_OnEvent then orig_CharacterSelect_OnEvent(a1,a2,a3,a4,a5,a6,a7,a8,a9) end if event == "CHARACTER_LIST_UPDATE" then LoginManager:OnCharactersLoad() end end local orig_CharacterSelect_EnterWorld = CharacterSelect_EnterWorld CharacterSelect_EnterWorld = function (a1,a2,a3,a4,a5,a6,a7,a8,a9) LoginManager:EnterWorld() end --------