Files
GuildRecipes_Octo/MessageHandler.lua
T
2026-07-15 23:00:59 -07:00

319 lines
8.3 KiB
Lua

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