2 -- We can't send more than 256 bytes per message.
4 local max_msg_size
= 256-4-1-1 -- To allow room for "QHpr\t" ... "\0"
5 local max_chunk_size
= max_msg_size
- 2 -- To allow room for prefix of "x:"
6 local enabled_sharing
= false
8 function QuestHelper
:SendData(data
,name
)
9 if QuestHelper_Pref
.comm
then
11 self
:TextOut("SENT/"..name
..":|cff00ff00"..data
.."|r")
13 self
:TextOut("SENT/PARTY:|cff00ff00"..data
.."|r")
17 if string.len(data
) > max_msg_size
then
18 -- Large pieces of data are broken into pieces.
21 local chunk
= string.sub(data
, i
, i
+ max_chunk_size
- 1)
22 i
= i
+ max_chunk_size
23 if i
> string.len(data
) then
25 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "X:"..chunk
, name
and "WHISPER" or "PARTY", name
)
28 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "x:"..chunk
, name
and "WHISPER" or "PARTY", name
)
32 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", data
, name
and "WHISPER" or "PARTY", name
)
43 local function EscapeString(str
)
44 for i
= 1,#escapes
,2 do
45 str
= string.gsub(str
, escapes
[i
], escapes
[i
+1])
51 local function UnescapeString(str
)
52 for i
= #escapes
,1,-2 do
53 str
= string.gsub(str
, escapes
[i
], escapes
[i
-1])
60 local function GetList(str
)
61 while table.remove(temp_table
) do end
62 for arg
in string.gmatch(str
, "([^:]*):?") do
63 table.insert(temp_table
, arg
)
66 -- If i remove this assert, make sure I keep the side effect.
67 assert(table.remove(temp_table
) == "")
75 QuestHelper sends its addon messages with the prefix 'QHpr'
78 Sent to new users, letting them know we know nothing about them. VERSION is the communication they are using.
79 Both clients normally send a syn: and bother respond to the other with hello:. If one client just reloaded,
80 this will just be a syn: from the reloading client and a hello: from the existing client.
82 As a special case, syn:0 indicates that the user has turned off objective sharing. Don't need to reply with
83 hello in this case. You can, but of course, they won't see or care.
86 Sent in response to syn: VERSION is the communication version they are using.
88 id:<ID>:<CATEGORY>:<WHAT>
89 Lets other users know that they are sharing an objective under ID. CATEGORY and WHAT are escaped strings,
90 to be passed to QuestHelper:GetObjective().
92 dep:<ID>:<DEP1>:<DEP2>:...
93 Lets other users know that one of their objectives depends on another. ID is the id of the objective, and
94 is followed by the IDs of the objectives it depends on.
95 For the sake of sanity, only quest objectives are allowed to have dependencies, and can't depend on
96 other quest objectives.
98 upd:<ID>:<PRI>:<HAVE>:<NEED>
99 Lets other users know that something about one of their shared objectives has changed.
102 Lets other users know that they have removed an objective that they were previously sharing.
105 User wants to send a message larger than what blizzard will allow.
106 DATA is appended to the previous data chunk.
107 Will ignore if we don't know their version yet, since they might have been in the middle
108 of sending something when we noticed it.
111 Same as x:, but this is the last chunk of data, and after this the combined data can be used as a message.
115 local shared_objectives
= {}
117 local shared_users
= 0
119 local function CreateUser(name
)
120 if QuestHelper_Pref
.comm
then
121 QuestHelper
:TextOut("Created user: "..name
)
124 user
= QuestHelper
:CreateTable()
129 user
.obj
=QuestHelper
:CreateTable()
131 for i
, obj
in ipairs(shared_objectives
) do -- Mark this user as knowing nothing about any of our objectives.
139 local function SharedObjectiveReason(user
, objective
)
140 if objective
.cat
== "quest" then
141 return QHFormat("PEER_TURNIN", user
.name
, select(3, string.find(objective
.obj
, "^%d+/%d*/(.*)$")) or "something impossible")
142 elseif objective
.cat
== "loc" then
144 local _
, _
, c
, z
, x
, y
= string.find(objective
.obj
, "^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
147 _
, _
, i
, x
, y
= string.find(objective
.obj
, "^(%d+),([%d%.]+),([%d%.]+)$")
149 i
= QuestHelper_IndexLookup
[c
] and QuestHelper_IndexLookup
[c
][z
]
152 return QHFormat("PEER_LOCATON", user
.name
, i
and QuestHelper_NameLookup
[i
] or "the black empty void")
153 elseif objective
.cat
== "item" then
154 return QHFormat("PEER_ITEM", user
.name
, objective
.obj
)
156 return QHFormat("PEER_OTHER", user
.name
, objective
.obj
)
160 local function ReleaseUser(user
)
161 for id
, objective
in pairs(user
.obj
) do
162 QuestHelper
:SetObjectiveProgress(objective
, user
.name
, nil, nil)
163 QuestHelper
:RemoveObjectiveWatch(objective
, SharedObjectiveReason(user
, objective
))
167 for i
, obj
in ipairs(shared_objectives
) do
172 if QuestHelper_Pref
.comm
then
173 QuestHelper
:TextOut("Released user: "..user
.name
)
176 QuestHelper
:ReleaseTable(user
.obj
)
177 QuestHelper
:ReleaseTable(user
)
180 function QuestHelper
:DoShareObjective(objective
)
181 for i
= 1, #shared_objectives
do assert(objective
~= shared_objectives
[i
]) end -- Just testing.
183 assert(objective
.peer
== nil)
184 objective
.peer
= self
:CreateTable()
186 for name
, user
in pairs(users
) do
187 -- Peers know nothing about this objective.
188 objective
.peer
[user
] = 0
191 for o
in pairs(objective
.before
) do
193 for u
, l
in pairs(o
.peer
) do
194 -- Peers don't know about this dependency.
195 o
.peer
[u
] = math
.min(l
, 1)
200 table.insert(shared_objectives
, objective
)
203 function QuestHelper
:DoUnshareObjective(objective
)
204 for i
= 1, #shared_objectives
do
205 if objective
== shared_objectives
[i
] then
206 local need_announce
= false
208 assert(objective
.peer
)
210 for user
, level
in pairs(objective
.peer
) do
215 objective
.peer
[user
] = nil
218 self
:ReleaseTable(objective
.peer
)
221 if need_announce
then
222 self
:SendData("rem:"..objective
.id
)
225 table.remove(shared_objectives
, i
)
230 assert(false) -- Should have found the objective.
233 function QuestHelper
:HandleRemoteData(data
, name
)
234 if enabled_sharing
then
235 local user
= users
[name
]
237 user
= CreateUser(name
)
241 local _
, _
, message_type
, message_data
= string.find(data
, "^(.-):(.*)$")
243 if message_type
== "x" then
244 if user
.version
> 0 then
245 --self:TextOut("RECV/"..name..":<chunk>")
246 user
.xmsg
= (user
.xmsg
or "")..message_data
248 --self:TextOut("RECV/"..name..":<ignored chunk>")
251 elseif message_type
== "X" then
252 if user
.version
> 0 then
253 --self:TextOut("RECV/"..name..":<chunk end>")
254 _
, _
, message_type
, message_data
= string.find((user
.xmsg
or "")..message_data
, "^(.-):(.*)$")
257 --self:TextOut("RECV/"..name..":<ignored chunk end>")
262 if QuestHelper_Pref
.comm
then
263 self
:TextOut("RECV/"..name
..":|cff00ff00"..data
.."|r")
266 if message_type
== "syn" then
267 -- User has just noticed us. Is either new, or reloaded their UI.
269 local new_version
= tonumber(message_data
) or 0
271 if new_version
== 0 and user
.version
> 0 then
272 shared_users
= shared_users
- 1
273 elseif new_version
> 0 and user
.version
== 0 then
274 shared_users
= shared_users
+ 1
277 self
.sharing
= shared_users
> 0
278 user
.version
= new_version
280 for i
, obj
in ipairs(shared_objectives
) do -- User apparently knows nothing about us.
285 for id
, obj
in pairs(user
.obj
) do -- And apparently all their objective ids are now null and void.
286 self
:SetObjectiveProgress(obj
, user
.name
, nil, nil)
287 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
291 -- Say hello to the new user.
292 if user
.version
> 0 then
293 self
:SendData("hello:"..comm_version
, name
)
295 elseif message_type
== "hello" then
296 local new_version
= tonumber(message_data
) or 0
298 if new_version
== 0 and user
.version
> 0 then
299 shared_users
= shared_users
- 1
300 elseif new_version
> 0 and user
.version
== 0 then
301 shared_users
= shared_users
+ 1
304 self
.sharing
= shared_users
> 0
305 user
.version
= new_version
307 if user
.version
> comm_version
then
308 self
:TextOut(QHFormat("PEER_NEWER", name
))
309 elseif user
.version
< comm_version
then
310 self
:TextOut(QHFormat("PEER_OLDER", name
))
313 elseif message_type
== "id" then
314 local list
= GetList(message_data
)
315 local id
, cat
, what
= tonumber(list
[1]), list
[2], list
[3]
316 if id
and cat
and what
and not user
.obj
[id
] then
317 user
.obj
[id
] = self
:GetObjective(UnescapeString(cat
), UnescapeString(what
))
318 self
:AddObjectiveWatch(user
.obj
[id
], SharedObjectiveReason(user
, user
.obj
[id
]))
320 elseif message_type
== "dep" then
321 local list
= GetList(message_data
)
322 local id
= tonumber(list
[1])
323 local obj
= id
and user
.obj
[id
]
324 if obj
and obj
.cat
== "quest" then
326 local depid
= tonumber(list
[i
])
327 local depobj
= depid
and user
.obj
[depid
]
328 if depobj
and depobj
.cat
~= "quest" then
329 self
:ObjectiveObjectDependsOn(obj
, depobj
)
331 if depobj
.cat
== "item" then
332 if not depobj
.quest
then
339 elseif message_type
== "upd" then
340 local _
, _
, id
, priority
, have
, need
= string.find(message_data
, "^(%d+):(%d+):([^:]*):(.*)")
341 id
, priority
= tonumber(id
), tonumber(priority
)
343 if id
and priority
and have
and need
then
344 local obj
= user
.obj
[id
]
346 have
, need
= UnescapeString(have
), UnescapeString(need
)
347 have
, need
= tonumber(have
) or have
, tonumber(need
) or need
348 if have
== "" or need
== "" then have
, need
= nil, nil end
349 self
:SetObjectivePriority(obj
, priority
)
350 self
:SetObjectiveProgress(obj
, user
.name
, have
, need
)
353 elseif message_type
== "rem" then
354 local id
= tonumber(message_data
)
355 local obj
= id
and user
.obj
[id
]
357 self
:SetObjectiveProgress(obj
, name
, nil, nil)
358 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
362 self
:TextOut(QHFormat("UNKNOWN_MESSAGE", message_type
, name
))
367 function QuestHelper
:PumpCommMessages()
368 if shared_users
> 0 and enabled_sharing
then
369 local best_level
, best_count
, best_obj
= 3, 255, nil
371 for i
, o
in pairs(shared_objectives
) do
372 local level
, count
= 255, 0
374 for u
, l
in pairs(o
.peer
) do
375 if u
.version
> 0 then
376 level
= math
.min(l
, level
)
381 if level
< best_level
or (level
== best_level
and count
> best_count
) then
382 best_level
, best_count
, best_obj
= level
, count
, o
387 if best_level
== 0 then
388 self
:SendData("id:"..best_obj
.id
..":"..EscapeString(best_obj
.cat
)..":"..EscapeString(best_obj
.obj
))
390 elseif best_level
== 1 then
391 if next(best_obj
.after
, nil) then
392 local data
, meaningful
= "dep:"..best_obj
.id
, false
393 for o
in pairs(best_obj
.after
) do
395 data
= data
.. ":" .. o
.id
404 elseif best_level
== 2 then
405 local prog
= best_obj
.progress
and best_obj
.progress
[UnitName("player")]
407 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
..":"..EscapeString(prog
[1])..":"..EscapeString(prog
[2]))
409 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
.."::")
414 for u
in pairs(best_obj
.peer
) do -- All peers have just seen this.
415 if u
.version
> 0 then
416 best_obj
.peer
[u
] = math
.max(best_obj
.peer
[u
], best_level
)
423 function QuestHelper
:HandlePartyChange()
424 if enabled_sharing
then
425 for name
, user
in pairs(users
) do
430 local name
, realm
= UnitName("party"..i
)
431 -- For some mysterous reason, out of range party members return an empty string as the realm name instead of nil.
432 if name
and name
~= UNKNOWNOBJECT
and not realm
or realm
== "" then
433 local user
= users
[name
]
435 user
= CreateUser(name
)
439 if not user
.syn_req
then
440 self
:SendData("syn:"..comm_version
, name
)
450 for name
, user
in pairs(users
) do
451 if not user
.seen
then
454 elseif user
.version
> 0 then
460 self
.sharing
= count
> 0
464 function QuestHelper
:EnableSharing()
465 if not enabled_sharing
then
466 enabled_sharing
= true
467 self
:HandlePartyChange()
471 function QuestHelper
:DisableSharing()
472 if enabled_sharing
then
473 enabled_sharing
= false
474 for name
, user
in pairs(users
) do
475 if user
.version
> 0 then self
:SendData("syn:0", name
) end