Loot Profile Class
The LootProfile class is the primary data structure for managing loot distribution in the Spectrum Federation addon. Each LootProfile instance represents a named profile containing Members, Loot Logs, and administrative metadata. Profiles can be created, managed, and synchronized across multiple users for guild loot coordination. You can have multiple classes but only one can be active at a time.
Purpose and Role: LootProfile instances serve as containers for all loot-related data within a specific context (e.g., a raid tier, guild group, or loot council). Each profile maintains:
- Unique Identity: Stable profile ID for tracking across renames and synchronization
- Member roster: A list of LootProfileMember instances representing raid participants
- Event history: An array of LootLog instances recording all profile activity
- Administrative control: Author, owner, and admin user lists for permission management
- State tracking: Active/inactive status for managing multiple concurrent profiles
- Counter management: Per-author counters for preventing log collisions in multi-writer scenarios
The class uses Lua's metatable-based OOP pattern to provide instance methods for profile management, member administration, and log tracking.
Overview
Each loot profile is represented by a LootProfile instance that tracks:
- Identity: Stable profile ID (generated at creation) and human-readable profile name
- Ownership: Author (creator) and owner (current controller)
- Member Roster: Array of LootProfileMember instances
- Event History: Array of LootLog instances (append-only, chronologically sorted)
- Admin Control: List of admin user identifiers
- State: Active/inactive status
- Counters: Per-author counter map for multi-writer log collision prevention
Data Flow
Loot Profiles follow a log-driven, permission-controlled architecture:
- Profile Creation - User creates a new profile, becoming the initial author and owner
- Member Management - Admins add members to the profile roster
- Event Logging - All changes are recorded as immutable LootLog entries
- State Updates - Member instances are rebuilt from log history
- Synchronization - Profiles can be shared across users (future feature)
This ensures administrative control while maintaining data integrity through the log system.
Profile ID Generation
Each profile is assigned a stable, globally-unique identifier at creation time. This ID is separate from the human-readable profile name and remains constant even if the profile is renamed.
Format: p_<time><random1><random2>
- Prefix:
p_(indicates profile ID) - Time: 8-digit hexadecimal server timestamp
- Random1: 8-digit hexadecimal random number (31-bit)
- Random2: 8-digit hexadecimal random number (31-bit)
Generation Algorithm:
local function GenerateProfileId()
local time = GetServerTime() or time()
local ran1 = math.random(0, 0x7fffffff) -- 31-bit random
local ran2 = math.random(0, 0x7fffffff) -- 31-bit random
return ("p_%08x%08x%08x"):format(time, ran1, ran2)
end
Example IDs:
p_67890abc12def345678901abp_6789abcd4f5e67891a2b3c4d
Collision Resistance:
- Time Component: Server timestamp ensures temporal uniqueness
- Random Components: 62 bits of randomness (2^62 ≈ 4.6 quintillion combinations)
- Combined: Extremely low collision probability even across millions of profiles
WoW's Random Number Generator: Modern WoW clients use securerandom for math.random(), providing cryptographically secure randomness suitable for ID generation.
Use Cases:
- Profile tracking across renames
- Profile synchronization between users (future feature)
- Log entry validation and integrity
- Import/export operations
Permission System
The LootProfile class implements an admin-based permission model:
- Author: Original creator of the profile (immutable)
- Owner: Current controller of the profile (transferable)
- Admin Users: List of users with administrative permissions
- Standard Users: Read-only access (future feature)
Admin Permissions:
- Add/remove members
- Award points and assign gear
- Create loot log entries
- Modify profile settings
Current User Validation:
- The
IsCurrentUserAdmin()method checks if the active player is in the admin list - Methods that modify profile data enforce admin permissions
- Non-admin users cannot make changes (returns
falsewith debug warning)
Counter System
The LootProfile class implements a per-author counter system to prevent log entry collisions in multi-writer scenarios. This is critical for profile synchronization where multiple users can independently create log entries.
Architecture:
- Per-Profile, Per-Author: Each profile maintains a separate counter for each author
- Monotonically Increasing: Counters only increment, never decrement
- Collision Prevention: Ensures unique log identifiers within a profile
Storage Structure:
_authorCounters = {
["Shadowbane-Garona"] = 5, -- This author has created 5 logs
["Healz-Garona"] = 3, -- This author has created 3 logs
["Tanky-Garona"] = 7, -- This author has created 7 logs
}
Usage Pattern:
- User creates a log entry
- Profile allocates next counter for that author via
AllocateNextCounter() - Counter increments atomically (5 → 6)
- Log entry uses counter as part of its unique identifier
- Next log from same author gets counter 7
Why This Matters:
Without per-author counters, two users could simultaneously create logs with the same identifier, causing conflicts during synchronization. The counter ensures each author's logs are uniquely numbered within the profile.
Example Scenario:
-- User A creates logs: counter = 1, 2, 3
local counterA1 = profile:AllocateNextCounter("UserA-Garona") -- 1
local counterA2 = profile:AllocateNextCounter("UserA-Garona") -- 2
local counterA3 = profile:AllocateNextCounter("UserA-Garona") -- 3
-- User B creates logs independently: counter = 1, 2
local counterB1 = profile:AllocateNextCounter("UserB-Garona") -- 1
local counterB2 = profile:AllocateNextCounter("UserB-Garona") -- 2
-- No collisions: UserA's logs are 1-3, UserB's logs are 1-2
-- Combined identifier: (profileId, author, counter) is globally unique
Integration with Loot Logs:
When creating a log entry, the counter is passed to the LootLog constructor:
local counter = profile:AllocateNextCounter(author)
local logEntry = SF.LootLog.new(eventType, eventData, {
author = author,
counter = counter,
-- ... other options
})
This creates a composite key (profileId, author, counter) that uniquely identifies each log entry across all profiles and authors.
Class Structure
Properties
Each LootProfile instance has the following private properties:
| Property | Type | Description | Getter Method | Setter Method |
|---|---|---|---|---|
_profileId |
string | Stable unique identifier (generated at creation) | GetProfileId() |
SetProfileIdIfNil() |
_profileName |
string | Human-readable name of the profile | GetProfileName() |
SetProfileName() |
_author |
string | Original creator ("Name-Realm" format) | GetAuthor() |
N/A (immutable) |
_owner |
string | Current owner ("Name-Realm" format) | GetOwnerId() |
SetOwner() |
_members |
table | Array of LootProfileMember instances | GetMemberList() |
AddMember() |
_lootLogs |
table | Array of LootLog instances (chronologically sorted) | GetLootLogs() |
AddLootLog() |
_adminUsers |
table | Array of admin user identifiers ("Name-Realm" format) | GetAdminUsers(), IsCurrentUserAdmin() |
AddAdminUser() |
_activeProfile |
boolean | Active/inactive status | IsActive() |
SetActive() |
_authorCounters |
table | Per-author counter map for log collision prevention | N/A | AllocateNextCounter() |
Creating Profiles
Constructor
The LootProfile class uses dot notation for the constructor (factory function pattern):
Parameters:
profileName(string, required) - Unique name for the profile
Validation:
- Profile name must be a non-empty string
- Automatically generates a stable profile ID using server time + random numbers
- Automatically creates a profile creation log entry with profileId embedded
- Automatically adds the current player as author, owner, and first admin
- Creates an initial LootProfileMember instance for the author
- Initializes per-author counter system for log collision prevention
Example:
-- Create a new profile
local raidProfile = SF.LootProfile.new("Mythic Raid Team 1")
-- Profile is automatically initialized with:
-- - Profile ID: "p_67890abc12def345678901ab" (generated)
-- - Author: Current player
-- - Owner: Current player
-- - Admin users: [Current player]
-- - Members: [Current player as admin member]
-- - Loot logs: [Profile creation event with profileId embedded]
-- - Active: false
-- - Author counters: { ["CurrentPlayer-Realm"] = 1 }
Error Handling:
- Returns
nilif profile name validation fails - Returns
nilif log entry creation fails - Returns
nilif author member creation fails - Logs warnings to debug system on failure
Initial State
New profiles are created with:
- Profile ID: Generated stable identifier (format:
p_<time><random1><random2>) - Author/Owner: Current player's full identifier
- Admin Users: Author added to admin list
- Members: Author added as first member with admin role
- Loot Logs: Profile creation event logged with profileId embedded
- Active Status:
false(inactive by default) - Author Counters: Initialized with creator's first counter (1)
Instance Methods
All instance methods use colon notation (:) which automatically passes self:
Identity Methods
GetProfileId()
Returns the profile's stable unique identifier.
Returns:
string- Profile ID in formatp_<time><random1><random2>
Example:
local profileId = profile:GetProfileId()
print("Profile ID:", profileId) -- "p_67890abc12def345678901ab"
Usage Note: The profile ID is generated at creation and remains stable even if the profile is renamed. This is used for tracking profiles across synchronization and migrations.
SetProfileIdIfNil(profileId)
Sets the profile ID only if it is currently nil. Used for importing profiles or migrating from older formats that didn't have profile IDs.
Parameters:
profileId(string, required) - Stable profile identifier to set
Validation:
- Only sets if
_profileIdis currentlynil - Must be a non-empty string
- Logs warning if invalid profileId provided
- Does nothing if profile already has an ID
Example:
-- Import profile from external source
local importedProfile = SF.LootProfile.new("Imported Profile")
importedProfile:SetProfileIdIfNil("p_12345678abcdef0012345678")
-- Later attempts do nothing (profile already has ID)
importedProfile:SetProfileIdIfNil("p_differentid123456789012") -- Ignored
Usage Note: This is a one-time setter for migrations. Normal profile creation automatically generates an ID.
GetProfileName()
Returns the profile's human-readable name.
Returns:
string- Profile name
Example:
GetAuthor()
Returns the profile's original author (creator) identifier.
Returns:
string- Author identifier in "Name-Realm" format
Example:
GetOwnerId()
Returns the profile's current owner identifier.
Returns:
string- Owner identifier in "Name-Realm" format
Example:
Usage Note: The owner can be different from the author if ownership has been transferred via SetOwner(). This method was renamed from GetOwner() to maintain consistency with other ID-based methods.
IsOwner(memberId)
Checks if a given member ID matches the profile's owner.
Parameters:
memberId(string, required) - Member identifier in "Name-Realm" format
Returns:
boolean-trueif the memberId is the owner,falseotherwise
Example:
local currentUser = SF:GetPlayerFullIdentifier()
if profile:IsOwner(currentUser) then
print("You are the owner of this profile")
else
print("You are not the owner")
end
-- Check if another member is the owner
if profile:IsOwner("Guildmaster-Garona") then
print("Guildmaster is the owner")
end
Usage Note: Uses NameUtil for case-insensitive, realm-aware comparison. Empty or nil member IDs return false.
Counter Management
AllocateNextCounter(author)
Allocates and returns the next counter value for a given author. This is used for preventing log entry collisions in multi-writer scenarios where multiple users can create logs for the same profile.
Parameters:
author(string, required) - Author identifier in "Name-Realm" format
Returns:
number- Next counter value for this author (increments on each call)nil- If author validation fails
Validation:
- Must be a non-empty string
- Logs warning if invalid author provided
Example:
-- Get current player's next counter
local currentUser = SF:GetPlayerFullIdentifier()
local counter1 = profile:AllocateNextCounter(currentUser)
print("Counter:", counter1) -- 1
-- Next allocation increments
local counter2 = profile:AllocateNextCounter(currentUser)
print("Counter:", counter2) -- 2
-- Different authors have independent counters
local counter3 = profile:AllocateNextCounter("OtherPlayer-Garona")
print("Counter:", counter3) -- 1
Usage Note: This is called internally when creating log entries. Each author maintains their own counter to prevent collisions when multiple users create logs simultaneously. The counter is per-profile, per-author, ensuring unique identifiers for all logs.
State Management
IsActive()
Checks if the profile is currently active.
Returns:
boolean-trueif active,falseif inactive
Example:
if profile:IsActive() then
print("Profile is currently active")
else
print("Profile is inactive")
end
SetActive(isActive)
Sets the profile's active/inactive status.
Parameters:
isActive(boolean) -trueto activate,falseto deactivate
Example:
Usage Note: Only one profile should be active at a time in the UI. The active profile is used for loot helper operations.
Timestamp Methods
GetCreationTime()
Retrieves the profile's creation timestamp from the first PROFILE_CREATION log entry.
Returns:
number- Creation timestamp (Unix epoch time)nil- If no creation log found
Example:
local created = profile:GetCreationTime()
if created then
local formattedTime = SF:FormatTimestampForUser(created)
print("Profile created:", formattedTime)
end
GetLastModifiedTime()
Finds the most recent timestamp across all loot log entries.
Returns:
number- Most recent log timestampnil- If no logs exist
Example:
local modified = profile:GetLastModifiedTime()
if modified then
local formattedTime = SF:FormatTimestampForUser(modified)
print("Last modified:", formattedTime)
end
Usage Note: This iterates through all log entries to find the maximum timestamp. Logs are kept sorted chronologically for efficiency.
Permission Methods
IsCurrentUserAdmin()
Checks if the currently logged-in player is an admin of this profile.
Returns:
boolean-trueif current user is admin,falseotherwise
Example:
if profile:IsCurrentUserAdmin() then
print("You have admin permissions for this profile")
-- Show admin UI controls
else
print("You do not have admin permissions")
-- Hide admin UI controls
end
Usage Note: This method is critical for enforcing permissions. Many profile modification methods check this internally before allowing changes.
Member Management
GetMemberList()
Retrieves a list of all member identifiers in the profile.
Returns:
table- Array of member full identifiers ("Name-Realm" format)
Example:
local members = profile:GetMemberList()
for i, memberID in ipairs(members) do
print(i, memberID)
end
-- Output:
-- 1 Shadowbane-Garona
-- 2 Tanky-Garona
-- 3 Healz-Garona
Usage Note: Returns identifiers only. To access member objects, iterate through the _members property directly (internal use).
GetLootLogs()
Retrieves the complete array of loot log entries for the profile.
Returns:
table- Array of LootLog instances (chronologically sorted by timestamp)
Example:
local logs = profile:GetLootLogs()
print("Total log entries:", #logs)
-- Iterate through logs
for i, log in ipairs(logs) do
local timestamp = SF:FormatTimestampForUser(log:GetTimestamp())
local eventType = log:GetEventType()
print(string.format("[%s] %s", timestamp, eventType))
end
Usage Note: Returns the internal _lootLogs array. Logs are automatically kept sorted by timestamp. Used by Member instances to rebuild state via UpdateFromLootLog().
GetAdminUsers()
Retrieves a list of all admin user identifiers in the profile.
Returns:
table- Array of admin user full identifiers ("Name-Realm" format)
Example:
local admins = profile:GetAdminUsers()
print("Profile admins:")
for i, adminID in ipairs(admins) do
print(i, adminID)
end
-- Output:
-- Profile admins:
-- 1 Shadowbane-Garona
-- 2 Guildmaster-Garona
Usage Note: Returns the internal _adminUsers array. This lists all users who have administrative permissions on the profile. Use with IsCurrentUserAdmin() to check if the active player has admin rights.
getAdminMemberIds()
Retrieves a copy of all admin member IDs in the profile.
Returns:
table- Array of admin member identifiers ("Name-Realm" format)
Example:
local adminIds = profile:getAdminMemberIds()
print("Admin count:", #adminIds)
for i, adminId in ipairs(adminIds) do
print(string.format("Admin %d: %s", i, adminId))
end
Usage Note: Returns a copy (not a reference) of the _adminUsers array to prevent external modification.
getMemberIds()
Retrieves a sorted list of all member IDs in the profile.
Returns:
table- Sorted array of member identifiers ("Name-Realm" format)
Example:
local memberIds = profile:getMemberIds()
print("Total members:", #memberIds)
for i, memberId in ipairs(memberIds) do
print(string.format("%d. %s", i, memberId))
end
Usage Note: Returns a new array containing all member IDs, sorted alphabetically. Useful for UI dropdowns and member selection lists.
getMemberByID(id)
Retrieves a member instance by their identifier.
Parameters:
id(string, required) - Member identifier in "Name-Realm" format
Returns:
Member- Member instance if foundnil- If member not found or invalid ID provided
Example:
local member = profile:getMemberByID("Shadowbane-Garona")
if member then
local points = member:GetPointBalance()
print("Member points:", points)
else
print("Member not found")
end
Usage Note: Uses NameUtil for normalized comparison. Also available as GetMemberByID() (capitalized alias).
GetPointName()
Retrieves the custom point name for this profile.
Returns:
string- Point name (default: "Points")
Example:
local pointName = profile:GetPointName()
print("This profile uses:", pointName) -- "DKP" or "Points"
Usage Note: Returns "Points" if no custom name is set or if the name is empty.
GetRaidWideSafeMode()
Retrieves the raid-wide safe mode setting for this profile.
Returns:
boolean-trueif raid-wide safe mode is enabled,falseotherwise
Example:
if profile:GetRaidWideSafeMode() then
print("Raid-wide safe mode is ENABLED")
else
print("Raid-wide safe mode is DISABLED")
end
Usage Note: When enabled, sync operations are paused for all raid members.
GetRaidWideSafeModeOnCombat()
Retrieves the raid-wide safe mode on combat setting for this profile.
Returns:
boolean-trueif auto-safe mode during combat is enabled,falseotherwise
Example:
if profile:GetRaidWideSafeModeOnCombat() then
print("Safe mode will auto-enable during combat")
else
print("Safe mode will NOT auto-enable during combat")
end
Usage Note: When enabled, sync operations are automatically paused when any raid member enters combat.
AddMember(member)
Adds a LootProfileMember instance to the profile's member roster.
Parameters:
member(LootProfileMember) - Instance to add
Returns:
boolean-trueif added successfully,falseif validation fails
Validation:
- Must be a valid LootProfileMember instance (metatable check)
- Logs warning if invalid instance provided
Example:
-- Create a new member
local newMember = SF.Member.new("Tanky-Garona", SF.MemberRoles.MEMBER, "WARRIOR")
-- Add to profile
if profile:AddMember(newMember) then
SF:PrintSuccess("Member added successfully")
else
SF:PrintError("Failed to add member")
end
Usage Note: This adds the member to the roster but does not create a log entry. Consider creating a corresponding log entry for audit purposes.
AddAdminUser(member)
Adds a LootProfileMember instance to the profile's admin user list.
Parameters:
member(LootProfileMember) - Instance to promote to admin
Returns:
boolean-trueif added successfully,falseif validation fails
Validation:
- Must be a valid LootProfileMember instance (metatable check)
- Extracts full identifier from member and adds to admin list
- Logs warning if invalid instance provided
Example:
-- Get or create member
local member = SF.Member.new("Healz-Garona", SF.MemberRoles.ADMIN, "PALADIN")
-- Add as admin
if profile:AddAdminUser(member) then
SF:PrintSuccess("Admin user added successfully")
else
SF:PrintError("Failed to add admin user")
end
Usage Note: This grants admin permissions to the member. Consider logging this action with a ROLE_CHANGE log entry.
Profile Settings
SetProfileName(newName)
Updates the profile's name.
Parameters:
newName(string) - New profile name (must be non-empty)
Validation:
- Must be a non-empty string
- Logs warning if invalid name provided
- Does not update if validation fails
Example:
-- Rename profile
profile:SetProfileName("Season 1 Raid Team")
-- Invalid attempts (no change, warning logged)
profile:SetProfileName("") -- Empty string
profile:SetProfileName(nil) -- Not a string
profile:SetProfileName(123) -- Not a string
Usage Note: Consider creating a log entry to track profile renames for audit purposes.
SetOwner(newOwner)
Transfers profile ownership to a different user.
Parameters:
newOwner(string) - New owner identifier in "Name-Realm" format
Validation:
- Must match "Name-Realm" format (regex:
^[^%-]+%-[^%-]+$) - Logs warning if invalid format provided
- Does not update if validation fails
Example:
-- Transfer ownership
profile:SetOwner("Guildmaster-Garona")
-- Invalid attempts (no change, warning logged)
profile:SetOwner("InvalidFormat") -- No realm
profile:SetOwner("Name-Realm-Extra") -- Too many parts
profile:SetOwner("") -- Empty string
Usage Note: Ownership transfer does not automatically grant admin permissions. Add the new owner to the admin list separately if needed.
SetPointName(name)
Sets a custom point name for this profile.
Parameters:
name(string, required) - New point name
Returns:
boolean-trueif set successfully,falseif validation or permission check failsstring(optional) - Error message if operation failed
Permission Requirements:
- Current user must be an admin
Validation:
- Name cannot be empty after trimming whitespace
Example:
-- Set custom point name
local ok, err = profile:SetPointName("DKP")
if ok then
SF:PrintSuccess("Point name updated to DKP")
else
SF:PrintError("Failed to set point name: " .. (err or "Unknown error"))
end
-- Permission check example
local ok, err = profile:SetPointName("EP/GP")
if not ok and err:match("admin") then
SF:PrintWarning("You need admin permissions to change the point name")
end
Usage Note: The point name is displayed in the UI when showing member point balances.
SetRaidWideSafeMode(enabled)
Enables or disables raid-wide safe mode for this profile.
Parameters:
enabled(boolean, required) -trueto enable,falseto disable
Returns:
boolean-trueif set successfully,falseif permission check failsstring(optional) - Error message if operation failed
Permission Requirements:
- Current user must be an admin
Example:
-- Enable raid-wide safe mode
local ok, err = profile:SetRaidWideSafeMode(true)
if ok then
SF:PrintSuccess("Raid-wide safe mode ENABLED")
else
SF:PrintError("Failed to enable safe mode: " .. (err or "Unknown error"))
end
-- Disable raid-wide safe mode
profile:SetRaidWideSafeMode(false)
Usage Note: When enabled, sync operations are paused for all raid members.
SetRaidWideSafeModeOnCombat(enabled)
Enables or disables automatic safe mode activation during combat.
Parameters:
enabled(boolean, required) -trueto enable,falseto disable
Returns:
boolean-trueif set successfully,falseif permission check failsstring(optional) - Error message if operation failed
Permission Requirements:
- Current user must be an admin
Example:
-- Enable auto-safe mode during combat
local ok, err = profile:SetRaidWideSafeModeOnCombat(true)
if ok then
SF:PrintSuccess("Auto-safe mode during combat ENABLED")
else
SF:PrintError("Failed to enable: " .. (err or "Unknown error"))
end
-- Disable auto-safe mode during combat
profile:SetRaidWideSafeModeOnCombat(false)
Usage Note: When enabled, sync operations are automatically paused when any raid member enters combat and resumed when combat ends.
Loot Log Management
AddLootLog(lootLog)
Appends a LootLog entry to the profile's event history.
Parameters:
lootLog(LootLog) - LootLog instance to add
Returns:
boolean-trueif added successfully,falseif permission denied or validation fails
Permission Requirements:
- Current user must be an admin (checked via
IsCurrentUserAdmin()) - Returns
falsewith debug warning if non-admin attempts to add logs
Validation:
- Must be a valid LootLog instance (metatable check)
- Logs warning if invalid instance provided
Behavior:
- Appends log to the
_lootLogsarray - Automatically re-sorts logs by timestamp to maintain chronological order
- Ensures log history remains consistent
Example:
-- Create a point change log
local pointChange = SF.LootLog.new(
SF.LootLogEventTypes.POINT_CHANGE,
{
member = "Tanky-Garona",
change = SF.LootLogPointChangeTypes.INCREMENT
}
)
-- Add to profile
if profile:AddLootLog(pointChange) then
SF:PrintSuccess("Log entry added")
else
SF:PrintError("Failed to add log entry")
end
Error Cases:
- Non-admin user: Returns
false, logs "Current user is not an admin" - Invalid log instance: Returns
false, logs "Attempted to add invalid LootLog instance"
Usage Note: Logs are automatically sorted by timestamp after insertion, so insertion order doesn't matter. This ensures the log history always reflects chronological order.
Usage Examples
Creating a New Profile
-- Create profile
local profile = SF.LootProfile.new("Mythic Raid Team 1")
if not profile then
SF:PrintError("Failed to create profile")
return
end
-- Profile is now ready to use
SF:PrintSuccess("Profile created: " .. profile:GetProfileName())
Adding Members
-- Create members
local tank = SF.Member.new("Tanky-Garona", SF.MemberRoles.MEMBER, "WARRIOR")
local healer = SF.Member.new("Healz-Garona", SF.MemberRoles.MEMBER, "PALADIN")
local dps = SF.Member.new("Pewpew-Garona", SF.MemberRoles.MEMBER, "MAGE")
-- Add to profile
profile:AddMember(tank)
profile:AddMember(healer)
profile:AddMember(dps)
-- Promote healer to admin
profile:AddAdminUser(healer)
SF:PrintSuccess("Added 3 members to profile")
Logging Profile Activity
-- Get current user and allocate counter
local currentUser = SF:GetPlayerFullIdentifier()
local counter = profile:AllocateNextCounter(currentUser)
-- Award points to a member
local pointLog = SF.LootLog.new(
SF.LootLogEventTypes.POINT_CHANGE,
{
member = "Tanky-Garona",
change = SF.LootLogPointChangeTypes.INCREMENT
},
{
author = currentUser,
counter = counter
}
)
if profile:AddLootLog(pointLog) then
SF:PrintSuccess("Point awarded and logged")
end
-- Allocate next counter for another log
counter = profile:AllocateNextCounter(currentUser)
-- Assign gear to a member
local armorLog = SF.LootLog.new(
SF.LootLogEventTypes.ARMOR_CHANGE,
{
member = "Tanky-Garona",
slot = SF.ArmorSlots.HEAD,
action = SF.LootLogArmorActions.USED
},
{
author = currentUser,
counter = counter
}
)
if profile:AddLootLog(armorLog) then
SF:PrintSuccess("Gear assigned and logged")
end
Permission Checking
-- Check if current user can modify profile
if not profile:IsCurrentUserAdmin() then
SF:PrintError("You don't have permission to modify this profile")
return
end
-- User is admin, proceed with modifications
profile:SetProfileName("Updated Profile Name")
SF:PrintSuccess("Profile renamed")
Activating Profiles
-- Deactivate all profiles first (application logic)
for _, prof in pairs(SF.lootHelperDB.profiles) do
prof:SetActive(false)
end
-- Activate the selected profile
profile:SetActive(true)
SF:PrintSuccess("Profile activated: " .. profile:GetProfileName())
Displaying Profile Info
-- Get profile metadata
local profileId = profile:GetProfileId()
local name = profile:GetProfileName()
local author = profile:GetAuthor()
local created = profile:GetCreationTime()
local modified = profile:GetLastModifiedTime()
local isActive = profile:IsActive()
local members = profile:GetMemberList()
-- Format timestamps
local createdStr = SF:FormatTimestampForUser(created)
local modifiedStr = SF:FormatTimestampForUser(modified)
-- Display info
print("Profile ID:", profileId)
print("Profile:", name)
print("Created by:", author, "on", createdStr)
print("Last modified:", modifiedStr)
print("Active:", isActive and "Yes" or "No")
print("Members:", #members)
Integration with Other Classes
LootProfileMember
The LootProfile class stores an array of LootProfileMember instances:
- Each member represents a raid participant
- Members track point balances and armor slot states
- Member instances are rebuilt from LootLog history
Relationship:
- Profile contains members (
_membersarray) - Members reference their parent profile (future enhancement)
- Admin status stored in profile's
_adminUserslist
LootLog
The LootProfile class maintains an append-only array of LootLog entries:
- Each log records a single immutable event
- Logs are sorted chronologically by timestamp
- Logs serve as the single source of truth
Relationship:
- Profile contains logs (
_lootLogsarray) - Logs are created for profile events
- Member states are computed from logs
SavedVariables Integration
Profiles are stored in the SpectrumFederationDB SavedVariable:
-- Database structure
SF.lootHelperDB = {
profiles = {
["ProfileName"] = LootProfile instance,
["Another Profile"] = LootProfile instance,
-- ...
},
activeProfile = "ProfileName" -- Currently active profile name
}
Access Pattern:
-- Get active profile
local activeName = SF.lootHelperDB.activeProfile
local activeProfile = SF.lootHelperDB.profiles[activeName]
-- Iterate all profiles
for profileName, profile in pairs(SF.lootHelperDB.profiles) do
print(profileName, profile:GetCreationTime())
end
Best Practices
Profile Creation
Naming:
- Use descriptive, unique names
- Include tier/season information
- Consider raid group or team names
- Example: "Mythic Vault of the Incarnates - Team 1"
Initialization:
- Always check for
nilreturn fromnew() - Create initial members immediately after creation
- Set appropriate admin users during setup
Member Management
Adding Members:
- Create LootProfileMember instances first
- Add members to roster with
AddMember() - Promote admins with
AddAdminUser() - Consider logging member additions
Validation:
- Always check return values from add methods
- Handle failures gracefully with user feedback
- Log errors for debugging
Event Logging
Log Everything:
- Create logs for all profile modifications
- Include point awards, gear assignments, role changes
- Use appropriate event types and data structures
Counter Allocation:
- Always allocate a counter before creating log entries
- Call
AllocateNextCounter()once per log entry - Pass counter to LootLog constructor in options table
- Never reuse counters across different logs
Sort Order:
- Don't worry about insertion order
- Logs are automatically sorted by timestamp
- Query logs chronologically for state reconstruction
Permission Management
Admin Checks:
- Always call
IsCurrentUserAdmin()before UI displays - Disable admin controls for non-admin users
- Show appropriate error messages for permission denials
Ownership Transfer:
- Validate new owner identifier format
- Update admin list if needed
- Consider logging ownership changes
State Management
Active Profiles:
- Only activate one profile at a time
- Deactivate others before activating new one
- Update UI to reflect active profile changes
Profile Selection:
- Store active profile name in
SF.lootHelperDB.activeProfile - Retrieve profile object from
SF.lootHelperDB.profiles - Handle case where active profile doesn't exist
Error Handling
Constructor Failures
The new() constructor can return nil in several cases:
- Invalid Profile Name: Empty string or non-string type
- Log Creation Failed: LootLog.new() returned nil
- Member Creation Failed: SF.Member.new() returned nil
Example:
local profile = SF.LootProfile.new(profileName)
if not profile then
SF:PrintError("Failed to create profile. Check debug logs.")
if SF.Debug then SF.Debug:Show() end
return
end
Method Failures
Most methods return boolean to indicate success/failure:
AddMember()- Returnsfalseif invalid member instanceAddAdminUser()- Returnsfalseif invalid member instanceAddLootLog()- Returnsfalseif permission denied or invalid log
Example:
local success = profile:AddMember(newMember)
if not success then
SF:PrintError("Failed to add member")
-- Check debug logs for details
end
Debug Logging
All validation failures and errors are logged to the debug system:
if SF.Debug then
SF.Debug:Warn("LOOTPROFILE", "Invalid profile name provided: %s", tostring(profileName))
end
Enable Debug Logging:
Related Documentation
- Members Class - LootProfileMember class documentation
- Loot Logs Class - LootLog event logging system
- Loot Helper - Core loot helper functionality and database