---@class GuildRecipesOcto GuildRecipesOcto = GuildRecipesOcto or {} ---@class GuildRecipesOcto local m = GuildRecipesOcto ---@diagnostic disable-next-line: undefined-global local lib_stub = LibStub GuildRecipesOcto.name = "GuildRecipes_Octo" GuildRecipesOcto.prefix = "GROCTO" GuildRecipesOcto.tagcolor = "FFDD7744" GuildRecipesOcto.events = {} GuildRecipesOcto.debug_enabled = false GuildRecipesOcto.legacy_warning_enabled = false BINDING_HEADER_GUILDRECIPESOCTO = "GuildRecipes Octo" ---@class Item ---@field id integer ---@field name string ---@field quality integer? ---@field icon string? ---@alias NotAceTimer any ---@alias TimerId number ---@class AceTimer ---@field ScheduleTimer fun( self: NotAceTimer, callback: function, delay: number, ... ): TimerId ---@field ScheduleRepeatingTimer fun( self: NotAceTimer, callback: function, delay: number, arg: any ): TimerId ---@field CancelTimer fun( self: NotAceTimer, timer_id: number ) ---@field TimeLeft fun( self: NotAceTimer, timer_id: number ) ---@class AceSerializer ---@field Serialize fun( self: any, ... ): string ---@field Deserialize fun( self: any, str: string ): any ---@class AceComm ---@field RegisterComm fun( self: any, prefix: string, method: function? ) ---@field SendCommMessage fun( self: any, prefix: string, text: string, distribution: string, target: string?, prio: "BULK"|"NORMAL"|"ALERT"?, callbackFn: function?, callbackArg: any? ) function GuildRecipesOcto:init() self.frame = CreateFrame( "Frame" ) self.frame:SetScript( "OnEvent", function() if m.events[ event ] then m.events[ event ]() end end ) for k, _ in pairs( m.events ) do m.frame:RegisterEvent( k ) end end function GuildRecipesOcto.events.ADDON_LOADED() if arg1 == m.name then ---@type AceTimer m.ace_timer = lib_stub( "AceTimer-3.0" ) ---@type AceSerializer m.ace_serializer = lib_stub( "AceSerializer-3.0" ) ---@type AceComm m.ace_comm = lib_stub( "AceComm-3.0" ) ---@type MessageHandler m.msg = m.MessageHandler.new( m.ace_timer, m.ace_serializer, m.ace_comm ) ---@type TradeskillGui m.tsgui = m.Tradeskills.new() ---@type SlashCommand m.slash_command = m.SlashCommand.new( m.name, { "gro", "GuildRecipesOcto" } ) m.version = GetAddOnMetadata( m.name, "Version" ) m.info( string.format( "(v%s) Loaded", m.version ) ) end end function GuildRecipesOcto.events.PLAYER_LOGIN() -- Initialize DB GuildRecipesOctoDB = GuildRecipesOctoDB or {} m.db = GuildRecipesOctoDB m.db.players = m.db.players or {} m.db.tradeskills = m.db.tradeskills or {} m.db.tradeskills_last_update = m.db.tradeskills_last_update or {} m.db.frame_tradeskills = m.db.frame_tradeskills or {} m.player = UnitName( "player" ) m.player_class = UnitClass( "player" ) if m.slash_command then m.slash_command.init() end m.tooltip = CreateFrame( "GameTooltip", "GuildRecipesOctoTooltip", nil, "GameTooltipTemplate" ) m.tooltip:SetOwner( WorldFrame, "ANCHOR_NONE" ) if m.msg then m.update_data() end end function GuildRecipesOcto.events.TRADE_SKILL_SHOW() local reverse = m.build_reverse_trade_map( GetLocale() ) local tradeskill = reverse[ GetTradeSkillLine() ] if m.TRADE_SKILL_LOCALIZATION[ tradeskill ] then local num = GetNumTradeSkills() for i = 1, GetNumTradeSkills() do local _, type = GetTradeSkillInfo( i ) if type == "header" then num = num - 1 end end m.db.tradeskills[ tradeskill ] = m.db.tradeskills[ tradeskill ] or {} if m.count_recipes( m.db.tradeskills[ tradeskill ], m.player ) ~= num then for i = 1, GetNumTradeSkills() do local _, type = GetTradeSkillInfo( i ) if type ~= "header" then local id, name, quality = m.parse_item_link( GetTradeSkillItemLink( i ) ) local item = { id = id, name = name, quality = quality } m.update_tradeskill_item( tradeskill, item, { m.player } ) end end m.db.tradeskills_last_update[ tradeskill ] = m.get_server_timestamp() m.msg.send_tradeskill( tradeskill ) end end end function GuildRecipesOcto.events.CRAFT_SHOW() local reverse = m.build_reverse_trade_map( GetLocale() ) local tradeskill = reverse[ GetCraftName() ] if tradeskill == "Enchanting" then local num = GetNumCrafts() m.db.tradeskills[ tradeskill ] = m.db.tradeskills[ tradeskill ] or {} if m.count_recipes( m.db.tradeskills[ tradeskill ], m.player ) ~= num then for i = 1, num do local id, name, quality = m.parse_item_link( GetCraftItemLink( i ) ) local item = { id = id, name = name, quality = quality } m.update_tradeskill_item( tradeskill, item, { m.player } ) end m.db.tradeskills_last_update[ tradeskill ] = m.get_server_timestamp() m.msg.send_tradeskill( tradeskill ) end end end function GuildRecipesOcto.events.UNIT_INVENTORY_CHANGED() -- This can fire many times in quick succession (e.g. once per item when -- looting a stack), and each call to tsgui.update() re-scans the guild -- roster and redraws the visible list. Debounce so a burst of these only -- triggers one actual update, instead of one per event. if arg1 == "player" and m.tsgui.is_visible() and m.ace_timer then if m.inventory_update_timer then m.ace_timer.CancelTimer( m, m.inventory_update_timer ) end m.inventory_update_timer = m.ace_timer.ScheduleTimer( m, function() m.inventory_update_timer = nil if m.tsgui.is_visible() then m.tsgui.update() end end, 0.5 ) end end ---@param tradeskill string ---@param item Item ---@param players string[] function GuildRecipesOcto.update_tradeskill_item( tradeskill, item, players ) if item.id then local player_ids = {} for _, p in pairs( players ) do if not m.find( p, m.db.players ) then table.insert( m.db.players, p ) end local _, player_id = m.find( p, m.db.players ) if player_id then table.insert( player_ids, player_id ) end end if m.db.tradeskills[ tradeskill ][ item.id ] then m.debug( string.format( "Updating %s: %s", tradeskill, item.name ) ) if not players then m.debug( "ERROR, no players for: " .. tostring( item.name ) ) return end local recipe_players = m.comma_separated_to_table( m.db.tradeskills[ tradeskill ][ item.id ].p ) for _, p in pairs( players ) do local _, player_id = m.find( p, m.db.players ) if player_id and not m.find( tostring(player_id), recipe_players ) then table.insert( recipe_players, player_id ) end end m.db.tradeskills[ tradeskill ][ item.id ].p = m.table_to_comma_separated( recipe_players ) else m.debug( string.format( "Adding %s: %s", tradeskill, item.name ) ) m.db.tradeskills[ tradeskill ][ item.id ] = { n = item.name, q = item.quality, p = m.table_to_comma_separated( player_ids ) } end end end function GuildRecipesOcto.update_data() -- If we haven't updated in the last 48 hours, request an update if next(m.db.tradeskills_last_update) == nil then m.msg.request_tradeskills() else local now = m.get_server_timestamp() for tradeskill, last_update in pairs( m.db.tradeskills_last_update ) do if now >= last_update + 172800 then m.msg.request_tradeskill( tradeskill ) break end end end end ---@param bag_start integer ---@param bag_end integer ---@param name string ---@return integer function GuildRecipesOcto.find_item_count_bag( bag_start, bag_end, name ) local count = 0 for bag = bag_start, bag_end do local slots = GetContainerNumSlots( bag ) for slot = 1, slots do local _, item_count = GetContainerItemInfo( bag, slot ) if item_count and item_count > 0 then local _, item_name = m.parse_item_link( GetContainerItemLink( bag, slot ) ) if item_name == name then count = count + item_count end end end end return count end function GuildRecipesOcto.fix() for tradeskill, recipes in pairs( m.db.tradeskills ) do for id, recipe in pairs( recipes ) do if recipe.p and type( recipe.p ) == "table" then local players = "" for _, p in pairs( recipe.p ) do if players ~= "" then players = players .. "," end players = players .. p end recipe.p = players end end end end GuildRecipesOcto:init()