diff --git a/API.lua b/API.lua
new file mode 100644
index 0000000..127ccc3
--- /dev/null
+++ b/API.lua
@@ -0,0 +1,221 @@
+--[[
+Hello fellow addon developper,
+You will find bellow a way of adding custom book to Librarian.
+Prerequisite: To be sure your addon gets loaded after Librarian the easiest way is to add an optional dependency from your addon to Librarian
+If you are not used to it, you can copy this line to your Addon.txt file:
+## OptionalDependsOn: Librarian
+]]
+
+--[[
+LIBRARIAN:RegisterNewCategory(name, totalBooks, GetBookInfo, OpenCategory)
+* Should be called at the end of your addon initialization
+
+- categoryIdentifier (*string*) : Identifier that will be used to identify your category from outside of the addon (save or API)
+ You can use the same as the english name if you want
+- name (*string*) : Name of the category that will be displayed in the "Category" column
+- totalBooks (*integer*) : Total number of books in the category. Will be used when initializing Librarian with all the known book of your addon.
+ If you want to disable this feature you can give 0 and the rest of the addon should still work)
+- GetBookInfo (*function*) : It is the main function that will be used to retrieve data for a book
+ The bookIndex is an index starting from 1 to the total number of book or the maximum bookIndex received byt the OnBookDisplayed function (it can't be more than 10000 though)
+ Prototype:
+ * GetBookInfo(*integer* bookIndex)
+ ** _Returns:_ *string* _title_, *bool* _known_, *string* _body_, *[BookMedium|#BookMedium]* _medium_, *bool* _showTitle_
+- OpenCategory (*function*, optional) : This is called when the player press the key to "Go to Category" from Librarian.
+ So you should open your UI with the given book selected if you can.
+ You can give nil instead, then the option won't be shown to the player
+ Prototype:
+ * OpenCategory(*integer* bookIndex)
+]]
+
+function Librarian:RegisterNewCategory(categoryIdentifier, name, totalBooks, GetBookInfo, OpenCategory)
+ if not self.globalSavedVars.registeredCollectionList then
+ self.globalSavedVars.registeredCollectionList = {}
+ self.globalSavedVars.nextCollectionToRegister = 1
+ end
+
+ if not self.globalSavedVars.registeredCollectionList[categoryIdentifier] then
+ self.globalSavedVars.registeredCollectionList[categoryIdentifier] = self.globalSavedVars.nextCollectionToRegister
+ self.globalSavedVars.nextCollectionToRegister = self.globalSavedVars.nextCollectionToRegister + 1
+ end
+
+ local newCollection = {
+ collectionId = categoryIdentifier,
+ name = name,
+ totalBooks = totalBooks,
+ GetBookInfo = GetBookInfo,
+ OpenCategory = OpenCategory,
+ }
+ self.registeredCollection[self.globalSavedVars.registeredCollectionList[categoryIdentifier]] = newCollection
+ self:RefreshData()
+end
+
+--[[
+LIBRARIAN:OnBookDisplayed(categoryIdentifier, bookIndex)
+* Should be called when displaying a book with LoreReader UI
+* It will add the displayed book to Librarian and activate the read/unread icon and actions to lore reader
+
+
+- categoryIdentifier (string) : Identifier of the category you used when registering your new category
+- bookIndex (*integer*) : Index of the book shown in LoreReader and it will then be used with the GetBookInfo function you registered with your category
+]]
+
+function Librarian:OnBookDisplayed(categoryIdentifier, bookIndex)
+ for collectionIndex, collection in pairs(self.registeredCollection) do
+ if collection.collectionId == categoryIdentifier then
+ local title, _, body, medium, showTitle = collection.GetBookInfo(bookIndex)
+ if title and body and medium then
+ local bookId = self:GetCustomBookIdFromIndices(collectionIndex, bookIndex)
+ Librarian.OnShowBook(eventCode, title, body, medium, showTitle, bookId)
+ end
+ return
+ end
+ end
+end
+
+----------------------------------------------------------------------------------------------------------
+-- INTERNAL (shouldn't be called from outside)
+--
+-- Functions below are here to be able to switch between ESO official API and registered custom categories
+----------------------------------------------------------------------------------------------------------
+
+Librarian.constants.CUSTOM_CATEGORY = GetNumLoreCategories() + 1
+Librarian.constants.CUSTOM_BOOK_ID_START = 200000
+Librarian.constants.MAX_BOOK_PER_CUSTOM_COLLECTION = 10000
+Librarian.registeredCollection = {}
+
+function Librarian:GetNumLoreCategories()
+ if next(self.registeredCollection) == nil then
+ return GetNumLoreCategories()
+ else
+ return Librarian.constants.CUSTOM_CATEGORY
+ end
+end
+
+function Librarian:GetLoreCollectionCount(categoryIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ return select(2, GetLoreCategoryInfo(categoryIndex))
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ return self.globalSavedVars.nextCollectionToRegister - 1
+ end
+end
+
+function Librarian:GetLoreCollectionName(categoryIndex, collectionIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ return select(1, GetLoreCollectionInfo(categoryIndex, collectionIndex))
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ assert(collection)
+ return collection.name
+ end
+end
+
+function Librarian:GetLoreCollectionTotalBook(categoryIndex, collectionIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ return select(4, GetLoreCollectionInfo(categoryIndex, collectionIndex))
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ if collection then
+ return collection.totalBooks
+ end
+ end
+ return 0
+end
+
+function Librarian:IsLoreCollectionHidden(categoryIndex, collectionIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ return select(5, GetLoreCollectionInfo(categoryIndex, collectionIndex))
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ assert(self.registeredCollection[collectionIndex])
+ return false
+ end
+end
+
+function Librarian:CanOpenCollection(categoryIndex, collectionIndex)
+ if categoryIndex and collectionIndex then
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ return collectionIndex ~= nil
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ assert(collection)
+ return type(collection.OpenCategory) == "function"
+ end
+ end
+ return false
+end
+
+function Librarian:OpenCollection(categoryIndex, collectionIndex, bookIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ local collectionId = select(7, GetLoreCollectionInfo(categoryIndex, collectionIndex))
+ LORE_LIBRARY:SetCollectionIdToSelect(collectionId)
+ MAIN_MENU_KEYBOARD:ShowScene("loreLibrary")
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ assert(collection)
+ collection.OpenCategory(bookIndex)
+ end
+end
+
+function Librarian:GetBookInfo(categoryIndex, collectionIndex, bookIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ local title, _, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)
+ return title, known, bookId
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ assert(collection)
+ local title, known = collection.GetBookInfo(bookIndex)
+ local bookId = self:GetCustomBookIdFromIndices(collectionIndex, bookIndex)
+ return title, known, bookId
+ end
+end
+
+function Librarian:GetCustomBookIdFromIndices(collectionIndex, bookIndex)
+ return self.constants.CUSTOM_BOOK_ID_START + collectionIndex * self.constants.MAX_BOOK_PER_CUSTOM_COLLECTION + bookIndex
+end
+
+function Librarian:GetLoreBookIndicesFromBookId(bookId)
+ if bookId < self.constants.CUSTOM_BOOK_ID_START then
+ return GetLoreBookIndicesFromBookId(bookId)
+ else
+ local bookIndexAndCollection = bookId - self.constants.CUSTOM_BOOK_ID_START
+ local collectionIndex = math.floor(bookIndexAndCollection / self.constants.MAX_BOOK_PER_CUSTOM_COLLECTION)
+ if self.registeredCollection[collectionIndex] then
+ local bookIndex = bookIndexAndCollection - (collectionIndex * self.constants.MAX_BOOK_PER_CUSTOM_COLLECTION)
+ return self.constants.CUSTOM_CATEGORY, collectionIndex, bookIndex
+ end
+ end
+
+ return nil, nil, nil
+end
+
+function Librarian:ReadLoreBook(categoryIndex, collectionIndex, bookIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ return ReadLoreBook(categoryIndex, collectionIndex, bookIndex)
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ assert(collection)
+ local _, _, body, medium, showTitle = collection.GetBookInfo(bookIndex)
+ return body, medium, showTitle
+ end
+end
+
+function Librarian:GetBookTitleAndBody(categoryIndex, collectionIndex, bookIndex)
+ if categoryIndex < self.constants.CUSTOM_CATEGORY then
+ local title = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)
+ local body = ReadLoreBook(categoryIndex, collectionIndex, bookIndex)
+ return title, body
+ else
+ assert(categoryIndex == self.constants.CUSTOM_CATEGORY)
+ local collection = self.registeredCollection[collectionIndex]
+ assert(collection)
+ local title, _, body = collection.GetBookInfo(bookIndex)
+ return title, body
+ end
+end
diff --git a/CHANGELOG b/CHANGELOG
index 51c9568..ef6b3c9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,9 @@
-------------------------------------------------------------------------------
Librarian v3.0 2022-07-25
-------------------------------------------------------------------------------
+3.5 2022-10-03
+- Add API for external addon to be able to register their own books to Librarian
+
3.4 2022-09-11
- Add keybind in lore reader only when the book is recognized by Librarian (it avoids conflicting with other addon like TheLibrarium)
- Add possibility to delete books which don't have a category
diff --git a/ESOUIModifications.lua b/ESOUIModifications.lua
index f256c03..f8880c5 100644
--- a/ESOUIModifications.lua
+++ b/ESOUIModifications.lua
@@ -4,8 +4,8 @@ function Librarian:AddLoreReaderUnreadToggle()
if book.title then
return book.title == title
end
- local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId)
- local bookTitle = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)
+ local categoryIndex, collectionIndex, bookIndex = self:GetLoreBookIndicesFromBookId(book.bookId)
+ local bookTitle = self:GetBookInfo(categoryIndex, collectionIndex, bookIndex)
return bookTitle == title
end
diff --git a/Librarian.lua b/Librarian.lua
index b882729..df0d467 100644
--- a/Librarian.lua
+++ b/Librarian.lua
@@ -145,7 +145,7 @@ 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)
+ local body, medium, showTitle = self:ReadLoreBook(data.categoryIndex, data.collectionIndex, data.bookIndex)
if medium ~= 0 then
LORE_READER:SetupBook(data.title, body, medium, showTitle)
else
diff --git a/Librarian.txt b/Librarian.txt
index 798f8b7..06aa0c9 100644
--- a/Librarian.txt
+++ b/Librarian.txt
@@ -11,6 +11,7 @@
Librarian.lua
+API.lua
ESOUIModifications.lua
LibrarianData.lua
LibrarianSettings.lua
diff --git a/LibrarianData.lua b/LibrarianData.lua
index 38567d6..b6c24fa 100644
--- a/LibrarianData.lua
+++ b/LibrarianData.lua
@@ -23,22 +23,27 @@ function Librarian:ImportFromLoreLibrary()
local function step()
if bookIndex >= totalBooks then
if collectionIndex >= numCollections then
- if categoryIndex >= GetNumLoreCategories() then
+ if categoryIndex >= self:GetNumLoreCategories() then
endSearch()
return false
else
categoryIndex = categoryIndex + 1
local loreCategoryName
- numCollections = select(2, GetLoreCategoryInfo(categoryIndex))
+ numCollections = self:GetLoreCollectionCount(categoryIndex)
collectionIndex = 0
end
end
collectionIndex = collectionIndex + 1
- totalBooks = select(4, GetLoreCollectionInfo(categoryIndex, collectionIndex))
+ totalBooks = self:GetLoreCollectionTotalBook(categoryIndex, collectionIndex)
bookIndex = 0
+
+ if totalBooks == 0 then
+ -- if there is no book in this collection, it is probably an obsolete collection so let's skip it instead of crashing later here
+ return true
+ end
end
bookIndex = bookIndex + 1
- local title, _, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)
+ local title, known, bookId = self:GetBookInfo(categoryIndex, collectionIndex, bookIndex)
if known then
local book = self:FindBook(bookId)
local characterBook = self:FindCharacterBook(bookId)
@@ -94,7 +99,7 @@ function Librarian:AddBookToGlobalSave(book)
book.timeStamp = GetTimeStamp()
book.unread = true
- local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId)
+ local categoryIndex, collectionIndex, bookIndex = self: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
@@ -135,7 +140,7 @@ function Librarian:AddBook(book, refreshDataRightAway)
table.insert(self.characterBooks, characterBook)
if book.unread then
- local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId)
+ local categoryIndex, collectionIndex = self:GetLoreBookIndicesFromBookId(book.bookId)
if categoryIndex and collectionIndex then
self:AddUnreadBookInCollection(categoryIndex, collectionIndex)
end
@@ -207,7 +212,7 @@ end
function Librarian:ToggleReadBook(book)
book.unread = not book.unread
- local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId)
+ local categoryIndex, collectionIndex = self: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
diff --git a/LibrarianUI.lua b/LibrarianUI.lua
index a7668b4..3f9aefb 100644
--- a/LibrarianUI.lua
+++ b/LibrarianUI.lua
@@ -37,12 +37,10 @@ function Librarian:InitializeKeybindStripDescriptors()
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
+ return self.mouseOverRow and self:CanOpenCollection(self.mouseOverRow.data.categoryIndex, 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")
+ self:OpenCollection(self.mouseOverRow.data.categoryIndex, self.mouseOverRow.data.collectionIndex, self.mouseOverRow.data.bookIndex)
end,
},
{
@@ -50,7 +48,10 @@ function Librarian:InitializeKeybindStripDescriptors()
name = GetString(LIBRARIAN_DELETE_BOOK),
keybind = "UI_SHORTCUT_NEGATIVE",
visible = function()
- return self.mouseOverRow and (not self.mouseOverRow.data.categoryIndex or not self.mouseOverRow.data.collectionIndex)
+ return self.mouseOverRow
+ and (not self.mouseOverRow.data.categoryIndex
+ or not self.mouseOverRow.data.collectionIndex
+ or self.mouseOverRow.data.categoryIndex == self.constants.CUSTOM_CATEGORY)
end,
callback = function()
self.potentialBookIdToDelete = self.mouseOverRow.data.bookId
@@ -86,21 +87,24 @@ function Librarian:InitializeScene()
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)
+ local category, collection = self:GetLoreBookIndicesFromBookId(book.bookId)
if category and collection then
- local hiddenCollection = select(5, GetLoreCollectionInfo(category, collection))
- if hiddenCollection then
+ if self:IsLoreCollectionHidden(category, collection) then
return false
end
else
return false
end
end
+
+ if book.bookId > self.constants.CUSTOM_BOOK_ID_START then
+ local category, collection = self:GetLoreBookIndicesFromBookId(book.bookId)
+ if not category or not collection then
+ return false
+ end
+ end
return true
end
@@ -121,19 +125,18 @@ function Librarian:BuildMasterList()
-- 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)
+ data.categoryIndex, data.collectionIndex, data.bookIndex = self: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)
+ data.title, data.body = self:GetBookTitleAndBody(data.categoryIndex, data.collectionIndex, data.bookIndex)
+ data.category = self:GetLoreCollectionName(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
+ if not data.wordCount or data.wordCount == 0 then
local wordCount = 0
if data.body then
for w in data.body:gmatch("%S+") do
diff --git a/README b/README
index c23b0db..3a04960 100644
--- a/README
+++ b/README
@@ -12,13 +12,13 @@ https://account.elderscrollsonline.com/add-on-terms
-------------------------------------------------------------------------------
Description
-------------------------------------------------------------------------------
-Librarian records every book your character reads in-game and keeps a list of
-when it was found and whether you have marked it as read, allowing you to continue
-questing (and not hold up other players) while being confident you won't forget
-to read anything later.
+Librarian records every book your character reads in-game and keeps a list of
+when it was found and whether you have marked it as read, allowing you to
+continue questing (and not hold up other players) while being confident you
+won't forget to read anything later.
-Flamage (the original author) and Calia1120 (the second author) aren't playing ESO
-at present, so I'll update this addon!
+Flamage (the original author) and Calia1120 (the second author) aren't playing
+ESO at present, so I'll update this addon!
-------------------------------------------------------------------------------
Installation
@@ -43,3 +43,14 @@ For Mac: ~/Documents/Elder Scrolls Online/liveeu/
the addons menu. Enable your addons from there.
-------------------------------------------------------------------------------
+For fellow addon developpers
+-------------------------------------------------------------------------------
+If you have an addon adding books to the game and you would like them to be
+displayed in Librarian, go check the API.lua file, there are some information
+on how to process.
+
+And if you want to contribute, you can contact us (the 3 authors) on esoui.com
+or in the comment of the Librarian page :
+https://www.esoui.com/downloads/fileinfo.php?id=188
+Also, don't hesitate to check out the Librarian git repos :
+http://git.esoui.com/?a=summary&p=Librarian