Librarian = ZO_SortFilterList:Subclass() Librarian.name = "Librarian" Librarian.defaults = {} Librarian.menuSettings = { name = "LibrarianOptions", authors = "Orionik, |c4EFFF6Calia1120|r, Flamage", version = "3.2", optionSlashCommandText = "/librarianOptions" } Librarian.slashCommandText = "/librarian" local SORT_ARROW_UP = "EsoUI/Art/Miscellaneous/list_sortUp.dds" local SORT_ARROW_DOWN = "EsoUI/Art/Miscellaneous/list_sortDown.dds" local LIBRARIAN_DATA = 1 local LIBRARIAN_SEARCH = 1 local ENTRY_SORT_KEYS = { ["title"] = { }, ["unread"] = { tiebreaker = "timeStamp" }, ["timeStamp"] = { tiebreaker = "title" }, ["category"] = { tiebreaker = "title" }, ["wordCount"] = { tiebreaker = "title" } } function Librarian:New() return ZO_SortFilterList.New(self, LibrarianFrame) end function Librarian:Initialize(...) ZO_SortFilterList.Initialize(self, ...) self.masterList = {} self.unreadPerCollections = {} self.newBookCount = 0 self.sortHeaderGroup:SelectHeaderByKey("timeStamp") ZO_ScrollList_AddDataType(self.list, LIBRARIAN_DATA, "LibrarianBookRow", 30, function(control, data) self:SetupBookRow(control, data) end) ZO_ScrollList_EnableHighlight(self.list, "ZO_ThinListHighlight") self.localSavedVars = ZO_SavedVars:New("Librarian_SavedVariables", 1, nil, self.defaults, nil) self.globalSavedVars = ZO_SavedVars:NewAccountWide("Librarian_SavedVariables", 1, nil, self.defaults, nil) if not self.globalSavedVars.settings then self.globalSavedVars.settings = {} self.globalSavedVars.saveVersion = 2 end self.settings = self.globalSavedVars.settings if not self.globalSavedVars.books then self.globalSavedVars.books = {} end self.books = self.globalSavedVars.books if not self.localSavedVars.characterBooks then self.localSavedVars.characterBooks = {} self.localSavedVars.saveVersion = 2 end self.characterBooks = self.localSavedVars.characterBooks self.characterBooksCache = {} self.searchBox = GetControl(LibrarianFrame, "SearchBox") self.searchBox:SetHandler("OnTextChanged", function() self:OnSearchTextChanged() end) self.search = ZO_StringSearch:New() self.search:AddProcessor(LIBRARIAN_SEARCH, function(stringSearch, data, searchTerm, cache) return self:ProcessBookEntry(stringSearch, data, searchTerm, cache) end) self.sortFunction = function(listEntry1, listEntry2) return ZO_TableOrderingFunction(listEntry1.data, listEntry2.data, self.currentSortKey, ENTRY_SORT_KEYS, self.currentSortOrder) end LibrarianDeprecation:UpdateSavedVariables(self) local librarianSettings = LibrarianSettings:New(self.settings, self.menuSettings) local function OnShowAllBooksClicked(checkButton, isChecked) self.settings.showAllBooks = isChecked self:RefreshFilters() end local function GetShowAllBooks() return self.settings.showAllBooks end local showAllBooks = LibrarianFrameShowAllBooks ZO_CheckButton_SetToggleFunction(showAllBooks, OnShowAllBooksClicked) ZO_CheckButton_SetCheckState(showAllBooks, GetShowAllBooks()) local function OnSearchOnlyTitleClicked(checkButton, isChecked) if isChecked then LibrarianFrameSearchLabel:SetText(GetString(LIBRARIAN_TITLE_SEARCH)) else LibrarianFrameSearchLabel:SetText(GetString(LIBRARIAN_FULLTEXT_SEARCH)) end self.searchOnlyTitle = isChecked if self.searchBox:GetText() ~= "" then self:RefreshFilters() end end local searchOnlyTitleControl = LibrarianFrameSearchOnlyTitle ZO_CheckButton_SetToggleFunction(searchOnlyTitleControl, OnSearchOnlyTitleClicked) self.searchOnlyTitle = false self:RefreshData() self:InitializeKeybindStripDescriptors() self:InitializeScene() self:AddLoreReaderUnreadToggle() self:AddLoreLibraryIcons() self:ImportFromLoreLibrary() end function Librarian:AddLoreReaderUnreadToggle() local readerKeybinds if LORE_READER.keybindStripDescriptor then readerKeybinds = LORE_READER.keybindStripDescriptor else readerKeybinds = LORE_READER.PCKeybindStripDescriptor end local function IsSameBook(book, title) if book.title then return book.title == title end local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId) local bookTitle = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) return bookTitle == title end if readerKeybinds then local toggleKeybind = { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = function() local book = self:FindBook(self.lastShownBookId) if not book or (book.unread and IsSameBook(book, LORE_READER.titleText)) then if self.settings.showUnreadIndicatorInReader then self.loreReaderUnreadIndicator:SetHidden(false) else self.loreReaderUnreadIndicator:SetHidden(true) end return GetString(LIBRARIAN_MARK_READ) else self.loreReaderUnreadIndicator:SetHidden(true) return GetString(LIBRARIAN_MARK_UNREAD) end end, visible = function() local book = self:FindBook(self.lastShownBookId) return book and IsSameBook(book, LORE_READER.titleText) end, keybind = "UI_SHORTCUT_SECONDARY", callback = function() local book = self:FindBook(self.lastShownBookId) if book and IsSameBook(book, LORE_READER.titleText) then self:ToggleReadBook(book) KEYBIND_STRIP:UpdateKeybindButtonGroup(readerKeybinds) end end } table.insert(readerKeybinds, toggleKeybind) self.loreReaderKeybinds = readerKeybinds end self.loreReaderUnreadIndicator = WINDOW_MANAGER:CreateControl("LibrarianLoreReaderUnreadIndicator", ZO_LoreReaderBookContainer, CT_TEXTURE) self.loreReaderUnreadIndicator:SetAnchor(TOPLEFT, ZO_LoreReaderBookContainerFirstPage, TOPLEFT, -32, 3) self.loreReaderUnreadIndicator:SetAlpha(self.settings.unreadIndicatorTransparency) self.loreReaderUnreadIndicator:SetDimensions(32, 32) self.loreReaderUnreadIndicator:SetHidden(true) self.loreReaderUnreadIndicator:SetTexture("esoui/art/miscellaneous/new_icon.dds") end function Librarian:AddLoreLibraryIcons() if not self.settings.showUnreadIndicatorInLoreLibrary then return end -- ADD UNREAD ICON ON EACH UNREAD ENTRY local BOOK_DATA_TYPE = 1 local scrollList = LORE_LIBRARY.list:GetListControl() local initalDataType = scrollList.dataTypes[BOOK_DATA_TYPE] scrollList.dataTypes[BOOK_DATA_TYPE] = nil local function SetUpBookEntry(control, data) initalDataType.setupCallback(control, data) local _, _, known, bookId = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex) control.bookId = bookId local shouldUnreadIconBeHidden = true if known then local book = self:FindBook(bookId) if not book or book.unread then shouldUnreadIconBeHidden = false end end control:GetNamedChild("UnreadIcon"):SetHidden(shouldUnreadIconBeHidden) end ZO_ScrollList_AddDataType(scrollList, BOOK_DATA_TYPE, "Librarian_LoreLibrary_BookEntry", initalDataType.height, SetUpBookEntry) -- WHEN READING BOOK FROM LORE LIBRARY, UPDATE LASTSHOWNBOOKID local initial_ZO_LoreLibrary_ReadBook = ZO_LoreLibrary_ReadBook ZO_LoreLibrary_ReadBook = function(categoryIndex, collectionIndex, bookIndex) self.lastShownBookId = select(4, GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)) initial_ZO_LoreLibrary_ReadBook(categoryIndex, collectionIndex, bookIndex) end -- ORDER BOOK LIST TO MAKE UNREAD BOOK FIRST local function BookEntryComparator(leftScrollData, rightScrollData) local leftData = leftScrollData.data local rightData = rightScrollData.data local leftTitle, _, leftKnown, leftBookId = GetLoreBookInfo(leftData.categoryIndex, leftData.collectionIndex, leftData.bookIndex) local rightTitle, _, rightKnown, rightBookId = GetLoreBookInfo(rightData.categoryIndex, rightData.collectionIndex, rightData.bookIndex) if leftKnown == rightKnown then if leftKnown then local leftBook = self:FindBook(leftBookId) local rightBook = self:FindBook(rightBookId) if (leftBook and not leftBook.unread) == (rightBook and not rightBook.unread) then return leftTitle < rightTitle end return leftBook and leftBook.unread end end return leftKnown end LORE_LIBRARY.list.SortScrollList = function(loreLibrarySelf) local scrollData = ZO_ScrollList_GetDataList(loreLibrarySelf.list) table.sort(scrollData, BookEntryComparator) end -- ADD KEYBIND TO MARK BOOK AS READ FROM LORE LIBRARY local loreLibraryKeybinds = LORE_LIBRARY.keybindStripDescriptor -- Make current secondary tertiary for i, keybindDescriptor in ipairs(loreLibraryKeybinds) do if keybindDescriptor.keybind == "UI_SHORTCUT_SECONDARY" then keybindDescriptor.keybind = "UI_SHORTCUT_TERTIARY" end end local toggleKeybind = { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = function() local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() if selectedRow and selectedRow.known then local book = self:FindBook(selectedRow.bookId) if not book or book.unread then return GetString(LIBRARIAN_MARK_READ) else return GetString(LIBRARIAN_MARK_UNREAD) end end end, keybind = "UI_SHORTCUT_SECONDARY", visible = function() local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() return selectedRow and selectedRow.known end, callback = function() local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() if selectedRow then local book = self:FindBook(selectedRow.bookId) if not book then book = { bookId = selectedRow.bookId, title = selectedRow.text:GetText() } self:AddBook(book, true) end if book then self:ToggleReadBook(book) KEYBIND_STRIP:UpdateKeybindButtonGroup(loreLibraryKeybinds) end end end } table.insert(loreLibraryKeybinds, toggleKeybind) -- ADD UNREAD ICON ON COLLECTION IF IT CONTAINS AN UNREAD BOOK OR ADD COMPLETED MARK IF ALL ARE KNOWN AND READ local navigationEntryTemplateInfo = LORE_LIBRARY.navigationTree.templateInfo["ZO_LoreLibraryNavigationEntry"] local previousSetupFunction = navigationEntryTemplateInfo.setupFunction local function TreeEntrySetup(node, control, data, open) previousSetupFunction(node, control, data, open) local shouldHideIcon = true local statusIcon = control:GetNamedChild("StatusIcon") if data.numKnownBooks > 0 then if self.unreadPerCollections[data.categoryIndex] and self.unreadPerCollections[data.categoryIndex][data.collectionIndex] and self.unreadPerCollections[data.categoryIndex][data.collectionIndex] > 0 then shouldHideIcon = false statusIcon:SetTexture("esoui/art/miscellaneous/new_icon.dds") elseif data.numKnownBooks == data.totalBooks then shouldHideIcon = false statusIcon:SetTexture("esoui/art/miscellaneous/check.dds") end end statusIcon:SetHidden(shouldHideIcon) end navigationEntryTemplateInfo.setupFunction = TreeEntrySetup navigationEntryTemplateInfo.template = "Librarian_LoreLibraryNavigationEntry" navigationEntryTemplateInfo.objectPool.templateName = "Librarian_LoreLibraryNavigationEntry" -- Fix of ESO because a label header is equal to all of its entries with the current code (API 101033) LORE_LIBRARY.navigationTree.templateInfo["ZO_LabelHeader"].equalityFunction = navigationEntryTemplateInfo.equalityFunction end function Librarian:InitializeKeybindStripDescriptors() self.keybindStripDescriptor = { { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = GetString(SI_LORE_LIBRARY_READ), keybind = "UI_SHORTCUT_PRIMARY", visible = function() return self.mouseOverRow end, callback = function() self:ReadBook(self.mouseOverRow.data) end, }, { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = function() if not self.mouseOverRow then return nil end local book = self:FindBook(self.mouseOverRow.data.bookId) if book.unread then return GetString(LIBRARIAN_MARK_READ) else return GetString(LIBRARIAN_MARK_UNREAD) end end, keybind = "UI_SHORTCUT_SECONDARY", visible = function() return self.mouseOverRow end, callback = function() local book = self:FindBook(self.mouseOverRow.data.bookId) self:ToggleReadBook(book) end, }, { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = GetString(LIBRARIAN_GO_TO_CATEGORY), keybind = "UI_SHORTCUT_TERTIARY", visible = function() return self.mouseOverRow and self.mouseOverRow.data.categoryIndex and self.mouseOverRow.data.collectionIndex end, callback = function() local collectionId = select(7, GetLoreCollectionInfo(self.mouseOverRow.data.categoryIndex, self.mouseOverRow.data.collectionIndex)) LORE_LIBRARY:SetCollectionIdToSelect(collectionId) MAIN_MENU_KEYBOARD:ShowScene("loreLibrary") end, } } end function Librarian:InitializeScene() if not LIBRARIAN_SCENE then LIBRARIAN_TITLE_FRAGMENT = ZO_SetTitleFragment:New(LIBRARIAN_WINDOW_TITLE_LIBRARIAN) LIBRARIAN_SCENE = ZO_Scene:New("librarian", SCENE_MANAGER) LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.MOUSE_DRIVEN_UI_WINDOW) if self.settings.enableCharacterSpin then LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.FRAME_TARGET_STANDARD_RIGHT_PANEL) end LIBRARIAN_SCENE:AddFragment(ZO_FadeSceneFragment:New(LibrarianFrame)) LIBRARIAN_SCENE:AddFragment(RIGHT_BG_FRAGMENT) LIBRARIAN_SCENE:AddFragment(TITLE_FRAGMENT) LIBRARIAN_SCENE:AddFragment(LIBRARIAN_TITLE_FRAGMENT) LIBRARIAN_SCENE:AddFragment(CODEX_WINDOW_SOUNDS) LIBRARIAN_SCENE:RegisterCallback("StateChange", function(oldState, newState) if(newState == SCENE_SHOWING) then KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor) elseif(newState == SCENE_HIDDEN) then KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor) end end) end end function Librarian:ImportFromLoreLibrary() self.unreadPerCollections = {} local hasImportedBooks = false local chatEnabled = self.settings.chatEnabled local alertEnabled = self.settings.alertEnabled self.settings.chatEnabled = true self.settings.alertEnabled = false local identifier = "LibrarianLoreImport" local em = GetEventManager() local function endSearch() em:UnregisterForUpdate(identifier) self.settings.chatEnabled = chatEnabled self.settings.alertEnabled = alertEnabled if hasImportedBooks then self:RefreshAllData() else self:RefreshLoreLibraryData() end end local categoryIndex, collectionIndex, bookIndex = 0, 0, 0 local numCollections, totalBooks = 0, 0 local function step() if bookIndex >= totalBooks then if collectionIndex >= numCollections then if categoryIndex >= GetNumLoreCategories() then endSearch() return false else categoryIndex = categoryIndex + 1 local loreCategoryName numCollections = select(2, GetLoreCategoryInfo(categoryIndex)) collectionIndex = 0 end end collectionIndex = collectionIndex + 1 totalBooks = select(4, GetLoreCollectionInfo(categoryIndex, collectionIndex)) bookIndex = 0 end bookIndex = bookIndex + 1 local title, _, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) if known then local book = self:FindBook(bookId) local characterBook = self:FindCharacterBook(bookId) if book and characterBook and book.unread then self:AddUnreadBookInCollection(categoryIndex, collectionIndex) elseif not characterBook then local newBook = { bookId = bookId, title = title } self:AddBook(newBook, false) hasImportedBooks = true end end return true end local function steps() local gettime = GetGameTimeMilliseconds local start = gettime() -- do as much as possible in 5ms while ((gettime() - start) < 5) and step() do end end em:RegisterForUpdate(identifier, 0, steps) end function Librarian:BuildMasterList() local GetLoreBookIndicesFromBookId, GetLoreCollectionInfo = GetLoreBookIndicesFromBookId, GetLoreCollectionInfo local GetLoreBookInfo = GetLoreBookInfo local function ShouldDisplayBook(book) if not self.settings.showHiddenBook then local category, collection = GetLoreBookIndicesFromBookId(book.bookId) if category and collection then local hiddenCollection = select(5, GetLoreCollectionInfo(category, collection)) if hiddenCollection then return false end else return false end end return true end for i, book in ipairs(self.books) do if ShouldDisplayBook(book) then if not self.masterList[i] then self.masterList[i] = {} end local data = self.masterList[i] for k,v in pairs(book) do if k == "body" then data[k] = table.concat(book.body) else data[k] = v end end -- because we reuse the same list between 2 refresh, the data may already be filled up so no need to recompute it, these won't change if not data.categoryIndex or not data.collectionIndex or not data.bookIndex then data.categoryIndex, data.collectionIndex, data.bookIndex = GetLoreBookIndicesFromBookId(book.bookId) end if data.categoryIndex and data.collectionIndex and data.bookIndex and (not data.title or not data.body or not data.category) then data.title = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex) data.body = ReadLoreBook(data.categoryIndex, data.collectionIndex, data.bookIndex) data.category = GetLoreCollectionInfo(data.categoryIndex, data.collectionIndex) end if not data.category or data.category == "" then data.category = GetString(LIBRARIAN_NO_CATEGORY) end if not data.wordCount then local wordCount = 0 if data.body then for w in data.body:gmatch("%S+") do wordCount = wordCount + 1 end end data.wordCount = wordCount end data.type = LIBRARIAN_SEARCH local characterBook = self:FindCharacterBook(book.bookId) if characterBook then data.seenByCurrentCharacter = true data.timeStamp = characterBook.timeStamp else data.seenByCurrentCharacter = false data.timeStamp = book.timeStamp end else self.masterList[i] = nil end end end function Librarian:FilterScrollList() local scrollData = ZO_ScrollList_GetDataList(self.list) ZO_ClearNumericallyIndexedTable(scrollData) local bookCount = 0 local unreadCount = 0 local searchTerm = self.searchBox:GetText() for _, data in pairs(self.masterList) do if self.settings.showAllBooks or data.seenByCurrentCharacter then if(searchTerm == "" or self.search:IsMatch(searchTerm, data)) then table.insert(scrollData, ZO_ScrollList_CreateDataEntry(LIBRARIAN_DATA, data)) bookCount = bookCount + 1 if data.unread then unreadCount = unreadCount + 1 end end end end local message = string.format(GetString(LIBRARIAN_BOOK_COUNT), bookCount) if unreadCount > 0 then message = string.format(GetString(LIBRARIAN_UNREAD_COUNT), message, unreadCount) end LibrarianFrameBookCount:SetText(message) end function Librarian:SortScrollList() local scrollData = ZO_ScrollList_GetDataList(self.list) table.sort(scrollData, self.sortFunction) end function Librarian:SetupBookRow(control, data) control.data = data control.unread = GetControl(control, "Unread") control.found = GetControl(control, "Found") control.title = GetControl(control, "Title") control.category = GetControl(control, "Category") control.wordCount = GetControl(control, "WordCount") control.unread.nonRecolorable = true if data.unread then control.unread:SetAlpha(1) else control.unread:SetAlpha(0) end control.found.normalColor = ZO_NORMAL_TEXT control.found:SetText(self:FormatClockTime(data.timeStamp)) control.title.normalColor = ZO_NORMAL_TEXT control.title:SetText(data.title) control.category.normalColor = ZO_NORMAL_TEXT control.category:SetText(data.category) control.wordCount.normalColor = ZO_NORMAL_TEXT control.wordCount:SetText(data.wordCount) ZO_SortFilterList.SetupRow(self, control, data) end function Librarian:ProcessBookEntry(stringSearch, data, searchTerm, cache) local lowerSearchTerm = searchTerm:lower() if data.title and zo_plainstrfind(data.title:lower(), lowerSearchTerm) then return true end if not self.searchOnlyTitle and data.body and zo_plainstrfind(data.body:lower(), lowerSearchTerm) then return true end return false end function Librarian:FindCharacterBook(bookId) if not self.characterBooks then return nil end if self.characterBooksCache[bookId] then return self.characterBooksCache[bookId] end for _,book in ipairs(self.characterBooks) do if book.bookId == bookId then self.characterBooksCache[bookId] = book return book end end return nil end function Librarian:FindBook(bookId) for _,book in ipairs(self.books) do if book.bookId == bookId then return book end end end function Librarian:AddBookToGlobalSave(book) book.timeStamp = GetTimeStamp() book.unread = true local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId) if categoryIndex and collectionIndex and bookIndex then -- if these indexes exist in the API, this mean that we will be able to get these info when needed (so no need to save them) book.title = nil book.body = nil book.medium = nil book.showTitle = nil else local newBody = book.body book.body = {} while string.len(newBody) > 1024 do table.insert(book.body, string.sub(newBody, 0, 1024)) newBody = string.sub(newBody, 1025) end table.insert(book.body, newBody) end table.insert(self.books, book) end function Librarian:AddBook(book, refreshDataRightAway) if not self:FindCharacterBook(book.bookId) then local bookTitle = book.title -- storing it because AddBookToGlobalSave will delete it if we can retrieve it thanks to ESO API local foundBook = self:FindBook(book.bookId) if foundBook then book = foundBook else self:AddBookToGlobalSave(book) end local characterBook = { bookId = book.bookId, timeStamp = GetTimeStamp() } table.insert(self.characterBooks, characterBook) if book.unread then local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId) if categoryIndex and collectionIndex then self:AddUnreadBookInCollection(categoryIndex, collectionIndex) end end if refreshDataRightAway then self:RefreshAllData() end if self.settings.alertEnabled then --CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED, GetString(SI_LIBRARIAN_NEW_BOOK_FOUND)) local params = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED) params:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_DISPLAY_ANNOUNCEMENT ) params:SetText(GetString(LIBRARIAN_NEW_BOOK_FOUND)) CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(params) end if self.settings.chatEnabled then d(string.format(GetString(LIBRARIAN_NEW_BOOK_FOUND_WITH_TITLE), bookTitle)) end self.newBookCount = self.newBookCount + 1 if self.settings.reloadReminderBookCount and self.settings.reloadReminderBookCount > 0 and self.settings.reloadReminderBookCount <= self.newBookCount then d(GetString(LIBRARIAN_RELOAD_REMINDER)) end end end function Librarian:AddUnreadBookInCollection(categoryIndex, collectionIndex) if not self.unreadPerCollections[categoryIndex] then self.unreadPerCollections[categoryIndex] = {} end if not self.unreadPerCollections[categoryIndex][collectionIndex] then self.unreadPerCollections[categoryIndex][collectionIndex] = 0 end self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] + 1 end function Librarian:ToggleReadBook(book) book.unread = not book.unread local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId) if categoryIndex and collectionIndex then if not self.unreadPerCollections[categoryIndex] then self.unreadPerCollections[categoryIndex] = {} end if not self.unreadPerCollections[categoryIndex][collectionIndex] then self.unreadPerCollections[categoryIndex][collectionIndex] = 0 end if book.unread then self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] + 1 elseif self.unreadPerCollections[categoryIndex][collectionIndex] > 0 then self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] - 1 end end self:RefreshAllData() end function Librarian:Toggle() if LibrarianFrame:IsControlHidden() then SCENE_MANAGER:Show("librarian") else SCENE_MANAGER:Hide("librarian") end end function Librarian:ReadBook(data) self.lastShownBookId = data.bookId if data.categoryIndex and data.collectionIndex and data.bookIndex then local body, medium, showTitle = ReadLoreBook(data.categoryIndex, data.collectionIndex, data.bookIndex) if medium ~= 0 then LORE_READER:SetupBook(data.title, body, medium, showTitle) else d(GetString(LIBRARIAN_COULD_NOT_READ)) PlaySound(SOUNDS.GENERAL_ALERT_ERROR) return end else LORE_READER:SetupBook(data.title, data.body, data.medium, data.showTitle) end SCENE_MANAGER:Push("loreReaderInteraction") -- PlaySound(LORE_READER.OpenSound) end function Librarian:RefreshAllData() self:RefreshData() self:RefreshLoreLibraryData() end function Librarian:RefreshLoreLibraryData() if self.settings.showUnreadIndicatorInLoreLibrary then local selectedCategoryIndex = LORE_LIBRARY:GetSelectedCategoryIndex() local selectedCollectionIndex = LORE_LIBRARY:GetSelectedCollectionIndex() local selectedCollectionId = select(7, GetLoreCollectionInfo(selectedCategoryIndex, selectedCollectionIndex)) LORE_LIBRARY:SetCollectionIdToSelect(selectedCollectionId) LORE_LIBRARY:BuildCategoryList() end end function Librarian:FormatClockTime(time) local midnightSeconds = GetSecondsSinceMidnight() local utcSeconds = GetTimeStamp() % 86400 local offset = midnightSeconds - utcSeconds if offset < -43200 then offset = offset + 86400 end local dateString = GetDateStringFromTimestamp(time) local timeString = ZO_FormatTime((time + offset) % 86400, TIME_FORMAT_STYLE_CLOCK_TIME, self.settings.timeFormat) return string.format("%s %s", dateString, timeString) end function Librarian:OnSearchTextChanged() ZO_EditDefaultText_OnTextChanged(self.searchBox) -- Let's wait a little bit before updating the filters if the player didn't have time to enter its full string yet -- Because if we try to update the filters too much it sometimes crash local TIME_BEFORE_UPDATING_FILTERS = 600 -- ms self.lastTimeSearchModified = GetGameTimeMilliseconds() local function TryRefreshFilters() if self.lastTimeSearchModified + TIME_BEFORE_UPDATING_FILTERS <= GetGameTimeMilliseconds() then self:RefreshFilters() end end zo_callLater(TryRefreshFilters, TIME_BEFORE_UPDATING_FILTERS) end function Librarian.SlashCommand(args) Librarian:Toggle() end function Librarian.OnShowBook(eventCode, title, body, medium, showTitle, bookId) local book = { title = title, body = body, medium = medium, showTitle = showTitle, bookId = bookId } LIBRARIAN:AddBook(book, true) LIBRARIAN.lastShownBookId = bookId if LIBRARIAN.loreReaderKeybinds then KEYBIND_STRIP:UpdateKeybindButtonGroup(LIBRARIAN.loreReaderKeybinds) end end function Librarian.OnMouseEnterRow(control) LIBRARIAN:Row_OnMouseEnter(control) end function Librarian.OnMouseExitRow(control) LIBRARIAN:Row_OnMouseExit(control) end function Librarian.OnMouseUpRow(control, button, upInside) LIBRARIAN:ReadBook(control.data) end function Librarian.OnAddonLoaded(event, addon) if addon == Librarian.name then EVENT_MANAGER:UnregisterForEvent(Librarian.name, EVENT_ADD_ON_LOADED) LIBRARIAN = Librarian:New() EVENT_MANAGER:RegisterForEvent(Librarian.name, EVENT_SHOW_BOOK, Librarian.OnShowBook) end end SLASH_COMMANDS[Librarian.slashCommandText] = Librarian.SlashCommand EVENT_MANAGER:RegisterForEvent(Librarian.name, EVENT_ADD_ON_LOADED, Librarian.OnAddonLoaded)