1 QuestHelper_File
["comm.lua"] = "Development Version"
2 QuestHelper_Loadtime
["comm.lua"] = GetTime()
4 function QuestHelper
:HandleRemoteData() end
5 function QuestHelper
:PumpCommMessages() end
6 function QuestHelper
:HandlePartyChange() end
7 function QuestHelper
:EnableSharing() end
8 function QuestHelper
:DisableSharing() end
12 -- We can't send more than 256 bytes per message.
13 local comm_version
= 1
14 local max_msg_size
= 256-4-1-1 -- To allow room for "QHpr\t" ... "\0"
15 local max_chunk_size
= max_msg_size
- 2 -- To allow room for prefix of "x:"
16 local enabled_sharing
= false
18 function QuestHelper
:SendData(data
,name
)
19 if QuestHelper_Pref
.comm
then
21 self
:TextOut("SENT/"..name
..":|cff00ff00"..data
.."|r")
23 self
:TextOut("SENT/PARTY:|cff00ff00"..data
.."|r")
27 if string.len(data
) > max_msg_size
then
28 -- Large pieces of data are broken into pieces.
31 local chunk
= string.sub(data
, i
, i
+ max_chunk_size
- 1)
32 i
= i
+ max_chunk_size
33 if i
> string.len(data
) then
35 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "X:"..chunk
, name
and "WHISPER" or "PARTY", name
)
38 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", "x:"..chunk
, name
and "WHISPER" or "PARTY", name
)
42 ChatThrottleLib
:SendAddonMessage("BULK", "QHpr", data
, name
and "WHISPER" or "PARTY", name
)
53 local function EscapeString(str
)
54 for i
= 1,#escapes
,2 do
55 str
= string.gsub(str
, escapes
[i
], escapes
[i
+1])
61 local function UnescapeString(str
)
62 for i
= #escapes
,1,-2 do
63 str
= string.gsub(str
, escapes
[i
], escapes
[i
-1])
70 local function GetList(str
)
71 while table.remove(temp_table
) do end
72 for arg
in string.gmatch(str
, "([^:]*):?") do
73 table.insert(temp_table
, arg
)
76 -- If i remove this assert, make sure I keep the side effect.
77 assert(table.remove(temp_table
) == "")
85 QuestHelper sends its addon messages with the prefix 'QHpr'
88 Sent to new users, letting them know we know nothing about them. VERSION is the communication they are using.
89 Both clients normally send a syn: and bother respond to the other with hello:. If one client just reloaded,
90 this will just be a syn: from the reloading client and a hello: from the existing client.
92 As a special case, syn:0 indicates that the user has turned off objective sharing. Don't need to reply with
93 hello in this case. You can, but of course, they won't see or care.
96 Sent in response to syn: VERSION is the communication version they are using.
98 id:<ID>:<CATEGORY>:<WHAT>
99 Lets other users know that they are sharing an objective under ID. CATEGORY and WHAT are escaped strings,
100 to be passed to QuestHelper:GetObjective().
102 dep:<ID>:<DEP1>:<DEP2>:...
103 Lets other users know that one of their objectives depends on another. ID is the id of the objective, and
104 is followed by the IDs of the objectives it depends on.
105 For the sake of sanity, only quest objectives are allowed to have dependencies, and can't depend on
106 other quest objectives.
108 upd:<ID>:<PRI>:<HAVE>:<NEED>
109 Lets other users know that something about one of their shared objectives has changed.
112 Lets other users know that they have removed an objective that they were previously sharing.
115 User wants to send a message larger than what blizzard will allow.
116 DATA is appended to the previous data chunk.
117 Will ignore if we don't know their version yet, since they might have been in the middle
118 of sending something when we noticed it.
121 Same as x:, but this is the last chunk of data, and after this the combined data can be used as a message.
125 local shared_objectives
= {}
127 local shared_users
= 0
129 local function CreateUser(name
)
130 if QuestHelper_Pref
.comm
then
131 QuestHelper
:TextOut("Created user: "..name
)
134 user
= QuestHelper
:CreateTable()
139 user
.obj
=QuestHelper
:CreateTable()
141 for i
, obj
in ipairs(shared_objectives
) do -- Mark this user as knowing nothing about any of our objectives.
149 local function SharedObjectiveReason(user
, objective
)
150 if objective
.cat
== "quest" then
151 return QHFormat("PEER_TURNIN", user
.name
, select(3, string.find(objective
.obj
, "^%d+/%d*/(.*)$")) or "something impossible")
152 elseif objective
.cat
== "loc" then
154 local _
, _
, c
, z
, x
, y
= string.find(objective
.obj
, "^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
157 _
, _
, i
, x
, y
= string.find(objective
.obj
, "^(%d+),([%d%.]+),([%d%.]+)$")
159 i
= QuestHelper_IndexLookup
[c
] and QuestHelper_IndexLookup
[c
][z
]
162 return QHFormat("PEER_LOCATON", user
.name
, i
and QuestHelper_NameLookup
[i
] or "the black empty void")
163 elseif objective
.cat
== "item" then
164 return QHFormat("PEER_ITEM", user
.name
, objective
.obj
)
166 return QHFormat("PEER_OTHER", user
.name
, objective
.obj
)
170 local function ReleaseUser(user
)
171 for id
, objective
in pairs(user
.obj
) do
172 QuestHelper
:SetObjectiveProgress(objective
, user
.name
, nil, nil)
173 QuestHelper
:RemoveObjectiveWatch(objective
, SharedObjectiveReason(user
, objective
))
177 for i
, obj
in ipairs(shared_objectives
) do
182 if QuestHelper_Pref
.comm
then
183 QuestHelper
:TextOut("Released user: "..user
.name
)
186 QuestHelper
:ReleaseTable(user
.obj
)
187 QuestHelper
:ReleaseTable(user
)
190 function QuestHelper
:DoShareObjective(objective
)
191 for i
= 1, #shared_objectives
do assert(objective
~= shared_objectives
[i
]) end -- Just testing.
193 assert(objective
.peer
== nil)
194 objective
.peer
= self
:CreateTable()
196 for name
, user
in pairs(users
) do
197 -- Peers know nothing about this objective.
198 objective
.peer
[user
] = 0
201 for o
in pairs(objective
.before
) do
203 for u
, l
in pairs(o
.peer
) do
204 -- Peers don't know about this dependency.
205 o
.peer
[u
] = math
.min(l
, 1)
210 table.insert(shared_objectives
, objective
)
213 function QuestHelper
:DoUnshareObjective(objective
)
214 for i
= 1, #shared_objectives
do
215 if objective
== shared_objectives
[i
] then
216 local need_announce
= false
218 assert(objective
.peer
)
220 for user
, level
in pairs(objective
.peer
) do
225 objective
.peer
[user
] = nil
228 self
:ReleaseTable(objective
.peer
)
231 if need_announce
then
232 self
:SendData("rem:"..objective
.id
)
235 table.remove(shared_objectives
, i
)
240 assert(false) -- Should have found the objective.
243 function QuestHelper
:HandleRemoteData(data
, name
)
244 if enabled_sharing
then
245 local user
= users
[name
]
247 user
= CreateUser(name
)
251 local _
, _
, message_type
, message_data
= string.find(data
, "^(.-):(.*)$")
253 if message_type
== "x" then
254 if user
.version
> 0 then
255 --self:TextOut("RECV/"..name..":<chunk>")
256 user
.xmsg
= (user
.xmsg
or "")..message_data
258 --self:TextOut("RECV/"..name..":<ignored chunk>")
261 elseif message_type
== "X" then
262 if user
.version
> 0 then
263 --self:TextOut("RECV/"..name..":<chunk end>")
264 _
, _
, message_type
, message_data
= string.find((user
.xmsg
or "")..message_data
, "^(.-):(.*)$")
267 --self:TextOut("RECV/"..name..":<ignored chunk end>")
272 if QuestHelper_Pref
.comm
then
273 self
:TextOut("RECV/"..name
..":|cff00ff00"..data
.."|r")
276 if message_type
== "syn" then
277 -- User has just noticed us. Is either new, or reloaded their UI.
279 local new_version
= tonumber(message_data
) or 0
281 if new_version
== 0 and user
.version
> 0 then
282 shared_users
= shared_users
- 1
283 elseif new_version
> 0 and user
.version
== 0 then
284 shared_users
= shared_users
+ 1
287 self
.sharing
= shared_users
> 0
288 user
.version
= new_version
290 for i
, obj
in ipairs(shared_objectives
) do -- User apparently knows nothing about us.
295 for id
, obj
in pairs(user
.obj
) do -- And apparently all their objective ids are now null and void.
296 self
:SetObjectiveProgress(obj
, user
.name
, nil, nil)
297 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
301 -- Say hello to the new user.
302 if user
.version
> 0 then
303 self
:SendData("hello:"..comm_version
, name
)
305 elseif message_type
== "hello" then
306 local new_version
= tonumber(message_data
) or 0
308 if new_version
== 0 and user
.version
> 0 then
309 shared_users
= shared_users
- 1
310 elseif new_version
> 0 and user
.version
== 0 then
311 shared_users
= shared_users
+ 1
314 self
.sharing
= shared_users
> 0
315 user
.version
= new_version
317 if user
.version
> comm_version
then
318 self
:TextOut(QHFormat("PEER_NEWER", name
))
319 elseif user
.version
< comm_version
then
320 self
:TextOut(QHFormat("PEER_OLDER", name
))
323 elseif message_type
== "id" then
324 local list
= GetList(message_data
)
325 local id
, cat
, what
= tonumber(list
[1]), list
[2], list
[3]
326 if id
and cat
and what
and not user
.obj
[id
] then
327 user
.obj
[id
] = self
:GetObjective(UnescapeString(cat
), UnescapeString(what
))
328 self
:AddObjectiveWatch(user
.obj
[id
], SharedObjectiveReason(user
, user
.obj
[id
]))
330 elseif message_type
== "dep" then
331 local list
= GetList(message_data
)
332 local id
= tonumber(list
[1])
333 local obj
= id
and user
.obj
[id
]
334 if obj
and obj
.cat
== "quest" then
336 local depid
= tonumber(list
[i
])
337 local depobj
= depid
and user
.obj
[depid
]
338 if depobj
and depobj
.cat
~= "quest" then
339 self
:ObjectiveObjectDependsOn(obj
, depobj
)
341 if depobj
.cat
== "item" then
342 if not depobj
.quest
then
349 elseif message_type
== "upd" then
350 local _
, _
, id
, priority
, have
, need
= string.find(message_data
, "^(%d+):(%d+):([^:]*):(.*)")
351 id
, priority
= tonumber(id
), tonumber(priority
)
353 if id
and priority
and have
and need
then
354 local obj
= user
.obj
[id
]
356 have
, need
= UnescapeString(have
), UnescapeString(need
)
357 have
, need
= tonumber(have
) or have
, tonumber(need
) or need
358 if have
== "" or need
== "" then have
, need
= nil, nil end
359 self
:SetObjectivePriority(obj
, priority
)
360 self
:SetObjectiveProgress(obj
, user
.name
, have
, need
)
363 elseif message_type
== "rem" then
364 local id
= tonumber(message_data
)
365 local obj
= id
and user
.obj
[id
]
367 self
:SetObjectiveProgress(obj
, name
, nil, nil)
368 self
:RemoveObjectiveWatch(obj
, SharedObjectiveReason(user
, obj
))
372 self
:TextOut(QHFormat("UNKNOWN_MESSAGE", message_type
, name
))
377 function QuestHelper
:PumpCommMessages()
378 if shared_users
> 0 and enabled_sharing
then
379 local best_level
, best_count
, best_obj
= 3, 255, nil
381 for i
, o
in pairs(shared_objectives
) do
382 local level
, count
= 255, 0
384 for u
, l
in pairs(o
.peer
) do
385 if u
.version
> 0 then
386 level
= math
.min(l
, level
)
391 if level
< best_level
or (level
== best_level
and count
> best_count
) then
392 best_level
, best_count
, best_obj
= level
, count
, o
397 if best_level
== 0 then
398 self
:SendData("id:"..best_obj
.id
..":"..EscapeString(best_obj
.cat
)..":"..EscapeString(best_obj
.obj
))
400 elseif best_level
== 1 then
401 if next(best_obj
.after
, nil) then
402 local data
, meaningful
= "dep:"..best_obj
.id
, false
403 for o
in pairs(best_obj
.after
) do
405 data
= data
.. ":" .. o
.id
414 elseif best_level
== 2 then
415 local prog
= best_obj
.progress
and best_obj
.progress
[UnitName("player")]
417 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
..":"..EscapeString(prog
[1])..":"..EscapeString(prog
[2]))
419 self
:SendData("upd:"..best_obj
.id
..":"..best_obj
.priority
.."::")
424 for u
in pairs(best_obj
.peer
) do -- All peers have just seen this.
425 if u
.version
> 0 then
426 best_obj
.peer
[u
] = math
.max(best_obj
.peer
[u
], best_level
)
433 function QuestHelper
:HandlePartyChange()
434 if enabled_sharing
then
435 for name
, user
in pairs(users
) do
440 local name
, realm
= UnitName("party"..i
)
441 -- For some mysterous reason, out of range party members return an empty string as the realm name instead of nil.
442 if name
and name
~= UNKNOWNOBJECT
and not realm
or realm
== "" then
443 local user
= users
[name
]
445 user
= CreateUser(name
)
449 if not user
.syn_req
then
450 self
:SendData("syn:"..comm_version
, name
)
460 for name
, user
in pairs(users
) do
461 if not user
.seen
then
464 elseif user
.version
> 0 then
470 self
.sharing
= count
> 0
474 function QuestHelper
:EnableSharing()
475 if not enabled_sharing
then
476 enabled_sharing
= true
477 self
:HandlePartyChange()
481 function QuestHelper
:DisableSharing()
482 if enabled_sharing
then
483 enabled_sharing
= false
484 for name
, user
in pairs(users
) do
485 if user
.version
> 0 then self
:SendData("syn:0", name
) end