1 QuestHelper_File
["graph_core.lua"] = "Development Version"
2 QuestHelper_Loadtime
["graph_core.lua"] = GetTime()
4 -- Alright so what's the interface here
5 -- There's the obvious "find a path from A to B"
6 -- function QH_Graph_Pathfind(st, nd, make_path)
7 -- Right now, we're pretending that the world consists of infinite planes with links between them. That's where the whole "between-zone" thing occurs. So one way or another, we need to add links. We'll use name as an identifier so we can get rid of flight paths links later, and the coords will include a plane ID number.
8 -- function QH_Graph_Plane_Makelink(name, coord1, coord2, cost)
9 -- And then we need a way to get rid of links, so:
11 -- how does flying work zorba
15 -- Make a map from "phase" to "flyphase". Store all the links we're being told to make. When placing links, if the flyphase is flyable, we use the flyphase instead of the phase for placing. We don't place if it's an internal boundary (there's a few ways we can detect this, but let's just use the hacky one where we just look at the ID.) From there it's all pretty standard.
17 local function heap_left(x
) return (2*x
) end
18 local function heap_right(x
) return (2*x
+ 1) end
20 local function heap_sane(heap
)
22 local finishbefore
= 2
24 if i
== finishbefore
then
27 finishbefore
= finishbefore
* 2
29 dmp
= dmp
.. string.format("%f ", heap
[i
].c
)
34 assert(not heap
[heap_left(i
)] or heap
[i
].c
<= heap
[heap_left(i
)].c
)
35 assert(not heap
[heap_right(i
)] or heap
[i
].c
<= heap
[heap_right(i
)].c
)
39 local function heap_insert(heap
, item
)
41 table.insert(heap
, item
)
44 local ptd2
= math
.floor(pt
/ 2)
45 if heap
[ptd2
].c
<= heap
[pt
].c
then
56 local function heap_extract(heap
)
58 if #heap
== 1 then table.remove(heap
) return rv
end
59 heap
[1] = table.remove(heap
)
63 if heap
[heap_left(idx
)] and heap
[heap_left(idx
)].c
< heap
[minix
].c
then minix
= heap_left(idx
) end
64 if heap
[heap_right(idx
)] and heap
[heap_right(idx
)].c
< heap
[minix
].c
then minix
= heap_right(idx
) end
66 local tx
= heap
[minix
]
67 heap
[minix
] = heap
[idx
]
78 -- incremented on each graph iteration, so we don't have to clear everything. "GRaph ID" :D
81 -- Plane format: each plane contains an array of nodes that exist in that plane.
82 -- Each node contains both its parent ID and its coordinates within that plane. It may contain a node it links to, along with cost.
85 local plane_to_flyplane
= {}
86 local continent_to_flyplane
= {}
87 local flyplanes_enabled
= {}
88 local plane_multiplier
= {}
91 local function canoplane(plane
)
92 if flyplanes_enabled
[plane_to_flyplane
[plane]]
then return plane_to_flyplane
[plane
] else return plane
end
95 local function xydist(st
, nd
)
96 QuestHelper
: Assert(canoplane(st
.p
) == canoplane(nd
.p
))
97 local dx
, dy
= st
.x
- nd
.x
, st
.y
- nd
.y
98 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[canoplane(nd
.p
)] or 7) -- we're getting a result in seconds, not in yards
105 function QH_Graph_Pathfind(st
, nd
, reverse
, make_path
)
106 return QH_Graph_Pathmultifind(st
, {nd
}, reverse
, make_path
)[1]
111 function QH_Graph_Pathmultifind(st
, nda
, reverse
, make_path
)
112 QuestHelper
: Assert(not active
)
113 active
= true -- The fun thing about coroutines is that this is actually safe.
114 local out
= QuestHelper
:CreateTable("graphcore output") -- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
116 local undone
= QuestHelper
:CreateTable("graphcore undone")
119 local link
= QuestHelper
:CreateTable("graphcore link")
121 QuestHelper
: Assert(st
.x
and st
.y
and st
.p
)
123 for k
, v
in ipairs(nda
) do
124 QuestHelper
: Assert(v
.x
and v
.y
and v
.p
)
125 local cpvp
= canoplane(v
.p
)
126 if canoplane(st
.p
) == cpvp
then
127 out
[k
] = xydist(st
, v
)
130 --print("Destination plane insertion")
131 -- ugh I hate this optimization
132 local dest
= QuestHelper
:CreateTable("graphcore destination")
133 dest
.x
, dest
.y
, dest
.p
, dest
.goal
= v
.x
, v
.y
, cpvp
, k
135 table.insert(plane
[cpvp
], link
[k
])
137 remaining
= remaining
+ 1
142 local link_id
= reverse
and "rlink" or "link"
143 local link_cost_id
= reverse
and "rlink_cost" or "link_cost"
145 local dijheap
= QuestHelper
:CreateTable("graphcore heap center")
146 if plane
[canoplane(st
.p
)] then
147 --print("ST plane insertion")
148 for _
, v
in ipairs(plane
[canoplane(st
.p
)]) do
150 QuestHelper
: Assert(not v
.goal
)
151 local dst
= xydist(st
, v
)
156 local hep
= QuestHelper
:CreateTable("graphcore heap")
157 hep
.c
, hep
.cs
, hep
.n
= dst
+ v
[link_cost_id
], dst
, v
158 heap_insert(dijheap
, hep
)
163 while remaining
> 0 and #dijheap
> 0 do
165 local cdj
= heap_extract(dijheap
)
167 if undone
[cdj
.done
] then
168 undone
[cdj
.done
] = nil
169 remaining
= remaining
- 1
172 --print(string.format("Extracted cost %f/%s pointing at %f/%f/%d", cdj.c, tostring(cdj.n.scan_cost), cdj.n.x, cdj.n.y, cdj.n.p))
173 QuestHelper
: Assert(cdj
.n
[link_id
])
174 if cdj
.n
.scan_cost
== cdj
.cs
then -- if we've modified it since then, don't bother
175 local linkto
= cdj
.n
[link_id
]
176 local basecost
= cdj
.c
+ cdj
.n
[link_cost_id
]
177 if linkto
.scan_id
~= grid
or linkto
.scan_cost
> basecost
then
178 linkto
.scan_id
= grid
179 linkto
.scan_cost
= basecost
180 linkto
.scan_from
= nil
182 for _
, v
in ipairs(plane
[linkto
.p
]) do
184 -- One way or another, we gotta calculate this.
185 local goalcost
= basecost
+ xydist(linkto
, v
)
186 if not out
[v
.goal
] or out
[v
.goal
] > goalcost
then
187 out
[v
.goal
] = goalcost
190 local hep
= QuestHelper
:CreateTable("graphcore heap")
191 hep
.c
, hep
.done
= goalcost
, v
.goal
192 heap_insert(dijheap
, hep
)
194 elseif v
[link_id
] and (v
.scan_id
~= grid
or v
.scan_cost
> basecost
) then
195 local goalcost
= basecost
+ xydist(linkto
, v
)
196 if v
.scan_id
~= grid
or v
.scan_cost
> goalcost
then
198 v
.scan_cost
= goalcost
201 local hep
= QuestHelper
:CreateTable("graphcore heap")
202 hep
.c
, hep
.cs
, hep
.n
= goalcost
+ v
[link_cost_id
], goalcost
, v
203 heap_insert(dijheap
, hep
)
210 QuestHelper
:ReleaseTable(cdj
)
213 for _
, v
in ipairs(dijheap
) do
214 QuestHelper
:ReleaseTable(v
)
216 QuestHelper
:ReleaseTable(dijheap
)
219 --QuestHelper:TextOut(string.format("Earlyout with %d/%d remaining", #dijheap, remaining))
220 if remaining
> 0 then
221 for k
, v
in ipairs(nda
) do
223 QuestHelper
: Assert(false, string.format("Couldn't find path to %d/%f/%f", nda
[k
].p
, nda
[k
].x
, nda
[k
].y
))
227 QuestHelper
: Assert(remaining
== 0)
230 QuestHelper
: Assert(grid
< 1000000000) -- if this ever triggers I will be amazed
234 for k
, v
in pairs(out
) do
236 local rp
= QuestHelper
:CreateTable("graphcore return")
243 QuestHelper
: Assert(link
[k
].scan_from
)
244 local tpath
= reverse
and rp
or QuestHelper
:CreateTable("graphcore path reversal")
245 local cpx
= link
[k
].scan_from
248 QuestHelper
: Assert(cpx
.rlink
)
249 table.insert(tpath
, cpx
.rlink
)
251 QuestHelper
: Assert(cpx
.link
)
252 table.insert(tpath
, cpx
.link
)
254 QuestHelper
: Assert(cpx
)
255 table.insert(tpath
, cpx
)
261 for i
= #tpath
, 1, -1 do
262 table.insert(rp
, tpath
[i
])
265 QuestHelper
: Assert(tpath
~= rp
)
266 QuestHelper
:ReleaseTable(tpath
)
272 for k
, v
in ipairs(nda
) do
273 if plane
[canoplane(v
.p
)] and plane
[canoplane(v
.p
)][#plane
[canoplane(v
.p
)]]
.goal
then -- might not be the exact one, but we'll remove 'em all once we get there anyway :D
274 QuestHelper
:ReleaseTable(table.remove(plane
[canoplane(v
.p
)]))
278 QuestHelper
:ReleaseTable(link
)
279 QuestHelper
:ReleaseTable(undone
)
282 return out
-- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
285 function QH_Graph_Init()
286 for c
, d
in pairs(QuestHelper_IndexLookup
) do
287 if type(c
) == "number" then
288 QuestHelper
: Assert(d
[0])
289 continent_to_flyplane
[c
] = d
[0]
290 for z
, p
in pairs(d
) do
291 if type(z
) == "number" then
292 --QuestHelper:TextOut(string.format("%d/%d: %d", c, z, p))
293 plane_to_flyplane
[p
] = d
[0]
302 local function QH_Graph_Plane_ReallyMakeLink(item
)
303 local name
, coord1
, coord2
, cost
, cost_reverse
= unpack(item
)
305 QuestHelper
: Assert(not active
)
307 --QuestHelper: TextOut(string.format("Link '%s' made from %d/%f/%f to %d/%f/%f of cost %f, asymflag %s", name, coord1.p, coord1.x, coord1.y, coord2.p, coord2.x, coord2.y, cost, tostring(not not asymmetrical)))
308 QuestHelper
: Assert(name
)
309 QuestHelper
: Assert(coord1
)
310 QuestHelper
: Assert(coord2
)
311 QuestHelper
: Assert(cost
)
313 local node1
= {x
= coord1
.x
, y
= coord1
.y
, p
= canoplane(coord1
.p
), c
= coord1
.c
, map_desc
= coord1
.map_desc
, condense_class
= coord1
.condense_class
, name
= name
}
314 local node2
= {x
= coord2
.x
, y
= coord2
.y
, p
= canoplane(coord2
.p
), c
= coord2
.c
, map_desc
= coord2
.map_desc
, condense_class
= coord2
.condense_class
, name
= name
}
316 if node1
.p
== node2
.p
then
317 -- if they're the same location, we don't want to include them
318 -- right now, "the same location" is being done really, really cheaply
319 -- hey look me! I'm kind of a bastard!
321 if name
== "static_transition" then return end -- ha ha, yep, that's how we find out, tooootally reliable
324 if not plane
[node1
.p
] then plane
[node1
.p
] = {} end
325 if not plane
[node2
.p
] then plane
[node2
.p
] = {} end
327 node1
.link
, node1
.link_cost
, node2
.rlink
, node2
.rlink_cost
= node2
, cost
, node1
, cost
328 if cost_reverse
then node1
.rlink
, node1
.rlink_cost
, node2
.link
, node2
.link_cost
= node2
, cost_reverse
, node1
, cost_reverse
end
330 table.insert(plane
[node1
.p
], node1
)
331 table.insert(plane
[node2
.p
], node2
)
334 function QH_Graph_Plane_Makelink(name
, coord1
, coord2
, cost
, cost_reverse
)
335 QuestHelper
: Assert(not active
)
337 --QuestHelper: TextOut(string.format("Link '%s' made from %d/%f/%f to %d/%f/%f of cost %f, asymflag %s", name, coord1.p, coord1.x, coord1.y, coord2.p, coord2.x, coord2.y, cost, tostring(not not asymmetrical)))
338 QuestHelper
: Assert(name
)
339 QuestHelper
: Assert(coord1
)
340 QuestHelper
: Assert(coord2
)
341 QuestHelper
: Assert(cost
)
343 local tlink
= {name
, coord1
, coord2
, cost
, cost_reverse
}
344 if not linkages
[name
] then linkages
[name
] = {} end
345 table.insert(linkages
[name
], tlink
)
347 QH_Graph_Plane_ReallyMakeLink(tlink
)
350 local function QH_Graph_Plane_Destroylinkslocal(name
)
351 QuestHelper
: Assert(not active
)
353 for k
, v
in pairs(plane
) do
355 for tk
, tv
in ipairs(v
) do
356 if tv
.name
~= name
then
357 table.insert(repl
, tv
)
364 function QH_Graph_Plane_Destroylinks(name
)
365 QuestHelper
: Assert(not active
)
369 QH_Graph_Plane_Destroylinkslocal(name
)
372 function QH_Graph_Flyplaneset(fpset
, speed
)
373 QuestHelper
: Assert(not active
)
375 if not flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
then
376 flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
= true
377 for k
, v
in pairs(linkages
) do
378 QH_Graph_Plane_Destroylinkslocal(k
)
380 for _
, ite
in pairs(v
) do
381 QH_Graph_Plane_ReallyMakeLink(ite
)
385 plane_multiplier
[QuestHelper_IndexLookup
[fpset
][0]]
= speed