1 QuestHelper_File
["comm.lua"] = "Development Version"
3 -- We can't send more than 256 bytes per message.
5 local max_msg_size
= 256-4-1-1 -- To allow room for "QHpr\t" ... "\0"
6 local max_chunk_size
= max_msg_size
- 2 -- To allow room for prefix of "x:"
7 local enabled_sharing
= false
9 function QuestHelper
:SendData(data
,name
)
10 if QuestHelper_Pref
.comm
then
12 self
:TextOut("SENT/"..name
..":|cff00ff00"..data
.."|r")
14 self
:TextOut("SENT/PARTY:|cff00ff00"..data
.."|r")
18 if string.len(data
) > max_msg_size
then
19 -- Large pieces of data are broken into pieces.
22 local chunk
= string.sub(data
, i
, i
+ max_chunk_size
- 1)
23 i
= i
+ max_chunk_size
24 if i
> string.len(data
) then
26 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "X:"..chunk
, name
and "WHISPER" or "PARTY", name
)
29 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "x:"..chunk
, name
and "WHISPER" or "PARTY", name
)
33 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", data
, name
and "WHISPER" or "PARTY", name
)
44 local function EscapeString(str
)
45 for i
= 1,#escapes
,2 do
46 str
= string.gsub(str
, escapes
[i
], escapes
[i
+1])
52 local function UnescapeString(str
)
53 for i
= #escapes
,1,-2 do
54 str
= string.gsub(str
, escapes
[i
], escapes
[i
-1])
61 local function GetList(str
)
62 while table.remove(temp_table
) do end
63 for arg
in string.gmatch(str
, "([^:]*):?") do
64 table.insert(temp_table
, arg
)
67 -- If i remove this assert, make sure I keep the side effect.
68 assert(table.remove(temp_table
) == "")
76 QuestHelper sends its addon messages with the prefix 'QHpr'
79 Sent to new users, letting them know we know nothing about them. VERSION is the communication they are using.
80 Both clients normally send a syn: and bother respond to the other with hello:. If one client just reloaded,
81 this will just be a syn: from the reloading client and a hello: from the existing client.
83 As a special case, syn:0 indicates that the user has turned off objective sharing. Don't need to reply with
84 hello in this case. You can, but of course, they won't see or care.
87 Sent in response to syn: VERSION is the communication version they are using.
89 id:<ID>:<CATEGORY>:<WHAT>
90 Lets other users know that they are sharing an objective under ID. CATEGORY and WHAT are escaped strings,
91 to be passed to QuestHelper:GetObjective().
93 dep:<ID>:<DEP1>:<DEP2>:...
94 Lets other users know that one of their objectives depends on another. ID is the id of the objective, and
95 is followed by the IDs of the objectives it depends on.
96 For the sake of sanity, only quest objectives are allowed to have dependencies, and can't depend on
97 other quest objectives.
99 upd:<ID>:<PRI>:<HAVE>:<NEED>
100 Lets other users know that something about one of their shared objectives has changed.
103 Lets other users know that they have removed an objective that they were previously sharing.
106 User wants to send a message larger than what blizzard will allow.
107 DATA is appended to the previous data chunk.
108 Will ignore if we don't know their version yet, since they might have been in the middle
109 of sending something when we noticed it.
112 Same as x:, but this is the last chunk of data, and after this the combined data can be used as a message.
116 local shared_objectives
= {}
118 local shared_users
= 0
120 local function CreateUser(name
)
121 if QuestHelper_Pref
.comm
then
122 QuestHelper
:TextOut("Created user: "..name
)
125 user
= QuestHelper
:CreateTable()
130 user
.obj
=QuestHelper
:CreateTable()
132 for i
, obj
in ipairs(shared_objectives
) do -- Mark this user as knowing nothing about any of our objectives.
140 local function SharedObjectiveReason(user
, objective
)
141 if objective
.cat
== "quest" then
142 return QHFormat("PEER_TURNIN", user
.name
, select(3, string.find(objective
.obj
, "^%d+/%d*/(.*)$")) or "something impossible")
143 elseif objective
.cat
== "loc" then
145 local _
, _
, c
, z
, x
, y
= string.find(objective
.obj
, "^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
148 _
, _
, i
, x
, y
= string.find(objective
.obj
, "^(%d+),([%d%.]+),([%d%.]+)$")
150 i
= QuestHelper_IndexLookup
[c
] and QuestHelper_IndexLookup
[c
][z
]
153 return QHFormat("PEER_LOCATON", user
.name
, i
and QuestHelper_NameLookup
[i
] or "the black empty void")
154 elseif objective
.cat
== "item" then
155 return QHFormat("PEER_ITEM", user
.name
, objective
.obj
)
157 return QHFormat("PEER_OTHER", user
.name
, objective
.obj
)
161 local function ReleaseUser(user
)
162 for id
, objective
in pairs(user
.obj
) do
163 QuestHelper
:SetObjectiveProgress(objective
, user
.name
, nil, nil)
164 QuestHelper
:RemoveObjectiveWatch(objective
, SharedObjectiveReason(user
, objective
))
168 for i
, obj
in ipairs(shared_objectives
) do
173 if QuestHelper_Pref
.comm
then
174 QuestHelper
:TextOut("Released user: "..user
.name
)
177 QuestHelper
:ReleaseTable(user
.obj
)
178 QuestHelper
:ReleaseTable(user
)
181 function QuestHelper
:DoShareObjective(objective
)
182 for i
= 1, #shared_objectives
do assert(objective
~= shared_objectives
[i
]) end -- Just testing.
184 assert(objective
.peer
== nil)
185 objective
.peer
= self
:CreateTable()
187 for name
, user
in pairs(users
) do
188 -- Peers know nothing about this objective.
189 objective
.peer
[user
] = 0
192 for o
in pairs(objective
.before
) do
194 for u
, l
in pairs(o
.peer
) do
195 -- Peers don't know about this dependency.
196 o
.peer
[u
] = math
.min(l
, 1)
201 table.insert(shared_objectives
, objective
)
204 function QuestHelper
:DoUnshareObjective(objective
)
205 for i
= 1, #shared_objectives
do
206 if objective
== shared_objectives
[i
] then
207 local need_announce
= false
209 assert(objective
.peer
)
211 for user
, level
in pairs(objective
.peer
) do
216 objective
.peer
[user
] = nil
219 self
:ReleaseTable(objective
.peer
)
222 if need_announce
then
223 self
:SendData("rem:"..objective
.id
)
226 table.remove(shared_objectives
, i
)
231 assert(false) -- Should have found the objective.
234 function QuestHelper
:HandleRemoteData(data
, name
)
235 if enabled_sharing
then
236 local user
= users
[name
]
238 user
= CreateUser(name
)
242 local _
, _
, message_type
, message_data
= string.find(data
, "^(.-):(.*)$")
244 if message_type
== "x" then
245 if user
.version
> 0 then
246 --self:TextOut("RECV/"..name..":<chunk>")
247 user
.xmsg
= (user
.xmsg
or "")..message_data
249 --self:TextOut("RECV/"..name..":<ignored chunk>")
252 elseif message_type
== "X" then
253 if user
.version
> 0 then
254 --self:TextOut("RECV/"..name..":<chunk end>")
255 _
, _
, message_type
, message_data
= string.find((user
.xmsg
or "")..message_data
, "^(.-):(.*)$")
258 --self:TextOut("RECV/"..name..":<ignored chunk end>")
263 if QuestHelper_Pref
.comm
then
264 self
:TextOut("RECV/"..name
..":|cff00ff00"..data
.."|r")
267 if message_type
== "syn" then
268 -- User has just noticed us. Is either new, or reloaded their UI.
270 local new_version
= tonumber(message_data
) or 0
272 if new_version
== 0 and user
.version
> 0 then
273 shared_users
= shared_users
- 1
274 elseif new_version
> 0 and user
.version
== 0 then
275 shared_users
= shared_users
+ 1
278 self
.sharing
= shared_users
> 0
279 user
.version
= new_version
281 for i
, obj
in ipairs(shared_objectives
) do -- User apparently knows nothing about us.
286 for id
, obj
in pairs(user
.obj
) do -- And apparently all their objective ids are now null and void.
287 self
:SetObjectiveProgress(obj
, user
.name
, nil, nil)
288 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
292 -- Say hello to the new user.
293 if user
.version
> 0 then
294 self
:SendData("hello:"..comm_version
, name
)
296 elseif message_type
== "hello" then
297 local new_version
= tonumber(message_data
) or 0
299 if new_version
== 0 and user
.version
> 0 then
300 shared_users
= shared_users
- 1
301 elseif new_version
> 0 and user
.version
== 0 then
302 shared_users
= shared_users
+ 1
305 self
.sharing
= shared_users
> 0
306 user
.version
= new_version
308 if user
.version
> comm_version
then
309 self
:TextOut(QHFormat("PEER_NEWER", name
))
310 elseif user
.version
< comm_version
then
311 self
:TextOut(QHFormat("PEER_OLDER", name
))
314 elseif message_type
== "id" then
315 local list
= GetList(message_data
)
316 local id
, cat
, what
= tonumber(list
[1]), list
[2], list
[3]
317 if id
and cat
and what
and not user
.obj
[id
] then
318 user
.obj
[id
] = self
:GetObjective(UnescapeString(cat
), UnescapeString(what
))
319 self
:AddObjectiveWatch(user
.obj
[id
], SharedObjectiveReason(user
, user
.obj
[id
]))
321 elseif message_type
== "dep" then
322 local list
= GetList(message_data
)
323 local id
= tonumber(list
[1])
324 local obj
= id
and user
.obj
[id
]
325 if obj
and obj
.cat
== "quest" then
327 local depid
= tonumber(list
[i
])
328 local depobj
= depid
and user
.obj
[depid
]
329 if depobj
and depobj
.cat
~= "quest" then
330 self
:ObjectiveObjectDependsOn(obj
, depobj
)
332 if depobj
.cat
== "item" then
333 if not depobj
.quest
then
340 elseif message_type
== "upd" then
341 local _
, _
, id
, priority
, have
, need
= string.find(message_data
, "^(%d+):(%d+):([^:]*):(.*)")
342 id
, priority
= tonumber(id
), tonumber(priority
)
344 if id
and priority
and have
and need
then
345 local obj
= user
.obj
[id
]
347 have
, need
= UnescapeString(have
), UnescapeString(need
)
348 have
, need
= tonumber(have
) or have
, tonumber(need
) or need
349 if have
== "" or need
== "" then have
, need
= nil, nil end
350 self
:SetObjectivePriority(obj
, priority
)
351 self
:SetObjectiveProgress(obj
, user
.name
, have
, need
)
354 elseif message_type
== "rem" then
355 local id
= tonumber(message_data
)
356 local obj
= id
and user
.obj
[id
]
358 self
:SetObjectiveProgress(obj
, name
, nil, nil)
359 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
363 self
:TextOut(QHFormat("UNKNOWN_MESSAGE", message_type
, name
))
368 function QuestHelper
:PumpCommMessages()
369 if shared_users
> 0 and enabled_sharing
then
370 local best_level
, best_count
, best_obj
= 3, 255, nil
372 for i
, o
in pairs(shared_objectives
) do
373 local level
, count
= 255, 0
375 for u
, l
in pairs(o
.peer
) do
376 if u
.version
> 0 then
377 level
= math
.min(l
, level
)
382 if level
< best_level
or (level
== best_level
and count
> best_count
) then
383 best_level
, best_count
, best_obj
= level
, count
, o
388 if best_level
== 0 then
389 self
:SendData("id:"..best_obj
.id
..":"..EscapeString(best_obj
.cat
)..":"..EscapeString(best_obj
.obj
))
391 elseif best_level
== 1 then
392 if next(best_obj
.after
, nil) then
393 local data
, meaningful
= "dep:"..best_obj
.id
, false
394 for o
in pairs(best_obj
.after
) do
396 data
= data
.. ":" .. o
.id
405 elseif best_level
== 2 then
406 local prog
= best_obj
.progress
and best_obj
.progress
[UnitName("player")]
408 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
..":"..EscapeString(prog
[1])..":"..EscapeString(prog
[2]))
410 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
.."::")
415 for u
in pairs(best_obj
.peer
) do -- All peers have just seen this.
416 if u
.version
> 0 then
417 best_obj
.peer
[u
] = math
.max(best_obj
.peer
[u
], best_level
)
424 function QuestHelper
:HandlePartyChange()
425 if enabled_sharing
then
426 for name
, user
in pairs(users
) do
431 local name
, realm
= UnitName("party"..i
)
432 -- For some mysterous reason, out of range party members return an empty string as the realm name instead of nil.
433 if name
and name
~= UNKNOWNOBJECT
and not realm
or realm
== "" then
434 local user
= users
[name
]
436 user
= CreateUser(name
)
440 if not user
.syn_req
then
441 self
:SendData("syn:"..comm_version
, name
)
451 for name
, user
in pairs(users
) do
452 if not user
.seen
then
455 elseif user
.version
> 0 then
461 self
.sharing
= count
> 0
465 function QuestHelper
:EnableSharing()
466 if not enabled_sharing
then
467 enabled_sharing
= true
468 self
:HandlePartyChange()
472 function QuestHelper
:DisableSharing()
473 if enabled_sharing
then
474 enabled_sharing
= false
475 for name
, user
in pairs(users
) do
476 if user
.version
> 0 then self
:SendData("syn:0", name
) end