1 QuestHelper_File
["comm.lua"] = "Development Version"
2 QuestHelper_Loadtime
["comm.lua"] = GetTime()
4 -- We can't send more than 256 bytes per message.
6 local max_msg_size
= 256-4-1-1 -- To allow room for "QHpr\t" ... "\0"
7 local max_chunk_size
= max_msg_size
- 2 -- To allow room for prefix of "x:"
8 local enabled_sharing
= false
10 function QuestHelper
:SendData(data
,name
)
11 if QuestHelper_Pref
.comm
then
13 self
:TextOut("SENT/"..name
..":|cff00ff00"..data
.."|r")
15 self
:TextOut("SENT/PARTY:|cff00ff00"..data
.."|r")
19 if string.len(data
) > max_msg_size
then
20 -- Large pieces of data are broken into pieces.
23 local chunk
= string.sub(data
, i
, i
+ max_chunk_size
- 1)
24 i
= i
+ max_chunk_size
25 if i
> string.len(data
) then
27 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "X:"..chunk
, name
and "WHISPER" or "PARTY", name
)
30 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "x:"..chunk
, name
and "WHISPER" or "PARTY", name
)
34 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", data
, name
and "WHISPER" or "PARTY", name
)
45 local function EscapeString(str
)
46 for i
= 1,#escapes
,2 do
47 str
= string.gsub(str
, escapes
[i
], escapes
[i
+1])
53 local function UnescapeString(str
)
54 for i
= #escapes
,1,-2 do
55 str
= string.gsub(str
, escapes
[i
], escapes
[i
-1])
62 local function GetList(str
)
63 while table.remove(temp_table
) do end
64 for arg
in string.gmatch(str
, "([^:]*):?") do
65 table.insert(temp_table
, arg
)
68 -- If i remove this assert, make sure I keep the side effect.
69 assert(table.remove(temp_table
) == "")
77 QuestHelper sends its addon messages with the prefix 'QHpr'
80 Sent to new users, letting them know we know nothing about them. VERSION is the communication they are using.
81 Both clients normally send a syn: and bother respond to the other with hello:. If one client just reloaded,
82 this will just be a syn: from the reloading client and a hello: from the existing client.
84 As a special case, syn:0 indicates that the user has turned off objective sharing. Don't need to reply with
85 hello in this case. You can, but of course, they won't see or care.
88 Sent in response to syn: VERSION is the communication version they are using.
90 id:<ID>:<CATEGORY>:<WHAT>
91 Lets other users know that they are sharing an objective under ID. CATEGORY and WHAT are escaped strings,
92 to be passed to QuestHelper:GetObjective().
94 dep:<ID>:<DEP1>:<DEP2>:...
95 Lets other users know that one of their objectives depends on another. ID is the id of the objective, and
96 is followed by the IDs of the objectives it depends on.
97 For the sake of sanity, only quest objectives are allowed to have dependencies, and can't depend on
98 other quest objectives.
100 upd:<ID>:<PRI>:<HAVE>:<NEED>
101 Lets other users know that something about one of their shared objectives has changed.
104 Lets other users know that they have removed an objective that they were previously sharing.
107 User wants to send a message larger than what blizzard will allow.
108 DATA is appended to the previous data chunk.
109 Will ignore if we don't know their version yet, since they might have been in the middle
110 of sending something when we noticed it.
113 Same as x:, but this is the last chunk of data, and after this the combined data can be used as a message.
117 local shared_objectives
= {}
119 local shared_users
= 0
121 local function CreateUser(name
)
122 if QuestHelper_Pref
.comm
then
123 QuestHelper
:TextOut("Created user: "..name
)
126 user
= QuestHelper
:CreateTable()
131 user
.obj
=QuestHelper
:CreateTable()
133 for i
, obj
in ipairs(shared_objectives
) do -- Mark this user as knowing nothing about any of our objectives.
141 local function SharedObjectiveReason(user
, objective
)
142 if objective
.cat
== "quest" then
143 return QHFormat("PEER_TURNIN", user
.name
, select(3, string.find(objective
.obj
, "^%d+/%d*/(.*)$")) or "something impossible")
144 elseif objective
.cat
== "loc" then
146 local _
, _
, c
, z
, x
, y
= string.find(objective
.obj
, "^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
149 _
, _
, i
, x
, y
= string.find(objective
.obj
, "^(%d+),([%d%.]+),([%d%.]+)$")
151 i
= QuestHelper_IndexLookup
[c
] and QuestHelper_IndexLookup
[c
][z
]
154 return QHFormat("PEER_LOCATON", user
.name
, i
and QuestHelper_NameLookup
[i
] or "the black empty void")
155 elseif objective
.cat
== "item" then
156 return QHFormat("PEER_ITEM", user
.name
, objective
.obj
)
158 return QHFormat("PEER_OTHER", user
.name
, objective
.obj
)
162 local function ReleaseUser(user
)
163 for id
, objective
in pairs(user
.obj
) do
164 QuestHelper
:SetObjectiveProgress(objective
, user
.name
, nil, nil)
165 QuestHelper
:RemoveObjectiveWatch(objective
, SharedObjectiveReason(user
, objective
))
169 for i
, obj
in ipairs(shared_objectives
) do
174 if QuestHelper_Pref
.comm
then
175 QuestHelper
:TextOut("Released user: "..user
.name
)
178 QuestHelper
:ReleaseTable(user
.obj
)
179 QuestHelper
:ReleaseTable(user
)
182 function QuestHelper
:DoShareObjective(objective
)
183 for i
= 1, #shared_objectives
do assert(objective
~= shared_objectives
[i
]) end -- Just testing.
185 assert(objective
.peer
== nil)
186 objective
.peer
= self
:CreateTable()
188 for name
, user
in pairs(users
) do
189 -- Peers know nothing about this objective.
190 objective
.peer
[user
] = 0
193 for o
in pairs(objective
.before
) do
195 for u
, l
in pairs(o
.peer
) do
196 -- Peers don't know about this dependency.
197 o
.peer
[u
] = math
.min(l
, 1)
202 table.insert(shared_objectives
, objective
)
205 function QuestHelper
:DoUnshareObjective(objective
)
206 for i
= 1, #shared_objectives
do
207 if objective
== shared_objectives
[i
] then
208 local need_announce
= false
210 assert(objective
.peer
)
212 for user
, level
in pairs(objective
.peer
) do
217 objective
.peer
[user
] = nil
220 self
:ReleaseTable(objective
.peer
)
223 if need_announce
then
224 self
:SendData("rem:"..objective
.id
)
227 table.remove(shared_objectives
, i
)
232 assert(false) -- Should have found the objective.
235 function QuestHelper
:HandleRemoteData(data
, name
)
236 if enabled_sharing
then
237 local user
= users
[name
]
239 user
= CreateUser(name
)
243 local _
, _
, message_type
, message_data
= string.find(data
, "^(.-):(.*)$")
245 if message_type
== "x" then
246 if user
.version
> 0 then
247 --self:TextOut("RECV/"..name..":<chunk>")
248 user
.xmsg
= (user
.xmsg
or "")..message_data
250 --self:TextOut("RECV/"..name..":<ignored chunk>")
253 elseif message_type
== "X" then
254 if user
.version
> 0 then
255 --self:TextOut("RECV/"..name..":<chunk end>")
256 _
, _
, message_type
, message_data
= string.find((user
.xmsg
or "")..message_data
, "^(.-):(.*)$")
259 --self:TextOut("RECV/"..name..":<ignored chunk end>")
264 if QuestHelper_Pref
.comm
then
265 self
:TextOut("RECV/"..name
..":|cff00ff00"..data
.."|r")
268 if message_type
== "syn" then
269 -- User has just noticed us. Is either new, or reloaded their UI.
271 local new_version
= tonumber(message_data
) or 0
273 if new_version
== 0 and user
.version
> 0 then
274 shared_users
= shared_users
- 1
275 elseif new_version
> 0 and user
.version
== 0 then
276 shared_users
= shared_users
+ 1
279 self
.sharing
= shared_users
> 0
280 user
.version
= new_version
282 for i
, obj
in ipairs(shared_objectives
) do -- User apparently knows nothing about us.
287 for id
, obj
in pairs(user
.obj
) do -- And apparently all their objective ids are now null and void.
288 self
:SetObjectiveProgress(obj
, user
.name
, nil, nil)
289 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
293 -- Say hello to the new user.
294 if user
.version
> 0 then
295 self
:SendData("hello:"..comm_version
, name
)
297 elseif message_type
== "hello" then
298 local new_version
= tonumber(message_data
) or 0
300 if new_version
== 0 and user
.version
> 0 then
301 shared_users
= shared_users
- 1
302 elseif new_version
> 0 and user
.version
== 0 then
303 shared_users
= shared_users
+ 1
306 self
.sharing
= shared_users
> 0
307 user
.version
= new_version
309 if user
.version
> comm_version
then
310 self
:TextOut(QHFormat("PEER_NEWER", name
))
311 elseif user
.version
< comm_version
then
312 self
:TextOut(QHFormat("PEER_OLDER", name
))
315 elseif message_type
== "id" then
316 local list
= GetList(message_data
)
317 local id
, cat
, what
= tonumber(list
[1]), list
[2], list
[3]
318 if id
and cat
and what
and not user
.obj
[id
] then
319 user
.obj
[id
] = self
:GetObjective(UnescapeString(cat
), UnescapeString(what
))
320 self
:AddObjectiveWatch(user
.obj
[id
], SharedObjectiveReason(user
, user
.obj
[id
]))
322 elseif message_type
== "dep" then
323 local list
= GetList(message_data
)
324 local id
= tonumber(list
[1])
325 local obj
= id
and user
.obj
[id
]
326 if obj
and obj
.cat
== "quest" then
328 local depid
= tonumber(list
[i
])
329 local depobj
= depid
and user
.obj
[depid
]
330 if depobj
and depobj
.cat
~= "quest" then
331 self
:ObjectiveObjectDependsOn(obj
, depobj
)
333 if depobj
.cat
== "item" then
334 if not depobj
.quest
then
341 elseif message_type
== "upd" then
342 local _
, _
, id
, priority
, have
, need
= string.find(message_data
, "^(%d+):(%d+):([^:]*):(.*)")
343 id
, priority
= tonumber(id
), tonumber(priority
)
345 if id
and priority
and have
and need
then
346 local obj
= user
.obj
[id
]
348 have
, need
= UnescapeString(have
), UnescapeString(need
)
349 have
, need
= tonumber(have
) or have
, tonumber(need
) or need
350 if have
== "" or need
== "" then have
, need
= nil, nil end
351 self
:SetObjectivePriority(obj
, priority
)
352 self
:SetObjectiveProgress(obj
, user
.name
, have
, need
)
355 elseif message_type
== "rem" then
356 local id
= tonumber(message_data
)
357 local obj
= id
and user
.obj
[id
]
359 self
:SetObjectiveProgress(obj
, name
, nil, nil)
360 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
364 self
:TextOut(QHFormat("UNKNOWN_MESSAGE", message_type
, name
))
369 function QuestHelper
:PumpCommMessages()
370 if shared_users
> 0 and enabled_sharing
then
371 local best_level
, best_count
, best_obj
= 3, 255, nil
373 for i
, o
in pairs(shared_objectives
) do
374 local level
, count
= 255, 0
376 for u
, l
in pairs(o
.peer
) do
377 if u
.version
> 0 then
378 level
= math
.min(l
, level
)
383 if level
< best_level
or (level
== best_level
and count
> best_count
) then
384 best_level
, best_count
, best_obj
= level
, count
, o
389 if best_level
== 0 then
390 self
:SendData("id:"..best_obj
.id
..":"..EscapeString(best_obj
.cat
)..":"..EscapeString(best_obj
.obj
))
392 elseif best_level
== 1 then
393 if next(best_obj
.after
, nil) then
394 local data
, meaningful
= "dep:"..best_obj
.id
, false
395 for o
in pairs(best_obj
.after
) do
397 data
= data
.. ":" .. o
.id
406 elseif best_level
== 2 then
407 local prog
= best_obj
.progress
and best_obj
.progress
[UnitName("player")]
409 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
..":"..EscapeString(prog
[1])..":"..EscapeString(prog
[2]))
411 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
.."::")
416 for u
in pairs(best_obj
.peer
) do -- All peers have just seen this.
417 if u
.version
> 0 then
418 best_obj
.peer
[u
] = math
.max(best_obj
.peer
[u
], best_level
)
425 function QuestHelper
:HandlePartyChange()
426 if enabled_sharing
then
427 for name
, user
in pairs(users
) do
432 local name
, realm
= UnitName("party"..i
)
433 -- For some mysterous reason, out of range party members return an empty string as the realm name instead of nil.
434 if name
and name
~= UNKNOWNOBJECT
and not realm
or realm
== "" then
435 local user
= users
[name
]
437 user
= CreateUser(name
)
441 if not user
.syn_req
then
442 self
:SendData("syn:"..comm_version
, name
)
452 for name
, user
in pairs(users
) do
453 if not user
.seen
then
456 elseif user
.version
> 0 then
462 self
.sharing
= count
> 0
466 function QuestHelper
:EnableSharing()
467 if not enabled_sharing
then
468 enabled_sharing
= true
469 self
:HandlePartyChange()
473 function QuestHelper
:DisableSharing()
474 if enabled_sharing
then
475 enabled_sharing
= false
476 for name
, user
in pairs(users
) do
477 if user
.version
> 0 then self
:SendData("syn:0", name
) end