Add files via upload
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
GuildRecipesOcto = GuildRecipesOcto or {}
|
||||
|
||||
---@class GuildRecipesOcto
|
||||
local M = GuildRecipesOcto
|
||||
|
||||
local QUALITY_COLORS = {
|
||||
[ "ff9d9d9d" ] = 0, -- poor (gray)
|
||||
[ "ffffffff" ] = 1, -- common (white)
|
||||
[ "ff1eff00" ] = 2, -- uncommon (green)
|
||||
[ "ff0070dd" ] = 3, -- rare (blue)
|
||||
[ "ffa335ee" ] = 4, -- epic (purple)
|
||||
[ "ffff8000" ] = 5, -- legendary (orange)
|
||||
}
|
||||
|
||||
M.TRADE_SKILL_LOCALIZATION = {
|
||||
Alchemy = {
|
||||
enUS = "Alchemy",
|
||||
deDE = "Alchimie",
|
||||
frFR = "Alchimie",
|
||||
esES = "Alquimia",
|
||||
koKR = "연금술",
|
||||
zhCN = "炼金术",
|
||||
zhTW = "鍊金術",
|
||||
},
|
||||
Blacksmithing = {
|
||||
enUS = "Blacksmithing",
|
||||
deDE = "Schmiedekunst",
|
||||
frFR = "Forge",
|
||||
esES = "Herrería",
|
||||
koKR = "대장기술",
|
||||
zhCN = "锻造",
|
||||
zhTW = "鍛造",
|
||||
},
|
||||
Engineering = {
|
||||
enUS = "Engineering",
|
||||
deDE = "Ingenieurskunst",
|
||||
frFR = "Ingénierie",
|
||||
esES = "Ingeniería",
|
||||
koKR = "기계공학",
|
||||
zhCN = "工程学",
|
||||
zhTW = "工程學",
|
||||
},
|
||||
Leatherworking = {
|
||||
enUS = "Leatherworking",
|
||||
deDE = "Lederverarbeitung",
|
||||
frFR = "Travail du cuir",
|
||||
esES = "Peletería",
|
||||
koKR = "가죽세공",
|
||||
zhCN = "制皮",
|
||||
zhTW = "製皮",
|
||||
},
|
||||
Tailoring = {
|
||||
enUS = "Tailoring",
|
||||
deDE = "Schneiderei",
|
||||
frFR = "Couture",
|
||||
esES = "Sastrería",
|
||||
koKR = "재봉술",
|
||||
zhCN = "裁缝",
|
||||
zhTW = "裁縫",
|
||||
},
|
||||
Enchanting = {
|
||||
enUS = "Enchanting",
|
||||
deDE = "Verzauberkunst",
|
||||
frFR = "Enchantement",
|
||||
esES = "Encantamiento",
|
||||
koKR = "마법부여",
|
||||
zhCN = "附魔",
|
||||
zhTW = "附魔",
|
||||
},
|
||||
Jewelcrafting = {
|
||||
enUS = "Jewelcrafting",
|
||||
},
|
||||
}
|
||||
|
||||
function M.build_reverse_trade_map( locale )
|
||||
local map = {}
|
||||
for internal, locs in pairs( M.TRADE_SKILL_LOCALIZATION ) do
|
||||
local localized = locs[ locale ]
|
||||
if localized then
|
||||
map[ localized ] = internal
|
||||
end
|
||||
end
|
||||
return map
|
||||
end
|
||||
|
||||
---@param item_link string
|
||||
---@return integer? id
|
||||
---@return string? name
|
||||
---@return integer? quality
|
||||
function M.parse_item_link( item_link )
|
||||
if type( item_link ) ~= "string" then return nil, nil, nil end
|
||||
|
||||
local item_id = tonumber( string.match( item_link, ":(%d+)" ) )
|
||||
local name = string.match( item_link, "|h%[(.-)%]|h" )
|
||||
local quality
|
||||
|
||||
local color = string.match( item_link, "|c(%x%x%x%x%x%x%x%x)" )
|
||||
if color then
|
||||
quality = QUALITY_COLORS[ string.lower( color ) ]
|
||||
else
|
||||
local _, _, q = GetItemInfo( item_id )
|
||||
quality = q or 1
|
||||
end
|
||||
|
||||
return item_id, name, quality
|
||||
end
|
||||
|
||||
---@param item Item
|
||||
---@return string
|
||||
function M.get_item_name_colorized( item )
|
||||
local color = ITEM_QUALITY_COLORS[ item.quality or 1 ]
|
||||
local hex = string.format( "%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255 )
|
||||
|
||||
return string.format( "|cFF%s%s|r", hex, item.name )
|
||||
end
|
||||
|
||||
---@param id integer
|
||||
---@param name string
|
||||
---@param quality integer
|
||||
---@return string
|
||||
function M.make_item_link( id, name, quality )
|
||||
local color = ITEM_QUALITY_COLORS[ quality or 1 ]
|
||||
local hex = string.format( "%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255 )
|
||||
|
||||
return string.format( "|cFF%s|Hitem:%d:0:0:0|h[%s]|h|r", hex, id, name )
|
||||
end
|
||||
|
||||
function M.make_enchant_link( id, name )
|
||||
return string.format( "|cFF%s|Henchant:%d|h[%s]|h|r", "71d5ff", id, name )
|
||||
end
|
||||
|
||||
function M.colorize_player_by_class( name, class )
|
||||
if not class then return name end
|
||||
local color = RAID_CLASS_COLORS[ string.upper( class ) ]
|
||||
if not color.colorStr then
|
||||
color.colorStr = string.format( "ff%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255 )
|
||||
end
|
||||
return "|c" .. color.colorStr .. name .. "|r"
|
||||
end
|
||||
|
||||
function M.clean_string( input )
|
||||
input = string.gsub( input, "^%s+", "" )
|
||||
input = string.gsub( input, "%s+$", "" )
|
||||
input = string.gsub( input, "|c%x%x%x%x%x%x%x%x", "" )
|
||||
input = string.gsub( input, "|r", "" )
|
||||
|
||||
return input
|
||||
end
|
||||
|
||||
---@param player_name string
|
||||
---@return boolean
|
||||
---@nodiscard
|
||||
function M.guild_member_online( player_name )
|
||||
for i = 1, GetNumGuildMembers() do
|
||||
local name, _, _, _, _, _, _, _, online = GetGuildRosterInfo( i )
|
||||
if player_name == name and online == 1 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---@return table<string, boolean> -- name -> online, built from a single pass over the guild roster
|
||||
---@nodiscard
|
||||
-- Use this instead of calling guild_member_online() in a loop (e.g. once per
|
||||
-- synced player, or once per recipe row) -- each guild_member_online() call
|
||||
-- re-scans the entire roster, so doing that repeatedly is O(n*m) and can
|
||||
-- visibly stutter the game when triggered by a frequent event.
|
||||
function M.build_online_roster()
|
||||
local online = {}
|
||||
for i = 1, GetNumGuildMembers() do
|
||||
local name, _, _, _, _, _, _, _, is_online = GetGuildRosterInfo( i )
|
||||
if name and is_online == 1 then
|
||||
online[ name ] = true
|
||||
end
|
||||
end
|
||||
return online
|
||||
end
|
||||
|
||||
function M.get_server_timestamp()
|
||||
local server_hour, server_min = GetGameTime()
|
||||
local local_time = date( "*t" )
|
||||
|
||||
local t = {
|
||||
year = local_time.year,
|
||||
month = local_time.month,
|
||||
day = local_time.day,
|
||||
hour = server_hour,
|
||||
min = server_min,
|
||||
sec = 0,
|
||||
}
|
||||
|
||||
local hour_diff = server_hour - local_time.hour
|
||||
if hour_diff <= -20 then
|
||||
t.day = t.day + 1
|
||||
elseif hour_diff >= 20 then
|
||||
t.day = t.day - 1
|
||||
end
|
||||
|
||||
return time( t )
|
||||
end
|
||||
|
||||
---@param message string
|
||||
---@param short boolean?
|
||||
function M.info( message, short )
|
||||
local tag = string.format( "|c%s%s|r", M.tagcolor, short and "GRO" or "GuildRecipesOcto" )
|
||||
DEFAULT_CHAT_FRAME:AddMessage( string.format( "%s: %s", tag, message ) )
|
||||
end
|
||||
|
||||
---@param message string
|
||||
function M.error( message )
|
||||
local tag = string.format( "|c%s%s|r|cffff0000%s|r", M.tagcolor, "GR", "ERROR" )
|
||||
DEFAULT_CHAT_FRAME:AddMessage( string.format( "%s: %s", tag, message ) )
|
||||
end
|
||||
|
||||
function M.debug( message )
|
||||
if M.debug_enabled then
|
||||
M.info( message, true )
|
||||
end
|
||||
end
|
||||
|
||||
---@param t string[]
|
||||
---@return string
|
||||
---@nodiscard
|
||||
function M.table_to_comma_separated( t )
|
||||
local s = ""
|
||||
for _, v in pairs( t ) do
|
||||
if s ~= "" then s = s .. "," end
|
||||
s = s .. tostring( v )
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string[]
|
||||
---@nodiscard
|
||||
function M.comma_separated_to_table( s )
|
||||
local t = {}
|
||||
for v in string.gmatch( s, "([^,]+)" ) do
|
||||
table.insert( t, v )
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
---@param value string|number
|
||||
---@param t table
|
||||
---@param extract_field string?
|
||||
---@nodiscard
|
||||
function M.find( value, t, extract_field )
|
||||
if type( t ) ~= "table" or M.count( t ) == 0 then return nil end
|
||||
|
||||
for i, v in pairs( t ) do
|
||||
local val = extract_field and v[ extract_field ] or v
|
||||
if val == value then return v, i end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param field string?
|
||||
---@return number
|
||||
function M.count( t, field )
|
||||
local count = 0
|
||||
for _, e in pairs( t ) do
|
||||
if field and e[ field ] and e[ field ] > 0 or not field then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
---@param recipes table
|
||||
---@param player string
|
||||
---@return number
|
||||
function M.count_recipes( recipes, player )
|
||||
local _, player_id = M.find( player, M.db.players )
|
||||
local count = 0
|
||||
|
||||
if player_id and recipes then
|
||||
for _, recipe in pairs( recipes ) do
|
||||
if M.find( tostring( player_id ), M.comma_separated_to_table( recipe.p ) ) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
---@param id integer
|
||||
---@param ufunc function
|
||||
---@param data table?
|
||||
---@return nil
|
||||
function M.get_item_info( id, ufunc, data )
|
||||
local function callback( item_id, cbfunc, _data )
|
||||
local info = { GetItemInfo( item_id ) }
|
||||
cbfunc( {
|
||||
id = item_id,
|
||||
name = info[ 1 ],
|
||||
link = info[ 2 ],
|
||||
quality = info[ 3 ],
|
||||
texture = info[ 9 ],
|
||||
}, _data )
|
||||
end
|
||||
|
||||
if GetItemInfo( id ) then
|
||||
callback( id, ufunc, data )
|
||||
return
|
||||
end
|
||||
|
||||
if not M.item_info_frame then
|
||||
M.item_info_frame = CreateFrame( "Frame" )
|
||||
end
|
||||
|
||||
if not M.get_item_info_items then
|
||||
M.get_item_info_items = {}
|
||||
end
|
||||
|
||||
table.insert( M.get_item_info_items, {
|
||||
id = id,
|
||||
ufunc = ufunc,
|
||||
data = data
|
||||
} )
|
||||
|
||||
if not M.item_info_frame:GetScript( "OnUpdate" ) then
|
||||
local elapsed = 0
|
||||
local poll_interval = 0.1
|
||||
|
||||
M.item_info_frame:SetScript( "OnUpdate", function()
|
||||
local item_data = M.get_item_info_items[ 1 ]
|
||||
if not item_data then
|
||||
M.item_info_frame:SetScript( "OnUpdate", nil )
|
||||
return
|
||||
end
|
||||
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed < poll_interval then return end
|
||||
elapsed = 0
|
||||
|
||||
M.tooltip:SetOwner( WorldFrame, "ANCHOR_NONE" )
|
||||
M.tooltip:SetHyperlink( "item:" .. item_data.id )
|
||||
|
||||
if GetItemInfo( item_data.id ) then
|
||||
callback( item_data.id, item_data.ufunc, item_data.data )
|
||||
table.remove( M.get_item_info_items, 1 )
|
||||
end
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
---@param link string -- item link or spell link
|
||||
---@param callback function -- function(lines: string[]|nil) end
|
||||
---@param max_attempts number|nil -- retry limit (default 5)
|
||||
---@param delay number|nil
|
||||
function M.scan_tooltip( link, callback, max_attempts, delay )
|
||||
---@class TooltipScan
|
||||
---@field link string
|
||||
---@field callback fun(lines: string[]|nil)
|
||||
---@field max_attempts number
|
||||
---@field delay number
|
||||
---@field attempt number
|
||||
---@field elapsed number
|
||||
---@field initialized boolean
|
||||
|
||||
---@type TooltipScan | nil
|
||||
local current_scan = nil
|
||||
local scan_queue = {}
|
||||
local start_next_scan
|
||||
|
||||
local function process_scan()
|
||||
if not current_scan then
|
||||
M.tooltip_scan_frame:SetScript( "OnUpdate", nil )
|
||||
return
|
||||
end
|
||||
|
||||
current_scan.elapsed = current_scan.elapsed + arg1
|
||||
if current_scan.elapsed < current_scan.delay then return end
|
||||
|
||||
current_scan.elapsed = 0
|
||||
current_scan.attempt = current_scan.attempt + 1
|
||||
|
||||
M.tooltip:SetOwner( WorldFrame, "ANCHOR_NONE" )
|
||||
M.tooltip:ClearLines()
|
||||
M.tooltip:SetHyperlink( current_scan.link )
|
||||
|
||||
local num_lines = M.tooltip:NumLines()
|
||||
if num_lines and num_lines > 0 then
|
||||
local lines = {}
|
||||
for i = 1, num_lines do
|
||||
local line = getglobal( "GuildRecipesOctoTooltipTextLeft" .. i )
|
||||
if line then
|
||||
table.insert( lines, line:GetText() or "" )
|
||||
end
|
||||
end
|
||||
|
||||
local scan_callback = current_scan.callback
|
||||
current_scan = nil
|
||||
M.tooltip_scan_frame:SetScript( "OnUpdate", nil )
|
||||
scan_callback( lines )
|
||||
start_next_scan()
|
||||
return
|
||||
end
|
||||
|
||||
if current_scan.attempt >= current_scan.max_attempts then
|
||||
M.tooltip_scan_frame:SetScript( "OnUpdate", nil )
|
||||
M.debug( "Failed to scan tooltip for " .. current_scan.link )
|
||||
if current_scan.callback then
|
||||
current_scan.callback( nil )
|
||||
end
|
||||
|
||||
current_scan = nil
|
||||
start_next_scan()
|
||||
end
|
||||
end
|
||||
|
||||
function start_next_scan()
|
||||
current_scan = table.remove( scan_queue, 1 )
|
||||
|
||||
if not current_scan then
|
||||
M.tooltip_scan_frame:SetScript( "OnUpdate", nil )
|
||||
return
|
||||
end
|
||||
|
||||
current_scan.attempt = 0
|
||||
current_scan.elapsed = 0
|
||||
M.tooltip_scan_frame:SetScript( "OnUpdate", process_scan )
|
||||
end
|
||||
|
||||
if not M.tooltip_scan_frame then
|
||||
M.tooltip_scan_frame = CreateFrame( "Frame" )
|
||||
end
|
||||
|
||||
table.insert( scan_queue, {
|
||||
link = link,
|
||||
callback = callback,
|
||||
max_attempts = max_attempts or 5,
|
||||
delay = delay or 0.1,
|
||||
} )
|
||||
|
||||
if not current_scan then
|
||||
start_next_scan()
|
||||
end
|
||||
end
|
||||
|
||||
---@param tradeskill string
|
||||
---@return integer
|
||||
---@nodiscard
|
||||
function M.tradeskill_hash( tradeskill )
|
||||
local keys = {}
|
||||
local s = ""
|
||||
local ts = M.db.tradeskills[ tradeskill ]
|
||||
|
||||
if ts then
|
||||
for k in pairs( ts ) do
|
||||
table.insert( keys, k )
|
||||
end
|
||||
|
||||
table.sort( keys )
|
||||
|
||||
for i = 1, getn( keys ) do
|
||||
local _, c = string.gsub( ts[ keys[ i ] ].p, ",", "" )
|
||||
s = s .. keys[ i ] .. "=" .. c .. ";"
|
||||
end
|
||||
end
|
||||
|
||||
return M.hash_string( s )
|
||||
end
|
||||
|
||||
---@param str string
|
||||
---@return integer
|
||||
function M.hash_string( str )
|
||||
local hash = 5381
|
||||
for i = 1, string.len( str ) do
|
||||
local c = string.byte( str, i )
|
||||
hash = mod( hash * 33 + c, 4294967296 )
|
||||
end
|
||||
|
||||
return hash
|
||||
end
|
||||
|
||||
--- @param hex string
|
||||
--- @return number r
|
||||
--- @return number g
|
||||
--- @return number b
|
||||
--- @return number a
|
||||
function M.hex_to_rgba( hex )
|
||||
local r, g, b, a = string.match( hex, "^#?(%x%x)(%x%x)(%x%x)(%x?%x?)$" )
|
||||
|
||||
r, g, b = tonumber( r, 16 ) / 255, tonumber( g, 16 ) / 255, tonumber( b, 16 ) / 255
|
||||
a = a ~= "" and tonumber( a, 16 ) / 255 or 1
|
||||
return r, g, b, a
|
||||
end
|
||||
|
||||
---@param o any
|
||||
---@return string
|
||||
function M.dump( o )
|
||||
if not o then return "nil" end
|
||||
if type( o ) ~= 'table' then return tostring( o ) end
|
||||
|
||||
local entries = 0
|
||||
local s = "{"
|
||||
|
||||
for k, v in pairs( o ) do
|
||||
if (entries == 0) then s = s .. " " end
|
||||
|
||||
local key = type( k ) ~= "number" and '"' .. k .. '"' or k
|
||||
|
||||
if (entries > 0) then s = s .. ", " end
|
||||
|
||||
s = s .. "[" .. key .. "] = " .. M.dump( v )
|
||||
entries = entries + 1
|
||||
end
|
||||
|
||||
if (entries > 0) then s = s .. " " end
|
||||
return s .. "}"
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
if not string.gmatch then string.gmatch = string.gfind end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
string.match = function( str, pattern )
|
||||
if not str then return nil end
|
||||
|
||||
local _, _, r1, r2, r3, r4, r5, r6, r7, r8, r9 = string.find( str, pattern )
|
||||
return r1, r2, r3, r4, r5, r6, r7, r8, r9
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user