shrekflight.lua

by ShreksHellraiser
537 days agolua
COPY
1
-- shrekflight
2
-- Releasd under MIT by ShreksHellraiser
3

4
-- This is a script that provides basic creative-like flight and automatic flight to coordinates using plethora.
5
-- Requires a keyboard, entity sensor, introspection module, and kinetic module
6

7
-- To fly, simply double tap space, hold space to fly up, hold shift to fly down.
8
-- You can modify your horizontal and vertical speeds in this with the commands
9
-- ^vspeed and ^hspeed
10

11
-- This also includes a ^goto command, this accepts a coordinate and will automatically fly you to that location.
12
-- This is not *100%* reliable, but as long as you *first* set your position ~200 blocks above the ground and then
13
-- set your target to where you want you should be fine.
14
-- supports ~ style relative coordinates for your current gps location, and . for your set goto position.
15
-- ^toggle will disable/reenable goto, entering fly mode by double tapping will too.
16

17
local modules = peripheral.find("neuralInterface")
18
if not modules then
19
    error("Must have a neural interface", 0)
20
end
21

22
if not modules.hasModule("plethora:sensor") then error("Must have a sensor", 0) end
23
if not modules.hasModule("plethora:introspection") then error("Must have an introspection module", 0) end
24
if not modules.hasModule("plethora:kinetic", 0) then error("Must have a kinetic agument", 0) end
25
-- if not modules.hasModule("plethora:glasses") then error("Must have overlay glasses", 0) end
26

27
local function start_pid(k_p, k_i, k_d)
28
    local e, de, ie = 0, 0, 0
29
    ---Process a frame of the PID
30
    ---@param dt number frametime
31
    ---@param sp number desired setpoint
32
    ---@param pv number process variable
33
    return function(dt, sp, pv)
34
        local ne = sp - pv
35
        de = (ne - e) / dt  -- instantaneous derivative
36
        ie = ie + (ne * dt) -- summed integral
37
        e = ne              -- error
38
        return (k_p * e) + (k_i * ie) + (k_d * de)
39
    end
40
end
41

42
local target_coords = { 0, 200, 0 }
43
local goto_enable = false
44
local hover_enable = false
45

46
local function control()
47
    -- assert(chatbox.isConnected(), "Chatbox isn't connected!")
48
    while true do
49
        local event, user, command, args, data = os.pullEvent("command")
50
        if data.ownerOnly then
51
            if command == "goto" then
52
                local x, y, z = gps.locate()
53
                for i = 1, 3 do
54
                    if args[i] then
55
                        if args[i]:sub(1, 1) == "~" and x and y and z then
56
                            -- current coords
57
                            local offset = tonumber(args[i]:sub(2, -1)) or 0
58
                            print(offset)
59
                            target_coords[i] = ((i == 1 and x) or (i == 2 and y) or z) + offset
60
                        elseif args[i]:sub(1, 1) == "." then
61
                            -- change it
62
                            local offset = tonumber(args[i]:sub(2, -1)) or 0
63
                            target_coords[i] = target_coords[i] + offset
64
                        elseif tonumber(args[i]) then
65
                            target_coords[i] = tonumber(args[i])
66
                        end
67
                    end
68
                end
69
                print(("target %u %u %u"):format(target_coords[1], target_coords[2], target_coords[3]))
70
                goto_enable = true
71
                hover_enable = false
72
            elseif command == "toggle" then
73
                hover_enable = hover_enable and goto_enable
74
                goto_enable = not goto_enable
75
            end
76
        end
77
    end
78
end
79

80
local function calc_xz_angle(x, z)
81
    local xz_angle = math.atan(math.abs(x / z)) * 180 / math.pi
82
    if z == 0 then
83
        xz_angle = 90
84
    end
85

86
    if x > 0 then
87
        xz_angle = -xz_angle -- -90 @ +x
88
        if z < 0 then
89
            -- -180 @ -z
90
            xz_angle = -180 - xz_angle
91
        end
92
    elseif z < 0 then
93
        -- +180 @ -z
94
        xz_angle = -180 - xz_angle
95
    end
96
    return xz_angle
97
end
98

99
local function calc_yxz_angle(y, xz)
100
    local yxz_angle = -math.atan(math.abs(y / xz)) * 180 / math.pi
101
    if xz == 0 then
102
        yxz_angle = -90
103
    end
104

105
    -- hard coded boost to emphasize upward vertical movement
106
    -- if y > 2 and yxz_angle < 10 then
107
    --     yxz_angle = 20
108
    -- end
109
    if y < 0 then
110
        yxz_angle = -yxz_angle
111
    end
112
    return yxz_angle
113
end
114

115
local function p_pid()
116
    local y_pid = start_pid(0.05, 0.005, 0.06)
117
    local x_pid = start_pid(0.1, 0, 0)
118
    local z_pid = start_pid(0.1, 0, 0)
119
    local t0 = os.epoch("utc")
120
    sleep()
121
    while true do
122
        sleep(0.2)
123
        if goto_enable  then
124
            local x, y, z = gps.locate()
125
            if x and y and z and x == x and y == y and z == z then
126
                local t = os.epoch("utc")
127
                local dt = (t - t0) / 1000
128
                local y_impulse = y_pid(dt, target_coords[2], y)
129
                local x_impulse = x_pid(dt, target_coords[1], x)
130
                local z_impulse = z_pid(dt, target_coords[3], z)
131
                local x_vec = vector.new(x_impulse, 0, 0)
132
                local y_vec = vector.new(0, y_impulse, 0)
133
                local z_vec = vector.new(0, 0, z_impulse)
134
                local result = ((x_vec + y_vec + z_vec) / 3)
135

136
                local xz_hyp = math.sqrt(result.x ^ 2 + result.z ^ 2)
137
                local xz_angle = calc_xz_angle(result.x, result.z)
138
                local yxz_angle = calc_yxz_angle(result.y, xz_hyp)
139

140
                modules.launch(xz_angle, yxz_angle, math.min(4, math.abs(result:length())))
141
                t0 = t
142
            end
143
        end
144
    end
145
end
146

147
local target_vel = { 0, 0, 0 }
148
local y_impulse_scale = 0.5
149
local k_p = 4
150
local owner = modules.getMetaOwner()
151
local function v_pid()
152
    local x_pid = start_pid(0.1, 0, 0)
153
    local z_pid = start_pid(0.1, 0, 0)
154
    local t0 = os.epoch("utc")
155
    sleep()
156
    while true do
157
        local t = os.epoch("utc")
158
        while hover_enable and not goto_enable do
159
            t = os.epoch("utc")
160
            sleep(0)
161
            owner = modules.getMetaOwner()
162
            if not owner.isAirborne then
163
                hover_enable = false
164
            end
165
            local dt = (t - t0) / 1000
166
            local ticks = dt / 0.05
167
            local x, y, z = owner.motionX, owner.motionY, owner.motionZ
168

169
            local y_vel_err = (0.08 * ticks) + target_vel[2] - y
170
            -- IMPULSE STRENGTH LINEARLY CORROLATES TO VELOCITY
171
            local y_impulse = k_p * y_vel_err / y_impulse_scale
172

173
            local x_impulse = x_pid(dt, target_vel[1], x)
174
            local z_impulse = z_pid(dt, target_vel[3], z)
175

176
            local x_vec = vector.new(x_impulse, 0, 0)
177
            local y_vec = vector.new(0, y_impulse, 0)
178
            local z_vec = vector.new(0, 0, z_impulse)
179
            local result = ((x_vec + y_vec + z_vec) / 3)
180
            local xz_hyp = math.sqrt(result.x ^ 2 + result.z ^ 2)
181
            local xz_angle = calc_xz_angle(result.x, result.z)
182
            local yxz_angle = calc_yxz_angle(result.y, xz_hyp)
183
            local power = math.min(4, math.abs(result:length()))
184

185
            modules.launch(xz_angle, yxz_angle, power)
186
            t0 = t
187
        end
188
        os.pullEvent("velocity_control")
189
    end
190
end
191

192
local function wrap_angle(t)
193
    if t > 180 then
194
        t = -360 + t
195
    elseif t < -180 then
196
        t = 360 + t
197
    end
198
    return t
199
end
200

201
local function rad(deg)
202
    return deg / 180 * math.pi
203
end
204

205
local function deg(rad)
206
    return rad * 180 / math.pi
207
end
208

209
local v_speed = 0.5
210
local sprinting_speed = 6
211

212
local held_keys = {}
213
local function get_v_vectors()
214
    local theta = owner.yaw
215
    local result = vector.new(0, 0, 0)
216
    local forward_power = ((held_keys[keys.w] and 1) or (held_keys[keys.s] and -1) or 0)
217
    local sideways_power = ((held_keys[keys.d] and -1) or (held_keys[keys.a] and 1) or 0)
218
    local h_power = 3
219
    if owner.isSprinting then
220
        h_power = sprinting_speed
221
    end
222
    result.x = result.x + h_power * math.cos(rad(theta)) * sideways_power
223
    result.z = result.z + h_power * math.sin(rad(theta)) * sideways_power
224
    result.x = result.x + h_power * math.cos(rad(wrap_angle(theta + 90))) * forward_power
225
    result.z = result.z + h_power * math.sin(rad(wrap_angle(theta + 90))) * forward_power
226
    return result.x, result.z
227
end
228

229
local function v_control()
230
    local space_presses = 0
231
    local space_press_timer
232
    local t0 = os.epoch("utc")
233
    while true do
234
        local event, key, command, args, data = os.pullEvent()
235
        local t = os.epoch("utc")
236
        local dt = (t - t0) / 1000
237
        if event == "key" then
238
            if key == keys.space or key == keys.h then
239
                if not held_keys[keys.space] then
240
                    space_presses = space_presses + 1
241
                end
242
                if space_presses > 1 or key == keys.h then
243
                    -- double tapped
244
                    hover_enable = not hover_enable
245
                    goto_enable = false
246
                    print("double tap", hover_enable)
247
                    os.queueEvent("velocity_control")
248
                    space_presses = 0
249
                else
250
                    target_vel[2] = v_speed
251
                end
252
            else
253
                space_presses = 0
254
            end
255
            if key == keys.leftShift then
256
                target_vel[2] = -v_speed
257
            end
258
            held_keys[key] = true
259
        elseif event == "key_up" then
260
            held_keys[key] = false
261
            if key == keys.space or key == keys.leftShift then
262
                if space_press_timer then
263
                    os.cancelTimer(space_press_timer)
264
                end
265
                space_press_timer = os.startTimer(0.5)
266
                target_vel[2] = 0
267
            end
268
        elseif event == "timer" and key == space_press_timer then
269
            space_presses = 0
270
        elseif event == "command" and data.ownerOnly then
271
            local speed = tonumber(args[1])
272
            if command == "hspeed" and speed then
273
                sprinting_speed = speed --[[@as number]]
274
            elseif command == "vspeed" and speed then
275
                v_speed = speed
276
            end
277
        end
278
        t0 = t
279
        target_vel[1], target_vel[3] = get_v_vectors()
280
    end
281
end
282

283
parallel.waitForAny(v_pid, v_control, p_pid, control)
284