1441 lignes
42 Kio
Lua
1441 lignes
42 Kio
Lua
QuestHelper_File["objective.lua"] = "1.4.0"
|
|
QuestHelper_Loadtime["objective.lua"] = GetTime()
|
|
|
|
|
|
local UserIgnored = {
|
|
name = "user_manual_ignored",
|
|
no_disable = true,
|
|
friendly_reason = QHText("FILTERED_USER"),
|
|
AddException = function(self, node)
|
|
QH_Route_UnignoreNode(node, self) -- there isn't really state with this one
|
|
end
|
|
}
|
|
|
|
function QuestHelper:AddObjectiveOptionsToMenu(obj, menu)
|
|
local submenu = self:CreateMenu()
|
|
|
|
local pri = (QH_Route_GetClusterPriority(obj.cluster) or 0) + 3
|
|
for i = 1, 5 do
|
|
local name = QHText("PRIORITY"..i)
|
|
local item = self:CreateMenuItem(submenu, name)
|
|
local tex
|
|
|
|
if pri == i then
|
|
tex = self:CreateIconTexture(item, 10)
|
|
else
|
|
tex = self:CreateIconTexture(item, 12)
|
|
tex:SetVertexColor(1, 1, 1, 0)
|
|
end
|
|
|
|
item:AddTexture(tex, true)
|
|
item:SetFunction(QH_Route_SetClusterPriority, obj.cluster, i - 3)
|
|
end
|
|
|
|
self:CreateMenuItem(menu, QHText("PRIORITY")):SetSubmenu(submenu)
|
|
|
|
--[[if self.sharing then
|
|
submenu = self:CreateMenu()
|
|
local item = self:CreateMenuItem(submenu, QHText("SHARING_ENABLE"))
|
|
local tex = self:CreateIconTexture(item, 10)
|
|
if not obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
|
|
item:AddTexture(tex, true)
|
|
item:SetFunction(obj.Share, obj)
|
|
|
|
local item = self:CreateMenuItem(submenu, QHText("SHARING_DISABLE"))
|
|
local tex = self:CreateIconTexture(item, 10)
|
|
if obj.want_share then tex:SetVertexColor(1, 1, 1, 0) end
|
|
item:AddTexture(tex, true)
|
|
item:SetFunction(obj.Unshare, obj)
|
|
|
|
self:CreateMenuItem(menu, QHText("SHARING")):SetSubmenu(submenu)
|
|
end]]
|
|
|
|
--self:CreateMenuItem(menu, "(No options available)")
|
|
|
|
if not obj.map_suppress_ignore then
|
|
self:CreateMenuItem(menu, QHText("IGNORE")):SetFunction(function () QH_Route_IgnoreCluster(obj.cluster, UserIgnored) end) -- There is probably a nasty race condition here. I'm not entirely happy about it.
|
|
end
|
|
if obj.map_custom_menu then
|
|
obj.map_custom_menu(menu)
|
|
end
|
|
|
|
if obj.cluster and #obj.cluster > 1 and QH_Route_Ignored_Cluster_Active(obj.cluster) > 1 then
|
|
self:CreateMenuItem(menu, QHText("IGNORE_LOCATION")):SetFunction(QH_Route_IgnoreNode, obj, UserIgnored)
|
|
end
|
|
end
|
|
|
|
do return end
|
|
|
|
local function ObjectiveCouldBeFirst(self)
|
|
if (self.user_ignore == nil and self.auto_ignore) or self.user_ignore then
|
|
return false
|
|
end
|
|
|
|
for i, j in pairs(self.after) do
|
|
if i.watched then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function DefaultObjectiveKnown(self)
|
|
if self.user_ignore == nil then
|
|
if (self.filter_zone and QuestHelper_Pref.filter_zone) or
|
|
(self.filter_done and QuestHelper_Pref.filter_done) or
|
|
(self.filter_level and QuestHelper_Pref.filter_level) or
|
|
(self.filter_blocked and QuestHelper_Pref.filter_blocked) or
|
|
(self.filter_watched and QuestHelper_Pref.filter_watched) then
|
|
return false
|
|
end
|
|
elseif self.user_ignore then
|
|
return false
|
|
end
|
|
|
|
for i, j in pairs(self.after) do
|
|
if i.watched and not i:Known() then -- Need to know how to do everything before this objective.
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function ObjectiveReason(self, short)
|
|
local reason, rc = nil, 0
|
|
if self.reasons then
|
|
for r, c in pairs(self.reasons) do
|
|
if not reason or c > rc or (c == rc and r > reason) then
|
|
reason, rc = r, c
|
|
end
|
|
end
|
|
end
|
|
|
|
if not reason then reason = "Do some extremely secret unspecified something." end
|
|
|
|
if not short and self.pos and self.pos[6] then
|
|
reason = reason .. "\n" .. self.pos[6]
|
|
end
|
|
|
|
return reason
|
|
end
|
|
|
|
local function Uses(self, obj, text)
|
|
if self == obj then return end -- You cannot use yourself. A purse is not food.
|
|
local uses, used = self.uses, obj.used
|
|
|
|
if not uses then
|
|
uses = QuestHelper:CreateTable("uses")
|
|
self.uses = uses
|
|
end
|
|
|
|
if not used then
|
|
used = QuestHelper:CreateTable("used")
|
|
obj.used = used
|
|
end
|
|
|
|
if not uses[obj] then
|
|
uses[obj] = true
|
|
used[self] = text
|
|
obj:MarkUsed()
|
|
end
|
|
end
|
|
|
|
local function DoMarkUsed(self)
|
|
-- Objectives should call 'self:Uses(objective, text)' to mark objectives they use by don't directly depend on.
|
|
-- This information is used in tooltips.
|
|
-- text is passed to QHFormat with the name of the objective being used.
|
|
end
|
|
|
|
local function MarkUsed(self)
|
|
if not self.marked_used then
|
|
self.marked_used = 1
|
|
self:DoMarkUsed()
|
|
else
|
|
self.marked_used = self.marked_used + 1
|
|
end
|
|
end
|
|
|
|
local function MarkUnused(self)
|
|
--[[ assert(self.marked_used) ]]
|
|
|
|
if self.marked_used == 1 then
|
|
local uses = self.uses
|
|
|
|
if uses then
|
|
for obj in pairs(uses) do
|
|
obj.used[self] = nil
|
|
obj:MarkUnused()
|
|
end
|
|
|
|
QuestHelper:ReleaseTable(uses)
|
|
self.uses = nil
|
|
end
|
|
|
|
if self.used then
|
|
--[[ assert(not next(self.used)) ]]
|
|
QuestHelper:ReleaseTable(self.used)
|
|
self.used = nil
|
|
end
|
|
|
|
self.marked_used = nil
|
|
else
|
|
self.marked_used = self.marked_used - 1
|
|
end
|
|
end
|
|
|
|
local function DummyObjectiveKnown(self)
|
|
return (self.o.pos or self.fb.pos) and DefaultObjectiveKnown(self)
|
|
end
|
|
|
|
local function ItemKnown(self)
|
|
if not DefaultObjectiveKnown(self) then return false end
|
|
|
|
if self.o.vendor then
|
|
for i, npc in ipairs(self.o.vendor) do
|
|
local n = self.qh:GetObjective("monster", npc)
|
|
local faction = n.o.faction or n.fb.faction
|
|
if (not faction or faction == self.qh.faction) and n:Known() then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.fb.vendor then
|
|
for i, npc in ipairs(self.fb.vendor) do
|
|
local n = self.qh:GetObjective("monster", npc)
|
|
local faction = n.o.faction or n.fb.faction
|
|
if (not faction or faction == self.qh.faction) and n:Known() then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.o.pos or self.fb.pos then
|
|
return true
|
|
end
|
|
|
|
if self.o.drop then for monster in pairs(self.o.drop) do
|
|
if self.qh:GetObjective("monster", monster):Known() then
|
|
return true
|
|
end
|
|
end end
|
|
|
|
if self.fb.drop then for monster in pairs(self.fb.drop) do
|
|
if self.qh:GetObjective("monster", monster):Known() then
|
|
return true
|
|
end
|
|
end end
|
|
|
|
if self.o.contained then for item in pairs(self.o.contained) do
|
|
if self.qh:GetObjective("item", item):Known() then
|
|
return true
|
|
end
|
|
end end
|
|
|
|
if self.fb.contained then for item in pairs(self.fb.contained) do
|
|
if self.qh:GetObjective("item", item):Known() then
|
|
return true
|
|
end
|
|
end end
|
|
|
|
if self.quest then
|
|
local item=self.quest.o.item
|
|
item = item and item[self.obj]
|
|
|
|
if item then
|
|
if item.pos then
|
|
return true
|
|
end
|
|
if item.drop then
|
|
for monster in pairs(item.drop) do
|
|
if self.qh:GetObjective("monster", monster):Known() then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
item=self.quest.fb.item
|
|
item = item and item[self.obj]
|
|
if item then
|
|
if item.pos then
|
|
return true
|
|
end
|
|
if item.drop then
|
|
for monster in pairs(item.drop) do
|
|
if self.qh:GetObjective("monster", monster):Known() then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function ObjectiveAppendPositions(self, objective, weight, why, restrict)
|
|
local high = 0
|
|
|
|
if self.o.pos then for i, p in ipairs(self.o.pos) do
|
|
high = math.max(high, p[4])
|
|
end end
|
|
|
|
if self.fb.pos then for i, p in ipairs(self.fb.pos) do
|
|
high = math.max(high, p[4])
|
|
end end
|
|
|
|
high = weight/high
|
|
|
|
if self.o.pos then for i, p in ipairs(self.o.pos) do
|
|
if not restrict or not self.qh:Disallowed(p[1]) then
|
|
objective:AddLoc(p[1], p[2], p[3], p[4]*high, why)
|
|
end
|
|
end end
|
|
|
|
if self.fb.pos then for i, p in ipairs(self.fb.pos) do
|
|
if not restrict or not self.qh:Disallowed(p[1]) then
|
|
objective:AddLoc(p[1], p[2], p[3], p[4]*high, why)
|
|
end
|
|
end end
|
|
end
|
|
|
|
|
|
local function ObjectivePrepareRouting(self, anywhere)
|
|
self.setup_count = self.setup_count + 1
|
|
if not self.setup then
|
|
--[[ assert(not self.d) ]]
|
|
--[[ assert(not self.p) ]]
|
|
--[[ assert(not self.nm) ]]
|
|
--[[ assert(not self.nm2) ]]
|
|
--[[ assert(not self.nl) ]]
|
|
|
|
self.d = QuestHelper:CreateTable("objective.d")
|
|
self.p = QuestHelper:CreateTable("objective.p")
|
|
self.nm = QuestHelper:CreateTable("objective.nm")
|
|
self.nm2 = QuestHelper:CreateTable("objective.nm2")
|
|
self.nl = QuestHelper:CreateTable("objective.nl")
|
|
self.distance_cache = QuestHelper:CreateTable("objective.distance_cache")
|
|
|
|
if not anywhere then
|
|
self:AppendPositions(self, 1, nil, true)
|
|
|
|
if not next(self.p) then
|
|
QuestHelper:TextOut(QHFormat("INACCESSIBLE_OBJ", self.obj or "whatever it was you just requested"))
|
|
anywhere = true
|
|
end
|
|
end
|
|
|
|
if anywhere then
|
|
self:AppendPositions(self, 1, nil, false)
|
|
end
|
|
|
|
self:FinishAddLoc(args)
|
|
end
|
|
end
|
|
|
|
local function ItemAppendPositions(self, objective, weight, why, restrict)
|
|
why2 = why and why.."\n" or ""
|
|
|
|
if self.o.vendor then for i, npc in ipairs(self.o.vendor) do
|
|
local n = self.qh:GetObjective("monster", npc)
|
|
local faction = n.o.faction or n.fb.faction
|
|
if (not faction or faction == self.qh.faction) then
|
|
n:AppendPositions(objective, 1, why2..QHFormat("OBJECTIVE_PURCHASE", npc), restrict)
|
|
end
|
|
end end
|
|
|
|
if self.fb.vendor then for i, npc in ipairs(self.fb.vendor) do
|
|
local n = self.qh:GetObjective("monster", npc)
|
|
local faction = n.o.faction or n.fb.faction
|
|
if (not faction or faction == self.qh.faction) then
|
|
n:AppendPositions(objective, 1, why2..QHFormat("OBJECTIVE_PURCHASE", npc), restrict)
|
|
end
|
|
end end
|
|
|
|
if next(objective.p, nil) then
|
|
-- If we have points from vendors, then always use vendors. I don't want it telling you to killing the
|
|
-- towns people just because you had to talk to them anyway, and it saves walking to the store.
|
|
return
|
|
end
|
|
|
|
if self.o.drop then for monster, count in pairs(self.o.drop) do
|
|
local m = self.qh:GetObjective("monster", monster)
|
|
m:AppendPositions(objective, m.o.looted and count/m.o.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster), restrict)
|
|
end end
|
|
|
|
if self.fb.drop then for monster, count in pairs(self.fb.drop) do
|
|
local m = self.qh:GetObjective("monster", monster)
|
|
m:AppendPositions(objective, m.fb.looted and count/m.fb.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster), restrict)
|
|
end end
|
|
|
|
if self.o.contained then for item, count in pairs(self.o.contained) do
|
|
local i = self.qh:GetObjective("item", item)
|
|
i:AppendPositions(objective, i.o.opened and count/i.o.opened or 1, why2..QHFormat("OBJECTIVE_LOOT", item), restrict)
|
|
end end
|
|
|
|
if self.fb.contained then for item, count in pairs(self.fb.contained) do
|
|
local i = self.qh:GetObjective("item", item)
|
|
i:AppendPositions(objective, i.fb.opened and count/i.fb.opened or 1, why2..QHFormat("OBJECTIVE_LOOT", item), restrict)
|
|
end end
|
|
|
|
if self.o.pos then for i, p in ipairs(self.o.pos) do
|
|
if not restrict or not self.qh:Disallowed(p[1]) then
|
|
objective:AddLoc(p[1], p[2], p[3], p[4], why)
|
|
end
|
|
end end
|
|
|
|
if self.fb.pos then for i, p in ipairs(self.fb.pos) do
|
|
if not restrict or not self.qh:Disallowed(p[1]) then
|
|
objective:AddLoc(p[1], p[2], p[3], p[4], why)
|
|
end
|
|
end end
|
|
|
|
if self.quest then
|
|
local item_list=self.quest.o.item
|
|
if item_list then
|
|
local data = item_list[self.obj]
|
|
if data and data.drop then
|
|
for monster, count in pairs(data.drop) do
|
|
local m = self.qh:GetObjective("monster", monster)
|
|
m:AppendPositions(objective, m.o.looted and count/m.o.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster), restrict)
|
|
end
|
|
elseif data and data.pos then
|
|
for i, p in ipairs(data.pos) do
|
|
if not restrict or not self.qh:Disallowed(p[1]) then
|
|
objective:AddLoc(p[1], p[2], p[3], p[4], why)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
item_list=self.quest.fb.item
|
|
if item_list then
|
|
local data = item_list[self.obj]
|
|
if data and data.drop then
|
|
for monster, count in pairs(data.drop) do
|
|
local m = self.qh:GetObjective("monster", monster)
|
|
m:AppendPositions(objective, m.fb.looted and count/m.fb.looted or 1, why2..QHFormat("OBJECTIVE_SLAY", monster), restrict)
|
|
end
|
|
elseif data and data.pos then
|
|
for i, p in ipairs(data.pos) do
|
|
if not restrict or not self.qh:Disallowed(p[1]) then
|
|
objective:AddLoc(p[1], p[2], p[3], p[4], why)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ItemDoMarkUsed(self)
|
|
if self.o.vendor then for i, npc in ipairs(self.o.vendor) do
|
|
local n = self.qh:GetObjective("monster", npc)
|
|
local faction = n.o.faction or n.fb.faction
|
|
if (not faction or faction == self.qh.faction) then
|
|
self:Uses(n, "TOOLTIP_PURCHASE")
|
|
end
|
|
end end
|
|
|
|
if self.fb.vendor then for i, npc in ipairs(self.fb.vendor) do
|
|
local n = self.qh:GetObjective("monster", npc)
|
|
local faction = n.o.faction or n.fb.faction
|
|
if (not faction or faction == self.qh.faction) then
|
|
self:Uses(n, "TOOLTIP_PURCHASE")
|
|
end
|
|
end end
|
|
|
|
if self.o.drop then for monster, count in pairs(self.o.drop) do
|
|
self:Uses(self.qh:GetObjective("monster", monster), "TOOLTIP_SLAY")
|
|
end end
|
|
|
|
if self.fb.drop then for monster, count in pairs(self.fb.drop) do
|
|
self:Uses(self.qh:GetObjective("monster", monster), "TOOLTIP_SLAY")
|
|
end end
|
|
|
|
if self.o.contained then for item, count in pairs(self.o.contained) do
|
|
self:Uses(self.qh:GetObjective("item", item), "TOOLTIP_LOOT")
|
|
end end
|
|
|
|
if self.fb.contained then for item, count in pairs(self.fb.contained) do
|
|
self:Uses(self.qh:GetObjective("item", item), "TOOLTIP_LOOT")
|
|
end end
|
|
|
|
if self.quest then
|
|
local item_list=self.quest.o.item
|
|
if item_list then
|
|
local data = item_list[self.obj]
|
|
if data and data.drop then
|
|
for monster, count in pairs(data.drop) do
|
|
self:Uses(self.qh:GetObjective("monster", monster), "TOOLTIP_SLAY")
|
|
end
|
|
end
|
|
end
|
|
|
|
item_list=self.quest.fb.item
|
|
if item_list then
|
|
local data = item_list[self.obj]
|
|
if data and data.drop then
|
|
for monster, count in pairs(data.drop) do
|
|
self:Uses(self.qh:GetObjective("monster", monster), "TOOLTIP_SLAY")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
local function AddLoc(self, index, x, y, w, why)
|
|
--[[ assert(not self.setup) ]]
|
|
|
|
if w > 0 then
|
|
local pair = QuestHelper_ZoneLookup[index]
|
|
if not pair then return end -- that zone doesn't exist! We require more vespene gas. Not enough rage!
|
|
local c, z = pair[1], pair[2]
|
|
x, y = self.qh.Astrolabe:TranslateWorldMapPosition(c, z, x, y, c, 0)
|
|
|
|
x = x * self.qh.continent_scales_x[c]
|
|
y = y * self.qh.continent_scales_y[c]
|
|
local list = self.qh.zone_nodes[index]
|
|
|
|
local points = self.p[list]
|
|
if not points then
|
|
points = QuestHelper:CreateTable("objective.p[zone] (objective nodes per-zone)")
|
|
self.p[list] = points
|
|
end
|
|
|
|
for i, p in pairs(points) do
|
|
local u, v = x-p[3], y-p[4]
|
|
if u*u+v*v < 25 then -- Combine points within a threshold of 5 seconds travel time.
|
|
p[3] = (p[3]*p[5]+x*w)/(p[5]+w)
|
|
p[4] = (p[4]*p[5]+y*w)/(p[5]+w)
|
|
p[5] = p[5]+w
|
|
if w > p[7] then
|
|
p[6], p[7] = why, w
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
local new = QuestHelper:CreateTable("objective.p[zone] (possible objective node)")
|
|
new[1], new[2], new[3], new[4], new[5], new[6], new[7] = list, nil, x, y, w, why, w
|
|
table.insert(points, new)
|
|
end
|
|
end
|
|
|
|
local function FinishAddLoc(self, args)
|
|
local mx = 0
|
|
|
|
for z, pl in pairs(self.p) do
|
|
for i, p in ipairs(pl) do
|
|
if p[5] > mx then
|
|
self.location = p
|
|
mx = p[5]
|
|
end
|
|
end
|
|
end
|
|
|
|
if not self.zones then
|
|
-- Not using CreateTable, because it will not be released when routing is complete.
|
|
self.zones = {}
|
|
else
|
|
-- We could remove the already known zones, but I'm operating under the assumtion that locations will only be added,
|
|
-- not removed, so this isn't necessary.
|
|
end
|
|
|
|
-- Remove probably useless locations.
|
|
for z, pl in pairs(self.p) do
|
|
local remove_zone = true
|
|
local i = 1
|
|
while i <= #pl do
|
|
if pl[i][5] < mx*0.2 then
|
|
QuestHelper:ReleaseTable(pl[i])
|
|
table.remove(pl, i)
|
|
else
|
|
remove_zone = false
|
|
i = i + 1
|
|
end
|
|
end
|
|
if remove_zone then
|
|
QuestHelper:ReleaseTable(self.p[z])
|
|
self.p[z] = nil
|
|
else
|
|
self.zones[z.i] = true
|
|
end
|
|
end
|
|
|
|
local node_map = self.nm
|
|
local node_list = self.nl
|
|
|
|
for list, pl in pairs(self.p) do
|
|
local dist = self.d[list]
|
|
|
|
--[[ assert(not dist) ]]
|
|
|
|
if not dist then
|
|
dist = QuestHelper:CreateTable("self.d[list]")
|
|
self.d[list] = dist
|
|
end
|
|
|
|
for i, point in ipairs(pl) do
|
|
point[5] = mx/point[5] -- Will become 1 for the most desired location, and become larger and larger for less desireable locations.
|
|
|
|
point[2] = QuestHelper:CreateTable("possible objective node to zone edge cache")
|
|
|
|
for i, node in ipairs(list) do
|
|
QuestHelper: Assert(type(point[3]) == "number", string.format("p3 %s", tostring(point[3])))
|
|
QuestHelper: Assert(type(point[4]) == "number", string.format("p4 %s", tostring(point[4])))
|
|
QuestHelper: Assert(type(node.x) == "number", string.format("nx %s", tostring(node.x)))
|
|
QuestHelper: Assert(type(node.y) == "number", string.format("ny %s", tostring(node.y)))
|
|
local u, v = point[3]-node.x, point[4]-node.y
|
|
local d = math.sqrt(u*u+v*v)
|
|
|
|
point[2][i] = d
|
|
|
|
if dist[i] then
|
|
if d*point[5] < dist[i][1]*dist[i][2] then
|
|
dist[i][1], dist[i][2] = d, point[5]
|
|
node_map[node] = point
|
|
end
|
|
else
|
|
local pair = QuestHelper:CreateTable()
|
|
pair[1], pair[2] = d, point[5]
|
|
dist[i] = pair
|
|
|
|
if not node_map[node] then
|
|
table.insert(node_list, node)
|
|
node_map[node] = point
|
|
else
|
|
u, v = node_map[node][3]-node.x, node_map[node][4]-node.y
|
|
|
|
if dist[i][1]*dist[i][2] < math.sqrt(u*u+v*v)*node_map[node][5] then
|
|
node_map[node] = point
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Disabled because we're having some data sanity issues. This should be solved at buildtime, but I'm leery of mucking with the build system right now, so it isn't. Re-enable later.
|
|
--if not args or not args.failable then
|
|
-- if #node_list == 0 and QuestHelper:IsWrath() then QuestHelper:Error(self.cat.."/"..self.obj..": zero nodes!") end
|
|
--end
|
|
|
|
--[[ assert(not self.setup) ]]
|
|
self.setup = true
|
|
table.insert(self.qh.prepared_objectives, self)
|
|
end
|
|
|
|
local function GetPosition(self)
|
|
--[[ assert(self.setup) ]]
|
|
|
|
return self.location
|
|
end
|
|
|
|
local QH_TESTCACHE = nil -- make this "true" or something if you want to test caching (i.e. recalculate everything, then verify that the cache is valid)
|
|
|
|
-- Note: Pos is the starting point, the objective is the destination. These are different data formats - "self" can be a set of points.
|
|
-- More annotation here, if you're trying to learn the codebase. This function is a more complicated version of QH:ComputeTravelTime, so refer to that for information first before reading this one.
|
|
local function ObjectiveTravelTime(self, pos, nocache)
|
|
--[[ assert(self.setup) ]]
|
|
|
|
-- The caching is pretty obvious.
|
|
local key, cached
|
|
if not nocache then
|
|
--[[ assert(pos ~= QuestHelper.pos) ]]
|
|
if not pos.key then
|
|
pos.key = math.random()..""
|
|
end
|
|
key = pos.key
|
|
cached = self.distance_cache[key]
|
|
if cached then
|
|
if not QH_TESTCACHE then
|
|
return unpack(cached)
|
|
end
|
|
end
|
|
end
|
|
|
|
local graph = self.qh.world_graph
|
|
local nl = self.nl
|
|
|
|
graph:PrepareSearch()
|
|
|
|
-- This is quite similar to the same "create nodes for all zone links" in ComputeTravelTime except that it's creating nodes for all zone links for a set of possible destinations. I'm not sure if the weighting is backwards. It might be.
|
|
for z, l in pairs(self.d) do
|
|
for i, n in ipairs(z) do
|
|
if n.s == 0 then
|
|
n.e, n.w = unpack(l[i])
|
|
n.s = 3
|
|
elseif n.e * n.w < l[i][1]*l[i][2] then
|
|
n.e, n.w = unpack(l[i])
|
|
end
|
|
end
|
|
end
|
|
|
|
local d = pos[2]
|
|
for i, n in ipairs(pos[1]) do
|
|
graph:AddStartNode(n, d[i], nl)
|
|
end
|
|
|
|
local e = graph:DoSearch(nl)
|
|
|
|
-- d changes datatype here. I hate this codebase. Hell, e probably changes datatype also! yaaaay. what does .nm mean? what does .d mean?
|
|
d = e.g+e.e
|
|
e = self.nm[e]
|
|
|
|
-- There's something going on with weighting here that I don't understand
|
|
local l = self.p[pos[1]]
|
|
if l then
|
|
local x, y = pos[3], pos[4]
|
|
local score = d*e[5]
|
|
|
|
for i, n in ipairs(l) do
|
|
local u, v = x-n[3], y-n[4]
|
|
local d2 = math.sqrt(u*u+v*v)
|
|
local s = d2*n[5]
|
|
if s < score then
|
|
d, e, score = d2, n, s
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[ assert(e) ]]
|
|
if not nocache then
|
|
--[[ assert( not cached or (cached[1] == d and cached[2] == e)) ]]
|
|
if not QH_TESTCACHE or not cached then
|
|
local new = self.qh:CreateTable()
|
|
new[1], new[2] = d, e
|
|
self.distance_cache[key] = new
|
|
self.qh:CacheRegister(self)
|
|
end
|
|
else
|
|
if self.distance_cache and self.distance_cache[key] then
|
|
--[[ assert(self.distance_cache[key][1] == d) ]]
|
|
end
|
|
end
|
|
|
|
return d, e
|
|
end
|
|
|
|
-- Note: pos1 is the starting point, pos2 is the ending point, the objective is somewhere between them.
|
|
-- Yet more annotation! This one is based off ObjectiveTravelTime. Yes, it's nasty that there are three (edit: four) functions with basically the same goal. Have I mentioned this codebase kind of sucks?
|
|
local function ObjectiveTravelTime2(self, pos1, pos2, nocache)
|
|
--[[ assert(self.setup) ]]
|
|
|
|
-- caching is pretty simple as usual
|
|
local key, cached
|
|
if not nocache then
|
|
--[[ assert(pos1 ~= QuestHelper.pos) ]]
|
|
--[[ assert(pos2 ~= QuestHelper.pos) ]]
|
|
-- We don't want to cache distances involving the player's current position, as that would spam the table
|
|
if not pos1.key then
|
|
pos1.key = math.random()..""
|
|
end
|
|
if not pos2.key then
|
|
pos2.key = math.random()..""
|
|
end
|
|
key = pos1.key..pos2.key
|
|
cached = self.distance_cache[key]
|
|
if cached then
|
|
if not QH_TESTCACHE then
|
|
return unpack(cached)
|
|
end
|
|
end
|
|
end
|
|
|
|
local graph = self.qh.world_graph
|
|
local nl = self.nl
|
|
|
|
-- This is the standard pos1-to-self code that we're used to seeing . . .
|
|
graph:PrepareSearch()
|
|
|
|
for z, l in pairs(self.d) do
|
|
for i, n in ipairs(z) do
|
|
if n.s == 0 then
|
|
n.e, n.w = unpack(l[i])
|
|
n.s = 3
|
|
elseif n.e * n.w < l[i][1]*l[i][2] then
|
|
n.e, n.w = unpack(l[i])
|
|
end
|
|
end
|
|
end
|
|
|
|
local d = pos1[2]
|
|
for i, n in ipairs(pos1[1]) do
|
|
graph:AddStartNode(n, d[i], nl)
|
|
end
|
|
|
|
graph:DoFullSearch(nl)
|
|
|
|
graph:PrepareSearch()
|
|
|
|
-- . . . and here's where it gets wonky
|
|
-- Now, we need to figure out how long it takes to get to each node.
|
|
for z, point_list in pairs(self.p) do
|
|
if z == pos1[1] then
|
|
-- Will also consider min distance.
|
|
local x, y = pos1[3], pos1[4]
|
|
|
|
for i, p in ipairs(point_list) do
|
|
local a, b = p[3]-x, p[4]-y
|
|
local u, v = p[3], p[4]
|
|
local d = math.sqrt(a*a+b*b)
|
|
local w = p[5]
|
|
local score = d*w
|
|
for i, n in ipairs(z) do
|
|
a, b = n.x-u, n.y-v
|
|
local bleh = math.sqrt(a*a+b*b)+n.g
|
|
local s = bleh*w
|
|
if s < score then
|
|
d, score = bleh, d
|
|
end
|
|
end
|
|
p[7] = d
|
|
end
|
|
else
|
|
for i, p in ipairs(point_list) do
|
|
local x, y = p[3], p[4]
|
|
local w = p[5]
|
|
local d
|
|
local score
|
|
|
|
for i, n in ipairs(z) do
|
|
local a, b = n.x-x, n.y-y
|
|
local d2 = math.sqrt(a*a+b*b)+n.g
|
|
local s = d2*w
|
|
if not score or s < score then
|
|
d, score = d2, s
|
|
end
|
|
end
|
|
p[7] = d
|
|
end
|
|
end
|
|
end
|
|
|
|
d = pos2[2]
|
|
|
|
for i, n in ipairs(pos2[1]) do
|
|
n.e = d[i]
|
|
n.s = 3
|
|
end
|
|
|
|
local el = pos2[1]
|
|
local nm = self.nm2
|
|
|
|
for z, l in pairs(self.d) do
|
|
for i, n in ipairs(z) do
|
|
local x, y = n.x, n.y
|
|
local bp
|
|
local bg
|
|
local bs
|
|
for i, p in ipairs(self.p[z]) do
|
|
local a, b = x-p[3], y-p[4]
|
|
d = p[7]+math.sqrt(a*a+b*b)
|
|
s = d*p[5]
|
|
if not bs or s < bs then
|
|
bg, bp, bs = d, p, s
|
|
end
|
|
end
|
|
|
|
nm[n] = bp
|
|
-- Using score instead of distance, because we want nodes we're not really interested in to be less likely to get chosen.
|
|
graph:AddStartNode(n, bs, el)
|
|
end
|
|
end
|
|
|
|
local e = graph:DoSearch(pos2[1])
|
|
|
|
d = nm[e.p][7]
|
|
local d2 = e.g+e.e-e.p.g+(e.p.g/nm[e.p][5]-nm[e.p][7])
|
|
|
|
e = nm[e.p]
|
|
local total = (d+d2)*e[5]
|
|
|
|
if self.p[el] then
|
|
local x, y = pos2[3], pos2[4]
|
|
for i, p in ipairs(self.p[el]) do
|
|
local a, b = x-p[3], y-p[4]
|
|
local c = math.sqrt(a*a+b*b)
|
|
local t = (p[7]+c)*p[5]
|
|
if t < total then
|
|
total, d, d2, e = t, p[7], c, p
|
|
end
|
|
end
|
|
end
|
|
|
|
-- grim stabilization hack, since obviously the numbers it generates are only vaguely based in reality. This should be fixed and removed ASAP (you know, once I figure out WTF this thing is doing)
|
|
d = QuestHelper:ComputeTravelTime(pos1, e)
|
|
d2 = QuestHelper:ComputeTravelTime(e, pos2)
|
|
|
|
--[[ assert(e) ]]
|
|
if not nocache then
|
|
--[[ assert( not cached or (cached[1] == d and cached[2] == d2 and cached[3] == e)) ]]
|
|
if not QH_TESTCACHE or not cached then
|
|
local new = self.qh:CreateTable("ObjectiveTravelTime2 cache")
|
|
new[1], new[2], new[3] = d, d2, e
|
|
self.distance_cache[key] = new
|
|
self.qh:CacheRegister(self)
|
|
end
|
|
else
|
|
if self.distance_cache and self.distance_cache[key] then
|
|
--[[ assert(self.distance_cache[key][1] == d and self.distance_cache[key][2] == d2) ]]
|
|
end
|
|
end
|
|
|
|
--[[if pos1 and pos2 then -- Debug code so I can maybe actually fix the problems someday
|
|
QuestHelper:TextOut("Beginning dumping here")
|
|
|
|
local laxa = QuestHelper:ComputeTravelTime(pos1, e, true)
|
|
if math.abs(laxa-d) >= 0.0001 then
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(pos1))
|
|
QuestHelper:TextOut(QuestHelper:StringizeRecursive(pos1, 2))
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(e))
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
|
|
QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
|
|
----[[ QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxa.." vs "..d) ]] -- wonky commenting is thanks to the de-assert script, fix later
|
|
--[[end
|
|
local laxb = QuestHelper:ComputeTravelTime(e, pos2, true)
|
|
if math.abs(laxb-d2) >= 0.0001 then
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(pos2))
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(e))
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(e[1]))
|
|
QuestHelper:TextOut(QuestHelper:StringizeTable(e[2]))
|
|
QuestHelper:TextOut(QuestHelper:StringizeRecursive(e[1], 2))]]
|
|
----[[ QuestHelper:Assert(math.abs(laxa-d) < 0.0001, "Compare: "..laxb.." vs "..d2) ]]
|
|
--[[end
|
|
end]]
|
|
|
|
return d, d2, e
|
|
end
|
|
|
|
local function DoneRouting(self)
|
|
--[[ assert(self.setup_count > 0) ]]
|
|
--[[ assert(self.setup) ]]
|
|
|
|
if self.setup_count == 1 then
|
|
self.setup_count = 0
|
|
QuestHelper:ReleaseObjectivePathingInfo(self)
|
|
for i, obj in ipairs(self.qh.prepared_objectives) do
|
|
if o == obj then
|
|
table.remove(self.qh.prepared_objectives, i)
|
|
break
|
|
end
|
|
end
|
|
else
|
|
self.setup_count = self.setup_count - 1
|
|
end
|
|
end
|
|
|
|
local function IsObjectiveWatched(self)
|
|
-- Check if an objective is being watched. Note that this is an external query, not a simple Selector.
|
|
local info
|
|
|
|
if self.cat == "quest" then
|
|
info = QuestHelper.quest_log[self]
|
|
else
|
|
info = QuestHelper.quest_log[self.quest]
|
|
end
|
|
|
|
if info then
|
|
local index = info.index
|
|
if index then
|
|
if UberQuest then
|
|
-- UberQuest has it's own way of tracking quests.
|
|
local uq_settings = UberQuest_Config[UnitName("player")]
|
|
if uq_settings then
|
|
local list = uq_settings.selected
|
|
if list then
|
|
return list[GetQuestLogTitle(index)]
|
|
end
|
|
end
|
|
else
|
|
return IsQuestWatched(index)
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
|
|
local next_objective_id = 0
|
|
|
|
local function ObjectiveShare(self)
|
|
self.want_share = true
|
|
end
|
|
|
|
local function ObjectiveUnshare(self)
|
|
self.want_share = false
|
|
end
|
|
|
|
QuestHelper.default_objective_param =
|
|
{
|
|
CouldBeFirst=ObjectiveCouldBeFirst,
|
|
|
|
Uses=Uses,
|
|
DoMarkUsed=DoMarkUsed,
|
|
MarkUsed=MarkUsed,
|
|
MarkUnused=MarkUnused,
|
|
|
|
DefaultKnown=DefaultObjectiveKnown,
|
|
Known=DummyObjectiveKnown,
|
|
Reason=ObjectiveReason,
|
|
|
|
AppendPositions=ObjectiveAppendPositions,
|
|
PrepareRouting=ObjectivePrepareRouting,
|
|
AddLoc=AddLoc,
|
|
FinishAddLoc=FinishAddLoc,
|
|
DoneRouting=DoneRouting,
|
|
|
|
Position=GetPosition,
|
|
TravelTime=ObjectiveTravelTime,
|
|
TravelTime2=ObjectiveTravelTime2,
|
|
|
|
IsWatched=IsObjectiveWatched,
|
|
|
|
Share=ObjectiveShare, -- Invoke to share this objective with your peers.
|
|
Unshare=ObjectiveUnshare, -- Invoke to stop sharing this objective.
|
|
}
|
|
|
|
QuestHelper.default_objective_item_param =
|
|
{
|
|
Known = ItemKnown,
|
|
AppendPositions = ItemAppendPositions,
|
|
DoMarkUsed = ItemDoMarkUsed
|
|
}
|
|
|
|
for key, value in pairs(QuestHelper.default_objective_param) do
|
|
if not QuestHelper.default_objective_item_param[key] then
|
|
QuestHelper.default_objective_item_param[key] = value
|
|
end
|
|
end
|
|
|
|
QuestHelper.default_objective_meta = { __index = QuestHelper.default_objective_param }
|
|
QuestHelper.default_objective_item_meta = { __index = QuestHelper.default_objective_item_param }
|
|
|
|
function QuestHelper:NewObjectiveObject()
|
|
next_objective_id = next_objective_id+1
|
|
return
|
|
setmetatable({
|
|
qh=self,
|
|
id=next_objective_id,
|
|
|
|
want_share=false, -- True if we want this objective shared.
|
|
is_sharing=false, -- Set to true if we've told other users about this objective.
|
|
|
|
user_ignore=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
|
|
|
|
priority=3, -- A hint as to what priority the quest should have. Should be 1, 2, 3, 4, or 5.
|
|
real_priority=3, -- This will be set to the priority routing actually decided to assign it.
|
|
|
|
setup_count=0,
|
|
|
|
icon_id=12,
|
|
icon_bg=14,
|
|
|
|
match_zone=false,
|
|
match_level=false,
|
|
match_done=false,
|
|
|
|
before={}, -- List of objectives that this objective must appear before.
|
|
after={}, -- List of objectives that this objective must appear after.
|
|
|
|
-- Routing related junk.
|
|
|
|
--[[ Will be created as needed.
|
|
d=nil,
|
|
p=nil,
|
|
nm=nil, -- Maps nodes to their nearest zone/list/x/y position.
|
|
nm2=nil, -- Maps nodes to their nears position, but dynamically set in TravelTime2.
|
|
nl=nil, -- List of all the nodes we need to consider.
|
|
location=nil, -- Will be set to the best position for the node.
|
|
pos=nil, -- Zone node list, distance list, x, y, reason.
|
|
sop=nil ]]
|
|
}, QuestHelper.default_objective_meta)
|
|
end
|
|
|
|
local explicit_support_warning_given = false
|
|
|
|
function QuestHelper:GetObjective(category, objective)
|
|
local objective_list = self.objective_objects[category]
|
|
|
|
if not objective_list then
|
|
objective_list = {}
|
|
self.objective_objects[category] = objective_list
|
|
end
|
|
|
|
local objective_object = objective_list[objective]
|
|
|
|
if not objective_object then
|
|
if category == "quest" then
|
|
local level, hash, name = string.match(objective, "^(%d+)/(%d*)/(.*)$")
|
|
if not level then
|
|
level, name = string.match(objective, "^(%d+)/(.*)$")
|
|
if not level then
|
|
name = objective
|
|
end
|
|
end
|
|
|
|
if hash == "" then hash = nil end
|
|
objective_object = self:GetQuest(name, tonumber(level), tonumber(hash))
|
|
objective_list[objective] = objective_object
|
|
return objective_object
|
|
end
|
|
|
|
objective_object = self:NewObjectiveObject()
|
|
|
|
objective_object.cat = category
|
|
objective_object.obj = objective
|
|
|
|
if category == "item" then
|
|
setmetatable(objective_object, QuestHelper.default_objective_item_meta)
|
|
objective_object.icon_id = 2
|
|
elseif category == "monster" then
|
|
objective_object.icon_id = 1
|
|
elseif category == "object" then
|
|
objective_object.icon_id = 3
|
|
elseif category == "event" then
|
|
objective_object.icon_id = 4
|
|
elseif category == "loc" then
|
|
objective_object.icon_id = 6
|
|
elseif category == "reputation" then
|
|
objective_object.icon_id = 5
|
|
elseif category == "player" then
|
|
objective_object.icon_id = 1 -- not ideal, will improve later
|
|
else
|
|
if not explicit_support_warning_given then
|
|
self:TextOut("FIXME: Objective type '"..category.."' for objective '"..objective.."' isn't explicitly supported yet; hopefully the dummy handler will do something sensible.")
|
|
explicit_support_warning_given = true
|
|
end
|
|
end
|
|
|
|
objective_list[objective] = objective_object
|
|
|
|
if category == "loc" then
|
|
-- Loc is special, we don't store it, and construct it from the string.
|
|
-- Don't have any error checking here, will assume it's correct.
|
|
local i
|
|
local _, _, c, z, x, y = string.find(objective,"^(%d+),(%d+),([%d%.]+),([%d%.]+)$")
|
|
|
|
if not y then
|
|
_, _, i, x, y = string.find(objective,"^(%d+),([%d%.]+),([%d%.]+)$")
|
|
else
|
|
i = QuestHelper_IndexLookup[c][z]
|
|
end
|
|
|
|
objective_object.o = {pos={{tonumber(i),tonumber(x),tonumber(y),1}}}
|
|
objective_object.fb = {}
|
|
else
|
|
objective_list = QuestHelper_Objectives_Local[category]
|
|
if not objective_list then
|
|
objective_list = {}
|
|
QuestHelper_Objectives_Local[category] = objective_list
|
|
end
|
|
objective_object.o = objective_list[objective]
|
|
if not objective_object.o then
|
|
objective_object.o = {}
|
|
objective_list[objective] = objective_object.o
|
|
end
|
|
local l = QuestHelper_StaticData[self.locale]
|
|
if l then
|
|
objective_list = l.objective[category]
|
|
if objective_list then
|
|
objective_object.fb = objective_list[objective]
|
|
end
|
|
end
|
|
if not objective_object.fb then
|
|
objective_object.fb = {}
|
|
end
|
|
|
|
-- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
|
|
|
|
end
|
|
end
|
|
|
|
return objective_object
|
|
end
|
|
|
|
function QuestHelper:AppendObjectivePosition(objective, i, x, y, w)
|
|
if not i then return end -- We don't have a player position. We have a pile of poop. Enjoy your poop.
|
|
|
|
local pos = objective.o.pos
|
|
if not pos then
|
|
if objective.o.drop or objective.o.contained then
|
|
return -- If it's dropped by a monster, don't record the position we got the item at.
|
|
end
|
|
objective.o.pos = self:AppendPosition({}, i, x, y, w)
|
|
else
|
|
self:AppendPosition(pos, i, x, y, w)
|
|
end
|
|
end
|
|
|
|
function QuestHelper:AppendObjectiveDrop(objective, monster, count)
|
|
local drop = objective.o.drop
|
|
if drop then
|
|
drop[monster] = (drop[monster] or 0)+(count or 1)
|
|
else
|
|
objective.o.drop = {[monster] = count or 1}
|
|
objective.o.pos = nil -- If it's dropped by a monster, then forget the position we found it at.
|
|
end
|
|
end
|
|
|
|
function QuestHelper:AppendItemObjectiveDrop(item_object, item_name, monster_name, count)
|
|
local quest = self:ItemIsForQuest(item_object, item_name)
|
|
if quest and not item_object.o.vendor and not item_object.o.drop and not item_object.o.pos then
|
|
self:AppendQuestDrop(quest, item_name, monster_name, count)
|
|
else
|
|
if not item_object.o.drop and not item_object.o.pos then
|
|
self:PurgeQuestItem(item_object, item_name)
|
|
end
|
|
self:AppendObjectiveDrop(item_object, monster_name, count)
|
|
end
|
|
end
|
|
|
|
function QuestHelper:AppendItemObjectivePosition(item_object, item_name, i, x, y)
|
|
local quest = self:ItemIsForQuest(item_object, item_name)
|
|
if quest and not item_object.o.vendor and not item_object.o.drop and not item_object.o.pos then
|
|
self:AppendQuestPosition(quest, item_name, i, x, y)
|
|
else
|
|
if not item_object.o.vendor and not item_object.o.drop and not item_object.o.contained and not item_object.o.pos then
|
|
-- Just learned that this item doesn't depend on a quest to drop, remove any quest references to it.
|
|
self:PurgeQuestItem(item_object, item_name)
|
|
end
|
|
self:AppendObjectivePosition(item_object, i, x, y)
|
|
end
|
|
end
|
|
|
|
function QuestHelper:AppendItemObjectiveContainer(objective, container_name, count)
|
|
local container = objective.o.contained
|
|
if container then
|
|
container[container_name] = (container[container_name] or 0)+(count or 1)
|
|
else
|
|
objective.o.contained = {[container_name] = count or 1}
|
|
objective.o.pos = nil -- Forget the position.
|
|
end
|
|
end
|
|
|
|
function QuestHelper:AddObjectiveWatch(objective, reason)
|
|
if not objective.reasons then
|
|
objective.reasons = {}
|
|
end
|
|
|
|
if not next(objective.reasons, nil) then
|
|
objective.watched = true
|
|
objective:MarkUsed()
|
|
|
|
objective.filter_blocked = false
|
|
for obj in pairs(objective.swap_after or objective.after) do
|
|
if obj.watched then
|
|
objective.filter_blocked = true
|
|
break
|
|
end
|
|
end
|
|
|
|
for obj in pairs(objective.swap_before or objective.before) do
|
|
if obj.watched then
|
|
obj.filter_blocked = true
|
|
end
|
|
end
|
|
|
|
if self.to_remove[objective] then
|
|
self.to_remove[objective] = nil
|
|
else
|
|
self.to_add[objective] = true
|
|
end
|
|
end
|
|
|
|
objective.reasons[reason] = (objective.reasons[reason] or 0) + 1
|
|
end
|
|
|
|
function QuestHelper:RemoveObjectiveWatch(objective, reason)
|
|
if objective.reasons[reason] == 1 then
|
|
objective.reasons[reason] = nil
|
|
if not next(objective.reasons, nil) then
|
|
objective:MarkUnused()
|
|
objective.watched = false
|
|
|
|
for obj in pairs(objective.swap_before or objective.before) do
|
|
if obj.watched then
|
|
obj.filter_blocked = false
|
|
for obj2 in pairs(obj.swap_after or obj.after) do
|
|
if obj2.watched then
|
|
obj.filter_blocked = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.to_add[objective] then
|
|
self.to_add[objective] = nil
|
|
else
|
|
self.to_remove[objective] = true
|
|
end
|
|
end
|
|
else
|
|
objective.reasons[reason] = objective.reasons[reason] - 1
|
|
end
|
|
end
|
|
|
|
function QuestHelper:ObjectiveObjectDependsOn(objective, needs)
|
|
--[[ assert(objective ~= needs) ]] -- If this was true, ObjectiveIsKnown would get in an infinite loop.
|
|
-- TODO: Needs sanity checking, especially now that dependencies can be assigned by remote users.
|
|
|
|
|
|
-- We store the new relationships in objective.swap_[before|after],
|
|
-- creating and copying them from objective.[before|after],
|
|
-- the routing coroutine will check for those, swap them, and release the originals
|
|
-- when it gets to a safe place to do so.
|
|
|
|
if not (objective.swap_after or objective.after)[needs] then
|
|
if objective.peer then
|
|
for u, l in pairs(objective.peer) do
|
|
-- Make sure other users know that the dependencies for this objective changed.
|
|
objective.peer[u] = math.min(l, 1)
|
|
end
|
|
end
|
|
|
|
if not objective.swap_after then
|
|
objective.swap_after = self:CreateTable("swap_after")
|
|
for key,value in pairs(objective.after) do objective.swap_after[key] = value end
|
|
end
|
|
|
|
if not needs.swap_before then
|
|
needs.swap_before = self:CreateTable("swap_before")
|
|
for key,value in pairs(needs.before) do needs.swap_before[key] = value end
|
|
end
|
|
|
|
if needs.watched then
|
|
objective.filter_blocked = true
|
|
end
|
|
|
|
objective.swap_after[needs] = true
|
|
needs.swap_before[objective] = true
|
|
end
|
|
end
|
|
|
|
function QuestHelper:IgnoreObjective(objective)
|
|
if self.user_objectives[objective] then
|
|
self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
|
|
self.user_objectives[objective] = nil
|
|
else
|
|
objective.user_ignore = true
|
|
end
|
|
|
|
--self:ForceRouteUpdate()
|
|
end
|
|
|
|
function QuestHelper:SetObjectivePriority(objective, level)
|
|
level = math.min(5, math.max(1, math.floor((tonumber(level) or 3)+0.5)))
|
|
if level ~= objective.priority then
|
|
objective.priority = level
|
|
if objective.peer then
|
|
for u, l in pairs(objective.peer) do
|
|
-- Peers don't know about this new priority.
|
|
objective.peer[u] = math.min(l, 2)
|
|
end
|
|
end
|
|
--self:ForceRouteUpdate()
|
|
end
|
|
end
|
|
|
|
local function CalcObjectivePriority(obj)
|
|
local priority = obj.priority
|
|
|
|
for o in pairs(obj.before) do
|
|
if o.watched then
|
|
priority = math.min(priority, CalcObjectivePriority(o))
|
|
end
|
|
end
|
|
|
|
return priority
|
|
end
|
|
|
|
local function ApplyBlockPriority(obj, level)
|
|
for o in pairs(obj.before) do
|
|
if o.watched then
|
|
ApplyBlockPriority(o, level)
|
|
end
|
|
end
|
|
|
|
if obj.priority < level then QuestHelper:SetObjectivePriority(obj, level) end
|
|
end
|
|
|
|
function QuestHelper:SetObjectivePriorityPrompt(objective, level)
|
|
self:SetObjectivePriority(objective, level)
|
|
if CalcObjectivePriority(objective) ~= level then
|
|
local menu = self:CreateMenu()
|
|
self:CreateMenuTitle(menu, QHText("IGNORED_PRIORITY_TITLE"))
|
|
self:CreateMenuItem(menu, QHText("IGNORED_PRIORITY_FIX")):SetFunction(ApplyBlockPriority, objective, level)
|
|
self:CreateMenuItem(menu, QHText("IGNORED_PRIORITY_IGNORE")):SetFunction(self.nop)
|
|
menu:ShowAtCursor()
|
|
end
|
|
end
|
|
|
|
function QuestHelper:SetObjectiveProgress(objective, user, have, need)
|
|
if have and need then
|
|
local list = objective.progress
|
|
if not list then
|
|
list = self:CreateTable("objective.progress")
|
|
objective.progress = list
|
|
end
|
|
|
|
local user_progress = list[user]
|
|
if not user_progress then
|
|
user_progress = self:CreateTable("objective.progress[user]")
|
|
list[user] = user_progress
|
|
end
|
|
|
|
local pct = 0
|
|
local a, b = tonumber(have), tonumber(need)
|
|
if a and b then
|
|
if b ~= 0 then
|
|
pct = a/b
|
|
elseif a == 0 then
|
|
pct = 1
|
|
end
|
|
elseif a == b then
|
|
pct = 1
|
|
end
|
|
|
|
user_progress[1], user_progress[2], user_progress[3] = have, need, pct
|
|
else
|
|
if objective.progress then
|
|
if objective.progress[user] then
|
|
self:ReleaseTable(objective.progress[user])
|
|
objective.progress[user] = nil
|
|
|
|
if not next(objective.progress, nil) then
|
|
self:ReleaseTable(objective.progress)
|
|
objective.progress = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|