local function DeepCopy(originalTable) local copyTable if type(originalTable) == 'table' then copyTable = {} for key, value in pairs(originalTable) do copyTable[key] = DeepCopy(value) end else copyTable = originalTable end return copyTable end local function GetNextBookIndex(bookIndexes) if bookIndexes.bookIndex >= bookIndexes.totalBooks then if bookIndexes.collectionIndex >= bookIndexes.numCollections then if bookIndexes.categoryIndex >= GetNumLoreCategories() then return false else bookIndexes.categoryIndex = bookIndexes.categoryIndex + 1 bookIndexes.numCollections = select(2, GetLoreCategoryInfo(bookIndexes.categoryIndex)) bookIndexes.collectionIndex = 0 end end bookIndexes.collectionIndex = bookIndexes.collectionIndex + 1 bookIndexes.totalBooks = select(4, GetLoreCollectionInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex)) bookIndexes.bookIndex = 0 end bookIndexes.bookIndex = bookIndexes.bookIndex + 1 return true end function Librarian:GetBookIdWithTitle(title) -- list of book that aren't part of the eidetic memory if title == "Adventurers Wanted!" then return 4552 elseif title == "Behold Khunzar-ri's Ambition" then return 5925 elseif title == "Behold Khunzar-ri's Betrayal" then return 5673 elseif title == "Behold Khunzar-ri's Guile" then return 5724 elseif title == "Behold the Lunar Champion" then return 5669 elseif title == "Beldorr's Note" then return 2933 elseif title == "Bounty: Dragons!" then return 5708 elseif title == "Cadwell's Personal Anthem" then return 2517 elseif title == "Crumpled Note" then return 2925 elseif title == "Crumpled Nursery Rhyme" then return 5460 elseif title == "Details on the Midyear Mayhem" then return 4591 elseif title == "Diviner's Journal" then return 4048 elseif title == "Engraved Pedestal" then return 4006 elseif title == "Excavation Orders" then return 4047 elseif title == "Falsehoods and Fallacies of the Eight" then return 1279 elseif title == "Firuth's Writ" then return 4042 elseif title == "From the Exalted Viper" then -- real name is "From the Regent of Fanged Fury" return 2644 elseif title == "The Ghostly Stag" then return 138 elseif title == "Guardian's Decree" then return 4050 elseif title == "The Indrik's Glade" then return 5027 elseif title == "Journal of the King's Seneschal" then return 2015 elseif title == "Journal of Ulrich" then return 132 elseif title == "Journal's Final Pages" then return 2942 elseif title == "Jubilee Cake Voucher" then return 6215 elseif title == "Keeper's Letter" then return 4851 elseif title == "Khasaad's Treasure Map" then return 727 elseif title == "Laughing Moons Ledger" then return 1566 elseif title == "Legend of Shalug the Shark" then return 3285 elseif title == "Letter From Ember" then return 7292 elseif title == "Letter From Isobel" then return 7293 elseif title == "Mezhun's Field Journal" then return 2796 elseif title == "Nerulean's Guide to Phantoms Vol. II" then return 3049 elseif title == "Ode to a Torchbug" then return 2954 elseif title == "On Soul Shriven, vol 3" then return 1551 elseif title == "An Orc Weaponsmith In Murkmire, Part 1" then -- real name is "From Wrothgar to Lilmoth: A Smith's Tale, Vol 1" return 2850 elseif title == "An Orc Weaponsmith In Murkmire, Part 2" then -- real name is "From Wrothgar to Lilmoth: A Smith's Tale, Vol 2" return 2851 elseif title == "An Orc Weaponsmith In Murkmire, Part 3" then -- real name is "From Wrothgar to Lilmoth: A Smith's Tale, Vol 3" return 2852 elseif title == "Orcthane's Orders" then return 439 elseif title == "Plea to Maximinus" then return 2659 elseif title == "PRISONER: CLARISSE LAURENT" then return 2908 elseif title == "PRISONER: RAYNOR VANOS" then return 2907 elseif title == "PRISONER: TELENGER" then return 2909 elseif title == "Report on Dominion Activities" then return 147 elseif title == "Royal Messenger's Fate" then return 2701 elseif title == "Seeking New Members!" then return 7168 elseif title == "Six Are the Walking Ways" then return 4445 elseif title == "The Song of the Word" then return 4446 elseif title == "Southern Elsweyr Needs You!" then return 5697 elseif title == "Statue of Amminus Entius" then return 3419 elseif title == "Statue of Cavor Merula" then return 3416 elseif title == "Statue of Justia Desticus" then return 3417 elseif title == "Statue of Rusio Olo" then return 3418 elseif title == "Terran's Notes" then return 3047 elseif title == "The Tomb of Ja'darri" then return 5737 elseif title == "Torag ag Krazak, Uz" then return 3089 elseif title == "Tribute Challengers - Novice Tournament" then return 7169 elseif title == "Ushutha's Journal" then return 3162 elseif title == "The Year 2920, Vol. 28" then return 2945 end if not self.globalSavedVars.failedDeprecation then self.globalSavedVars.failedDeprecation = { librarianBookId = 100000, bookList = {} } end for _, book in ipairs(self.globalSavedVars.failedDeprecation.bookList) do if book.title == title then return book.bookId end end local nextBookId = self.globalSavedVars.failedDeprecation.librarianBookId local newFailedDeprecation = { title = title, bookId = nextBookId } table.insert(self.globalSavedVars.failedDeprecation.bookList, newFailedDeprecation) self.globalSavedVars.failedDeprecation.librarianBookId = nextBookId + 1 return nextBookId end function Librarian:UpdateSavedVariables() -- before version 3.0 there was no saveVersion -- In this version, the bookId is now used as identifier instead of the title (because several book have the same title) if not self.globalSavedVars.saveVersion then self.globalSavedVars.saveVersion = 1 -- let's copy the current save into another file just in case something wrong happen during deprecation local backupGlobalSavedVars = ZO_SavedVars:NewAccountWide("Librarian_SavedVariables_Backup", 1, nil, {}, nil) backupGlobalSavedVars.books = DeepCopy(self.books) -- if this player doesn't know any book yet, no need to go further if next(self.books) ~= nil then local GetLoreBookInfo = GetLoreBookInfo local bookIndexes = { categoryIndex = 0, collectionIndex = 0, bookIndex = 0, numCollections = 0, totalBooks = 0, } while true do if not GetNextBookIndex(bookIndexes) then break end local title, _, _, bookId = GetLoreBookInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex) local book = nil for _,bookIt in pairs(self.books) do if bookIt.title == title then book = bookIt break end end if book then if not book.bookId then book.bookId = bookId else -- there is a collision (2 books with same name) so we add an entry for the new one local newBook = { bookId = bookId, title = title } self:AddBookToGlobalSave(newBook) newBook.timeStamp = book.timeStamp end end end -- now we can erase all the field that are useless (ESO API is performant enough to get these data only when needed) -- it will also help keeping the save small and avoid issues with corrupted save for _,book in pairs(self.books) do if book.bookId then book.title = nil book.body = nil book.medium = nil book.showTitle = nil else book.bookId = self:GetBookIdWithTitle(book.title) end book.wordCount = nil -- it will be computed when building the list (it will allow to switch language and this will still be acurate) end end end if not self.localSavedVars.saveVersion then self.localSavedVars.saveVersion = 1 -- let's copy the current save into another file just in case something wrong happen during deprecation local backupLocalSavedVars = ZO_SavedVars:New("Librarian_SavedVariables_Backup", 1, nil, {}, nil) backupLocalSavedVars.characterBooks = DeepCopy(self.characterBooks) local GetLoreBookInfo = GetLoreBookInfo local bookIndexes = { categoryIndex = 0, collectionIndex = 0, bookIndex = 0, numCollections = 0, totalBooks = 0, } while true do if not GetNextBookIndex(bookIndexes) then break end local title, _, known, bookId = GetLoreBookInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex) if known then local characterBook = nil for _,characterBookIt in ipairs(self.characterBooks) do if characterBookIt.title == title then characterBook = characterBookIt break end end if characterBook then if not characterBook.bookId then characterBook.bookId = bookId else local newCharacterBook = { title = title, timeStamp = characterBook.timeStamp, bookId = bookId } table.insert(self.characterBooks, newCharacterBook) end end end end -- now we can remove the title from the characterBooks and rely only on the bookId (it saves space) for _,characterBook in ipairs(self.characterBooks) do if characterBook.bookId then characterBook.title = nil else characterBook.bookId = self:GetBookIdWithTitle(characterBook.title) end end end end