From 05f0d12908caf72b1d4413894ec96e236ede085f Mon Sep 17 00:00:00 2001 From: Eli White Date: Sun, 13 Oct 2024 17:48:00 -0700 Subject: [PATCH] Add support for transmitting Rating When playing with my friends, we want to see what dungeon to do based on what will give the most rating. We normally have to ask each other "what's your lowest". This commit adds support to automatically communicate dungeon rating information, to power a UI like `/keys`. This is essentially transmitting the entirety of `C_PlayerInfo.GetPlayerMythicPlusRatingSummary("player")`, with the additional ClassID information so my UI can color player names. I've validated this code with my friends in my own addon here: https://legacy.curseforge.com/wow/addons/details-mythic-group-rating --- LibOpenRaid.lua | 293 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) diff --git a/LibOpenRaid.lua b/LibOpenRaid.lua index 0c1833f..558a606 100644 --- a/LibOpenRaid.lua +++ b/LibOpenRaid.lua @@ -114,6 +114,9 @@ end local CONST_COMM_OPENNOTES_RECEIVED_PREFIX = "N" --when a note is received local CONST_COMM_OPENNOTES_REQUESTED_PREFIX = "Q" --when received a request to send your note + local CONST_COMM_RATING_DATA_PREFIX = "M" + local CONST_COMM_RATING_DATAREQUEST_PREFIX = "O" + local CONST_COMM_SENDTO_PARTY = "0x1" local CONST_COMM_SENDTO_RAID = "0x2" local CONST_COMM_SENDTO_GUILD = "0x4" @@ -440,6 +443,14 @@ end end end + --if this is isn't a rating data comm, check if the lib can receive comms + if (dataTypePrefix ~= CONST_COMM_RATING_DATA_PREFIX and dataTypePrefix ~= CONST_COMM_RATING_DATAREQUEST_PREFIX) then + if (not openRaidLib.IsCommAllowed()) then + openRaidLib.DiagnosticError("comm not allowed.") + return + end + end + if (CONST_DIAGNOSTIC_COMM_RECEIVED) then openRaidLib.diagnosticCommReceived(data) end @@ -488,6 +499,8 @@ end [CONST_COMM_KEYSTONE_DATAREQUEST_PREFIX] = {}, --received a request to send keystone data [CONST_COMM_OPENNOTES_RECEIVED_PREFIX] = {}, --received notes [CONST_COMM_OPENNOTES_REQUESTED_PREFIX] = {}, --requested notes + [CONST_COMM_RATING_DATA_PREFIX] = {}, --received rating data + [CONST_COMM_RATING_DATAREQUEST_PREFIX] = {}, --received a request to send rating data } function openRaidLib.commHandler.RegisterComm(prefix, func) @@ -824,6 +837,8 @@ end "KeystoneUpdate", "KeystoneWipe", "NoteUpdated", + "RatingUpdate", + "RatingWipe" } --save build the table to avoid lose registered events on older versions @@ -3195,6 +3210,284 @@ openRaidLib.commHandler.RegisterComm(CONST_COMM_COOLDOWNREQUEST_PREFIX, openRaid openRaidLib.internalCallback.RegisterCallback("onEnterGroup", openRaidLib.KeystoneInfoManager.OnPlayerEnterGroup) openRaidLib.internalCallback.RegisterCallback("mythicDungeonEnd", openRaidLib.KeystoneInfoManager.OnMythicDungeonFinished) +-------------------------------------------------------------------------------------------------------------------------------- +--~rating + + ---@class MythicPlusRatingMapSummary + ---@field challengeModeID number + ---@field mapScore number + ---@field bestRunLevel number + ---@field bestRunDurationMS number + ---@field finishedSuccess boolean + + ---@class ratinginfo + ---@field classID number + ---@field currentSeasonScore number + ---@field runs MythicPlusRatingMapSummary[] + + --manager constructor + openRaidLib.RatingInfoManager = { + --structure: + --[playerName] = ratinginfo + ---@type table + RatingData = {}, + } + + --API calls + --return a table containing all information of units + --format: [playerName-realm] = {information} + function openRaidLib.GetAllRatingInfo() + return openRaidLib.RatingInfoManager.GetAllRatingInfo() + end + + --return a table containing information of a single unit + function openRaidLib.GetRatingInfo(unitId) + local unitName = GetUnitName(unitId, true) or unitId + return openRaidLib.RatingInfoManager.GetRatingInfo(unitName) + end + + function openRaidLib.RequestRatingDataFromGuild() + if (IsInGuild()) then + local dataToSend = "" .. CONST_COMM_RATING_DATAREQUEST_PREFIX + openRaidLib.commHandler.SendCommData(dataToSend, 0x4) + diagnosticComm("RequestRatingDataFromGuild| " .. dataToSend) --debug + return true + else + return false + end + end + + function openRaidLib.RequestRatingDataFromParty() + if (IsInGroup() and not IsInRaid()) then + local dataToSend = "" .. CONST_COMM_RATING_DATAREQUEST_PREFIX + openRaidLib.commHandler.SendCommData(dataToSend, 0x1) + diagnosticComm("RequestRatingDataFromParty| " .. dataToSend) --debug + return true + else + return false + end + end + + function openRaidLib.RequestRatingDataFromRaid() + if (IsInRaid()) then + local dataToSend = "" .. CONST_COMM_RATING_DATAREQUEST_PREFIX + openRaidLib.commHandler.SendCommData(dataToSend, 0x2) + diagnosticComm("RequestRatingDataFromRaid| " .. dataToSend) --debug + return true + else + return false + end + end + + function openRaidLib.WipeRatingData() + wipe(openRaidLib.RatingInfoManager.RatingData) + --trigger public callback + openRaidLib.publicCallback.TriggerCallback("RatingWipe", openRaidLib.RatingInfoManager.RatingData) + + --rating are only available on retail + if (not checkClientVersion("retail")) then + return + end + + --generate rating info for the player + local unitName = UnitName("player") + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(unitName, true) + openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + + openRaidLib.publicCallback.TriggerCallback("RatingUpdate", unitName, ratingInfo, openRaidLib.RatingInfoManager.RatingData) + return true + end + + --privite stuff, these function can still be called, but not advised + ---@type ratinginfo + local ratingTablePrototype = { + classID = 0, + currentSeasonScore = 0, + runs = {} + } + + function openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + --- I really just want this whole thing + local summary = C_PlayerInfo.GetPlayerMythicPlusRatingSummary("player") + + ratingInfo.currentSeasonScore = summary.currentSeasonScore or 0 + ratingInfo.runs = summary.runs or {} + + local _, _, playerClassID = UnitClass("player") + ratingInfo.classID = playerClassID + end + + function openRaidLib.RatingInfoManager.GetAllRatingInfo() + return openRaidLib.RatingInfoManager.RatingData + end + + --get the rating info table or create a new one if 'createNew' is true + function openRaidLib.RatingInfoManager.GetRatingInfo(unitName, createNew) + local ratingInfo = openRaidLib.RatingInfoManager.RatingData[unitName] + if (not ratingInfo and createNew) then + ratingInfo = {} + openRaidLib.TCopy(ratingInfo, ratingTablePrototype) + openRaidLib.RatingInfoManager.RatingData[unitName] = ratingInfo + end + return ratingInfo + end + + local getRatingInfoToComm = function() + local playerName = UnitName("player") + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(playerName, true) + openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + + local dataToSend = "" .. CONST_COMM_RATING_DATA_PREFIX .. "," + + local runs = {} + for _, runInfo in ipairs(ratingInfo.runs) do + runs[#runs+1] = { + runInfo.challengeModeID, + runInfo.bestRunDurationMS, + runInfo.finishedSuccess and 1 or 0, + runInfo.mapScore, + runInfo.bestRunLevel + } + end + + dataToSend = dataToSend .. ratingInfo.classID .. "," + dataToSend = dataToSend .. ratingInfo.currentSeasonScore .. "," + dataToSend = dataToSend .. openRaidLib.PackTableAndSubTables(runs) + + return dataToSend + end + + function openRaidLib.RatingInfoManager.SendPlayerRatingInfoToParty() + local dataToSend = getRatingInfoToComm() + openRaidLib.commHandler.SendCommData(dataToSend, CONST_COMM_SENDTO_PARTY) + diagnosticComm("SendPlayerRatingInfoToParty| " .. dataToSend) --debug + end + + function openRaidLib.RatingInfoManager.SendPlayerRatingInfoToGuild() + local dataToSend = getRatingInfoToComm() + openRaidLib.commHandler.SendCommData(dataToSend, CONST_COMM_SENDTO_GUILD) + diagnosticComm("SendPlayerRatingInfoToGuild| " .. dataToSend) --debug + end + + --when a request data is received, only send the data to party and guild + --sending stuff to raid need to be called my the application with 'openRaidLib.RequestRatingDataFromRaid()' + function openRaidLib.RatingInfoManager.OnReceiveRequestData() + if (not checkClientVersion("retail")) then + return + end + + --update the information about the key stone the player has + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(UnitName("player"), true) + openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + + local _, instanceType = GetInstanceInfo() + if (instanceType == "party") then + openRaidLib.Schedules.NewUniqueTimer(math.random(1), openRaidLib.RatingInfoManager.SendPlayerRatingInfoToParty, "RatingInfoManager", "sendRatingInfoToParty_Schedule") + + elseif (instanceType == "raid" or instanceType == "pvp") then + openRaidLib.Schedules.NewUniqueTimer(math.random(0, 30) + math.random(1), openRaidLib.RatingInfoManager.SendPlayerRatingInfoToParty, "RatingInfoManager", "sendRatingInfoToParty_Schedule") + + else + openRaidLib.Schedules.NewUniqueTimer(math.random(4), openRaidLib.RatingInfoManager.SendPlayerRatingInfoToParty, "RatingInfoManager", "sendRatingInfoToParty_Schedule") + end + + if (IsInGuild()) then + openRaidLib.Schedules.NewUniqueTimer(math.random(0, 6) + math.random(), openRaidLib.RatingInfoManager.SendPlayerRatingInfoToGuild, "RatingInfoManager", "sendRatingInfoToGuild_Schedule") + end + end + openRaidLib.commHandler.RegisterComm(CONST_COMM_RATING_DATAREQUEST_PREFIX, openRaidLib.RatingInfoManager.OnReceiveRequestData) + + function openRaidLib.RatingInfoManager.OnReceiveRatingData(data, unitName) + if (not checkClientVersion("retail")) then + return + end + + local classID = tonumber(data[1]) + local currentSeasonScore = tonumber(data[2]) + + local unpackedTable = openRaidLib.UnpackTable(data, 3, false, true, 5) -- 5 is the number of items in the run table + + local runs = {} + for _, runInfo in ipairs(unpackedTable) do + local challengeModeID, bestRunDurationMS, finishedSuccess, mapScore, bestRunLevel = unpack(runInfo) + + runs[#runs+1] = { + challengeModeID = challengeModeID, + bestRunDurationMS = bestRunDurationMS, + finishedSuccess = finishedSuccess == 1 and true or false, + mapScore = mapScore, + bestRunLevel = bestRunLevel + } + end + + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(unitName, true) + ratingInfo.classID = classID + ratingInfo.currentSeasonScore = currentSeasonScore + ratingInfo.runs = runs + + --trigger public callback + openRaidLib.publicCallback.TriggerCallback("RatingUpdate", unitName, ratingInfo, openRaidLib.RatingInfoManager.RatingData) + end + openRaidLib.commHandler.RegisterComm(CONST_COMM_RATING_DATA_PREFIX, openRaidLib.RatingInfoManager.OnReceiveRatingData) + + --on entering a group, send rating information for the party + function openRaidLib.RatingInfoManager.OnPlayerEnterGroup() + --rating is only available on retail + if (not checkClientVersion("retail")) then + return + end + + if (IsInGroup() and not IsInRaid()) then + --update the information about the rating the player has + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(UnitName("player"), true) + openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + + --send to the group what rating the player has + openRaidLib.Schedules.NewUniqueTimer(1 + math.random(0, 2) + math.random(), openRaidLib.RatingInfoManager.SendPlayerRatingInfoToParty, "RatingInfoManager", "sendRatingInfoToParty_Schedule") + end + end + + local ratingManagerOnPlayerEnterWorld = function() + --hack: trigger a received data request to send data to party and guild when logging in + openRaidLib.RatingInfoManager.OnReceiveRequestData() + + --trigger public callback + local unitName = UnitName("player") + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(unitName, true) + openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + + openRaidLib.publicCallback.TriggerCallback("RatingUpdate", unitName, ratingInfo, openRaidLib.RatingInfoManager.RatingData) + end + + function openRaidLib.RatingInfoManager.OnPlayerEnterWorld() + --rating is only available on retail + if (not checkClientVersion("retail")) then + return + end + + C_Timer.After(2, ratingManagerOnPlayerEnterWorld) + end + + function openRaidLib.RatingInfoManager.OnMythicDungeonFinished() + --rating is only available on retail + if (not checkClientVersion("retail")) then + return + end + --hack: on received data send data to party and guild + openRaidLib.RatingInfoManager.OnReceiveRequestData() + + --trigger public callback + local unitName = UnitName("player") + local ratingInfo = openRaidLib.RatingInfoManager.GetRatingInfo(unitName, true) + openRaidLib.RatingInfoManager.UpdatePlayerRatingInfo(ratingInfo) + + openRaidLib.publicCallback.TriggerCallback("RatingUpdate", unitName, ratingInfo, openRaidLib.RatingInfoManager.RatingData) + end + + openRaidLib.internalCallback.RegisterCallback("onEnterWorld", openRaidLib.RatingInfoManager.OnPlayerEnterWorld) + openRaidLib.internalCallback.RegisterCallback("onEnterGroup", openRaidLib.RatingInfoManager.OnPlayerEnterGroup) + openRaidLib.internalCallback.RegisterCallback("mythicDungeonEnd", openRaidLib.RatingInfoManager.OnMythicDungeonFinished) + -------------------------------------------------------------------------------------------------------------------------------- --data