hyperlane/goto.lua

by hugeblank
250 days agolua
COPY
1
term.clear()
2
term.setCursorPos(1,1)
3
local args = table.pack(...)
4

5
local mods = {}
6
local ms = peripheral.getMethods("back")
7
for i = 1, #ms do
8
    mods[ms[i]] = true
9
end
10
assert(mods.getMetaOwner, "Entity Sensor & Introspection Module Required")
11
assert(mods.launch, "Kinetic Augment Required.")
12
assert(mods.getBlockMeta, "Automatic mode unavalible.")
13

14
local meta, mode, launch, yaw
15
local power = 0.5
16
local x, z = 0, 0
17

18
local laneData
19

20
do
21
    local file = http.get("https://p.sc3.io/api/v1/pastes/Q2EcezEjth/raw")
22
    laneData = textutils.unserializeJSON(file.readAll())
23
    file.close()
24

25
    for i = 1, #laneData.lanes-1 do
26
        local lanea = laneData.lanes[i]
27
        if not lanea.isects then
28
            lanea.isects = {}
29
        end
30
        for j = i+1, #laneData.lanes do
31
            local laneb = laneData.lanes[j]
32
            if not laneb.isects then
33
                laneb.isects = {}
34
            end
35
            if lanea.axis ~= laneb.axis and (lanea.pos >= laneb.start and lanea.pos <= laneb["end"]) then
36
                lanea.isects[#lanea.isects+1] = { on = laneb, pos = lanea.pos }
37
                laneb.isects[#laneb.isects+1] = { on = lanea, pos = laneb.pos }
38
            end
39
        end
40
    end
41
    for _, stop in ipairs(laneData.stops) do
42
        local found = false
43
        for _, lane in ipairs(laneData.lanes) do
44
            if lane.name == stop.on then
45
                -- if not lane.stops then
46
                --     lane.stops = {}
47
                -- end
48
                -- lane.stops[#lane.stops+1] = stop
49
                stop.on = lane
50
                found = true
51
                break
52
            end
53
        end
54
        assert(found, stop.name..", '"..tostring(stop.on).."' is not a valid lane")
55
    end
56
end
57

58
local function getxz(node)
59
    if node.on.axis == "z" then
60
        return node.on.pos, node.pos
61
    else
62
        return node.pos, node.on.pos
63
    end
64
end
65

66
local function squaredDistance(x1, z1, x2, z2)
67
    return math.pow(x2-x1, 2) + math.pow(z2-z1, 2)
68
end
69

70
local function aStar(start, goal, h)
71
    local openSet, cameFrom = {start}, {}
72
    local gScore = {
73
        [start] = 0
74
    }
75
    local fScore = {
76
        [start] = h(start)
77
    }
78

79
    while #openSet > 0 do
80
        local current, score, r
81
        for i, node in ipairs(openSet) do
82
            if not current or score > fScore[node] then
83
                current, score, r = node, fScore[node], i
84
            end
85
        end
86
        if current.on == goal.on then
87
            local total_path = {current, goal}
88
            while cameFrom[current] do
89
                current = cameFrom[current]
90
                table.insert(total_path, 1, current)
91
            end
92
            return total_path
93
        end
94
        table.remove(openSet, r)
95
        local x1, z1 = getxz(current)
96
        for i, neighbor in ipairs(current.on.isects) do
97
            local x2, z2 = getxz(neighbor)
98
            local tent_gScore = gScore[current] + squaredDistance(x1, z1, x2, z2)
99
            if not gScore[neighbor] or tent_gScore < gScore[neighbor] then
100
                cameFrom[neighbor] = current
101
                gScore[neighbor] = tent_gScore
102
                fScore[neighbor] = tent_gScore + h(neighbor)
103
                local add = true
104
                for _, node in ipairs(openSet) do
105
                    if node == neighbor then
106
                        add = false
107
                        break
108
                    end
109
                end
110
                if add then
111
                    openSet[#openSet+1] = neighbor
112
                end
113
            end
114
        end
115
    end
116
    error("Could not find path to "..goal.name)
117
end
118

119
local function route(to_stop, tx, tz)
120
    local start = {
121
        name = "Start"
122
    }
123
    for _, lane in ipairs(laneData.lanes) do
124
        if lane.pos == tz and lane.axis == "x" then
125
            start.on = lane
126
            start.pos = tx
127
        elseif lane.pos == tx and lane.axis == "z" then
128
            start.on = lane
129
            start.pos = tz
130
        end
131
        if start.on then
132
            break
133
        end
134
    end
135
    assert(start.on, "Not on lane? Something broke somewhere.")
136
    local sx, sz = getxz(to_stop)
137
    local route = aStar(start, to_stop, function(node)
138
        local nx, nz = getxz(node)
139
        return squaredDistance(nx, nz, sx, sz)
140
    end)
141
    return route
142
end
143

144
local onIce, routes = false
145

146
local function handleMetaOwner()
147
    while true do
148
        meta = peripheral.call("back", "getMetaOwner")
149
        x, z = x + meta.motionX, z + meta.motionZ
150
    end
151
end
152

153
local function handleRouting()
154
    local dest = table.concat(args):lower()
155
    for _, stop in ipairs(laneData.stops) do
156
        if dest == stop.name:lower() then
157
            dest = stop
158
            break
159
        end
160
    end
161
    assert(type(dest) ~= "string", "Could not find stop named"..table.concat(args))
162
    while true do
163
        if peripheral.call("back", "getBlockMeta", 0, -2, 0).name:match("slab") and peripheral.call("back", "getBlockMeta", 0, -3, 0).name == "minecraft:packed_ice" then
164
            if not onIce then
165
                routes = route(dest, math.floor(x), math.floor(z))
166
                -- term.setCursorPos(1,2)
167
                -- term.clearLine()
168
            end
169
            onIce = true
170
        else
171
            onIce = false
172
            -- term.setCursorPos(1,1)
173
            -- term.clearLine()
174
            -- term.setCursorPos(1,2)
175
            -- term.write("Not on a hyperlane. Get on lane to resume routing.")
176
            print("Not on a hyperlane. Get on lane to resume routing.")
177
        end
178
        sleep(0.5)
179
    end
180
end
181

182
local approach = 80 -- Start approach from 80 blocks away
183
local function reverse_lerp(goal, pos, vel)
184
    local distance = math.max(pos, goal) - math.min(pos, goal)
185
    if distance < approach then
186
        if math.abs(vel) > 0.5 then
187
            return 0, false
188
        end
189
        return math.max(math.pow(distance/approach, 2), distance <= 2 and 0.006 or 0.01), distance <= 0.1
190
    else
191
        return 1
192
    end
193
end
194

195
local function handleMovement()
196
    while true do
197
        if routes and onIce then
198
            -- term.setCursorPos(1, 1)
199
            -- term.write("Routing from current position")
200
            print("Routing from current position")
201
            local xAxis = table.remove(routes, 1).on.axis == 'x'
202
            while #routes > 0 and onIce do
203
                local route = table.remove(routes, 1)
204
                -- term.setCursorPos(1, 2)
205
                -- term.clearLine()
206
                -- term.write("Heading to "..(route.name or route.on.name))
207
                local gx, gz = getxz(route)
208
                gx, gz = gx + 0.5, gz + 0.5
209
                local pm = 1
210
                local done, get
211
                if xAxis then -- inverse of route point axis
212
                    get = function()
213
                        local y = 90
214
                        if gx-x > 0 then
215
                            y = y - 180
216
                        end
217
                        return gx, x, meta.motionX, y
218
                    end
219
                else
220
                    get = function()
221
                        local y = 180
222
                        if gz-z > 0 then
223
                            y = y - 180
224
                        end
225
                        return gz, z, meta.motionZ , y
226
                    end
227
                end
228
                print("Heading to "..(route.name or route.on.name))
229
                parallel.waitForAny(function() 
230
                        while true do
231
                            local g, p, v, y = get()
232
                            pm, done = reverse_lerp(g, p, v)
233
                            peripheral.call("back", "launch", y, 0, power*pm)
234
                        end
235
                end, function()
236
                    local prev = false
237
                    while true do
238
                        if prev and done then
239
                            return
240
                        end
241
                        prev = done
242
                        sleep(1)
243
                    end
244
                end)
245
                xAxis = not xAxis
246
            end
247
            -- term.setCursorPos(1, 1)
248
            -- term.clearLine()
249
            -- term.write("Arrived at destination")
250
            print("Arrived at destination")
251
            -- term.setCursorPos(1, 2)
252
            -- term.clearLine()
253
            -- term.setCursorPos(1, 2)
254
            sleep(1)
255
            return
256
        else
257
            sleep(1)
258
        end
259
    end
260
end
261

262
local function handleGPS()
263
    while true do
264
        local tx, _, tz = gps.locate()
265
        if tx then
266
            x, z = tx, tz
267
        end
268
        sleep(1)
269
    end
270
end
271

272
local ok, err = pcall(function()
273
    parallel.waitForAny(handleMetaOwner, handleRouting, handleMovement, handleGPS)
274
end)
275
if not ok then
276
    print(err)
277
    os.pullEvent("key")
278
end