Compare commits
10 Commits
98a08522c5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 80ecae6939 | |||
| 5efeca4a96 | |||
| d3aee746ca | |||
| 3cf489dc3c | |||
| e17c6268e4 | |||
| 62c12162e1 | |||
| ebaed7e301 | |||
| 4b218e7b73 | |||
| d7a2bd8cae | |||
| 0fa8512617 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
*.bak
|
*.bak
|
||||||
|
*.tmp
|
||||||
@ -2,14 +2,11 @@ local debug = false
|
|||||||
|
|
||||||
local CMH = {}
|
local CMH = {}
|
||||||
local datacache = {}
|
local datacache = {}
|
||||||
local delim = {"♠", "♥", "♚", "♛", "♜"}
|
local delim = {"", "", "", "", ""}
|
||||||
local pck = {REQ = 1, DAT = 2}
|
local pck = {REQ = "", DAT = ""}
|
||||||
|
|
||||||
-- HELPERS START
|
-- HELPERS START
|
||||||
local function debugOut(prefix, x, msg)
|
local function debugOut(prefix, x, msg)
|
||||||
prefix = prefix or ""
|
|
||||||
msg = msg or ""
|
|
||||||
x = x or ""
|
|
||||||
if(debug == true) then
|
if(debug == true) then
|
||||||
print("["..date("%X", time()).."][CSMH]["..x.."]["..prefix.."]: "..msg)
|
print("["..date("%X", time()).."][CSMH]["..x.."]["..prefix.."]: "..msg)
|
||||||
end
|
end
|
||||||
@ -52,7 +49,12 @@ local function ParseMessage(str)
|
|||||||
-- Convert value to correct type
|
-- Convert value to correct type
|
||||||
for k, v in pairs(valTemp) do
|
for k, v in pairs(valTemp) do
|
||||||
local varType = typeTemp[k]
|
local varType = typeTemp[k]
|
||||||
if(varType == 3) then -- Ints
|
if(varType == 2) then -- Strings
|
||||||
|
-- Special case for empty string parsing
|
||||||
|
if(v == string.char(tonumber('1A', 16))) then
|
||||||
|
v = ""
|
||||||
|
end
|
||||||
|
elseif(varType == 3) then -- Ints
|
||||||
v = tonumber(v)
|
v = tonumber(v)
|
||||||
elseif(varType == 4) then -- Tables
|
elseif(varType == 4) then -- Tables
|
||||||
v = Smallfolk.loads(v, #v)
|
v = Smallfolk.loads(v, #v)
|
||||||
@ -75,6 +77,10 @@ local function ProcessVariables(reqId, ...)
|
|||||||
|
|
||||||
for _, v in pairs(arg) do
|
for _, v in pairs(arg) do
|
||||||
if(type(v) == "string") then
|
if(type(v) == "string") then
|
||||||
|
-- Special case for empty string parsing
|
||||||
|
if(#v == 0) then
|
||||||
|
v = string.char(tonumber('1A', 16))
|
||||||
|
end
|
||||||
msg = msg .. delim[2]
|
msg = msg .. delim[2]
|
||||||
elseif(type(v) == "number") then
|
elseif(type(v) == "number") then
|
||||||
msg = msg .. delim[3]
|
msg = msg .. delim[3]
|
||||||
@ -109,16 +115,13 @@ function CMH.OnReceive(self, event, header, data, Type, sender)
|
|||||||
-- Ensure the sender and receiver is the same, the message is an addon message, and the message type is WHISPER
|
-- Ensure the sender and receiver is the same, the message is an addon message, and the message type is WHISPER
|
||||||
if event == "CHAT_MSG_ADDON" and sender == UnitName("player") and Type == "WHISPER" then
|
if event == "CHAT_MSG_ADDON" and sender == UnitName("player") and Type == "WHISPER" then
|
||||||
-- unpack and validate addon message structure
|
-- unpack and validate addon message structure
|
||||||
local pfx, source, pckId = header:match("(...)(%u)(%d%d)")
|
local pfx, source, pckId = header:match("(.)(%u)(.)")
|
||||||
if not pfx or not source or not pckId then
|
if not pfx or not source or not pckId then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Make sure we're only processing addon messages using our framework prefix character as well as client messages
|
-- Make sure we're only processing addon messages using our framework prefix character as well as client messages
|
||||||
if(pfx == delim[1] and source == "S") then
|
if(pfx == delim[1] and source == "S") then
|
||||||
-- convert ID to number so we can compare with our packet list
|
|
||||||
pckId = tonumber(pckId)
|
|
||||||
|
|
||||||
if(pckId == pck.REQ) then
|
if(pckId == pck.REQ) then
|
||||||
debugOut("REQ", "Rx", "REQ received, data: "..data)
|
debugOut("REQ", "Rx", "REQ received, data: "..data)
|
||||||
CMH.OnREQ(sender, data)
|
CMH.OnREQ(sender, data)
|
||||||
@ -242,7 +245,7 @@ end
|
|||||||
-- Tx START
|
-- Tx START
|
||||||
|
|
||||||
function CMH.SendREQ(functionId, linkCount, reqId, addon)
|
function CMH.SendREQ(functionId, linkCount, reqId, addon)
|
||||||
local header = string.format("%01s%01s%02d", delim[1], "C", pck.REQ)
|
local header = string.format("%01s%01s%01s", delim[1], "C", pck.REQ)
|
||||||
local data = string.format("%02d%03d%06s%0"..tostring(#addon).."s", functionId, linkCount, reqId, addon)
|
local data = string.format("%02d%03d%06s%0"..tostring(#addon).."s", functionId, linkCount, reqId, addon)
|
||||||
SendAddonMessage(header, data, "WHISPER", UnitName("player"))
|
SendAddonMessage(header, data, "WHISPER", UnitName("player"))
|
||||||
debugOut("REQ", "Tx", "Sent REQ with ID "..reqId..", sending DAT..")
|
debugOut("REQ", "Tx", "Sent REQ with ID "..reqId..", sending DAT..")
|
||||||
@ -250,7 +253,7 @@ end
|
|||||||
|
|
||||||
function CMH.SendDAT(reqId)
|
function CMH.SendDAT(reqId)
|
||||||
-- Build data message header
|
-- Build data message header
|
||||||
local header = string.format("%01s%01s%02d", delim[1], "C", pck.DAT)
|
local header = string.format("%01s%01s%01s", delim[1], "C", pck.DAT)
|
||||||
|
|
||||||
-- iterate all items in the message data cache and send
|
-- iterate all items in the message data cache and send
|
||||||
-- functions can also be trigger functions without any data, only send header and no payload
|
-- functions can also be trigger functions without any data, only send header and no payload
|
||||||
|
|||||||
@ -38,6 +38,15 @@ function StatPointUI.OnLogin(event, player)
|
|||||||
StatPointUI.SetStats(player:GetGUIDLow())
|
StatPointUI.SetStats(player:GetGUIDLow())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function StatPointUI.AddStatPoint(guid)
|
||||||
|
local player = GetPlayerByGUID(guid)
|
||||||
|
if(player) then
|
||||||
|
StatPointUI.cache[guid][6] = StatPointUI.cache[guid][6]+1
|
||||||
|
CharDBQuery("UPDATE character_stats_extra SET `points`=`points`+1 WHERE `guid`="..guid..";")
|
||||||
|
player:SendServerResponse(config.Prefix, 1, StatPointUI.cache[guid])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function StatPointUI.SetStats(guid, stat)
|
function StatPointUI.SetStats(guid, stat)
|
||||||
stat = stat or nil
|
stat = stat or nil
|
||||||
local player = GetPlayerByGUID(guid)
|
local player = GetPlayerByGUID(guid)
|
||||||
@ -121,6 +130,11 @@ function OnStatResetRequest(player, argTable)
|
|||||||
player:SendServerResponse(config.Prefix, 1, StatPointUI.cache[player:GetGUIDLow()])
|
player:SendServerResponse(config.Prefix, 1, StatPointUI.cache[player:GetGUIDLow()])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Helper function to add a stat point to the player through other scripts
|
||||||
|
function Player:AddPoint()
|
||||||
|
StatPointUI.AddStatPoint(self:GetGUIDLow())
|
||||||
|
end
|
||||||
|
|
||||||
RegisterPlayerEvent(3, StatPointUI.OnLogin)
|
RegisterPlayerEvent(3, StatPointUI.OnLogin)
|
||||||
RegisterServerEvent(33, StatPointUI.OnElunaStartup)
|
RegisterServerEvent(33, StatPointUI.OnElunaStartup)
|
||||||
RegisterClientRequests(config)
|
RegisterClientRequests(config)
|
||||||
12
README.md
12
README.md
@ -2,14 +2,14 @@
|
|||||||
# CSMH
|
# CSMH
|
||||||
|
|
||||||
### What is CSMH?
|
### What is CSMH?
|
||||||
CSMH is a Client and Server Message Handler framework for communication between Eluna and the WoW interface. It has been tested on Mangos and TrinityCore 3.3.5a, but it will probably work for other versions as well.
|
CSMH is a **C**lient and **S**erver **M**essage **H**andler framework for communication between Eluna and the WoW client interface. It has been tested on Mangos and TrinityCore 3.3.5a, but it will probably work for other versions as well.
|
||||||
|
|
||||||
CSMH consists of two parts, the Client Message Handler and the Server Message Handler respectively.
|
CSMH consists of two parts, the Client Message Handler and the Server Message Handler respectively.
|
||||||
|
|
||||||
### How does this compare to AIO?
|
### How does this compare to AIO?
|
||||||
While AIO is the most used solution of this kind, it has its drawbacks as well. While it allows you to write all your code server-side, it also limits you to the Lua API only. AIO also sends the full addon code to the client on startup and reload, which is fairly network intensive. Upside of AIO is its ease of distribution compared to dedicated client-side addons.
|
While AIO is the most used solution of this kind, it has its drawbacks as well. While it allows you to write all your code server-side, it also limits you to the Lua API only. AIO also sends the full addon code to the client on startup and reload, which is relatively bandwidth intensive compared to communication only. Upside of AIO is its ease of distribution compared to dedicated client-side addons.
|
||||||
|
|
||||||
CSMH is only meant to transport data between the client and the server, and is therefore not as network intensive as AIO. You write your server-side code on the server, and you distribute your client-side code either as an addon, or in a patch. This allows you to use XML and templates, as well as the full Lua API.
|
CSMH is only meant to transport data between the client and the server, and is therefore not as bandwidth intensive as AIO. You write your server-side code on the server, and you distribute your client-side code either as an addon, or in a patch. This allows you to use XML and templates, as well as the full Lua API.
|
||||||
|
|
||||||
Both AIO and CSMH uses smallfolk for serialization, and is compatible with each other. You can use both AIO and CSMH in the same project.
|
Both AIO and CSMH uses smallfolk for serialization, and is compatible with each other. You can use both AIO and CSMH in the same project.
|
||||||
|
|
||||||
@ -45,14 +45,14 @@ The CMH can be distributed either as a stand-alone addon, or through a patch. Fi
|
|||||||
- **RegisterServerResponses** takes a config table (see Both section for structure) and registers functionId's to corresponding client side functions. You then use these functionId's in SendServerResponse.
|
- **RegisterServerResponses** takes a config table (see Both section for structure) and registers functionId's to corresponding client side functions. You then use these functionId's in SendServerResponse.
|
||||||
|
|
||||||
`SendClientRequest(prefix, functionId, ...)`
|
`SendClientRequest(prefix, functionId, ...)`
|
||||||
- **SendClientRequest** sends a Client Request to the Server, with *prefix* and *functionId* defined in your server config table. Takes any amount of string, integer, table or boolean variables and sends to the corresponding function.
|
- **SendClientRequest** sends a Client Request to the Server, with *prefix* and *functionId* defined in your server config table. Takes any amount of string, integer, table or boolean variables and sends to the corresponding function. Nil variables are not supported. This is default Lua behavior.
|
||||||
|
|
||||||
### Server:
|
### Server:
|
||||||
`RegisterClientRequests(config table)`
|
`RegisterClientRequests(config table)`
|
||||||
- **RegisterClientRequests** takes a config table (see Both section for structure) and registers functionId's to corresponding server side functions. You then use these functionId's in SendClientRequest.
|
- **RegisterClientRequests** takes a config table (see Both section for structure) and registers functionId's to corresponding server side functions. You then use these functionId's in SendClientRequest.
|
||||||
|
|
||||||
`Player:SendServerResponse(prefix, functionId, ...)`
|
`Player:SendServerResponse(prefix, functionId, ...)`
|
||||||
- **SendServerResponse** sends a Server Response to the Client, with *prefix* and *functionId* defined in your client config table. Takes any amount of string, integer, table or boolean variables and sends to the corresponding function. Requires *player* object.
|
- **SendServerResponse** sends a Server Response to the Client, with *prefix* and *functionId* defined in your client config table. Takes any amount of string, integer, table or boolean variables and sends to the corresponding function. Nil variables are not supported. This is default Lua behavior. Requires *player* object.
|
||||||
|
|
||||||
### Both:
|
### Both:
|
||||||
local config = {
|
local config = {
|
||||||
@ -68,7 +68,7 @@ The CMH can be distributed either as a stand-alone addon, or through a patch. Fi
|
|||||||
## Credits:
|
## Credits:
|
||||||
- [Stoneharry](https://github.com/stoneharry)
|
- [Stoneharry](https://github.com/stoneharry)
|
||||||
- [Terrorblade](https://github.com/Terrorblade)
|
- [Terrorblade](https://github.com/Terrorblade)
|
||||||
- Kaev
|
- [Kaev](https://github.com/kaev)
|
||||||
- [Rochet / AIO](https://github.com/Rochet2)
|
- [Rochet / AIO](https://github.com/Rochet2)
|
||||||
- [Eluna](https://github.com/ElunaLuaEngine/Eluna)
|
- [Eluna](https://github.com/ElunaLuaEngine/Eluna)
|
||||||
- [smallfolk](https://github.com/gvx/Smallfolk)
|
- [smallfolk](https://github.com/gvx/Smallfolk)
|
||||||
@ -4,14 +4,11 @@ local debug = false
|
|||||||
|
|
||||||
local SMH = {}
|
local SMH = {}
|
||||||
local datacache = {}
|
local datacache = {}
|
||||||
local delim = {"♠", "♥", "♚", "♛", "♜"}
|
local delim = {"", "", "", "", ""}
|
||||||
local pck = {REQ = 1, DAT = 2}
|
local pck = {REQ = "", DAT = ""}
|
||||||
|
|
||||||
-- HELPERS START
|
-- HELPERS START
|
||||||
local function debugOut(prefix, x, msg)
|
local function debugOut(prefix, x, msg)
|
||||||
prefix = prefix or ""
|
|
||||||
msg = msg or ""
|
|
||||||
x = x or ""
|
|
||||||
if(debug == true) then
|
if(debug == true) then
|
||||||
print("["..os.clock().."][CSMH]["..x.."]["..prefix.."]: "..msg)
|
print("["..os.clock().."][CSMH]["..x.."]["..prefix.."]: "..msg)
|
||||||
end
|
end
|
||||||
@ -54,7 +51,12 @@ local function ParseMessage(str)
|
|||||||
-- Convert value to correct type
|
-- Convert value to correct type
|
||||||
for k, v in pairs(valTemp) do
|
for k, v in pairs(valTemp) do
|
||||||
local varType = typeTemp[k]
|
local varType = typeTemp[k]
|
||||||
if(varType == 3) then -- Ints
|
if(varType == 2) then -- strings
|
||||||
|
-- special case for empty string parsing
|
||||||
|
if(v == string.char(tonumber('1A', 16))) then
|
||||||
|
v = ""
|
||||||
|
end
|
||||||
|
elseif(varType == 3) then -- Ints
|
||||||
v = tonumber(v)
|
v = tonumber(v)
|
||||||
elseif(varType == 4) then -- Tables
|
elseif(varType == 4) then -- Tables
|
||||||
v = smallfolk.loads(v)
|
v = smallfolk.loads(v)
|
||||||
@ -77,6 +79,10 @@ local function ProcessVariables(sender, reqId, ...)
|
|||||||
|
|
||||||
for _, v in pairs(arg) do
|
for _, v in pairs(arg) do
|
||||||
if(type(v) == "string") then
|
if(type(v) == "string") then
|
||||||
|
-- Special case for empty string parsing
|
||||||
|
if(#v == 0) then
|
||||||
|
v = string.char(tonumber('1A', 16))
|
||||||
|
end
|
||||||
msg = msg .. delim[2]
|
msg = msg .. delim[2]
|
||||||
elseif(type(v) == "number") then
|
elseif(type(v) == "number") then
|
||||||
msg = msg .. delim[3]
|
msg = msg .. delim[3]
|
||||||
@ -119,16 +125,13 @@ function SMH.OnReceive(event, sender, _type, header, data, target)
|
|||||||
-- Ensure the sender and receiver is the same, and the message type is WHISPER
|
-- Ensure the sender and receiver is the same, and the message type is WHISPER
|
||||||
if sender:GetName() == target:GetName() and _type == 7 then
|
if sender:GetName() == target:GetName() and _type == 7 then
|
||||||
-- unpack and validate addon message structure
|
-- unpack and validate addon message structure
|
||||||
local pfx, source, pckId = header:match("(...)(%u)(%d%d)")
|
local pfx, source, pckId = header:match("(.)(%u)(.)")
|
||||||
if not pfx or not source or not pckId then
|
if not pfx or not source or not pckId then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Make sure we're only processing addon messages using our framework prefix character as well as client messages
|
-- Make sure we're only processing addon messages using our framework prefix character as well as client messages
|
||||||
if(pfx == delim[1] and source == "C") then
|
if(pfx == delim[1] and source == "C") then
|
||||||
-- convert ID to number so we can compare with our packet list
|
|
||||||
pckId = tonumber(pckId)
|
|
||||||
|
|
||||||
if(pckId == pck.REQ) then
|
if(pckId == pck.REQ) then
|
||||||
debugOut("REQ", "Rx", "REQ received, data: "..data)
|
debugOut("REQ", "Rx", "REQ received, data: "..data)
|
||||||
SMH.OnREQ(sender, data)
|
SMH.OnREQ(sender, data)
|
||||||
@ -254,7 +257,7 @@ end
|
|||||||
-- Tx START
|
-- Tx START
|
||||||
|
|
||||||
function SMH.SendREQ(sender, functionId, linkCount, reqId, addon)
|
function SMH.SendREQ(sender, functionId, linkCount, reqId, addon)
|
||||||
local header = string.format("%01s%01s%02d", delim[1], "S", pck.REQ)
|
local header = string.format("%01s%01s%01s", delim[1], "S", pck.REQ)
|
||||||
local data = string.format("%02d%03d%06s%0"..tostring(#addon).."s", functionId, linkCount, reqId, addon)
|
local data = string.format("%02d%03d%06s%0"..tostring(#addon).."s", functionId, linkCount, reqId, addon)
|
||||||
sender:SendAddonMessage(header, data, 7, sender)
|
sender:SendAddonMessage(header, data, 7, sender)
|
||||||
debugOut("REQ", "Tx", "Sent REQ with ID "..reqId..", sending DAT..")
|
debugOut("REQ", "Tx", "Sent REQ with ID "..reqId..", sending DAT..")
|
||||||
@ -262,7 +265,7 @@ end
|
|||||||
|
|
||||||
function SMH.SendDAT(sender, reqId)
|
function SMH.SendDAT(sender, reqId)
|
||||||
-- Build data message header
|
-- Build data message header
|
||||||
local header = string.format("%01s%01s%02d", delim[1], "S", pck.DAT)
|
local header = string.format("%01s%01s%01s", delim[1], "S", pck.DAT)
|
||||||
|
|
||||||
-- iterate all items in the message data cache and send
|
-- iterate all items in the message data cache and send
|
||||||
-- functions can also be trigger functions without any data, only send header and no payload
|
-- functions can also be trigger functions without any data, only send header and no payload
|
||||||
|
|||||||
Reference in New Issue
Block a user