enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
838 lines
31 KiB
Lua
838 lines
31 KiB
Lua
-- Bagshui Profiles
|
|
-- Exposes: BSProfiles (and Bagshui.components.Profiles)
|
|
-- Raises: BAGSHUI_PROFILE_UPDATE
|
|
--
|
|
-- Each profile contains one of every BS_PROFILE_TYPE, which allows things like
|
|
-- sharing the same Design profile across multiple characters but giving each
|
|
-- one its own Structure profile.
|
|
--
|
|
-- The built-in profiles are available for use as templates only; since they're
|
|
-- not editable it doesn't make sense to make them selectable for active use.
|
|
|
|
Bagshui:AddComponent(function()
|
|
-- ### Profile objects look like this:
|
|
-- ```
|
|
-- {
|
|
-- name = "Name",
|
|
-- structure = { profile data },
|
|
-- design = { profile data },
|
|
-- }
|
|
-- ```
|
|
--
|
|
-- ### `Structure` profile:
|
|
-- ```
|
|
-- {
|
|
-- primary = {
|
|
-- -- A `layout` table is an array of the rows that make up the layout,
|
|
-- -- with each row being an array of the groups within that row.
|
|
-- -- Additional group data (colors, sort order, etc.) can also be present.
|
|
-- ---@type { name: string, categories: string[] }[]
|
|
-- layout = {
|
|
-- {
|
|
-- {
|
|
-- name = "Group 1",
|
|
-- categories = {
|
|
-- "categoryId1",
|
|
-- "categoryId2",
|
|
-- "categoryIdN",
|
|
-- },
|
|
-- },
|
|
-- {
|
|
-- name = "Group 2",
|
|
-- categories = {
|
|
-- "categoryId3",
|
|
-- },
|
|
-- },
|
|
--
|
|
-- },
|
|
-- },
|
|
-- -- Structure profiles will also contain any inventory-scoped setting
|
|
-- -- with `profileScope = BS_SETTING_PROFILE_SCOPE.STRUCTURE` in Config\Settings.lua.
|
|
-- },
|
|
--
|
|
-- -- Docked inventory classes currently don't have the option to select their
|
|
-- -- Structure profile independent of the class to which they're docked, so
|
|
-- -- their unique layout is stored in the same profile, just in a different spot.
|
|
-- ---@type table<BS_INVENTORY_TYPE, { layout: table }>
|
|
-- docked = {
|
|
-- Keyring = { <same data structure as `primary` }
|
|
-- }
|
|
-- }
|
|
-- ```
|
|
--
|
|
-- ### `Design` profile:
|
|
-- These are simply a list of settings, since Designs are driven entirely by
|
|
-- inventory-scoped settings that have `profileScope = BS_SETTING_PROFILE_SCOPE.DESIGN`
|
|
-- in Config\Settings.lua.
|
|
--
|
|
-- ### Note about `BS_PROFILE_SKELETON`:
|
|
-- Building out a skeleton for all the profile data is too much of a pain,
|
|
-- so we're going to accept whatever is there by adding BS_OBJECT_LIST_TEMPLATE_TABLE_WILDCARD
|
|
-- in the loop below. It all gets validated as it's used anyway.
|
|
---@type table<string|BS_PROFILE_TYPE, string|table>
|
|
local profileSkeleton = {
|
|
name = "",
|
|
}
|
|
|
|
-- The profile template is intentionally somewhat empty because:
|
|
-- - We're only providing the absolute minimum needed to start a layout.
|
|
-- - Layout for docked inventory will be filled in by Inventory:ValidateLayout().
|
|
-- - Keys for any other profile types will be added in the loop below.
|
|
local profileTemplate = {
|
|
name = "",
|
|
structure = {
|
|
primary = {
|
|
layout = {
|
|
{
|
|
{
|
|
name = string.format(L.Prefix_New, L.Group),
|
|
categories = {
|
|
BS_DEFAULT_CATEGORY_ID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
--- Translate a profile type (PythonCase) to storage key (camelCase).
|
|
---@param profileType any
|
|
---@return string
|
|
local function getProfileTypeStorageKey(profileType)
|
|
return BsUtil.LowercaseFirstLetter(profileType)
|
|
end
|
|
|
|
-- Fill in the skeleton and template keys.
|
|
for _, profileType in pairs(BS_PROFILE_TYPE) do
|
|
local profileTypeStorageKey = getProfileTypeStorageKey(profileType)
|
|
profileSkeleton[profileTypeStorageKey] = BS_OBJECT_LIST_TEMPLATE_TABLE_WILDCARD
|
|
if not profileTemplate[profileTypeStorageKey] then
|
|
profileTemplate[profileTypeStorageKey] = {}
|
|
end
|
|
end
|
|
|
|
Bagshui:AddConstants({
|
|
BS_PROFILE_SKELETON = profileSkeleton,
|
|
BS_NEW_PROFILE_TEMPLATE = profileTemplate,
|
|
})
|
|
|
|
local PROFILES_DATA_STORAGE_KEY = "profiles"
|
|
|
|
local Profiles = Bagshui.prototypes.ObjectList:New({
|
|
|
|
dataStorageKey = PROFILES_DATA_STORAGE_KEY,
|
|
objectVersion = Bagshui.config.Profiles.version,
|
|
objectMigrationFunction = Bagshui.config.Profiles.migrate,
|
|
objectName = "Profile",
|
|
objectNamePlural = "Profiles",
|
|
objectSkeleton = BS_PROFILE_SKELETON,
|
|
objectTemplate = BS_NEW_PROFILE_TEMPLATE,
|
|
defaults = Bagshui.config.Profiles.defaults,
|
|
wikiPage = BS_WIKI_PAGES.Profiles,
|
|
|
|
-- Track when `GetUsableProfileId()` has performed a copy operation so it won't
|
|
-- occur multiple times when multiple inventory classes request it in sequence.
|
|
-- More details in `GetUsableProfileId()`.
|
|
---@type table<string,boolean>
|
|
profileCreationCopyDone = {},
|
|
|
|
debugResetOnLoad = false and BS_DEBUG,
|
|
})
|
|
Bagshui.environment.BsProfiles = Profiles
|
|
Bagshui.components.Profiles = Profiles
|
|
|
|
--- Initialize the Profiles class.
|
|
function Profiles:Init()
|
|
-- Bagshui:PrintDebug("Profiles Init()")
|
|
|
|
-- Calls ObjectList:Init().
|
|
self._super.Init(self)
|
|
|
|
-- Ensure profile-related settings are valid and have the data they need to function.
|
|
for _, profileType in pairs(BS_PROFILE_TYPE) do
|
|
-- Reset to Bagshui defaults if any currently configured default profiles don't exist.
|
|
if not self.list[BsSettings["defaultProfile" .. profileType]] then
|
|
-- Bagshui:PrintDebug("defaultProfile" .. profileType .. " doesn't exist!")
|
|
BsSettings:SetDefaults(true, nil, nil, "defaultProfile" .. profileType)
|
|
BsSettings:SetDefaults(true, nil, nil, "createNewProfile" .. profileType)
|
|
end
|
|
|
|
-- Link the list of profiles to corresponding settings' lists of valid choices.
|
|
Bagshui.prototypes.Settings._settingInfo["profile" .. profileType].choices = self.list
|
|
Bagshui.prototypes.Settings._settingInfo["defaultProfile" .. profileType].choices = self.list
|
|
end
|
|
|
|
-- Add auto-split menu that lists all profiles.
|
|
Bagshui.prototypes.Menus:AddAutoSplitMenu(BS_AUTO_SPLIT_MENU_TYPE.PROFILES, {
|
|
defaultIdList = self.sortedIdLists.name,
|
|
itemList = self.list,
|
|
sortFunc = function(sortOrderIds)
|
|
self:SortIdList(sortOrderIds)
|
|
end,
|
|
nameFunc = function(id)
|
|
return self:GetName(id) or tostring(id)
|
|
end,
|
|
-- Manage...
|
|
extraItems = {
|
|
{
|
|
text = string.format(L.Symbol_Ellipsis, L.Manage),
|
|
tooltipTitle = L.Manage,
|
|
tooltipText = string.format(L.Prefix_Manage, L.Profiles),
|
|
checked = false,
|
|
value = {
|
|
func = function()
|
|
Bagshui:CloseMenus()
|
|
self:Open()
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
end
|
|
|
|
--- When a profile is deleted, also clean up the flags that track whether
|
|
--- the initialization for that profile ID has happened in case the ID is reused.
|
|
---@param objectId string|number
|
|
---@return boolean success
|
|
function Profiles:Delete(objectId)
|
|
if self._super.Delete(self, objectId) then
|
|
for _, profileType in pairs(BS_PROFILE_TYPE) do
|
|
self.profileCreationCopyDone[objectId .. profileType] = nil
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Retrieve a copy of a profile for exporting.
|
|
---@param objectId string|number
|
|
---@return table exportableData
|
|
function Profiles:Export(objectId)
|
|
-- Do the export.
|
|
local export = self._super.Export(self, objectId)
|
|
|
|
-- Add dependency lists.
|
|
if export.object then
|
|
export.dependencies[BsCategories] = {}
|
|
export.dependencies[BsSortOrders] = {}
|
|
|
|
local profile = self.list[objectId]
|
|
|
|
-- Primary Structure.
|
|
self:HandleDependenciesForImportExport(profile.structure.primary, export)
|
|
|
|
-- Docked Structure(s).
|
|
if type(profile.structure.docked) == "table" then
|
|
for _, dockedStructure in pairs(profile.structure.docked) do
|
|
self:HandleDependenciesForImportExport(dockedStructure, export)
|
|
end
|
|
end
|
|
end
|
|
return export
|
|
end
|
|
|
|
--- Import the given data as a new profile.
|
|
---@param profile table
|
|
---@param dependencyMap table<table, table<number, number>>? Define the mapping of Group and Sort Order IDs that were in the export to what they have become on import. See `ObjectList:Export()` for expected format.
|
|
---@param force boolean? Import even if an identical object already exists.
|
|
---@return number? newObjectId nil when import fails.
|
|
function Profiles:Import(profile, dependencyMap, force)
|
|
-- Update dependencies in primary Structure.
|
|
self:HandleDependenciesForImportExport(profile.structure.primary, nil, dependencyMap)
|
|
|
|
-- Update dependencies in docked Structure(s).
|
|
if type(profile.structure.docked) == "table" then
|
|
for _, dockedStructure in pairs(profile.structure.docked) do
|
|
self:HandleDependenciesForImportExport(dockedStructure, nil, dependencyMap)
|
|
end
|
|
end
|
|
|
|
-- Do the import.
|
|
return self._super.Import(self, profile)
|
|
end
|
|
|
|
--- Get the list of other ObjectList classes on which this one depends.
|
|
---@return ObjectList[]? dependencies
|
|
function Profiles:GetImportExportDependencies()
|
|
return { BsCategories, BsSortOrders }
|
|
end
|
|
|
|
--- When an export is happening, update the export object's `dependencies` table.
|
|
--- Otherwise, update the given `structure`'s dependency references based on `dependencyMap`.
|
|
---@param structure table Profile Structure component.
|
|
---@param export table? Profile export object.
|
|
---@param dependencyMap table? See `Profiles:Import()`.
|
|
function Profiles:HandleDependenciesForImportExport(structure, export, dependencyMap)
|
|
-- Categories.
|
|
for rowNum, row in ipairs(structure.layout) do
|
|
for groupNum, group in ipairs(row) do
|
|
if type(group.categories) == "table" then
|
|
for i = 1, table.getn(group.categories) do
|
|
if export then
|
|
if not BsCategories:IsBuiltIn(group.categories[i]) then
|
|
table.insert(export.dependencies[BsCategories], group.categories[i])
|
|
end
|
|
elseif dependencyMap then
|
|
self:UpdateDependency(group.categories, i, BsCategories, dependencyMap)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Sort Orders.
|
|
if export then
|
|
if not BsSortOrders:IsBuiltIn(structure.defaultSortOrder) then
|
|
table.insert(export.dependencies[BsSortOrders], structure.defaultSortOrder)
|
|
end
|
|
elseif dependencyMap then
|
|
self:UpdateDependency(structure, "defaultSortOrder", BsSortOrders, dependencyMap)
|
|
end
|
|
|
|
for rowNum, row in ipairs(structure.layout) do
|
|
for groupNum, group in ipairs(row) do
|
|
if group.sortOrder then
|
|
if export then
|
|
if not BsSortOrders:IsBuiltIn(group.sortOrder) then
|
|
table.insert(export.dependencies[BsSortOrders], group.sortOrder)
|
|
end
|
|
elseif dependencyMap then
|
|
self:UpdateDependency(group, "sortOrder", BsSortOrders, dependencyMap)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Replace a reference to a dependency.
|
|
---@param objectTable table List of object IDs.
|
|
---@param objectKey any Key within `objectTable` whose value should be updated.
|
|
---@param objectList table Bagshui ObjectList within `dependencyMap` where object ID mappings can be found.
|
|
---@param dependencyMap table Object mapping. See `ObjectList:Export()` for notes.
|
|
function Profiles:UpdateDependency(objectTable, objectKey, objectList, dependencyMap)
|
|
if
|
|
dependencyMap
|
|
and dependencyMap[objectList]
|
|
and objectTable
|
|
and objectTable[objectKey]
|
|
and dependencyMap[objectList][objectTable[objectKey]]
|
|
then
|
|
objectTable[objectKey] = dependencyMap[objectList][objectTable[objectKey]]
|
|
end
|
|
end
|
|
|
|
--- Given a profile ID and type, returns the profile that SHOULD be used along with its id.
|
|
--- Also validates the profile structure, so this is the function that other classes should call.
|
|
--- Wrapper for `Profiles:GetUsableProfileId()`, so see that function for more details.
|
|
---@param profileId string|number A profile identifier.
|
|
---@param profileType BS_PROFILE_TYPE
|
|
---@return table profileToUse Profile object that should be used.
|
|
---@return string|number ID of the profile that should be used.
|
|
function Profiles:GetUsableProfile(profileId, profileType)
|
|
assert(profileType, "Profiles:GetUsableProfile() - profileType is required")
|
|
profileId = self:GetUsableProfileId(profileId, profileType)
|
|
self:VerifyProfileStructure(profileId)
|
|
return self.list[profileId], profileId
|
|
end
|
|
|
|
--- Given a profile ID and type, returns the ID of the profile that SHOULD be used.
|
|
--- Order of priority:
|
|
--- 1. Given profile, if valid and NOT a builtin.§
|
|
--- 2. Profile named after the current character, if one exists.§
|
|
--- 3. The account-wide default profile, if it exists.
|
|
--- 4. The default profile as configured in BS_DEFAULT_PROFILE.
|
|
---
|
|
--- § Only if `profileId` is nil, which indicates a first login. In that case
|
|
--- always proceed to defaults and cloning.
|
|
---
|
|
--- Cloning logic:
|
|
--- If a profile is selected in step 3, it will be cloned if createNewProfile[profileType] == true.
|
|
--- If a profile is selected in step 4, it will ALWAYS be cloned.
|
|
---@param profileId string|number A profile identifier.
|
|
---@param profileType BS_PROFILE_TYPE
|
|
---@return string|number # ID of the profile that should be used.
|
|
function Profiles:GetUsableProfileId(profileId, profileType)
|
|
-- Bagshui:PrintDebug("GetUsableProfileId() " .. profileType .. " : " .. tostring(profileId))
|
|
|
|
-- If the profile ID exists, just use it, so long as it's not a builtin
|
|
-- (which needs to be cloned) or a first login.
|
|
if profileId and self.list[profileId] and not self.list[profileId].builtin then
|
|
return profileId
|
|
end
|
|
|
|
-- First fallback is a profile named after the current character.
|
|
local characterProfileId
|
|
if not profileId or not self.list[profileId] then
|
|
for existingProfileId, existingProfileInfo in pairs(self.list) do
|
|
if existingProfileInfo.name == Bagshui.currentCharacterId then
|
|
characterProfileId = existingProfileId
|
|
end
|
|
end
|
|
end
|
|
-- Only immediately fall back to the character-named profile if it's not a first login.
|
|
if profileId and characterProfileId then
|
|
return characterProfileId
|
|
end
|
|
|
|
-- Check to see if we should be directly using or cloning an existing profile.
|
|
local defaultProfileId = BsSettings["defaultProfile" .. profileType]
|
|
local clone = BsSettings["createNewProfile" .. profileType]
|
|
local profileTypeStorageKey = self:GetProfileStorageKey(profileType)
|
|
|
|
-- Make sure that profile exists and has the needed profile data.
|
|
-- If not, fall back to the default (which needs to be cloned).
|
|
if not self.list[defaultProfileId] or not self.list[defaultProfileId][profileTypeStorageKey] then
|
|
defaultProfileId = BS_DEFAULT_PROFILE[profileType]
|
|
clone = true
|
|
end
|
|
|
|
-- Builtin profiles always require cloning because they're read-only.
|
|
if self.list[defaultProfileId].builtin then
|
|
clone = true
|
|
end
|
|
|
|
-- We've found a valid profile that should be used directly.
|
|
if not clone then
|
|
return defaultProfileId
|
|
end
|
|
|
|
-- We need to clone the profile.
|
|
local newProfileId
|
|
-- Don't try to create the same character profile multiple times.
|
|
if characterProfileId then
|
|
-- On first login when cloning is needed, the character profile may have
|
|
-- already been created by another Inventory class instance or because a
|
|
-- different profile type was requested first. We need to be sure the
|
|
-- settings are correct, but also only set them once. Doing it multiple
|
|
-- times can mess up the pointer for the Inventory layout table until
|
|
-- the UI is reloaded (issue #121).
|
|
local fullId = characterProfileId .. profileTypeStorageKey
|
|
if not self.profileCreationCopyDone[fullId] then
|
|
self:Copy(defaultProfileId, characterProfileId, profileTypeStorageKey)
|
|
self.profileCreationCopyDone[fullId] = true
|
|
end
|
|
newProfileId = characterProfileId
|
|
else
|
|
-- Character profile doesn't exist yet.
|
|
newProfileId = self:Clone(defaultProfileId, Bagshui.currentCharacterId)
|
|
end
|
|
|
|
return newProfileId
|
|
end
|
|
|
|
--- Given a profile ID, ensure its structure is ready for use.
|
|
---@param profileId string|number A profile identifier.
|
|
function Profiles:VerifyProfileStructure(profileId)
|
|
if not self.list[profileId] then
|
|
return
|
|
end
|
|
|
|
for _, profileType in pairs(BS_PROFILE_TYPE) do
|
|
if not self.list[profileId][BsUtil.LowercaseFirstLetter(profileType)] then
|
|
self.list[profileId][BsUtil.LowercaseFirstLetter(profileType)] = {}
|
|
end
|
|
end
|
|
|
|
-- Additional table creation for Structure profiles.
|
|
if not self.list[profileId].structure.primary then
|
|
self.list[profileId].structure.primary = {}
|
|
end
|
|
if not self.list[profileId].structure.primary.layout then
|
|
self.list[profileId].structure.primary.layout = {}
|
|
BsUtil.TableCopy(BS_NEW_PROFILE_TEMPLATE.structure.primary.layout, self.list[profileId].structure.primary.layout)
|
|
end
|
|
|
|
if not self.list[profileId].structure.docked then
|
|
self.list[profileId].structure.docked = {}
|
|
end
|
|
for inventoryType, inventoryConfig in pairs(Bagshui.config.Inventory) do
|
|
if inventoryConfig.dockTo then
|
|
if not self.list[profileId].structure.docked[inventoryType] then
|
|
self.list[profileId].structure.docked[inventoryType] = {}
|
|
end
|
|
if not self.list[profileId].structure.docked[inventoryType].layout then
|
|
self.list[profileId].structure.docked[inventoryType].layout = {}
|
|
BsUtil.TableCopy(
|
|
BS_NEW_PROFILE_TEMPLATE.structure.primary.layout,
|
|
self.list[profileId].structure.docked[inventoryType].layout
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Translate a profile type (PythonCase) to storage key (camelCase).
|
|
---@param profileType any
|
|
---@return string
|
|
function Profiles:GetProfileStorageKey(profileType)
|
|
return getProfileTypeStorageKey(profileType)
|
|
end
|
|
|
|
local _getUses_defaultProfiles = {}
|
|
local _getUses_inventoryTypes = {}
|
|
local _getUses_activeProfiles = {}
|
|
|
|
--- Populate the "uses" metadata properties of the profile with tooltip-ready strings.
|
|
---
|
|
--- Produces something like...
|
|
---
|
|
--- ```none
|
|
--- Profile uses:
|
|
--- Default:
|
|
--- • Structure
|
|
--- • Design
|
|
---
|
|
--- <Character Name>:
|
|
--- - Bags (Structure, Design)
|
|
--- - Bank (Design)
|
|
--- ```
|
|
---@param objectId string|number Profile ID.
|
|
---@param objectMetadata table Metadata table created by `ObjectList:RefreshUsage()`.
|
|
---@param compact boolean? Minimized version that doesn't take as many lines to display. Primarily intended for use by `Profiles:GetProfilesUsingObject()`.
|
|
---@return boolean inUse Whether the object is being used.
|
|
function Profiles:GetUses(objectId, objectMetadata, compact)
|
|
if not BsCharacterData.list then
|
|
return false
|
|
end
|
|
|
|
self:ResetUses(objectMetadata)
|
|
|
|
-- BsUtil.TableClear(self._getUses_temp)
|
|
BsUtil.TableClear(_getUses_defaultProfiles)
|
|
|
|
-- Determine whether this is the default profile.
|
|
for _, profileType in ipairs(BS_PROFILE_ORDER) do
|
|
profileType = BS_PROFILE_TYPE[profileType]
|
|
if BsSettings["defaultProfile" .. profileType] == objectId then
|
|
table.insert(_getUses_defaultProfiles, L["Profile_" .. profileType])
|
|
end
|
|
end
|
|
if table.getn(_getUses_defaultProfiles) > 0 then
|
|
if compact then
|
|
-- • Default: Structure, Design
|
|
self:AddUse(
|
|
objectMetadata,
|
|
"uses",
|
|
"• " .. string.format(L.Symbol_Colon, L.Default) .. " " .. table.concat(_getUses_defaultProfiles, ", ")
|
|
)
|
|
else
|
|
-- Default:
|
|
-- • Structure
|
|
-- • Design
|
|
self:AddUse(objectMetadata, "uses", string.format(L.Symbol_Colon, L.Default))
|
|
self:AddUse(objectMetadata, "uses", _getUses_defaultProfiles, nil, nil, "• ")
|
|
end
|
|
end
|
|
|
|
-- Find inventories where this is the active profile by iterating characters,
|
|
-- then going through their inventories.
|
|
for characterId, characterData in pairs(BsCharacterData.list) do
|
|
BsUtil.TableClear(_getUses_inventoryTypes)
|
|
-- Iterate inventory types.
|
|
for _, inventoryType in ipairs(BS_INVENTORY_TYPE_UI_ORDER) do
|
|
-- Skip docked inventories (see inventoryName comment).
|
|
if not Bagshui.components[inventoryType].dockTo then
|
|
-- When there is another class docked to this one, show it as Primary/Docked
|
|
-- since the docked class always shares the same profile settings as the primary.
|
|
local inventoryName = L[inventoryType]
|
|
.. (Bagshui.dockedInventories[inventoryType] and ("/" .. L[Bagshui.dockedInventories[inventoryType]]) or "")
|
|
|
|
BsUtil.TableClear(_getUses_activeProfiles)
|
|
|
|
-- The dockTo check isn't really necessary since we're skipping docked inventories,
|
|
-- but it's getting left in case it's useful in the future.
|
|
local inventoryData =
|
|
characterData[Bagshui.components[Bagshui.components[inventoryType].dockTo or inventoryType].inventoryTypeSavedVars]
|
|
|
|
-- Check active profile setting for each profile type in this inventory class.
|
|
for _, profileType in ipairs(BS_PROFILE_ORDER) do
|
|
profileType = BS_PROFILE_TYPE[profileType]
|
|
if
|
|
inventoryData
|
|
and inventoryData.settings
|
|
and inventoryData.settings["profile" .. profileType] == objectId
|
|
then
|
|
-- In compact mode, use the abbreviated version (i.e. Struct instead of Structure).
|
|
table.insert(_getUses_activeProfiles, L["Profile_" .. (compact and "Abbrev_" or "") .. profileType])
|
|
end
|
|
end
|
|
|
|
if table.getn(_getUses_activeProfiles) > 0 then
|
|
-- Bags (Structure, Design)
|
|
-- or for compact:
|
|
-- Bags (Struct, Dsgn)
|
|
table.insert(
|
|
_getUses_inventoryTypes,
|
|
inventoryName .. " (" .. table.concat(_getUses_activeProfiles, ", ") .. ")"
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
if table.getn(_getUses_inventoryTypes) > 0 then
|
|
if compact then
|
|
-- - <Character Name>
|
|
-- Bags (Struct); Bank (Struct, Dsgn)
|
|
-- table.insert(self._getUses_temp, "- " .. BsCharacter:FormatCharacterName(characterId, true) .. "\n " .. table.concat(_getUses_inventoryTypes, "; "))
|
|
self:AddUse(objectMetadata, "uses", "- " .. BsCharacter:FormatCharacterName(characterId, true))
|
|
self:AddUse(objectMetadata, "uses", " " .. table.concat(_getUses_inventoryTypes, "; "))
|
|
else
|
|
-- <Character Name>:
|
|
-- - Bags (Structure, Design)
|
|
-- - Bank (Design)
|
|
-- table.insert(self._getUses_temp, BsCharacter:FormatCharacterName(characterId, true) .. ":\n- " .. table.concat(_getUses_inventoryTypes, "\n- "))
|
|
self:AddUse(objectMetadata, "uses", BsCharacter:FormatCharacterName(characterId, true))
|
|
self:AddUse(objectMetadata, "uses", _getUses_inventoryTypes, nil, nil, "- ")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Heading.
|
|
if not compact and table.getn(objectMetadata.usesLeft) > 0 then
|
|
self:AddUse(
|
|
objectMetadata,
|
|
"uses",
|
|
BS_FONT_COLOR.BAGSHUI .. L.Object_ProfileUses .. FONT_COLOR_CODE_CLOSE,
|
|
nil,
|
|
1
|
|
)
|
|
end
|
|
|
|
return table.getn(objectMetadata.usesLeft) > 0
|
|
end
|
|
|
|
-- Reusable metadata table for `Profiles:GetProfilesUsingObject()`, since we need to get profile use information.
|
|
-- This table will be passed to `Profiles:GetUses()` instead of the normal object metadata table, and it will
|
|
-- fill in `usesLeft` and `usesRight`, which will then be transferred to the actual object's `usesOfProfilesLeft/Right`.
|
|
local _getProfilesUsingObject_tempMetadata = {
|
|
usesLeft = {},
|
|
usesRight = {},
|
|
usesOfProfilesLeft = {},
|
|
usesOfProfilesRight = {},
|
|
}
|
|
|
|
--- Find all profiles that reference a given object. Returns a single newline-separated
|
|
--- string, same as `GetUses()` (and is called from the `GetUses()` function of Categories
|
|
--- and SortOrders).
|
|
---
|
|
--- There is so much shared code between Categories and Sort Orders that it made
|
|
--- sense to consolidate it all here.
|
|
---
|
|
--- Produces something like...
|
|
---
|
|
--- ### Categories
|
|
---
|
|
--- ```none
|
|
--- Used in profiles:
|
|
--- Bagshui Bind on Equip [4:2]
|
|
--- <Character> - <Realm> Bind on Equip [4:2]
|
|
--- ```
|
|
--- ,
|
|
--- ```none
|
|
--- Profile uses:
|
|
--- Bagshui
|
|
--- • Default: Structure, Design
|
|
--- <Character> - <Realm>
|
|
--- - <Character> [<Class>] • <Realm>
|
|
--- Bags/Keyring (Struct, Dsgn); Bank (Struct, Dsgn)
|
|
--- ```
|
|
---
|
|
--- ### Sort Orders
|
|
---
|
|
--- #### For defaults:
|
|
--- ```none
|
|
--- Used in profiles:
|
|
--- Bagshui Default
|
|
--- <Character> - <Realm> Default
|
|
--- ```
|
|
--- ,
|
|
--- ```none
|
|
--- Profile uses:
|
|
--- (Same as above)
|
|
--- ```
|
|
---
|
|
--- #### For those assigned to specific groups:
|
|
---
|
|
--- ```none
|
|
--- Used in profiles:
|
|
--- Bagshui
|
|
--- - Food [2:4]
|
|
--- <Character> - <Realm>
|
|
--- - Food [2:4]
|
|
--- ```
|
|
--- ,
|
|
--- ```none
|
|
--- Profile uses:
|
|
--- (Same as above)
|
|
--- ```
|
|
---@param objectId string|number Unique object identifier.
|
|
---@param objectMetadata table Metadata table created by `ObjectList:RefreshUsage()`.
|
|
---@param objectClass table The class (BsCategories or BsSortOrders) that the object belongs to.
|
|
---@return boolean inUse Whether the object is being used.
|
|
function Profiles:GetProfilesUsingObject(objectId, objectMetadata, objectClass)
|
|
self:ResetUses(objectMetadata)
|
|
|
|
-- We're going to iterate through all profiles, then within each profile,
|
|
-- check the primary and docked Structure. These are the only places
|
|
-- Categories and Sort Orders are referenced; there's no need to look at the
|
|
-- Design parts.
|
|
|
|
for _, profileId in ipairs(self.sortedIdLists.name) do
|
|
local profile = self.list[profileId]
|
|
-- Get the use information about this profile in compact form to add to the bottom of the output.
|
|
local profileInUse = self:GetUses(profileId, _getProfilesUsingObject_tempMetadata, true)
|
|
local profileName = (profileInUse and HIGHLIGHT_FONT_COLOR_CODE or GRAY_FONT_COLOR_CODE)
|
|
.. (self:GetName(profileId, false, false) or tostring(profileId))
|
|
local usedInProfile = false
|
|
if type(profile.structure) == "table" then
|
|
-- Primary Structure.
|
|
if
|
|
self:GetObjectUsesInStructure(
|
|
objectId,
|
|
objectMetadata,
|
|
objectClass,
|
|
profileName .. FONT_COLOR_CODE_CLOSE,
|
|
profile.structure.primary
|
|
)
|
|
then
|
|
usedInProfile = true
|
|
end
|
|
|
|
-- Docked Structure(s).
|
|
if type(profile.structure.docked) == "table" then
|
|
for dockedInventoryType, dockedStructure in pairs(profile.structure.docked) do
|
|
if
|
|
self:GetObjectUsesInStructure(
|
|
objectId,
|
|
objectMetadata,
|
|
objectClass,
|
|
profileName .. " (" .. L[dockedInventoryType] .. ")" .. FONT_COLOR_CODE_CLOSE,
|
|
dockedStructure
|
|
)
|
|
then
|
|
usedInProfile = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Add profile use information when the object is used in this profile.
|
|
if usedInProfile and table.getn(_getProfilesUsingObject_tempMetadata.usesLeft) > 0 then
|
|
self:AddUse(objectMetadata, "usesOfProfiles", profileName .. FONT_COLOR_CODE_CLOSE)
|
|
-- Transfer profile use data from temp tables to this object's metadata.
|
|
self:AddUse(
|
|
objectMetadata,
|
|
"usesOfProfiles",
|
|
_getProfilesUsingObject_tempMetadata.usesLeft,
|
|
_getProfilesUsingObject_tempMetadata.usesRight
|
|
)
|
|
end
|
|
end
|
|
|
|
-- Add header to uses.
|
|
if table.getn(objectMetadata.usesLeft) > 0 then
|
|
self:AddUse(
|
|
objectMetadata,
|
|
"uses",
|
|
BS_FONT_COLOR.BAGSHUI .. L.Object_UsedInProfiles .. FONT_COLOR_CODE_CLOSE,
|
|
nil,
|
|
1
|
|
)
|
|
end
|
|
|
|
-- Add header to profile uses.
|
|
if table.getn(objectMetadata.usesOfProfilesLeft) > 0 then
|
|
self:AddUse(
|
|
objectMetadata,
|
|
"usesOfProfiles",
|
|
BS_FONT_COLOR.BAGSHUI .. L.Object_ProfileUses .. FONT_COLOR_CODE_CLOSE,
|
|
nil,
|
|
1
|
|
)
|
|
end
|
|
|
|
return table.getn(objectMetadata.usesLeft) > 0
|
|
end
|
|
|
|
-- Reusable table for GetObjectUsesInStructure().
|
|
local _getObjectUsesInStructure_details = {}
|
|
|
|
--- Get information about where the given object (Category or Sort Order) is used within
|
|
--- the given Structure profile component.
|
|
---
|
|
--- There is so much shared code between Categories and Sort Orders that it made
|
|
--- sense to consolidate it all here.
|
|
---@param objectId string|number Unique ID for the object.
|
|
---@param objectMetadata table Metadata table created by `ObjectList:RefreshUsage()`.
|
|
---@param objectClass table Class that owns the object (the actual class object, not any sort of string). So for Categories, pass the `BsCategories` object.
|
|
---@param profileName string Profile to which the `structure` component belongs.
|
|
---@param structure table Structure profile component to be examined.
|
|
---@return boolean inUse
|
|
function Profiles:GetObjectUsesInStructure(objectId, objectMetadata, objectClass, profileName, structure)
|
|
if type(structure) ~= "table" or type(structure.layout) ~= "table" then
|
|
return false
|
|
end
|
|
|
|
if objectClass == BsCategories then
|
|
-- Iterate the layout to find which group, if any, contains the given category.
|
|
for rowNum, row in ipairs(structure.layout) do
|
|
for groupNum, group in ipairs(row) do
|
|
if type(group.categories) == "table" then
|
|
for _, categoryInLayout in ipairs(group.categories) do
|
|
if categoryInLayout == objectId then
|
|
-- [Category Name] [Group Using Category]
|
|
self:AddUse(
|
|
objectMetadata,
|
|
"uses",
|
|
profileName,
|
|
(group.name or L.UnnamedGroup)
|
|
.. GRAY_FONT_COLOR_CODE
|
|
.. " ["
|
|
.. rowNum
|
|
.. ":"
|
|
.. groupNum
|
|
.. "]"
|
|
.. FONT_COLOR_CODE_CLOSE
|
|
)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif objectClass == BsSortOrders then
|
|
-- For Sort Orders, we need to know whether it is the default for the Structure,
|
|
-- and also need to iterate and find all groups where it's the chosen Sort Order.
|
|
BsUtil.TableClear(_getObjectUsesInStructure_details)
|
|
|
|
local isDefault = (structure.defaultSortOrder == objectId)
|
|
|
|
-- Fill the details table with a list of groups that use this sort order so
|
|
-- we can collapse them into a single comma-separated string.
|
|
for rowNum, row in ipairs(structure.layout) do
|
|
for groupNum, group in ipairs(row) do
|
|
if group.sortOrder == objectId then
|
|
table.insert(
|
|
_getObjectUsesInStructure_details,
|
|
group.name .. GRAY_FONT_COLOR_CODE .. " [" .. rowNum .. ":" .. groupNum .. "]" .. FONT_COLOR_CODE_CLOSE
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- [Profile Name] Default
|
|
-- • [Group1], [Group2], [Group3]
|
|
if isDefault or table.getn(_getObjectUsesInStructure_details) > 0 then
|
|
self:AddUse(objectMetadata, "uses", profileName, isDefault and L.Default or nil)
|
|
if table.getn(_getObjectUsesInStructure_details) > 0 then
|
|
self:AddUse(objectMetadata, "uses", "• " .. table.concat(_getObjectUsesInStructure_details, ", "))
|
|
end
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
end)
|