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 QH_Timeslice_Yield
= QH_Timeslice_Yield
-- performance hack :(
19 local function heap_left(x
) return (2*x
) end
20 local function heap_right(x
) return (2*x
+ 1) end
22 local function heap_sane(heap
)
24 local finishbefore
= 2
26 if i
== finishbefore
then
29 finishbefore
= finishbefore
* 2
31 dmp
= dmp
.. string.format("%f ", heap
[i
].c
)
36 assert(not heap
[heap_left(i
)] or heap
[i
].c
<= heap
[heap_left(i
)].c
)
37 assert(not heap
[heap_right(i
)] or heap
[i
].c
<= heap
[heap_right(i
)].c
)
41 local function heap_insert(heap
, item
)
43 table.insert(heap
, item
)
46 local ptd2
= math
.floor(pt
/ 2)
47 if heap
[ptd2
].c
<= heap
[pt
].c
then
58 local function heap_extract(heap
)
60 if #heap
== 1 then table.remove(heap
) return rv
end
61 heap
[1] = table.remove(heap
)
65 if heap
[heap_left(idx
)] and heap
[heap_left(idx
)].c
< heap
[minix
].c
then minix
= heap_left(idx
) end
66 if heap
[heap_right(idx
)] and heap
[heap_right(idx
)].c
< heap
[minix
].c
then minix
= heap_right(idx
) end
68 local tx
= heap
[minix
]
69 heap
[minix
] = heap
[idx
]
80 -- incremented on each graph iteration, so we don't have to clear everything. "GRaph ID" :D
83 -- Plane format: each plane contains an array of nodes that exist in that plane.
84 -- Each node contains both its parent ID and its coordinates within that plane. It may contain a node it links to, along with cost.
87 local plane_to_flyplane
= {}
88 local continent_to_flyplane
= {}
89 local flyplanes_enabled
= {}
90 local plane_multiplier
= {}
93 local function canoplane(plane
)
94 if flyplanes_enabled
[plane_to_flyplane
[plane]]
then return plane_to_flyplane
[plane
] else return plane
end
97 local function xydist_raw(plane
, stx
, sty
, ndx
, ndy
)
98 local dx
, dy
= stx
- ndx
, sty
- ndy
99 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[plane
] or 7)
101 local function xydist(st
, nd
)
102 QuestHelper
: Assert(canoplane(st
.p
) == canoplane(nd
.p
))
103 local dx
, dy
= st
.x
- nd
.x
, st
.y
- nd
.y
104 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[canoplane(nd
.p
)] or 7) -- we're getting a result in seconds, not in yards
111 function QH_Graph_Pathfind(st
, nd
, reverse
, make_path
)
112 return QH_Graph_Pathmultifind(st
, {nd
}, reverse
, make_path
)[1]
117 function QH_Graph_Pathmultifind(st
, nda
, reverse
, make_path
)
118 --QuestHelper:TextOut("Starting PMF")
119 QuestHelper
: Assert(not active
)
120 active
= true -- The fun thing about coroutines is that this is actually safe.
121 local out
= QuestHelper
:CreateTable("graphcore output") -- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
123 local undone
= QuestHelper
:CreateTable("graphcore undone")
126 local link
= QuestHelper
:CreateTable("graphcore link")
128 --local stats = QuestHelper:CreateTable("graphcore --stats")
130 QuestHelper
: Assert(st
.x
and st
.y
and st
.p
)
132 --stats.dests_complex = 0
133 --stats.dests_total = 0
135 for k
, v
in ipairs(nda
) do
136 QuestHelper
: Assert(v
.x
and v
.y
and v
.p
)
137 local cpvp
= canoplane(v
.p
)
139 --print("Destination plane insertion")
140 local dest
= QuestHelper
:CreateTable("graphcore destination")
141 dest
.x
, dest
.y
, dest
.p
, dest
.goal
= v
.x
, v
.y
, cpvp
, k
143 table.insert(plane
[cpvp
], link
[k
])
145 remaining
= remaining
+ 1
146 --stats.dests_complex = --stats.dests_complex + 1
148 --stats.dests_total = --stats.dests_total + 1
151 local link_id
= reverse
and "rlinks" or "links"
153 --stats.node_initialized_first = 0
154 --stats.node_done = 0
155 --stats.node_done_already = 0
156 --stats.node_modified_before_use = 0
157 --stats.node_link_reprocessed = 0
158 --stats.node_link_first = 0
159 --stats.node_link_alreadydone = 0
160 --stats.node_inner_reprocessed = 0
161 --stats.node_inner_first = 0
162 --stats.node_inner_alreadydone = 0
164 local dijheap
= QuestHelper
:CreateTable("graphcore heap container")
165 local stnode
= QuestHelper
:CreateTable("graphcore stnode")
167 stnode
.x
, stnode
.y
, stnode
.p
, stnode
.c
, stnode
.scan_id
, stnode
.scan_cost
= st
.x
, st
.y
, canoplane(st
.p
), st
.c
, grid
, 0
168 QuestHelper
: Assert(plane
[canoplane(st
.p
)])
169 table.insert(plane
[stnode
.p
], stnode
)
171 local hep
= QuestHelper
:CreateTable("graphcore heap")
172 hep
.c
, hep
.n
= 0, stnode
-- more than the subtraction, less than the minimum
173 heap_insert(dijheap
, hep
)
176 --stats.heap_max = #dijheap
178 --QuestHelper:TextOut("Starting routing")
180 while remaining
> 0 and #dijheap
> 0 do
182 --stats.heap_max = math.max(--stats.heap_max, #dijheap)
183 local cdj
= heap_extract(dijheap
)
186 --print(string.format("Extracted cost %f/%s pointing at %d/%f/%f", cdj.c, tostring(cdj.n.scan_cost), cdj.n.p, cdj.n.x, cdj.n.y))
187 if snode
.scan_cost
== cdj
.c
then -- if we've modified it since then, don't bother
188 -- Are we at an end node?
190 -- we wouldn't be here if we hadn't found a better solution
191 QuestHelper
: Assert(undone
[snode
.goal
])
192 out
[snode
.goal
] = cdj
.c
193 undone
[snode
.goal
] = nil
194 remaining
= remaining
- 1
197 -- Link to everything else on the plane
198 if not snode
.pi
then -- Did we come from this plane? If so, there's no reason to scan it again (flag means "plane internal")
199 for _
, v
in ipairs(plane
[snode
.p
]) do
200 if v
.scan_id
~= grid
or v
.scan_cost
> snode
.scan_cost
then
201 local dst
= xydist(snode
, v
)
202 local modcost
= snode
.scan_cost
+ dst
203 --print(string.format("Doing %d/%f vs %s/%s at %d/%f/%f", grid, modcost, tostring(v.scan_id), tostring(v.scan_cost), v.p, v.x, v.y))
204 if v
.scan_id
~= grid
or v
.scan_cost
> modcost
then
206 v
.scan_cost
= modcost
208 v
.scan_processed
= false
210 v
.scan_outnode_from
= nil
213 local snude
= QuestHelper
:CreateTable("graphcore heap")
214 snude
.c
, snude
.n
, snude
.pi
= modcost
, v
, true
215 heap_insert(dijheap
, snude
)
216 --print(string.format("Inserting %f at %d/%f/%f, plane", snude.c, v.p, v.x, v.y))
223 -- Link to everything we link to
224 if snode
[link_id
] then
225 for _
, lnk
in pairs(snode
[link_id
]) do
226 local mc2
= snode
.scan_cost
+ lnk
.cost
227 local linkto
= lnk
.link
228 if linkto
.scan_id
~= grid
or linkto
.scan_cost
> mc2
then
229 linkto
.scan_id
= grid
230 linkto
.scan_cost
= mc2
231 linkto
.scan_from
= snode
232 linkto
.scan_outnode
= lnk
.outnode_to
233 linkto
.scan_outnode_from
= lnk
.outnode_from
235 local hep
= QuestHelper
:CreateTable("graphcore heap")
236 hep
.c
, hep
.n
= mc2
, linkto
237 heap_insert(dijheap
, hep
)
238 --print(string.format("Inserting %f at %d/%f/%f, link", hep.c, linkto.p, linkto.x, linkto.y))
243 QuestHelper
:ReleaseTable(cdj
)
246 --QuestHelper:TextOut("Starting pathing")
248 for _
, v
in ipairs(dijheap
) do
249 QuestHelper
:ReleaseTable(v
)
251 QuestHelper
:ReleaseTable(dijheap
)
254 --QuestHelper:TextOut(string.format("Earlyout with %d/%d remaining", #dijheap, remaining))
255 if remaining
> 0 then
256 for k
, v
in ipairs(nda
) do
258 QuestHelper
: Assert(false, string.format("Couldn't find path to %d/%f/%f from %d/%f/%f", nda
[k
].p
, nda
[k
].x
, nda
[k
].y
, st
.p
, st
.x
, st
.y
))
262 QuestHelper
: Assert(remaining
== 0)
265 QuestHelper
: Assert(grid
< 1000000000) -- if this ever triggers I will be amazed
269 for k
, v
in pairs(out
) do
272 local rp
= QuestHelper
:CreateTable("graphcore return")
279 QuestHelper
: Assert(link
[k
].scan_from
)
280 local tpath
= reverse
and rp
or QuestHelper
:CreateTable("graphcore path reversal")
281 local cpx
= link
[k
].scan_from
284 QuestHelper
: Assert(cpx
)
286 if cpx
.scan_outnode
then
287 table.insert(tpath
, cpx
.scan_outnode
)
289 if cpx
.scan_outnode_from
then
290 table.insert(tpath
, cpx
.scan_outnode_from
)
295 QuestHelper
: Assert(not mdlast
)
298 for i
= #tpath
, 1, -1 do
299 table.insert(rp
, tpath
[i
])
302 QuestHelper
: Assert(tpath
~= rp
)
303 QuestHelper
:ReleaseTable(tpath
)
309 QuestHelper
:ReleaseTable(table.remove(plane
[stnode
.p
])) -- always added last, so we remove it first
310 for k
, v
in ipairs(nda
) do
311 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
312 QuestHelper
:ReleaseTable(table.remove(plane
[canoplane(v
.p
)]))
316 QuestHelper
:ReleaseTable(link
)
317 QuestHelper
:ReleaseTable(undone
)
319 --QuestHelper:TextOut("Finishing")
321 --for k, v in pairs(stats) do
326 return out
-- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
329 function QH_Graph_Init()
330 for c
, d
in pairs(QuestHelper_IndexLookup
) do
331 if type(c
) == "number" then
332 QuestHelper
: Assert(d
[0])
333 continent_to_flyplane
[c
] = d
[0]
334 for z
, p
in pairs(d
) do
335 if type(z
) == "number" then
336 --QuestHelper:TextOut(string.format("%d/%d: %d", c, z, p))
337 plane_to_flyplane
[p
] = d
[0]
346 local function findnode(coord
)
347 local p
= canoplane(coord
.p
)
348 if not plane
[p
] then plane
[p
] = {} end
349 for _
, v
in ipairs(plane
[p
]) do
350 if v
.x
== coord
.x
and v
.y
== coord
.y
and v
.name
== coord
.name
then
355 local nd
= {x
= coord
.x
, y
= coord
.y
, p
= p
, name
= coord
.name
}
356 table.insert(plane
[p
], nd
)
360 local function QH_Graph_Plane_ReallyMakeLink(item
)
361 local name
, coord1
, coord2
, cost
, cost_reverse
= unpack(item
)
363 QuestHelper
: Assert(not active
)
365 --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)))
366 QuestHelper
: Assert(name
)
367 QuestHelper
: Assert(coord1
)
368 QuestHelper
: Assert(coord2
)
369 QuestHelper
: Assert(cost
)
371 local n1p
= canoplane(coord1
.p
)
372 local n2p
= canoplane(coord2
.p
)
374 if canoplane(coord1
.p
) == canoplane(coord2
.p
) then
375 if name
== "static_transition" then return end -- ha ha, yep, that's how we find out, tooootally reliable
377 local xyd
= xydist_raw(canoplane(coord1
.p
), coord1
.x
, coord1
.y
, coord2
.x
, coord2
.y
)
378 if cost
>= xyd
and (not cost_reverse
or cost_reverse
>= xyd
) then
383 local node1
= findnode(coord1
)
384 local node2
= findnode(coord2
)
386 local n1d
= {x
= coord1
.x
, y
= coord1
.y
, p
= coord1
.p
, c
= coord1
.c
, map_desc
= coord1
.map_desc
, condense_class
= coord1
.condense_class
}
387 local n2d
= {x
= coord2
.x
, y
= coord2
.y
, p
= coord2
.p
, c
= coord2
.c
, map_desc
= coord2
.map_desc
, condense_class
= coord2
.condense_class
}
389 if not node1
.links
then node1
.links
= {} end
390 if not node2
.rlinks
then node2
.rlinks
= {} end
392 table.insert(node1
.links
, {cost
= cost
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
393 table.insert(node2
.rlinks
, {cost
= cost
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
396 if not node1
.rlinks
then node1
.rlinks
= {} end
397 if not node2
.links
then node2
.links
= {} end
399 table.insert(node1
.rlinks
, {cost
= cost_reverse
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
400 table.insert(node2
.links
, {cost
= cost_reverse
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
404 function QH_Graph_Plane_Makelink(name
, coord1
, coord2
, cost
, cost_reverse
)
405 QuestHelper
: Assert(not active
)
407 --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)))
408 QuestHelper
: Assert(name
)
409 QuestHelper
: Assert(coord1
)
410 QuestHelper
: Assert(coord2
)
411 QuestHelper
: Assert(cost
)
413 QuestHelper
: Assert(cost
>= 0)
414 QuestHelper
: Assert(not cost_reverse
or cost_reverse
>= 0)
416 --cost = math.max(cost, 0.01)
417 --if cost_reverse then cost_reverse = math.max(cost_reverse, 0.01) end
419 local tlink
= {name
, coord1
, coord2
, cost
, cost_reverse
}
420 if not linkages
[name
] then linkages
[name
] = {} end
421 table.insert(linkages
[name
], tlink
)
423 QH_Graph_Plane_ReallyMakeLink(tlink
)
426 local function QH_Graph_Plane_Destroylinkslocal(name
)
427 QuestHelper
: Assert(not active
)
429 for k
, v
in pairs(plane
) do
431 for tk
, tv
in ipairs(v
) do
432 if tv
.name
~= name
then
433 table.insert(repl
, tv
)
440 function QH_Graph_Plane_Destroylinks(name
)
441 QuestHelper
: Assert(not active
)
445 QH_Graph_Plane_Destroylinkslocal(name
)
448 function QH_Graph_Flyplaneset(fpset
, speed
)
449 QuestHelper
: Assert(not active
)
451 if not flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
then
452 flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
= true
453 for k
, v
in pairs(linkages
) do
454 QH_Graph_Plane_Destroylinkslocal(k
)
456 for _
, ite
in pairs(v
) do
457 QH_Graph_Plane_ReallyMakeLink(ite
)
461 plane_multiplier
[QuestHelper_IndexLookup
[fpset
][0]]
= speed