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 -- performance hack :(
18 local QH_Timeslice_Yield
= QH_Timeslice_Yield
19 local tinsert = table.insert
20 local pairs
, ipairs
= pairs
, ipairs
22 local function heap_left(x
) return (2*x
) end
23 local function heap_right(x
) return (2*x
+ 1) end
25 local function heap_sane(heap
)
27 local finishbefore
= 2
29 if i
== finishbefore
then
32 finishbefore
= finishbefore
* 2
34 dmp
= dmp
.. string.format("%f ", heap
[i
].c
)
39 assert(not heap
[heap_left(i
)] or heap
[i
].c
<= heap
[heap_left(i
)].c
)
40 assert(not heap
[heap_right(i
)] or heap
[i
].c
<= heap
[heap_right(i
)].c
)
44 local function heap_insert(heap
, item
)
49 local ptd2
= math
.floor(pt
/ 2)
50 if heap
[ptd2
].c
<= heap
[pt
].c
then
61 local function heap_extract(heap
)
63 if #heap
== 1 then table.remove(heap
) return rv
end
64 heap
[1] = table.remove(heap
)
68 if heap
[heap_left(idx
)] and heap
[heap_left(idx
)].c
< heap
[minix
].c
then minix
= heap_left(idx
) end
69 if heap
[heap_right(idx
)] and heap
[heap_right(idx
)].c
< heap
[minix
].c
then minix
= heap_right(idx
) end
71 local tx
= heap
[minix
]
72 heap
[minix
] = heap
[idx
]
83 -- incremented on each graph iteration, so we don't have to clear everything. "GRaph ID" :D
86 -- Plane format: each plane contains an array of nodes that exist in that plane.
87 -- Each node contains both its parent ID and its coordinates within that plane. It may contain a node it links to, along with cost.
90 local plane_to_flyplane
= {}
91 local continent_to_flyplane
= {}
92 local flyplanes_enabled
= {}
93 local plane_multiplier
= {}
97 local function canoplane(plane
)
98 if flyplanes_enabled
[plane_to_flyplane
[plane]]
then return plane_to_flyplane
[plane
] else return plane
end
101 local function xydist_raw(plane
, stx
, sty
, ndx
, ndy
)
102 local dx
, dy
= stx
- ndx
, sty
- ndy
103 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[plane
] or 7)
105 local function xydist(st
, nd
)
106 QuestHelper
: Assert(canoplane(st
.p
) == canoplane(nd
.p
))
107 local dx
, dy
= st
.x
- nd
.x
, st
.y
- nd
.y
108 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[canoplane(nd
.p
)] or 7) -- we're getting a result in seconds, not in yards
115 function QH_Graph_Pathfind(st
, nd
, reverse
, make_path
)
116 return QH_Graph_Pathmultifind(st
, {nd
}, reverse
, make_path
)[1]
121 function QH_Graph_Pathmultifind(st
, nda
, reverse
, make_path
)
122 --QuestHelper:TextOut("Starting PMF")
123 QuestHelper
: Assert(not active
)
124 active
= true -- The fun thing about coroutines is that this is actually safe.
125 local out
= QuestHelper
:CreateTable("graphcore output") -- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
127 local undone
= QuestHelper
:CreateTable("graphcore undone")
130 local link
= QuestHelper
:CreateTable("graphcore link")
132 --local stats = QuestHelper:CreateTable("graphcore --stats")
134 QuestHelper
: Assert(st
.x
and st
.y
and st
.p
)
136 --stats.dests_complex = 0
137 --stats.dests_total = 0
139 for k
, v
in ipairs(nda
) do
140 QuestHelper
: Assert(v
.x
and v
.y
and v
.p
)
141 local cpvp
= canoplane(v
.p
)
143 --print("Destination plane insertion")
144 local dest
= QuestHelper
:CreateTable("graphcore destination")
145 dest
.x
, dest
.y
, dest
.p
, dest
.goal
= v
.x
, v
.y
, cpvp
, k
147 tinsert(plane
[cpvp
], link
[k
])
149 remaining
= remaining
+ 1
150 --stats.dests_complex = --stats.dests_complex + 1
152 --stats.dests_total = --stats.dests_total + 1
155 local link_id
= reverse
and "rlinks" or "links"
157 --stats.node_initialized_first = 0
158 --stats.node_done = 0
159 --stats.node_done_already = 0
160 --stats.node_modified_before_use = 0
161 --stats.node_link_reprocessed = 0
162 --stats.node_link_first = 0
163 --stats.node_link_alreadydone = 0
164 --stats.node_inner_reprocessed = 0
165 --stats.node_inner_first = 0
166 --stats.node_inner_alreadydone = 0
168 local dijheap
= QuestHelper
:CreateTable("graphcore heap container")
169 local stnode
= QuestHelper
:CreateTable("graphcore stnode")
171 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
172 QuestHelper
: Assert(plane
[canoplane(st
.p
)])
173 tinsert(plane
[stnode
.p
], stnode
)
175 local hep
= QuestHelper
:CreateTable("graphcore heap")
176 hep
.c
, hep
.n
= 0, stnode
-- more than the subtraction, less than the minimum
177 heap_insert(dijheap
, hep
)
180 --stats.heap_max = #dijheap
182 --QuestHelper:TextOut("Starting routing")
184 while remaining
> 0 and #dijheap
> 0 do
186 --stats.heap_max = math.max(--stats.heap_max, #dijheap)
187 local cdj
= heap_extract(dijheap
)
190 --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))
191 if snode
.scan_cost
== cdj
.c
then -- if we've modified it since then, don't bother
192 -- Are we at an end node?
194 -- we wouldn't be here if we hadn't found a better solution
195 QuestHelper
: Assert(undone
[snode
.goal
])
196 out
[snode
.goal
] = cdj
.c
197 undone
[snode
.goal
] = nil
198 remaining
= remaining
- 1
201 -- Link to everything else on the plane
202 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")
203 for _
, v
in ipairs(plane
[snode
.p
]) do
204 if v
.scan_id
~= grid
or v
.scan_cost
> snode
.scan_cost
then
205 local dst
= xydist(snode
, v
)
206 local modcost
= snode
.scan_cost
+ dst
207 --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))
208 if v
.scan_id
~= grid
or v
.scan_cost
> modcost
then
210 v
.scan_cost
= modcost
212 v
.scan_processed
= false
214 v
.scan_outnode_from
= nil
217 local snude
= QuestHelper
:CreateTable("graphcore heap")
218 snude
.c
, snude
.n
, snude
.pi
= modcost
, v
, true
219 heap_insert(dijheap
, snude
)
220 --print(string.format("Inserting %f at %d/%f/%f, plane", snude.c, v.p, v.x, v.y))
227 -- Link to everything we link to
228 if snode
[link_id
] then
229 for _
, lnk
in pairs(snode
[link_id
]) do
230 local mc2
= snode
.scan_cost
+ lnk
.cost
231 local linkto
= lnk
.link
232 if linkto
.scan_id
~= grid
or linkto
.scan_cost
> mc2
then
233 linkto
.scan_id
= grid
234 linkto
.scan_cost
= mc2
235 linkto
.scan_from
= snode
236 linkto
.scan_outnode
= lnk
.outnode_to
237 linkto
.scan_outnode_from
= lnk
.outnode_from
239 local hep
= QuestHelper
:CreateTable("graphcore heap")
240 hep
.c
, hep
.n
= mc2
, linkto
241 heap_insert(dijheap
, hep
)
242 --print(string.format("Inserting %f at %d/%f/%f, link", hep.c, linkto.p, linkto.x, linkto.y))
247 QuestHelper
:ReleaseTable(cdj
)
250 --QuestHelper:TextOut("Starting pathing")
252 for _
, v
in ipairs(dijheap
) do
253 QuestHelper
:ReleaseTable(v
)
255 QuestHelper
:ReleaseTable(dijheap
)
258 --QuestHelper:TextOut(string.format("Earlyout with %d/%d remaining", #dijheap, remaining))
259 if remaining
> 0 then
260 for k
, v
in ipairs(nda
) do
262 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
))
266 QuestHelper
: Assert(remaining
== 0)
269 QuestHelper
: Assert(grid
< 1000000000) -- if this ever triggers I will be amazed
273 for k
, v
in pairs(out
) do
276 local rp
= QuestHelper
:CreateTable("graphcore return")
283 QuestHelper
: Assert(link
[k
].scan_from
)
284 local tpath
= reverse
and rp
or QuestHelper
:CreateTable("graphcore path reversal")
285 local cpx
= link
[k
].scan_from
288 QuestHelper
: Assert(cpx
)
290 if cpx
.scan_outnode
then
291 tinsert(tpath
, cpx
.scan_outnode
)
293 if cpx
.scan_outnode_from
then
294 tinsert(tpath
, cpx
.scan_outnode_from
)
299 QuestHelper
: Assert(not mdlast
)
302 for i
= #tpath
, 1, -1 do
303 tinsert(rp
, tpath
[i
])
306 QuestHelper
: Assert(tpath
~= rp
)
307 QuestHelper
:ReleaseTable(tpath
)
313 QuestHelper
:ReleaseTable(table.remove(plane
[stnode
.p
])) -- always added last, so we remove it first
314 for k
, v
in ipairs(nda
) do
315 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
316 QuestHelper
:ReleaseTable(table.remove(plane
[canoplane(v
.p
)]))
320 QuestHelper
:ReleaseTable(link
)
321 QuestHelper
:ReleaseTable(undone
)
323 --QuestHelper:TextOut("Finishing")
325 --for k, v in pairs(stats) do
330 return out
-- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
333 function QH_Graph_Init()
334 for c
, d
in pairs(QuestHelper_IndexLookup
) do
335 if type(c
) == "number" then
336 QuestHelper
: Assert(d
[0])
337 continent_to_flyplane
[c
] = d
[0]
338 for z
, p
in pairs(d
) do
339 if type(z
) == "number" then
340 --QuestHelper:TextOut(string.format("%d/%d: %d", c, z, p))
341 plane_to_flyplane
[p
] = d
[0]
350 local function findnode(coord
)
351 local p
= canoplane(coord
.p
)
352 if not plane
[p
] then plane
[p
] = {} end
353 for _
, v
in ipairs(plane
[p
]) do
354 if v
.x
== coord
.x
and v
.y
== coord
.y
and v
.name
== coord
.name
then
359 local nd
= {x
= coord
.x
, y
= coord
.y
, p
= p
, name
= coord
.name
}
360 tinsert(plane
[p
], nd
)
364 local function QH_Graph_Plane_ReallyMakeLink(item
)
365 local name
, coord1
, coord2
, cost
, cost_reverse
= unpack(item
)
367 QuestHelper
: Assert(not active
)
369 --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)))
370 QuestHelper
: Assert(name
)
371 QuestHelper
: Assert(coord1
)
372 QuestHelper
: Assert(coord2
)
373 QuestHelper
: Assert(cost
)
375 local n1p
= canoplane(coord1
.p
)
376 local n2p
= canoplane(coord2
.p
)
379 if name
== "static_transition" then return end -- ha ha, yep, that's how we find out, tooootally reliable
381 local xyd
= xydist_raw(n1p
, coord1
.x
, coord1
.y
, coord2
.x
, coord2
.y
)
382 if plane_cull
[n1p
] then return end -- DENIED
383 if cost
>= xyd
and (not cost_reverse
or cost_reverse
>= xyd
) then
388 local node1
= findnode(coord1
)
389 local node2
= findnode(coord2
)
391 local n1d
= {x
= coord1
.x
, y
= coord1
.y
, p
= coord1
.p
, c
= coord1
.c
, map_desc
= coord1
.map_desc
, condense_class
= coord1
.condense_class
}
392 local n2d
= {x
= coord2
.x
, y
= coord2
.y
, p
= coord2
.p
, c
= coord2
.c
, map_desc
= coord2
.map_desc
, condense_class
= coord2
.condense_class
}
394 if not node1
.links
then node1
.links
= {} end
395 if not node2
.rlinks
then node2
.rlinks
= {} end
397 tinsert(node1
.links
, {cost
= cost
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
398 tinsert(node2
.rlinks
, {cost
= cost
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
401 if not node1
.rlinks
then node1
.rlinks
= {} end
402 if not node2
.links
then node2
.links
= {} end
404 tinsert(node1
.rlinks
, {cost
= cost_reverse
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
405 tinsert(node2
.links
, {cost
= cost_reverse
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
409 function QH_Graph_Plane_Makelink(name
, coord1
, coord2
, cost
, cost_reverse
)
410 QuestHelper
: Assert(not active
)
412 --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)))
413 QuestHelper
: Assert(name
)
414 QuestHelper
: Assert(coord1
)
415 QuestHelper
: Assert(coord2
)
416 QuestHelper
: Assert(cost
)
418 QuestHelper
: Assert(cost
>= 0)
419 QuestHelper
: Assert(not cost_reverse
or cost_reverse
>= 0)
421 --cost = math.max(cost, 0.01)
422 --if cost_reverse then cost_reverse = math.max(cost_reverse, 0.01) end
424 local tlink
= {name
, coord1
, coord2
, cost
, cost_reverse
}
425 if not linkages
[name
] then linkages
[name
] = {} end
426 tinsert(linkages
[name
], tlink
)
428 QH_Graph_Plane_ReallyMakeLink(tlink
)
431 local function QH_Graph_Plane_Destroylinkslocal(name
)
432 QuestHelper
: Assert(not active
)
434 for k
, v
in pairs(plane
) do
436 for tk
, tv
in ipairs(v
) do
437 if tv
.name
~= name
then
445 function QH_Graph_Plane_Destroylinks(name
)
446 QuestHelper
: Assert(not active
)
450 QH_Graph_Plane_Destroylinkslocal(name
)
453 function QH_Graph_Flyplaneset(fpset
, speed
, cull
)
454 QuestHelper
: Assert(not active
)
456 local index
= QuestHelper_IndexLookup
[fpset
][0]
457 if not flyplanes_enabled
[index
] or plane_multiplier
[index
] ~= speed
or plane_cull
[index
] ~= cull
then
459 flyplanes_enabled
[index
] = true
460 plane_multiplier
[index
] = speed
461 plane_cull
[index
] = cull
463 for k
, v
in pairs(linkages
) do
464 QH_Graph_Plane_Destroylinkslocal(k
)
466 for _
, ite
in pairs(v
) do
467 QH_Graph_Plane_ReallyMakeLink(ite
)