1
0
Bifurcation 0
Ce dépôt a été archivé le 2020-03-15. Vous pouvez voir ses fichiers ou le cloner, mais pas ouvrir de ticket ou de demandes d'ajout, ni soumettre de changements.
questhelperredux/QuestHelper/objective.lua

1446 lignes
42 KiB
Lua
Brut Lien permanent Vue normale Historique

local GetTime = QuestHelper_GetTime
QuestHelper_File["objective.lua"] = "4.0.1.$svnversion$"
2010-10-24 23:17:33 +02:00
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()
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if pri == i then
tex = self:CreateIconTexture(item, 10)
else
tex = self:CreateIconTexture(item, 12)
tex:SetVertexColor(1, 1, 1, 0)
end
2010-10-24 23:17:33 +02:00
item:AddTexture(tex, true)
item:SetFunction(QH_Route_SetClusterPriority, obj.cluster, i - 3)
end
2010-10-24 23:17:33 +02:00
self:CreateMenuItem(menu, QHText("PRIORITY")):SetSubmenu(submenu)
2010-10-24 23:17:33 +02:00
--[[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)
2010-10-24 23:17:33 +02:00
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)
2010-10-24 23:17:33 +02:00
self:CreateMenuItem(menu, QHText("SHARING")):SetSubmenu(submenu)
end]]
2010-10-24 23:17:33 +02:00
--self:CreateMenuItem(menu, "(No options available)")
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
for i, j in pairs(self.after) do
if i.watched then
return false
end
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if not reason then reason = "Do some extremely secret unspecified something." end
2010-10-24 23:17:33 +02:00
if not short and self.pos and self.pos[6] then
reason = reason .. "\n" .. self.pos[6]
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if not uses then
uses = QuestHelper:CreateTable("uses")
self.uses = uses
end
2010-10-24 23:17:33 +02:00
if not used then
used = QuestHelper:CreateTable("used")
obj.used = used
end
2010-10-24 23:17:33 +02:00
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) ]]
2010-10-24 23:17:33 +02:00
if self.marked_used == 1 then
local uses = self.uses
2010-10-24 23:17:33 +02:00
if uses then
for obj in pairs(uses) do
obj.used[self] = nil
obj:MarkUnused()
end
2010-10-24 23:17:33 +02:00
QuestHelper:ReleaseTable(uses)
self.uses = nil
end
2010-10-24 23:17:33 +02:00
if self.used then
--[[ assert(not next(self.used)) ]]
QuestHelper:ReleaseTable(self.used)
self.used = nil
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if self.o.pos or self.fb.pos then
return true
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if self.quest then
local item=self.quest.o.item
item = item and item[self.obj]
if item then
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
item=self.quest.fb.item
item = item and item[self.obj]
if item then
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
return false
end
local function ObjectiveAppendPositions(self, objective, weight, why, restrict)
local high = 0
2010-10-24 23:17:33 +02:00
if self.o.pos then for i, p in ipairs(self.o.pos) do
high = math.max(high, p[4])
end end
2010-10-24 23:17:33 +02:00
if self.fb.pos then for i, p in ipairs(self.fb.pos) do
high = math.max(high, p[4])
end end
2010-10-24 23:17:33 +02:00
high = weight/high
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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) ]]
2010-10-24 23:17:33 +02:00
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")
2010-10-24 23:17:33 +02:00
if not anywhere then
self:AppendPositions(self, 1, nil, true)
2010-10-24 23:17:33 +02:00
if not next(self.p) then
QuestHelper:TextOut(QHFormat("INACCESSIBLE_OBJ", self.obj or "whatever it was you just requested"))
anywhere = true
end
end
2010-10-24 23:17:33 +02:00
if anywhere then
self:AppendPositions(self, 1, nil, false)
end
2010-10-24 23:17:33 +02:00
self:FinishAddLoc(args)
end
end
local function ItemAppendPositions(self, objective, weight, why, restrict)
why2 = why and why.."\n" or ""
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
item_list=self.quest.fb.item
if item_list then
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
item_list=self.quest.fb.item
if item_list then
2010-10-24 23:17:33 +02:00
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
---------------
-- NEEDS CONVERSION FOR Map, Floor.
2010-10-24 23:17:33 +02:00
local function AddLoc(self, index, x, y, w, why)
--[[ assert(not self.setup) ]]
2010-10-24 23:17:33 +02:00
if w > 0 then
local pair = QuestHelper_ZoneLookup[index]
QuestHelper: Assert(pair, "Index does not exist... it is probably 26 or 38 and the value really is... '" .. tostring(index) .. "'")
2010-10-24 23:17:33 +02:00
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)
2010-10-24 23:17:33 +02:00
x = x * self.qh.continent_scales_x[c]
y = y * self.qh.continent_scales_y[c]
local list = self.qh.zone_nodes[index]
2010-10-24 23:17:33 +02:00
local points = self.p[list]
if not points then
points = QuestHelper:CreateTable("objective.p[zone] (objective nodes per-zone)")
self.p[list] = points
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
-- 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
2010-10-24 23:17:33 +02:00
local node_map = self.nm
local node_list = self.nl
2010-10-24 23:17:33 +02:00
for list, pl in pairs(self.p) do
local dist = self.d[list]
2010-10-24 23:17:33 +02:00
--[[ assert(not dist) ]]
2010-10-24 23:17:33 +02:00
if not dist then
dist = QuestHelper:CreateTable("self.d[list]")
self.d[list] = dist
end
2010-10-24 23:17:33 +02:00
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.
2010-10-24 23:17:33 +02:00
point[2] = QuestHelper:CreateTable("possible objective node to zone edge cache")
2010-10-24 23:17:33 +02:00
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)
2010-10-24 23:17:33 +02:00
point[2][i] = d
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
-- 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
2010-10-24 23:17:33 +02:00
--[[ assert(not self.setup) ]]
self.setup = true
table.insert(self.qh.prepared_objectives, self)
end
local function GetPosition(self)
--[[ assert(self.setup) ]]
2010-10-24 23:17:33 +02:00
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) ]]
2010-10-24 23:17:33 +02:00
-- 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
2010-10-24 23:17:33 +02:00
graph:PrepareSearch()
2010-10-24 23:17:33 +02:00
-- 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
2010-10-24 23:17:33 +02:00
local d = pos[2]
for i, n in ipairs(pos[1]) do
graph:AddStartNode(n, d[i], nl)
end
2010-10-24 23:17:33 +02:00
local e = graph:DoSearch(nl)
2010-10-24 23:17:33 +02:00
-- 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]
2010-10-24 23:17:33 +02:00
-- 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]
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
--[[ 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) ]]
2010-10-24 23:17:33 +02:00
-- 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
2010-10-24 23:17:33 +02:00
-- This is the standard pos1-to-self code that we're used to seeing . . .
graph:PrepareSearch()
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
local d = pos1[2]
for i, n in ipairs(pos1[1]) do
graph:AddStartNode(n, d[i], nl)
end
2010-10-24 23:17:33 +02:00
graph:DoFullSearch(nl)
2010-10-24 23:17:33 +02:00
graph:PrepareSearch()
2010-10-24 23:17:33 +02:00
-- . . . 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]
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
d = pos2[2]
2010-10-24 23:17:33 +02:00
for i, n in ipairs(pos2[1]) do
n.e = d[i]
n.s = 3
end
2010-10-24 23:17:33 +02:00
local el = pos2[1]
local nm = self.nm2
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
local e = graph:DoSearch(pos2[1])
2010-10-24 23:17:33 +02:00
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])
2010-10-24 23:17:33 +02:00
e = nm[e.p]
local total = (d+d2)*e[5]
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
-- 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) ]]
2010-10-24 23:17:33 +02:00
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,
2010-10-24 23:17:33 +02:00
Uses=Uses,
DoMarkUsed=DoMarkUsed,
MarkUsed=MarkUsed,
MarkUnused=MarkUnused,
2010-10-24 23:17:33 +02:00
DefaultKnown=DefaultObjectiveKnown,
Known=DummyObjectiveKnown,
Reason=ObjectiveReason,
2010-10-24 23:17:33 +02:00
AppendPositions=ObjectiveAppendPositions,
PrepareRouting=ObjectivePrepareRouting,
AddLoc=AddLoc,
FinishAddLoc=FinishAddLoc,
DoneRouting=DoneRouting,
2010-10-24 23:17:33 +02:00
Position=GetPosition,
TravelTime=ObjectiveTravelTime,
TravelTime2=ObjectiveTravelTime2,
IsWatched=IsObjectiveWatched,
2010-10-24 23:17:33 +02:00
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,
2010-10-24 23:17:33 +02:00
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.
2010-10-24 23:17:33 +02:00
user_ignore=nil, -- When nil, will use filters. Will ignore, when true, always show (if known).
2010-10-24 23:17:33 +02:00
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.
2010-10-24 23:17:33 +02:00
setup_count=0,
2010-10-24 23:17:33 +02:00
icon_id=12,
icon_bg=14,
2010-10-24 23:17:33 +02:00
match_zone=false,
match_level=false,
match_done=false,
2010-10-24 23:17:33 +02:00
before={}, -- List of objectives that this objective must appear before.
after={}, -- List of objectives that this objective must appear after.
2010-10-24 23:17:33 +02:00
-- Routing related junk.
2010-10-24 23:17:33 +02:00
--[[ 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]
2010-10-24 23:17:33 +02:00
if not objective_list then
objective_list = {}
self.objective_objects[category] = objective_list
end
2010-10-24 23:17:33 +02:00
local objective_object = objective_list[objective]
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if hash == "" then hash = nil end
objective_object = self:GetQuest(name, tonumber(level), tonumber(hash))
objective_list[objective] = objective_object
return objective_object
end
2010-10-24 23:17:33 +02:00
objective_object = self:NewObjectiveObject()
2010-10-24 23:17:33 +02:00
objective_object.cat = category
objective_object.obj = objective
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
objective_list[objective] = objective_object
2010-10-24 23:17:33 +02:00
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%.]+)$")
2010-10-24 23:17:33 +02:00
if not y then
_, _, i, x, y = string.find(objective,"^(%d+),([%d%.]+),([%d%.]+)$")
else
i = QuestHelper_IndexLookup[c][z]
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
-- TODO: If we have some other source of information (like LightHeaded) add its data to objective_object.fb
2010-10-24 23:17:33 +02:00
end
end
2010-10-24 23:17:33 +02:00
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.
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if not next(objective.reasons, nil) then
objective.watched = true
objective:MarkUsed()
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
for obj in pairs(objective.swap_before or objective.before) do
if obj.watched then
obj.filter_blocked = true
end
end
2010-10-24 23:17:33 +02:00
if self.to_remove[objective] then
self.to_remove[objective] = nil
else
self.to_add[objective] = true
end
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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.
2010-10-24 23:17:33 +02:00
-- 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.
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if needs.watched then
objective.filter_blocked = true
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
--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
2010-10-24 23:17:33 +02:00
for o in pairs(obj.before) do
if o.watched then
priority = math.min(priority, CalcObjectivePriority(o))
end
end
2010-10-24 23:17:33 +02:00
return priority
end
local function ApplyBlockPriority(obj, level)
for o in pairs(obj.before) do
if o.watched then
ApplyBlockPriority(o, level)
end
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
local user_progress = list[user]
if not user_progress then
user_progress = self:CreateTable("objective.progress[user]")
list[user] = user_progress
end
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
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
2010-10-24 23:17:33 +02:00
if not next(objective.progress, nil) then
self:ReleaseTable(objective.progress)
objective.progress = nil
end
end
end
end
end