GuildRecipesOcto = GuildRecipesOcto or {} ---@class GuildRecipesOcto local m = GuildRecipesOcto if m.MessageHandler then return end ---@type MessageCommand local MessageCommand = { RequestTradeskill = "RTS", Tradeskill = "TS", Ping = "PING", Pong = "PONG", VersionCheck = "VERSIONCHECK", Version = "VERSION", } ---@alias MessageCommand ---| "RTS" ---| "TS" ---| "PING" ---| "PONG" ---| "VERSIONCHECK" ---| "VERSION" ---@class MessageHandler ---@field send_tradeskill fun( tradeskill: string ) ---@field request_tradeskill fun( tradeskill: string ) ---@field request_tradeskills fun() ---@field version_check fun() local M = {} ---@param ace_timer AceTimer ---@param ace_serializer AceSerializer ---@param ace_comm AceComm function M.new( ace_timer, ace_serializer, ace_comm ) local pinging = false local best_ping = {} local var_names = { n = "name", q = "quality", c = "count", p = "players", d = "data", i = "items", ts = "tradeskill", h = "hashes", lu = "last_update", --tlu = "tradeskills_last_update", Alc = "Alchemy", Bla = "Blacksmithing", Eng = "Engineering", Enc = "Enchanting", Lea = "Leatherworking", Tai = "Tailoring", Jew = "Jewelcrafting", } setmetatable( var_names, { __index = function( _, key ) return key end } ); ---@param t table local function decode( t ) local l = {} for key, value in pairs( t ) do if type( value ) == "table" then value = decode( value ) end l[ var_names[ key ] ] = value end return l end ---@param command MessageCommand ---@param data table? local function broadcast( command, data ) m.debug( string.format( "Broadcasting %s", command ) ) ace_comm:SendCommMessage( m.prefix, command .. "::" .. ace_serializer.Serialize( M, data ), "GUILD", nil, "NORMAL" ) end local function send_tradeskill( tradeskill ) m.debug( string.format( "Sending %s", tradeskill ) ) if not m.db.tradeskills[ tradeskill ] then m.debug( string.format( "No data for tradeskill: %s", tradeskill ) ) return end local data = { tradeskill = tradeskill, recipes = {} } for id, item in pairs( m.db.tradeskills[ tradeskill ] ) do local players = {} for _, p in pairs( m.comma_separated_to_table( item.p ) ) do table.insert( players, m.db.players[ tonumber( p ) ] ) end table.insert( data.recipes, { id = id, p = players } ) end broadcast( MessageCommand.Tradeskill, data ) end local function send_tradeskills() for tradeskill in pairs( m.db.tradeskills ) do send_tradeskill( tradeskill ) end end local function request_tradeskill( tradeskill ) pinging = true best_ping = {} broadcast( MessageCommand.Ping, { [string.sub( tradeskill, 1, 3 )] = m.tradeskill_hash( tradeskill ) } ) end local function request_tradeskills() pinging = true best_ping = {} local hashes = {} for tradeskill in pairs(m.TRADE_SKILL_LOCALIZATION) do hashes[ string.sub( tradeskill, 1, 3 ) ] = m.tradeskill_hash( tradeskill ) end broadcast( MessageCommand.Ping, hashes ) end local function version_check() broadcast( MessageCommand.VersionCheck ) end ---@param command string ---@param data table ---@param sender string local function on_command( command, data, sender ) if command == MessageCommand.Tradeskill then -- -- Receive tradeskill -- m.debug( string.format( "Receiving %s from %s.", data.tradeskill, sender ) ) local tradeskill = data.tradeskill m.db.tradeskills[ tradeskill ] = m.db.tradeskills[ tradeskill ] or {} for _, v in pairs( data.recipes ) do if v.id then ---@type Item local item = nil if m.db.tradeskills[ tradeskill ][ v.id ] then item = { id = v.id, name = m.db.tradeskills[ tradeskill ][ v.id ].n, quality = m.db.tradeskills[ tradeskill ][ v.id ].q } else if tradeskill == "Enchanting" then if v and v.id then local name = m.Enchants[ v.id ] and m.Enchants[ v.id ].name if not name then m.error( string.format( "Unknown enchantment received (%d)", v.id ) ) else item = { id = v.id, name = name, } end else m.debug( "empty enchant data??" ) end else m.debug( "Fetching item info for " .. tostring( v.id ) ) m.get_item_info( v.id, function( item_info, players ) if item_info then local i = { id = item_info.id, name = item_info.name, quality = item_info.quality } m.update_tradeskill_item( tradeskill, i, players ) else m.debug( "No item_info for " .. tostring( v.id ) ) end end, v.players ) end end if item then --m.debug( "got item" ) m.update_tradeskill_item( tradeskill, item, v.players ) end end end m.db.tradeskills_last_update[ tradeskill ] = m.get_server_timestamp() elseif command == MessageCommand.RequestTradeskill and data.player == m.player then -- -- Request for tradeskill -- m.debug(m.dump(data)) send_tradeskill( data.tradeskill ) elseif command == MessageCommand.Ping and sender ~= m.player then -- -- Recive ping -- for tradeskill, hash in pairs( data or {} ) do local local_hash = m.tradeskill_hash( tradeskill ) if local_hash == hash then data[ tradeskill ] = nil else data[ tradeskill ] = m.db.tradeskills_last_update[ tradeskill ] or 0 end end m.debug( m.dump( data ) ) broadcast( MessageCommand.Pong, data ) elseif command == MessageCommand.Pong and pinging then -- -- Receive pong -- m.debug( m.dump( data ) ) for tradeskill, last_update in pairs( data or {} ) do if not best_ping[ tradeskill ] or (last_update > best_ping[ tradeskill ].last_update) then best_ping[ tradeskill ] = { player = sender, last_update = last_update } end end if ace_timer:TimeLeft( M[ "ping_timer" ] ) == 0 then M[ "ping_timer" ] = ace_timer.ScheduleTimer( M, function() if pinging then m.debug( "Ping timeout, requesting tradeskills from best pings." ) pinging = false for tradeskill, ping_info in pairs( best_ping ) do m.debug( string.format( "Requesting %s from %s (last update: %d)", tradeskill, ping_info.player, ping_info.last_update ) ) broadcast( MessageCommand.RequestTradeskill, { player = ping_info.player, ts = tradeskill } ) end end end, 2 ) end elseif command == MessageCommand.VersionCheck then -- -- Receive version request -- broadcast( MessageCommand.Version, { requester = sender, version = m.version, class = m.player_class } ) elseif command == MessageCommand.Version then -- -- Receive version -- if data.requester == m.player then m.info( string.format( "%s [v%s]", m.colorize_player_by_class( sender, data.class ), data.version ), true ) end end end local function on_comm_received( prefix, data_str, _, sender ) if prefix ~= m.prefix or sender == m.player then return end local command = string.match( data_str, "^(.-)::" ) data_str = string.gsub( data_str, "^.-::", "" ) m.debug( "Received " .. command ) local success, data = ace_serializer.Deserialize( M, data_str ) if success then if data then data = decode( data ) end on_command( command, data, sender ) else m.error( "Corrupt data in addon message!" ) end end ace_comm.RegisterComm( M, m.prefix, on_comm_received ) -- Also listen on the original GuildRecipes prefix. If we hear another -- player broadcasting on it, they're running the outdated addon -- send -- them a one-time whisper per session letting them know it's been replaced. local warned_players = {} local function on_legacy_comm_received( prefix, data_str, _, sender ) if sender == m.player then return end if not m.legacy_warning_enabled then return end if warned_players[ sender ] then return end warned_players[ sender ] = true SendChatMessage( "GuildRecipes is out of date and will not work correctly with OctoWoW, please check the Discord for replacement version.", "WHISPER", nil, sender ) end ace_comm.RegisterComm( M, "GRECIPES", on_legacy_comm_received ) ---@type MessageHandler return { send_tradeskill = send_tradeskill, request_tradeskill = request_tradeskill, request_tradeskills = request_tradeskills, version_check = version_check } end m.MessageHandler = M return M