Initial code push
This commit is contained in:
141
Client/CMH.lua
Normal file
141
Client/CMH.lua
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
local CMH = {}
|
||||||
|
|
||||||
|
local links = {}
|
||||||
|
|
||||||
|
function CMH.OnReceive(self, event, prefix, _, Type, sender)
|
||||||
|
if event == "CHAT_MSG_ADDON" and sender == UnitName("player") and Type == "WHISPER" then
|
||||||
|
local source, functionId, link, linkCount, MSG = prefix:match("(%D)(%d%d)(%d%d%d)(%d%d%d)(.+)");
|
||||||
|
if not source or not functionId or not link or not linkCount or not MSG then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if(source == "S") then
|
||||||
|
functionId, link, linkCount = tonumber(functionId), tonumber(link), tonumber(linkCount);
|
||||||
|
links[functionId] = links[functionId] or {count = 0};
|
||||||
|
links[functionId][link] = MSG;
|
||||||
|
links[functionId].count = links[functionId].count + 1;
|
||||||
|
if (links[functionId].count ~= linkCount) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fullMessage = table.concat(links[functionId]);
|
||||||
|
links[functionId] = {count = 0};
|
||||||
|
|
||||||
|
local VarTable = ParseMessage(fullMessage)
|
||||||
|
if not VarTable then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not(CMH[VarTable[1]]) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local func = CMH[VarTable[1]][functionId]
|
||||||
|
if func then
|
||||||
|
_G[func](sender, VarTable)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local CMHFrame = CreateFrame("Frame")
|
||||||
|
CMHFrame:RegisterEvent("CHAT_MSG_ADDON")
|
||||||
|
CMHFrame:SetScript("OnEvent", CMH.OnReceive)
|
||||||
|
|
||||||
|
function RegisterServerResponses(config)
|
||||||
|
if(CMH[config.Prefix]) then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
CMH[config.Prefix] = {}
|
||||||
|
|
||||||
|
for functionId, functionName in pairs(config.Functions) do
|
||||||
|
CMH[config.Prefix][functionId] = functionName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ParseMessage(str)
|
||||||
|
local output = {}
|
||||||
|
local valTemp = {}
|
||||||
|
local typeTemp = {}
|
||||||
|
local delim = {"♠", "♥", "♚", "♛", "♜"}
|
||||||
|
|
||||||
|
local valMatch = "[^"..table.concat(delim).."]+"
|
||||||
|
local typeMatch = "["..table.concat(delim).."]+"
|
||||||
|
|
||||||
|
-- Get values
|
||||||
|
for value in str:gmatch(valMatch) do
|
||||||
|
table.insert(valTemp, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get type from delimiter
|
||||||
|
for varType in str:gmatch(typeMatch) do
|
||||||
|
for k, v in pairs(delim) do
|
||||||
|
if(v == varType) then
|
||||||
|
table.insert(typeTemp, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert value to correct type
|
||||||
|
for k, v in pairs(valTemp) do
|
||||||
|
local varType = typeTemp[k]
|
||||||
|
if(varType == 3) then -- Ints
|
||||||
|
v = tonumber(v)
|
||||||
|
elseif(varType == 4) then -- Tables
|
||||||
|
v = Smallfolk.loads(v, #v)
|
||||||
|
elseif(varType == 5) then -- Booleans
|
||||||
|
if(v == "true") then v = true else v = false end
|
||||||
|
end
|
||||||
|
table.insert(output, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
valTemp = nil
|
||||||
|
typeTemp = nil
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
function SendClientRequest(prefix, functionId, ...)
|
||||||
|
-- ♠ = Prefix prefix
|
||||||
|
-- ♥ = ArgumentPrefix for Strings
|
||||||
|
-- ♚ = ArgumentPrefix for Ints
|
||||||
|
-- ♛ = ArgumentPrefix for Tables
|
||||||
|
-- ♜ = ArgumentPrefix for Boolean
|
||||||
|
|
||||||
|
local arg = {...}
|
||||||
|
local splitLength = 230
|
||||||
|
local msg = "♠" .. prefix
|
||||||
|
|
||||||
|
for _, v in pairs(arg) do
|
||||||
|
if(type(v) == "string") then
|
||||||
|
msg = msg .. "♥"
|
||||||
|
elseif(type(v) == "number") then
|
||||||
|
msg = msg .. "♚"
|
||||||
|
elseif(type(v) == "table") then
|
||||||
|
-- use Smallfolk to convert table structure to string
|
||||||
|
v = Smallfolk.dumps(v)
|
||||||
|
msg = msg .. "♛"
|
||||||
|
elseif(type(v) == "boolean") then
|
||||||
|
v = tostring(v)
|
||||||
|
msg = msg .. "♜"
|
||||||
|
end
|
||||||
|
msg = msg .. v
|
||||||
|
end
|
||||||
|
|
||||||
|
local splits = math.ceil(msg:len() / splitLength)
|
||||||
|
local send
|
||||||
|
local counter = 1
|
||||||
|
for i=1, msg:len(), splitLength do
|
||||||
|
send = string.format("%01s%03d%02d%02d", "C", functionId, counter, splits)
|
||||||
|
if ((i + splitLength) > msg:len()) then
|
||||||
|
send = send .. msg:sub(i, msg:len())
|
||||||
|
else
|
||||||
|
send = send .. msg:sub(i, i + splitLength - 1)
|
||||||
|
end
|
||||||
|
counter = counter + 1
|
||||||
|
|
||||||
|
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
|
||||||
|
end
|
||||||
|
end
|
||||||
8
Client/CMH.toc
Normal file
8
Client/CMH.toc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
## Interface: 30300
|
||||||
|
## Title: CMH
|
||||||
|
## Notes: Client & Server Message Handler framework for communication between server and client.
|
||||||
|
## Version: 1.0
|
||||||
|
## Author: Foereaper, Stoneharry, Terrorblade
|
||||||
|
|
||||||
|
smallfolk.lua
|
||||||
|
CMH.lua
|
||||||
152
Client/FrameXML.toc
Normal file
152
Client/FrameXML.toc
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# Do not delete the following line!
|
||||||
|
## Interface: 30300
|
||||||
|
##DebugHook.lua
|
||||||
|
GlobalStrings.lua
|
||||||
|
Constants.lua
|
||||||
|
Fonts.xml
|
||||||
|
FontStyles.xml
|
||||||
|
Localization.xml
|
||||||
|
## add new modules below here
|
||||||
|
|
||||||
|
## CMH START
|
||||||
|
smallfolk.lua
|
||||||
|
CMH.lua
|
||||||
|
## CMH END
|
||||||
|
|
||||||
|
BasicControls.xml
|
||||||
|
WorldFrame.xml
|
||||||
|
UIParent.xml
|
||||||
|
AnimTimerFrame.xml
|
||||||
|
MoneyFrame.lua
|
||||||
|
MoneyFrame.xml
|
||||||
|
MoneyInputFrame.lua
|
||||||
|
MoneyInputFrame.xml
|
||||||
|
GameTooltip.xml
|
||||||
|
UIMenu.xml
|
||||||
|
UIDropDownMenu.xml
|
||||||
|
UIPanelTemplates.lua
|
||||||
|
UIPanelTemplates.xml
|
||||||
|
SecureTemplates.xml
|
||||||
|
SecureHandlerTemplates.xml
|
||||||
|
ItemButtonTemplate.xml
|
||||||
|
SparkleFrame.xml
|
||||||
|
HybridScrollFrame.lua
|
||||||
|
HybridScrollFrame.xml
|
||||||
|
GameMenuFrame.xml
|
||||||
|
CharacterFrameTemplates.xml
|
||||||
|
TextStatusBar.lua
|
||||||
|
TextStatusBar.xml
|
||||||
|
UIErrorsFrame.xml
|
||||||
|
AutoComplete.xml
|
||||||
|
StaticPopup.xml
|
||||||
|
Sound.lua
|
||||||
|
OptionsFrameTemplates.xml
|
||||||
|
OptionsPanelTemplates.xml
|
||||||
|
VideoOptionsFrame.xml
|
||||||
|
VideoOptionsPanels.xml
|
||||||
|
AudioOptionsFrame.xml
|
||||||
|
AudioOptionsPanels.xml
|
||||||
|
InterfaceOptionsFrame.xml
|
||||||
|
InterfaceOptionsPanels.xml
|
||||||
|
AlertFrames.xml
|
||||||
|
MirrorTimer.xml
|
||||||
|
CoinPickupFrame.xml
|
||||||
|
StackSplitFrame.xml
|
||||||
|
FadingFrame.xml
|
||||||
|
ZoneText.xml
|
||||||
|
BattlefieldFrame.xml
|
||||||
|
MainMenuBar.xml
|
||||||
|
MainMenuBarMicroButtons.xml
|
||||||
|
TutorialFrame.xml
|
||||||
|
Minimap.xml
|
||||||
|
GameTime.xml
|
||||||
|
Cooldown.xml
|
||||||
|
ActionButtonTemplate.xml
|
||||||
|
ActionBarFrame.xml
|
||||||
|
MultiActionBars.xml
|
||||||
|
##ActionWindow.xml
|
||||||
|
BuffFrame.xml
|
||||||
|
CombatFeedback.xml
|
||||||
|
CastingBarFrame.xml
|
||||||
|
UnitPopup.xml
|
||||||
|
UnitFrame.xml
|
||||||
|
BNet.xml
|
||||||
|
HistoryKeeper.lua
|
||||||
|
BNConversations.xml
|
||||||
|
ChatFrame.xml
|
||||||
|
FloatingChatFrame.xml
|
||||||
|
VoiceChat.xml
|
||||||
|
ReadyCheck.xml
|
||||||
|
PlayerFrame.xml
|
||||||
|
PartyFrame.xml
|
||||||
|
TargetFrame.xml
|
||||||
|
TotemFrame.xml
|
||||||
|
PetFrame.xml
|
||||||
|
StatsFrame.xml
|
||||||
|
SpellBookFrame.xml
|
||||||
|
CharacterFrame.xml
|
||||||
|
EquipmentManager.lua
|
||||||
|
PaperDollFrame.xml
|
||||||
|
PetPaperDollFrame.xml
|
||||||
|
SkillFrame.xml
|
||||||
|
ReputationFrame.xml
|
||||||
|
HonorFrame.xml
|
||||||
|
QuestFrame.xml
|
||||||
|
QuestPOI.xml
|
||||||
|
WatchFrame.xml
|
||||||
|
QuestLogFrame.xml
|
||||||
|
QuestInfo.xml
|
||||||
|
MerchantFrame.xml
|
||||||
|
TradeFrame.xml
|
||||||
|
ContainerFrame.xml
|
||||||
|
LootFrame.xml
|
||||||
|
ItemTextFrame.xml
|
||||||
|
TaxiFrame.xml
|
||||||
|
BankFrame.xml
|
||||||
|
FriendsFrame.xml
|
||||||
|
RaidFrame.xml
|
||||||
|
ChannelFrame.xml
|
||||||
|
PetActionBarFrame.xml
|
||||||
|
MultiCastActionBarFrame.xml
|
||||||
|
BonusActionBarFrame.xml
|
||||||
|
MainMenuBarBagButtons.xml
|
||||||
|
WorldMapFrame.xml
|
||||||
|
CinematicFrame.xml
|
||||||
|
ItemRef.xml
|
||||||
|
ComboFrame.xml
|
||||||
|
TabardFrame.xml
|
||||||
|
GuildRegistrarFrame.xml
|
||||||
|
PetitionFrame.xml
|
||||||
|
HelpFrame.xml
|
||||||
|
KnowledgeBaseFrame.xml
|
||||||
|
ColorPickerFrame.xml
|
||||||
|
GossipFrame.xml
|
||||||
|
MailFrame.xml
|
||||||
|
PetStable.xml
|
||||||
|
DurabilityFrame.xml
|
||||||
|
WorldStateFrame.xml
|
||||||
|
DressUpFrame.xml
|
||||||
|
RaidWarning.xml
|
||||||
|
ClassTrainerFrameTemplates.xml
|
||||||
|
PVPFrame.xml
|
||||||
|
PVPBattlegroundFrame.xml
|
||||||
|
ArenaFrame.xml
|
||||||
|
ArenaRegistrarFrame.xml
|
||||||
|
LFGFrame.xml
|
||||||
|
LFDFrame.xml
|
||||||
|
LFRFrame.xml
|
||||||
|
MovieRecordingProgress.xml
|
||||||
|
MacOptionsFrame.xml
|
||||||
|
RatingMenuFrame.xml
|
||||||
|
TalentFrameBase.lua
|
||||||
|
TalentFrameTemplates.xml
|
||||||
|
RuneFrame.xml
|
||||||
|
EasyMenu.lua
|
||||||
|
ChatConfigFrame.xml
|
||||||
|
MovieFrame.xml
|
||||||
|
VehicleMenuBar.xml
|
||||||
|
AlternatePowerBar.xml
|
||||||
|
AnimationSystem.lua
|
||||||
|
|
||||||
|
## add new modules above here
|
||||||
|
LocalizationPost.xml
|
||||||
218
Client/smallfolk.lua
Normal file
218
Client/smallfolk.lua
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
local M = {}
|
||||||
|
Smallfolk = M
|
||||||
|
local expect_object, dump_object
|
||||||
|
local error, tostring, pairs, type, floor, huge, concat = error, tostring, pairs, type, math.floor, math.huge, table.concat
|
||||||
|
|
||||||
|
local dump_type = {}
|
||||||
|
|
||||||
|
function dump_type:string(nmemo, memo, acc)
|
||||||
|
local nacc = #acc
|
||||||
|
acc[nacc + 1] = '"'
|
||||||
|
acc[nacc + 2] = self:gsub('"', '""')
|
||||||
|
acc[nacc + 3] = '"'
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function dump_type:number(nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ("%.17g"):format(self)
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function dump_type:table(nmemo, memo, acc)
|
||||||
|
--[[
|
||||||
|
if memo[self] then
|
||||||
|
acc[#acc + 1] = '@'
|
||||||
|
acc[#acc + 1] = tostring(memo[self])
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
nmemo = nmemo + 1
|
||||||
|
]]
|
||||||
|
memo[self] = nmemo
|
||||||
|
acc[#acc + 1] = '{'
|
||||||
|
local nself = #self
|
||||||
|
for i = 1, nself do -- don't use ipairs here, we need the gaps
|
||||||
|
nmemo = dump_object(self[i], nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ','
|
||||||
|
end
|
||||||
|
for k, v in pairs(self) do
|
||||||
|
if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > nself then
|
||||||
|
nmemo = dump_object(k, nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ':'
|
||||||
|
nmemo = dump_object(v, nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ','
|
||||||
|
end
|
||||||
|
end
|
||||||
|
acc[#acc] = acc[#acc] == '{' and '{}' or '}'
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function dump_object(object, nmemo, memo, acc)
|
||||||
|
if object == true then
|
||||||
|
acc[#acc + 1] = 't'
|
||||||
|
elseif object == false then
|
||||||
|
acc[#acc + 1] = 'f'
|
||||||
|
elseif object == nil then
|
||||||
|
acc[#acc + 1] = 'n'
|
||||||
|
elseif object ~= object then
|
||||||
|
if (''..object):sub(1,1) == '-' then
|
||||||
|
acc[#acc + 1] = 'N'
|
||||||
|
else
|
||||||
|
acc[#acc + 1] = 'Q'
|
||||||
|
end
|
||||||
|
elseif object == huge then
|
||||||
|
acc[#acc + 1] = 'I'
|
||||||
|
elseif object == -huge then
|
||||||
|
acc[#acc + 1] = 'i'
|
||||||
|
else
|
||||||
|
local t = type(object)
|
||||||
|
if not dump_type[t] then
|
||||||
|
error('cannot dump type ' .. t)
|
||||||
|
end
|
||||||
|
return dump_type[t](object, nmemo, memo, acc)
|
||||||
|
end
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.dumps(object)
|
||||||
|
local nmemo = 0
|
||||||
|
local memo = {}
|
||||||
|
local acc = {}
|
||||||
|
dump_object(object, nmemo, memo, acc)
|
||||||
|
return concat(acc)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function invalid(i)
|
||||||
|
error('invalid input at position ' .. i)
|
||||||
|
end
|
||||||
|
|
||||||
|
local nonzero_digit = {['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
|
||||||
|
local is_digit = {['0'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
|
||||||
|
local function expect_number(string, start)
|
||||||
|
local i = start
|
||||||
|
local head = string:sub(i, i)
|
||||||
|
if head == '-' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
end
|
||||||
|
if nonzero_digit[head] then
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
until not is_digit[head]
|
||||||
|
elseif head == '0' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
else
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
if head == '.' then
|
||||||
|
local oldi = i
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
until not is_digit[head]
|
||||||
|
if i == oldi + 1 then
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if head == 'e' or head == 'E' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
if head == '+' or head == '-' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
end
|
||||||
|
if not is_digit[head] then
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
until not is_digit[head]
|
||||||
|
end
|
||||||
|
return tonumber(string:sub(start, i - 1)), i
|
||||||
|
end
|
||||||
|
|
||||||
|
local expect_object_head = {
|
||||||
|
t = function(string, i) return true, i end,
|
||||||
|
f = function(string, i) return false, i end,
|
||||||
|
n = function(string, i) return nil, i end,
|
||||||
|
Q = function(string, i) return -(0/0), i end,
|
||||||
|
N = function(string, i) return 0/0, i end,
|
||||||
|
I = function(string, i) return 1/0, i end,
|
||||||
|
i = function(string, i) return -1/0, i end,
|
||||||
|
['"'] = function(string, i)
|
||||||
|
local nexti = i - 1
|
||||||
|
repeat
|
||||||
|
nexti = string:find('"', nexti + 1, true) + 1
|
||||||
|
until string:sub(nexti, nexti) ~= '"'
|
||||||
|
return string:sub(i, nexti - 2):gsub('""', '"'), nexti
|
||||||
|
end,
|
||||||
|
['0'] = function(string, i)
|
||||||
|
return expect_number(string, i - 1)
|
||||||
|
end,
|
||||||
|
['{'] = function(string, i, tables)
|
||||||
|
local nt, k, v = {}
|
||||||
|
local j = 1
|
||||||
|
tables[#tables + 1] = nt
|
||||||
|
if string:sub(i, i) == '}' then
|
||||||
|
return nt, i + 1
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
k, i = expect_object(string, i, tables)
|
||||||
|
if string:sub(i, i) == ':' then
|
||||||
|
v, i = expect_object(string, i + 1, tables)
|
||||||
|
nt[k] = v
|
||||||
|
else
|
||||||
|
nt[j] = k
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
local head = string:sub(i, i)
|
||||||
|
if head == ',' then
|
||||||
|
i = i + 1
|
||||||
|
elseif head == '}' then
|
||||||
|
return nt, i + 1
|
||||||
|
else
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
--[[
|
||||||
|
['@'] = function(string, i, tables)
|
||||||
|
local match = string:match('^%d+', i)
|
||||||
|
local ref = tonumber(match)
|
||||||
|
if tables[ref] then
|
||||||
|
return tables[ref], i + #match
|
||||||
|
end
|
||||||
|
invalid(i)
|
||||||
|
end,
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
expect_object_head['1'] = expect_object_head['0']
|
||||||
|
expect_object_head['2'] = expect_object_head['0']
|
||||||
|
expect_object_head['3'] = expect_object_head['0']
|
||||||
|
expect_object_head['4'] = expect_object_head['0']
|
||||||
|
expect_object_head['5'] = expect_object_head['0']
|
||||||
|
expect_object_head['6'] = expect_object_head['0']
|
||||||
|
expect_object_head['7'] = expect_object_head['0']
|
||||||
|
expect_object_head['8'] = expect_object_head['0']
|
||||||
|
expect_object_head['9'] = expect_object_head['0']
|
||||||
|
expect_object_head['-'] = expect_object_head['0']
|
||||||
|
expect_object_head['.'] = expect_object_head['0']
|
||||||
|
|
||||||
|
expect_object = function(string, i, tables)
|
||||||
|
local head = string:sub(i, i)
|
||||||
|
if expect_object_head[head] then
|
||||||
|
return expect_object_head[head](string, i + 1, tables)
|
||||||
|
end
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.loads(string, maxsize)
|
||||||
|
if #string > (maxsize or 10000) then
|
||||||
|
error 'input too large'
|
||||||
|
end
|
||||||
|
return (expect_object(string, 1, {}))
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
171
Example_StatPointUI/Client/StatPointUI.lua
Normal file
171
Example_StatPointUI/Client/StatPointUI.lua
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
-- Requiring of Client Message Handler not needed
|
||||||
|
-- Handled by addon dependency
|
||||||
|
|
||||||
|
local config = {
|
||||||
|
Prefix = "StatPointUI",
|
||||||
|
Functions = {
|
||||||
|
[1] = "OnCacheReceived"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local StatPointUI = {
|
||||||
|
cache = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatPointUI.OnLogin()
|
||||||
|
-- Load all UI assets before requesting cache from server
|
||||||
|
StatPointUI.OnLoad()
|
||||||
|
SendClientRequest(config.Prefix, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.OnLoad()
|
||||||
|
-- Create the main UI frame
|
||||||
|
StatPointUI.mainFrame = CreateFrame("Frame", config.Prefix, CharacterFrame)
|
||||||
|
StatPointUI.mainFrame:SetToplevel(true)
|
||||||
|
StatPointUI.mainFrame:SetSize(200, 260)
|
||||||
|
StatPointUI.mainFrame:SetBackdrop({
|
||||||
|
bgFile = [[Interface\TutorialFrame\TutorialFrameBackground]],
|
||||||
|
edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]],
|
||||||
|
edgeSize = 16,
|
||||||
|
tileSize = 32,
|
||||||
|
insets = {left = 5, right = 5, top = 5, bottom = 5}
|
||||||
|
})
|
||||||
|
StatPointUI.mainFrame:SetPoint("TOPRIGHT",170,-20)
|
||||||
|
StatPointUI.mainFrame:Hide()
|
||||||
|
|
||||||
|
-- Title bar
|
||||||
|
StatPointUI.titleBar = CreateFrame("Frame", config.Prefix.."TitleBar", StatPointUI.mainFrame)
|
||||||
|
StatPointUI.titleBar:SetSize(135, 25)
|
||||||
|
StatPointUI.titleBar:SetBackdrop(
|
||||||
|
{
|
||||||
|
bgFile = [[Interface/CHARACTERFRAME/UI-Party-Background]],
|
||||||
|
edgeFile = [[Interface/DialogFrame/UI-DialogBox-Border]],
|
||||||
|
tile = true,
|
||||||
|
edgeSize = 16,
|
||||||
|
tileSize = 16,
|
||||||
|
insets = {left = 5, right = 5, top = 5, bottom = 5}
|
||||||
|
})
|
||||||
|
StatPointUI.titleBar:SetPoint("TOP", 0, 9)
|
||||||
|
|
||||||
|
-- Titlebar text
|
||||||
|
StatPointUI.titleBarText = StatPointUI.titleBar:CreateFontString(config.Prefix.."TitleBarText")
|
||||||
|
StatPointUI.titleBarText:SetFont("Fonts\\FRIZQT__.TTF", 13)
|
||||||
|
StatPointUI.titleBarText:SetSize(190, 5)
|
||||||
|
StatPointUI.titleBarText:SetPoint("CENTER", 0, 0)
|
||||||
|
StatPointUI.titleBarText:SetText("|cffFFC125Attribute Points|r")
|
||||||
|
|
||||||
|
-- Generate row tables
|
||||||
|
local rowOffset = -30
|
||||||
|
local titleOffset = -100
|
||||||
|
local btnOffset = 40
|
||||||
|
local rowContent = {"Strength", "Agility", "Stamina", "Intellect", "Spirit"}
|
||||||
|
|
||||||
|
for k, v in pairs(rowContent) do
|
||||||
|
|
||||||
|
StatPointUI[v] = {}
|
||||||
|
-- Value (dummy, overwritten by server values
|
||||||
|
StatPointUI[v].Val = StatPointUI.mainFrame:CreateFontString(config.Prefix..v.."Val")
|
||||||
|
StatPointUI[v].Val:SetFont("Fonts\\FRIZQT__.TTF", 15)
|
||||||
|
|
||||||
|
if(k == 1) then
|
||||||
|
StatPointUI[v].Val:SetPoint("CENTER", StatPointUI.titleBar, "CENTER", 30, rowOffset)
|
||||||
|
else
|
||||||
|
StatPointUI[v].Val:SetPoint("CENTER", StatPointUI[rowContent[k-1]].Val, "CENTER", 0, rowOffset)
|
||||||
|
end
|
||||||
|
StatPointUI[v].Val:SetText("0")
|
||||||
|
|
||||||
|
-- Title
|
||||||
|
StatPointUI[v].Title = StatPointUI.mainFrame:CreateFontString(config.Prefix..v.."Title")
|
||||||
|
StatPointUI[v].Title:SetFont("Fonts\\FRIZQT__.TTF", 15)
|
||||||
|
StatPointUI[v].Title:SetPoint("LEFT", StatPointUI[v].Val, "LEFT", titleOffset, 0)
|
||||||
|
StatPointUI[v].Title:SetText(v..":")
|
||||||
|
|
||||||
|
-- Increase button
|
||||||
|
StatPointUI[v].Button = CreateFrame("Button", config.Prefix..v.."Button", StatPointUI.mainFrame)
|
||||||
|
StatPointUI[v].Button:SetSize(20, 20)
|
||||||
|
StatPointUI[v].Button:SetPoint("RIGHT", StatPointUI[v].Val, "RIGHT", btnOffset, 0)
|
||||||
|
StatPointUI[v].Button:EnableMouse(false)
|
||||||
|
StatPointUI[v].Button:Disable()
|
||||||
|
StatPointUI[v].Button:SetNormalTexture("Interface/BUTTONS/UI-SpellbookIcon-NextPage-Up")
|
||||||
|
StatPointUI[v].Button:SetHighlightTexture("Interface/BUTTONS/UI-Panel-MinimizeButton-Highlight")
|
||||||
|
StatPointUI[v].Button:SetPushedTexture("Interface/BUTTONS/UI-SpellbookIcon-NextPage-Down")
|
||||||
|
StatPointUI[v].Button:SetDisabledTexture("Interface/BUTTONS/UI-SpellbookIcon-NextPage-Disabled")
|
||||||
|
StatPointUI[v].Button:SetScript("OnMouseUp", function() SendClientRequest(config.Prefix, 2, k); PlaySound("UChatScrollButton"); end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Attribute points left
|
||||||
|
StatPointUI.pointsLeftVal = StatPointUI.mainFrame:CreateFontString(config.Prefix.."PointsLeftVal")
|
||||||
|
StatPointUI.pointsLeftVal:SetFont("Fonts\\FRIZQT__.TTF", 15)
|
||||||
|
StatPointUI.pointsLeftVal:SetPoint("CENTER", StatPointUI[rowContent[#rowContent]].Val, "CENTER", 0, rowOffset)
|
||||||
|
StatPointUI.pointsLeftVal:SetText("0")
|
||||||
|
|
||||||
|
StatPointUI.pointsLeftTitle = StatPointUI.mainFrame:CreateFontString(config.Prefix.."PointsLeftVal")
|
||||||
|
StatPointUI.pointsLeftTitle:SetFont("Fonts\\FRIZQT__.TTF", 15)
|
||||||
|
StatPointUI.pointsLeftTitle:SetPoint("LEFT", StatPointUI.pointsLeftVal, "LEFT", titleOffset, 0)
|
||||||
|
StatPointUI.pointsLeftTitle:SetText("Points left:")
|
||||||
|
|
||||||
|
-- Reset button
|
||||||
|
StatPointUI.resetButton = CreateFrame("Button", config.Prefix.."ResetButton", StatPointUI.mainFrame)
|
||||||
|
StatPointUI.resetButton:SetSize(100, 25)
|
||||||
|
StatPointUI.resetButton:SetPoint("CENTER", StatPointUI.titleBar, "CENTER", 0, -220)
|
||||||
|
StatPointUI.resetButton:EnableMouse(true)
|
||||||
|
StatPointUI.resetButton:SetText("RESET")
|
||||||
|
StatPointUI.resetButton:SetNormalFontObject("GameFontNormalSmall")
|
||||||
|
|
||||||
|
local ntex = StatPointUI.resetButton:CreateTexture()
|
||||||
|
ntex:SetTexture("Interface/Buttons/UI-Panel-Button-Up")
|
||||||
|
ntex:SetTexCoord(0, 0.625, 0, 0.6875)
|
||||||
|
ntex:SetAllPoints()
|
||||||
|
StatPointUI.resetButton:SetNormalTexture(ntex)
|
||||||
|
|
||||||
|
local htex = StatPointUI.resetButton:CreateTexture()
|
||||||
|
htex:SetTexture("Interface/Buttons/UI-Panel-Button-Highlight")
|
||||||
|
htex:SetTexCoord(0, 0.625, 0, 0.6875)
|
||||||
|
htex:SetAllPoints()
|
||||||
|
StatPointUI.resetButton:SetHighlightTexture(htex)
|
||||||
|
|
||||||
|
local ptex = StatPointUI.resetButton:CreateTexture()
|
||||||
|
ptex:SetTexture("Interface/Buttons/UI-Panel-Button-Down")
|
||||||
|
ptex:SetTexCoord(0, 0.625, 0, 0.6875)
|
||||||
|
ptex:SetAllPoints()
|
||||||
|
StatPointUI.resetButton:SetPushedTexture(ptex)
|
||||||
|
|
||||||
|
StatPointUI.resetButton:SetScript("OnMouseUp", function() SendClientRequest(config.Prefix, 3); PlaySound("UChatScrollButton"); end)
|
||||||
|
|
||||||
|
-- Hook the character frame and hide/show with the char frame
|
||||||
|
PaperDollFrame:HookScript("OnShow", function() StatPointUI.mainFrame:Show() end)
|
||||||
|
PaperDollFrame:HookScript("OnHide", function() StatPointUI.mainFrame:Hide() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function OnCacheReceived(sender, argTable)
|
||||||
|
StatPointUI.cache = argTable[2]
|
||||||
|
local rowContent = {"Strength", "Agility", "Stamina", "Intellect", "Spirit"}
|
||||||
|
for i = 1, 5 do
|
||||||
|
StatPointUI[rowContent[i]].Val:SetText(StatPointUI.cache[i])
|
||||||
|
|
||||||
|
-- If a point has been spent in the stat row, set color to green, otherwise white
|
||||||
|
if(StatPointUI.cache[i] > 0) then
|
||||||
|
StatPointUI[rowContent[i]].Val:SetTextColor(0,1,0,1)
|
||||||
|
else
|
||||||
|
StatPointUI[rowContent[i]].Val:SetTextColor(1,1,1,1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Disable buttons if the player has no more stats to spend
|
||||||
|
if(StatPointUI.cache[6] > 0) then
|
||||||
|
StatPointUI[rowContent[i]].Button:EnableMouse(true)
|
||||||
|
StatPointUI[rowContent[i]].Button:Enable()
|
||||||
|
else
|
||||||
|
StatPointUI[rowContent[i]].Button:EnableMouse(false)
|
||||||
|
StatPointUI[rowContent[i]].Button:Disable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
StatPointUI.pointsLeftVal:SetText(StatPointUI.cache[6])
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterServerResponses(config)
|
||||||
|
|
||||||
|
-- Event frame to trigger cache request on both login and reload
|
||||||
|
local EventFrame = CreateFrame("Frame")
|
||||||
|
EventFrame:RegisterEvent("PLAYER_LOGIN")
|
||||||
|
EventFrame:SetScript("OnEvent", function() StatPointUI.OnLogin() end)
|
||||||
8
Example_StatPointUI/Client/StatPointUI.toc
Normal file
8
Example_StatPointUI/Client/StatPointUI.toc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
## Interface: 30300
|
||||||
|
## Title: StatPointUI
|
||||||
|
## Notes: CMH Stat Point UI example script.
|
||||||
|
## Version: 1.0
|
||||||
|
## Author: Foereaper, Kaev
|
||||||
|
##RequiredDeps: CMH
|
||||||
|
|
||||||
|
StatPointUI.lua
|
||||||
126
Example_StatPointUI/Server/StatPointUI.lua
Normal file
126
Example_StatPointUI/Server/StatPointUI.lua
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
-- Require the Server Message Handler
|
||||||
|
require("SMH")
|
||||||
|
|
||||||
|
local config = {
|
||||||
|
Prefix = "StatPointUI",
|
||||||
|
Functions = {
|
||||||
|
[1] = "OnFullCacheRequest",
|
||||||
|
[2] = "OnSpendPointRequest",
|
||||||
|
[3] = "OnStatResetRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local StatPointUI = {
|
||||||
|
cache = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatPointUI.LoadData(guid)
|
||||||
|
local query = CharDBQuery("SELECT `str`, `agi`, `stam`, `int`, `spirit`, `points` FROM character_stats_extra WHERE `guid`="..guid..";");
|
||||||
|
if(query) then
|
||||||
|
StatPointUI.cache[guid] = {
|
||||||
|
query:GetUInt32(0), -- Strength
|
||||||
|
query:GetUInt32(1), -- Agility
|
||||||
|
query:GetUInt32(2), -- Stamina
|
||||||
|
query:GetUInt32(3), -- Intellect
|
||||||
|
query:GetUInt32(4), -- Spirit
|
||||||
|
query:GetUInt32(5) -- statpoints
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StatPointUI.cache[guid] = {0, 0, 0, 0, 0, 0};
|
||||||
|
CharDBQuery("INSERT INTO character_stats_extra(`guid`, `str`, `agi`, `stam`, `int`, `spirit`, `points`) VALUES ("..guid..", 0, 0, 0, 0, 0, 0);");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.OnLogin(event, player)
|
||||||
|
if not(StatPointUI.cache[player:GetGUIDLow()]) then
|
||||||
|
StatPointUI.LoadData(player:GetGUIDLow())
|
||||||
|
end
|
||||||
|
StatPointUI.SetStats(player:GetGUIDLow())
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.SetStats(guid, stat)
|
||||||
|
stat = stat or nil
|
||||||
|
local player = GetPlayerByGUID(guid)
|
||||||
|
local auras = {7464, 7471, 7477, 7468, 7474}
|
||||||
|
if(player) then
|
||||||
|
if stat == nil then
|
||||||
|
for i = 1, 5 do
|
||||||
|
local aura = player:GetAura(auras[i])
|
||||||
|
if (aura) then
|
||||||
|
aura:SetStackAmount(StatPointUI.cache[guid][i])
|
||||||
|
else
|
||||||
|
if(StatPointUI.cache[guid][i] > 0) then
|
||||||
|
player:AddAura(auras[i], player):SetStackAmount(StatPointUI.cache[guid][i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local aura = player:GetAura(auras[stat])
|
||||||
|
if (aura) then
|
||||||
|
aura:SetStackAmount(StatPointUI.cache[guid][stat])
|
||||||
|
else
|
||||||
|
if(StatPointUI.cache[player:GetGUIDLow()][stat] > 0) then
|
||||||
|
player:AddAura(auras[stat], player):SetStackAmount(StatPointUI.cache[guid][stat])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.ResetStats(guid)
|
||||||
|
local player = GetPlayerByGUID(guid)
|
||||||
|
local auras = {7464, 7471, 7477, 7468, 7474}
|
||||||
|
for _, aura in pairs(auras) do
|
||||||
|
player:RemoveAura(aura)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.OnElunaStartup(event)
|
||||||
|
-- Re-cache online players' data in case of a hot reload
|
||||||
|
for _, player in pairs(GetPlayersInWorld()) do
|
||||||
|
StatPointUI.LoadData(player:GetGUIDLow())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.OnPointSpent(guid, stat)
|
||||||
|
local inttostr = {"str", "agi", "stam", "int", "spirit"}
|
||||||
|
CharDBQuery("UPDATE character_stats_extra SET `"..inttostr[stat].."` = `"..inttostr[stat].."` + 1, `points`=`points`-1 WHERE `guid`="..guid..";")
|
||||||
|
StatPointUI.cache[guid][stat] = StatPointUI.cache[guid][stat]+1
|
||||||
|
StatPointUI.cache[guid][6] = StatPointUI.cache[guid][6]-1
|
||||||
|
StatPointUI.SetStats(guid, stat)
|
||||||
|
end
|
||||||
|
|
||||||
|
function StatPointUI.OnPointsReset(guid)
|
||||||
|
local total = 0
|
||||||
|
for _, points in pairs(StatPointUI.cache[guid]) do
|
||||||
|
total = total+points
|
||||||
|
end
|
||||||
|
CharDBQuery("UPDATE character_stats_extra SET `str`=0, `agi`=0, `stam`=0, `int`=0, `spirit`=0, `points`="..total.." WHERE `guid`="..guid..";");
|
||||||
|
StatPointUI.cache[guid] = {0, 0, 0, 0, 0, total};
|
||||||
|
StatPointUI.ResetStats(guid)
|
||||||
|
end
|
||||||
|
|
||||||
|
function OnFullCacheRequest(player, argTable)
|
||||||
|
player:SendServerResponse(config.Prefix, 1, StatPointUI.cache[player:GetGUIDLow()])
|
||||||
|
end
|
||||||
|
|
||||||
|
function OnSpendPointRequest(player, argTable)
|
||||||
|
if(StatPointUI.cache[player:GetGUIDLow()][6] > 0) then
|
||||||
|
-- Double check that the stat requested is actually a valid number
|
||||||
|
if(tonumber(argTable[2]) <= 5 and tonumber(argTable[2]) >= 0) then
|
||||||
|
StatPointUI.OnPointSpent(player:GetGUIDLow(), argTable[2])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
player:SendBroadcastMessage("You have no points left!")
|
||||||
|
end
|
||||||
|
player:SendServerResponse(config.Prefix, 1, StatPointUI.cache[player:GetGUIDLow()])
|
||||||
|
end
|
||||||
|
|
||||||
|
function OnStatResetRequest(player, argTable)
|
||||||
|
StatPointUI.OnPointsReset(player:GetGUIDLow())
|
||||||
|
player:SendServerResponse(config.Prefix, 1, StatPointUI.cache[player:GetGUIDLow()])
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterPlayerEvent(3, StatPointUI.OnLogin)
|
||||||
|
RegisterServerEvent(33, StatPointUI.OnElunaStartup)
|
||||||
|
RegisterClientRequests(config)
|
||||||
10
Example_StatPointUI/Server/character_stats_extra.sql
Normal file
10
Example_StatPointUI/Server/character_stats_extra.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- Dumping structure for table character.character_stats_extra
|
||||||
|
CREATE TABLE IF NOT EXISTS `character_stats_extra` (
|
||||||
|
`guid` int(11) DEFAULT NULL,
|
||||||
|
`str` int(11) DEFAULT NULL,
|
||||||
|
`agi` int(11) DEFAULT NULL,
|
||||||
|
`stam` int(11) DEFAULT NULL,
|
||||||
|
`int` int(11) DEFAULT NULL,
|
||||||
|
`spirit` int(11) DEFAULT NULL,
|
||||||
|
`points` int(11) DEFAULT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
50
README.md
50
README.md
@ -1,2 +1,50 @@
|
|||||||
# CSMH
|
# CSMH
|
||||||
Client and Server Message Handler framework for communication between Eluna and WoW
|
|
||||||
|
### 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 consists of two parts, the Client Message Handler and the Server Message Handler respectively.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### What does CSMH do and *not* do?
|
||||||
|
CSMH intentionally does not do certain things, primarily for ease of integration and personal preferences around implementations like; Flood protection, data validation, packet filtering etc.
|
||||||
|
|
||||||
|
CSMH **does** verify the sender and recipient of a message, to prevent messages being sent and accepted on someone else's behalf.
|
||||||
|
|
||||||
|
CSMH **does not** natively check and verify what is being sent to and from the client and server. It is up to you to sanity check data being sent back and forth. A good rule of thumb is to never inherently trust data being sent to the server from the client, you need to verify data before accepting it.
|
||||||
|
|
||||||
|
CSMH also **does not** have any form of built in flood protection. This is again up to you to decide on an implementation of your choice.
|
||||||
|
|
||||||
|
### How do I use CSMH?
|
||||||
|
I would recommend going through the examples provided in this repo to get a feel for how registering, sending and receiving data on both the client and the server works. A full API and how-to will be posted soon™.
|
||||||
|
|
||||||
|
## Installation:
|
||||||
|
|
||||||
|
### Server:
|
||||||
|
- Copy everything from the Server directory to your Eluna scripts directory. That's it!
|
||||||
|
|
||||||
|
### Client:
|
||||||
|
The CMH can be distributed either as a stand-alone addon, or through a patch. Files are provided for both solutions in the Client directory, but be aware of the differences:
|
||||||
|
|
||||||
|
#### Addon:
|
||||||
|
- Copy **CMH.Lua**, **CMH.toc** and **smallfolk.lua** to **Interface\AddOns\CMH**
|
||||||
|
|
||||||
|
#### MPQ Patch:
|
||||||
|
- Copy **CMH.Lua**, **FrameXML.toc** and **smallfolk.lua** to **Interface\FrameXML**
|
||||||
|
|
||||||
|
## API:
|
||||||
|
Soon™
|
||||||
|
|
||||||
|
## Credits:
|
||||||
|
- [Stoneharry](https://github.com/stoneharry)
|
||||||
|
- [Terrorblade](https://github.com/Terrorblade)
|
||||||
|
- Kaev
|
||||||
|
- [Rochet / AIO](https://github.com/Rochet2)
|
||||||
|
- [Eluna](https://github.com/ElunaLuaEngine/Eluna)
|
||||||
|
- [smallfolk](https://github.com/gvx/Smallfolk)
|
||||||
50
README.md.bak
Normal file
50
README.md.bak
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# 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 consists of two parts, the Client Message Handler and the Server Message Handler respectively.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### What does CSMH do and *not* do?
|
||||||
|
CSMH intentionally does not do certain things, primarily for ease of integration and personal preferences around implementations like; Flood protection, data validation, packet filtering etc.
|
||||||
|
|
||||||
|
CSMH **does** verify the sender and recipient of a message, to prevent messages being sent and accepted on someone else's behalf.
|
||||||
|
|
||||||
|
CSMH **does not** natively check and verify what is being sent to and from the client and server. It is up to you to sanity check data being sent back and forth. A good rule of thumb is to never inherently trust data being sent to the server from the client, you need to verify data before accepting it.
|
||||||
|
|
||||||
|
CSMH also **does not** have any form of built in flood protection. This is again up to you to decide on an implementation of your choice.
|
||||||
|
|
||||||
|
### How do I use CSMH?
|
||||||
|
I would recommend going through the examples provided in this repo to get a feel for how registering, sending and receiving data on both the client and the server works. A full API and how-to will be posted soon™.
|
||||||
|
|
||||||
|
## Installation:
|
||||||
|
|
||||||
|
### Server:
|
||||||
|
- Copy everything from the Server directory to your Eluna scripts directory. That's it!
|
||||||
|
|
||||||
|
### Client:
|
||||||
|
CSMH can be distributed either as a stand-alone addon, or through a patch. Files are provided for both solutions in the Client directory, but be aware of the differences:
|
||||||
|
|
||||||
|
#### Addon:
|
||||||
|
- Copy **CMH.Lua**, **CMH.toc** and **smallfolk.lua** to **Interface\AddOns\CMH**
|
||||||
|
|
||||||
|
#### MPQ Patch:
|
||||||
|
- Copy **CMH.Lua**, **FrameXML.toc** and **smallfolk.lua** to **Interface\FrameXML**
|
||||||
|
|
||||||
|
## API:
|
||||||
|
Soon™
|
||||||
|
|
||||||
|
## Credits:
|
||||||
|
- [Stoneharry](https://github.com/stoneharry)
|
||||||
|
- [Terrorblade](https://github.com/Terrorblade)
|
||||||
|
- Kaev
|
||||||
|
- [Rochet / AIO](https://github.com/Rochet2)
|
||||||
|
- [Eluna](https://github.com/ElunaLuaEngine/Eluna)
|
||||||
|
- [smallfolk](https://github.com/gvx/Smallfolk)
|
||||||
218
Server/.lib/smallfolk.lua
Normal file
218
Server/.lib/smallfolk.lua
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
local M = {}
|
||||||
|
Smallfolk = M
|
||||||
|
local expect_object, dump_object
|
||||||
|
local error, tostring, pairs, type, floor, huge, concat = error, tostring, pairs, type, math.floor, math.huge, table.concat
|
||||||
|
|
||||||
|
local dump_type = {}
|
||||||
|
|
||||||
|
function dump_type:string(nmemo, memo, acc)
|
||||||
|
local nacc = #acc
|
||||||
|
acc[nacc + 1] = '"'
|
||||||
|
acc[nacc + 2] = self:gsub('"', '""')
|
||||||
|
acc[nacc + 3] = '"'
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function dump_type:number(nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ("%.17g"):format(self)
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function dump_type:table(nmemo, memo, acc)
|
||||||
|
--[[
|
||||||
|
if memo[self] then
|
||||||
|
acc[#acc + 1] = '@'
|
||||||
|
acc[#acc + 1] = tostring(memo[self])
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
nmemo = nmemo + 1
|
||||||
|
]]
|
||||||
|
memo[self] = nmemo
|
||||||
|
acc[#acc + 1] = '{'
|
||||||
|
local nself = #self
|
||||||
|
for i = 1, nself do -- don't use ipairs here, we need the gaps
|
||||||
|
nmemo = dump_object(self[i], nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ','
|
||||||
|
end
|
||||||
|
for k, v in pairs(self) do
|
||||||
|
if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > nself then
|
||||||
|
nmemo = dump_object(k, nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ':'
|
||||||
|
nmemo = dump_object(v, nmemo, memo, acc)
|
||||||
|
acc[#acc + 1] = ','
|
||||||
|
end
|
||||||
|
end
|
||||||
|
acc[#acc] = acc[#acc] == '{' and '{}' or '}'
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function dump_object(object, nmemo, memo, acc)
|
||||||
|
if object == true then
|
||||||
|
acc[#acc + 1] = 't'
|
||||||
|
elseif object == false then
|
||||||
|
acc[#acc + 1] = 'f'
|
||||||
|
elseif object == nil then
|
||||||
|
acc[#acc + 1] = 'n'
|
||||||
|
elseif object ~= object then
|
||||||
|
if (''..object):sub(1,1) == '-' then
|
||||||
|
acc[#acc + 1] = 'N'
|
||||||
|
else
|
||||||
|
acc[#acc + 1] = 'Q'
|
||||||
|
end
|
||||||
|
elseif object == huge then
|
||||||
|
acc[#acc + 1] = 'I'
|
||||||
|
elseif object == -huge then
|
||||||
|
acc[#acc + 1] = 'i'
|
||||||
|
else
|
||||||
|
local t = type(object)
|
||||||
|
if not dump_type[t] then
|
||||||
|
error('cannot dump type ' .. t)
|
||||||
|
end
|
||||||
|
return dump_type[t](object, nmemo, memo, acc)
|
||||||
|
end
|
||||||
|
return nmemo
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.dumps(object)
|
||||||
|
local nmemo = 0
|
||||||
|
local memo = {}
|
||||||
|
local acc = {}
|
||||||
|
dump_object(object, nmemo, memo, acc)
|
||||||
|
return concat(acc)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function invalid(i)
|
||||||
|
error('invalid input at position ' .. i)
|
||||||
|
end
|
||||||
|
|
||||||
|
local nonzero_digit = {['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
|
||||||
|
local is_digit = {['0'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true}
|
||||||
|
local function expect_number(string, start)
|
||||||
|
local i = start
|
||||||
|
local head = string:sub(i, i)
|
||||||
|
if head == '-' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
end
|
||||||
|
if nonzero_digit[head] then
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
until not is_digit[head]
|
||||||
|
elseif head == '0' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
else
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
if head == '.' then
|
||||||
|
local oldi = i
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
until not is_digit[head]
|
||||||
|
if i == oldi + 1 then
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if head == 'e' or head == 'E' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
if head == '+' or head == '-' then
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
end
|
||||||
|
if not is_digit[head] then
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
head = string:sub(i, i)
|
||||||
|
until not is_digit[head]
|
||||||
|
end
|
||||||
|
return tonumber(string:sub(start, i - 1)), i
|
||||||
|
end
|
||||||
|
|
||||||
|
local expect_object_head = {
|
||||||
|
t = function(string, i) return true, i end,
|
||||||
|
f = function(string, i) return false, i end,
|
||||||
|
n = function(string, i) return nil, i end,
|
||||||
|
Q = function(string, i) return -(0/0), i end,
|
||||||
|
N = function(string, i) return 0/0, i end,
|
||||||
|
I = function(string, i) return 1/0, i end,
|
||||||
|
i = function(string, i) return -1/0, i end,
|
||||||
|
['"'] = function(string, i)
|
||||||
|
local nexti = i - 1
|
||||||
|
repeat
|
||||||
|
nexti = string:find('"', nexti + 1, true) + 1
|
||||||
|
until string:sub(nexti, nexti) ~= '"'
|
||||||
|
return string:sub(i, nexti - 2):gsub('""', '"'), nexti
|
||||||
|
end,
|
||||||
|
['0'] = function(string, i)
|
||||||
|
return expect_number(string, i - 1)
|
||||||
|
end,
|
||||||
|
['{'] = function(string, i, tables)
|
||||||
|
local nt, k, v = {}
|
||||||
|
local j = 1
|
||||||
|
tables[#tables + 1] = nt
|
||||||
|
if string:sub(i, i) == '}' then
|
||||||
|
return nt, i + 1
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
k, i = expect_object(string, i, tables)
|
||||||
|
if string:sub(i, i) == ':' then
|
||||||
|
v, i = expect_object(string, i + 1, tables)
|
||||||
|
nt[k] = v
|
||||||
|
else
|
||||||
|
nt[j] = k
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
local head = string:sub(i, i)
|
||||||
|
if head == ',' then
|
||||||
|
i = i + 1
|
||||||
|
elseif head == '}' then
|
||||||
|
return nt, i + 1
|
||||||
|
else
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
--[[
|
||||||
|
['@'] = function(string, i, tables)
|
||||||
|
local match = string:match('^%d+', i)
|
||||||
|
local ref = tonumber(match)
|
||||||
|
if tables[ref] then
|
||||||
|
return tables[ref], i + #match
|
||||||
|
end
|
||||||
|
invalid(i)
|
||||||
|
end,
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
expect_object_head['1'] = expect_object_head['0']
|
||||||
|
expect_object_head['2'] = expect_object_head['0']
|
||||||
|
expect_object_head['3'] = expect_object_head['0']
|
||||||
|
expect_object_head['4'] = expect_object_head['0']
|
||||||
|
expect_object_head['5'] = expect_object_head['0']
|
||||||
|
expect_object_head['6'] = expect_object_head['0']
|
||||||
|
expect_object_head['7'] = expect_object_head['0']
|
||||||
|
expect_object_head['8'] = expect_object_head['0']
|
||||||
|
expect_object_head['9'] = expect_object_head['0']
|
||||||
|
expect_object_head['-'] = expect_object_head['0']
|
||||||
|
expect_object_head['.'] = expect_object_head['0']
|
||||||
|
|
||||||
|
expect_object = function(string, i, tables)
|
||||||
|
local head = string:sub(i, i)
|
||||||
|
if expect_object_head[head] then
|
||||||
|
return expect_object_head[head](string, i + 1, tables)
|
||||||
|
end
|
||||||
|
invalid(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.loads(string, maxsize)
|
||||||
|
if #string > (maxsize or 10000) then
|
||||||
|
error 'input too large'
|
||||||
|
end
|
||||||
|
return (expect_object(string, 1, {}))
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
145
Server/SMH.lua
Normal file
145
Server/SMH.lua
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
local smallfolk = smallfolk or require("smallfolk")
|
||||||
|
local SMH = {}
|
||||||
|
local links = {}
|
||||||
|
|
||||||
|
function SMH.OnReceive(event, sender, _type, prefix, _, target)
|
||||||
|
if not sender or not target or not sender.GetName or not target.GetName or type(sender) ~= "userdata" or type(target) ~= "userdata" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if sender:GetName() == target:GetName() and _type == 7 then
|
||||||
|
local source, functionId, link, linkCount, MSG = prefix:match("(%D)(%d%d%d)(%d%d)(%d%d)(.+)");
|
||||||
|
if not source or not functionId or not link or not linkCount or not MSG then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if(source == "C") then
|
||||||
|
functionId, link, linkCount = tonumber(functionId), tonumber(link), tonumber(linkCount);
|
||||||
|
|
||||||
|
links[sender:GetGUIDLow()] = links[sender:GetGUIDLow()] or {}
|
||||||
|
links[sender:GetGUIDLow()][functionId] = links[sender:GetGUIDLow()][functionId] or {count = 0};
|
||||||
|
links[sender:GetGUIDLow()][functionId][link] = MSG;
|
||||||
|
links[sender:GetGUIDLow()][functionId].count = links[sender:GetGUIDLow()][functionId].count + 1;
|
||||||
|
if (links[sender:GetGUIDLow()][functionId].count ~= linkCount) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fullMessage = table.concat(links[sender:GetGUIDLow()][functionId]);
|
||||||
|
links[sender:GetGUIDLow()][functionId] = {count = 0};
|
||||||
|
|
||||||
|
local VarTable = ParseMessage(fullMessage)
|
||||||
|
if not VarTable then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not(SMH[VarTable[1]]) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local func = SMH[VarTable[1]][functionId]
|
||||||
|
if func then
|
||||||
|
_G[func](sender, VarTable)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterServerEvent(30, SMH.OnReceive)
|
||||||
|
|
||||||
|
function RegisterClientRequests(config)
|
||||||
|
-- If a config table with the Prefix already exists, abort loading it into the register.
|
||||||
|
if(SMH[config.Prefix]) then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create subtable for PrefixName
|
||||||
|
SMH[config.Prefix] = {}
|
||||||
|
|
||||||
|
-- Insert function ID and function name into the register table.
|
||||||
|
for functionId, functionName in pairs(config.Functions) do
|
||||||
|
SMH[config.Prefix][functionId] = functionName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ParseMessage(str)
|
||||||
|
local output = {}
|
||||||
|
local valTemp = {}
|
||||||
|
local typeTemp = {}
|
||||||
|
local delim = {"♠", "♥", "♚", "♛", "♜"}
|
||||||
|
|
||||||
|
local valMatch = "[^"..table.concat(delim).."]+"
|
||||||
|
local typeMatch = "["..table.concat(delim).."]+"
|
||||||
|
|
||||||
|
-- Get values
|
||||||
|
for value in str:gmatch(valMatch) do
|
||||||
|
table.insert(valTemp, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get type from delimiter
|
||||||
|
for varType in str:gmatch(typeMatch) do
|
||||||
|
for k, v in pairs(delim) do
|
||||||
|
if(v == varType) then
|
||||||
|
table.insert(typeTemp, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert value to correct type
|
||||||
|
for k, v in pairs(valTemp) do
|
||||||
|
local varType = typeTemp[k]
|
||||||
|
if(varType == 3) then -- Ints
|
||||||
|
v = tonumber(v)
|
||||||
|
elseif(varType == 4) then -- Tables
|
||||||
|
v = smallfolk.loads(v)
|
||||||
|
elseif(varType == 5) then -- Booleans
|
||||||
|
if(v == "true") then v = true else v = false end
|
||||||
|
end
|
||||||
|
table.insert(output, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
valTemp = nil
|
||||||
|
typeTemp = nil
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
function Player:SendServerResponse(prefix, functionId, ...)
|
||||||
|
-- ♠ = Prefix prefix
|
||||||
|
-- ♥ = ArgumentPrefix for Strings
|
||||||
|
-- ♚ = ArgumentPrefix for Ints
|
||||||
|
-- ♛ = ArgumentPrefix for Tables
|
||||||
|
-- ♜ = ArgumentPrefix for Boolean
|
||||||
|
|
||||||
|
local arg = {...}
|
||||||
|
local splitLength = 230
|
||||||
|
local msg = "♠" .. prefix
|
||||||
|
|
||||||
|
for _, v in pairs(arg) do
|
||||||
|
if(type(v) == "string") then
|
||||||
|
msg = msg .. "♥"
|
||||||
|
elseif(type(v) == "number") then
|
||||||
|
msg = msg .. "♚"
|
||||||
|
elseif(type(v) == "table") then
|
||||||
|
-- use Smallfolk to convert table structure to string
|
||||||
|
v = smallfolk.dumps(v)
|
||||||
|
msg = msg .. "♛"
|
||||||
|
elseif(type(v) == "boolean") then
|
||||||
|
v = tostring(v)
|
||||||
|
msg = msg .. "♜"
|
||||||
|
end
|
||||||
|
msg = msg .. v
|
||||||
|
end
|
||||||
|
|
||||||
|
local splits = math.ceil(msg:len() / splitLength)
|
||||||
|
local send
|
||||||
|
local counter = 1
|
||||||
|
for i=1, msg:len(), splitLength do
|
||||||
|
send = string.format("%01s%02d%03d%03d", "S", functionId, counter, splits)
|
||||||
|
if ((i + splitLength) > msg:len()) then
|
||||||
|
send = send .. msg:sub(i, msg:len())
|
||||||
|
else
|
||||||
|
send = send .. msg:sub(i, i + splitLength - 1)
|
||||||
|
end
|
||||||
|
counter = counter + 1
|
||||||
|
self:SendAddonMessage(send, "", 7, self)
|
||||||
|
end
|
||||||
|
end
|
||||||
144
Server/SMH.lua.bak
Normal file
144
Server/SMH.lua.bak
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
local smallfolk = smallfolk or require("smallfolk")
|
||||||
|
local SMH = {}
|
||||||
|
local links = {}
|
||||||
|
|
||||||
|
function SMH.OnReceive(event, sender, _type, prefix, _, target)
|
||||||
|
if not sender or not target or not sender.GetName or not target.GetName or type(sender) ~= "userdata" or type(target) ~= "userdata" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if sender:GetName() == target:GetName() and _type == 7 then
|
||||||
|
local source, functionId, link, linkCount, MSG = prefix:match("(%D)(%d%d%d)(%d%d)(%d%d)(.+)");
|
||||||
|
if not source or not functionId or not link or not linkCount or not MSG then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if(source == "C") then
|
||||||
|
functionId, link, linkCount = tonumber(functionId), tonumber(link), tonumber(linkCount);
|
||||||
|
|
||||||
|
links[sender:GetGUIDLow()] = links[sender:GetGUIDLow()] or {}
|
||||||
|
links[sender:GetGUIDLow()][functionId] = links[sender:GetGUIDLow()][functionId] or {count = 0};
|
||||||
|
links[sender:GetGUIDLow()][functionId][link] = MSG;
|
||||||
|
links[sender:GetGUIDLow()][functionId].count = links[sender:GetGUIDLow()][functionId].count + 1;
|
||||||
|
if (links[sender:GetGUIDLow()][functionId].count ~= linkCount) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fullMessage = table.concat(links[sender:GetGUIDLow()][functionId]);
|
||||||
|
links[sender:GetGUIDLow()][functionId] = {count = 0};
|
||||||
|
|
||||||
|
local VarTable = ParseMessage(fullMessage)
|
||||||
|
if not VarTable then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not(SMH[VarTable[1]]) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local func = SMH[VarTable[1]][functionId]
|
||||||
|
if func then
|
||||||
|
_G[func](sender, VarTable)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterServerEvent(30, SMH.OnReceive)
|
||||||
|
|
||||||
|
function RegisterClientRequests(config)
|
||||||
|
-- If a config table with the Prefix already exists, abort loading it into the register.
|
||||||
|
if(SMH[config.Prefix]) then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create subtable for PrefixName
|
||||||
|
SMH[config.Prefix] = {}
|
||||||
|
|
||||||
|
-- Insert function ID and function name into the register table.
|
||||||
|
for functionId, functionName in pairs(config.Functions) do
|
||||||
|
SMH[config.Prefix][functionId] = functionName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ParseMessage(str)
|
||||||
|
local output = {}
|
||||||
|
local valTemp = {}
|
||||||
|
local typeTemp = {}
|
||||||
|
local delim = {"♠", "♥", "♚", "♛", "♜"}
|
||||||
|
|
||||||
|
local valMatch = "[^"..table.concat(delim).."]+"
|
||||||
|
local typeMatch = "["..table.concat(delim).."]+"
|
||||||
|
|
||||||
|
-- Get values
|
||||||
|
for value in str:gmatch(valMatch) do
|
||||||
|
table.insert(valTemp, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get type from delimiter
|
||||||
|
for varType in str:gmatch(typeMatch) do
|
||||||
|
for k, v in pairs(delim) do
|
||||||
|
if(v == varType) then
|
||||||
|
table.insert(typeTemp, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert value to correct type
|
||||||
|
for k, v in pairs(valTemp) do
|
||||||
|
local varType = typeTemp[k]
|
||||||
|
if(varType == 3) then -- Ints
|
||||||
|
v = tonumber(v)
|
||||||
|
elseif(varType == 4) then -- Tables
|
||||||
|
v = smallfolk.loads(v)
|
||||||
|
elseif(varType == 5) then -- Booleans
|
||||||
|
if(v == "true") then v = true else v = false end
|
||||||
|
end
|
||||||
|
table.insert(output, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
valTemp = nil
|
||||||
|
typeTemp = nil
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
function Player:SendServerResponse(prefix, functionId, ...)
|
||||||
|
-- ♠ = Prefix prefix
|
||||||
|
-- ♥ = ArgumentPrefix for Strings
|
||||||
|
-- ♚ = ArgumentPrefix for Ints
|
||||||
|
-- ♛ = ArgumentPrefix for Tables
|
||||||
|
-- ♜ = ArgumentPrefix for Boolean
|
||||||
|
|
||||||
|
local arg = {...}
|
||||||
|
local splitLength = 230
|
||||||
|
local msg = "♠" .. prefix
|
||||||
|
|
||||||
|
for _, v in pairs(arg) do
|
||||||
|
if(type(v) == "string") then
|
||||||
|
msg = msg .. "♥"
|
||||||
|
elseif(type(v) == "number") then
|
||||||
|
msg = msg .. "♚"
|
||||||
|
elseif(type(v) == "table") then
|
||||||
|
-- use Smallfolk to convert table structure to string
|
||||||
|
v = smallfolk.dumps(v)
|
||||||
|
msg = msg .. "♛"
|
||||||
|
elseif(type(v) == "boolean") then
|
||||||
|
v = tostring(v)
|
||||||
|
msg = msg .. "♜"
|
||||||
|
end
|
||||||
|
msg = msg .. v
|
||||||
|
end
|
||||||
|
|
||||||
|
local splits = math.ceil(msg:len() / splitLength)
|
||||||
|
local send
|
||||||
|
local counter = 1
|
||||||
|
for i=1, msg:len(), splitLength do
|
||||||
|
send = string.format("%01s%02d%03d%03d", "S", functionId, counter, splits)
|
||||||
|
if ((i + splitLength) > msg:len()) then
|
||||||
|
send = send .. msg:sub(i, msg:len())
|
||||||
|
else
|
||||||
|
send = send .. msg:sub(i, i + splitLength - 1)
|
||||||
|
end
|
||||||
|
counter = counter + 1
|
||||||
|
self:SendAddonMessage(send, "", 7, self)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user