-- CC0/Unlicense Emilia 2020 turtle = {} local mod_prefix = minetest.get_modpath(minetest.get_current_modname()) tlang = dofile(mod_prefix .. "/tlang.lua") function turtle.coord(x, y, z) return {x = x, y = y, z = z} end turtle.pos1 = turtle.coord(0, 0, 0) turtle.pos2 = turtle.coord(0, 0, 0) local function format_coord(c) return tostring(c.x) .. " " .. tostring(c.y) .. " " .. tostring(c.z) end local function parse_coord(c) end -- can include ~ + - along with num and , local function parse_relative_coord(c) end function turtle.ordercoord(c) if c.x == nil then return {x = c[1], y = c[2], z = c[3]} else return c end end -- x or {x,y,z} or {x=x,y=y,z=z} function turtle.optcoord(x, y, z) if y and z then return turtle.coord(x, y, z) else return turtle.ordercoord(x) end end -- swap x and y if x > y local function swapg(x, y) if x > y then return y, x else return x, y end end -- swaps coordinates around such that (matching ords of) c1 < c2 and the overall cuboid is the same shape function turtle.rectify(c1, c2) c1.x, c2.x = swapg(c1.x, c2.x) c1.y, c2.y = swapg(c1.y, c2.y) c1.z, c2.z = swapg(c1.z, c2.z) return c1, c2 end -- converts a coordinate to a system where 0,0 is the southwestern corner of the world function turtle.zeroidx(c) local side = 30912 return turtle.coord(c.x + side, c.y + side, c.z + side) end -- swaps coords and subtracts such that c1 == {0, 0, 0} and c2 is the distance from c1 -- returns rectified c1/c2 and the relativized version function turtle.relativize(c1, c2) c1, c2 = turtle.rectify(c1, c2) local c1z = turtle.zeroidx(c1) local c2z = turtle.zeroidx(c2) local rel = turtle.coord(c2z.x - c1z.x, c2z.y - c1z.y, c2z.z - c1z.z) return c1, rel end -- get the inventory index of the best tool to mine x, y, z -- returns a wield index, which starts at 0 function turtle.get_best_tool_index(x, y, z) local node = minetest.get_node_or_nil(turtle.optcoord(x, y, z)) if not node then return end local nodecaps = minetest.get_node_def(node.name).groups local idx = minetest.localplayer:get_wield_index() + 1 local best = math.huge for i, v in ipairs(minetest.get_inventory("current_player").main) do for gk, gv in pairs(v:get_tool_capabilities().groupcaps) do local level = nodecaps[gk] if level and gv.times[level] < best then idx = i best = gv.times[level] end end end return idx - 1 end -- switch to the fastest tool to mine x, y, z function turtle.switch_best(x, y, z) local prev = minetest.localplayer:get_wield_index() local index = turtle.get_best_tool_index(x, y, z) if prev ~= index then minetest.localplayer:set_wield_index(index) end end function turtle.mine(x, y, z) turtle.switch_best(x, y, z) minetest.dig_node(turtle.optcoord(x, y, z)) end function turtle.place(x, y, z) minetest.place_node(turtle.optcoord(x, y, z)) end function turtle.cadd(c1, c2) return turtle.coord(c1.x + c2.x, c1.y + c2.y, c1.z + c2.z) end function turtle.relcoord(x, y, z) local pos = minetest.localplayer:get_pos() return turtle.cadd(pos, turtle.optcoord(x, y, z)) end local function between(x, y, z) -- x is between y and z (inclusive) return y <= x and x <= z end function turtle.dircoord(f, y, r) local rot = minetest.localplayer:get_yaw() % 360 local coord = turtle.optcoord(f, y, r) local f = coord.x local y = coord.y local r = coord.z if between(rot, 315, 360) or between(rot, 0, 45) then -- north return turtle.relcoord(r, y, f) elseif between(rot, 135, 225) then -- south return turtle.relcoord(-r, y, -f) elseif between(rot, 225, 315) then -- east return turtle.relcoord(f, y, r) elseif between(rot, 45, 135) then -- west return turtle.relcoord(-f, y, -r) end return turtle.relcoord(0, 0, 0) end function turtle.move(x, y, z) minetest.localplayer:set_pos(turtle.optcoord(x, y, z)) end function turtle.advance(amount) amount = amount or 1 turtle.move(turtle.dircoord(amount, 0, 0)) end function turtle.descend(amount) amount = amount or 1 turtle.move(turtle.relcoord(0, -amount, 0)) end function turtle.rotate_abs(deg) minetest.localplayer:set_yaw(deg) end function turtle.rotate(deg) local prev = minetest.localplayer:get_yaw() minetest.localplayer:set_yaw((prev + deg) % 360) end function turtle.rotate_right(deg) deg = deg or 90 turtle.rotate(-deg) end function turtle.rotate_left(deg) deg = deg or 90 turtle.rotate(deg) end function turtle.isblock(block, x, y, z) local node = minetest.get_node_or_nil(turtle.optcoord(x, y, z)) return node ~= nil and block == node.name end function turtle.checkmine(x, y, z) while true do turtle.mine(x, y, z) busysleep(0.125) -- i hate lua minetest.log(tostring(turtle.isblock("air", x, y, z))) if turtle.isblock("air", x, y, z) then break end end end function turtle.tp(coords) minetest.localplayer:set_pos(coords) end function turtle.moveto(x, y, z) turtle.tp(turtle.optcoord(x, y, z)) end function turtle.linemine(distance, func) for i = 1, distance do turtle.checkmine(turtle.dircoord(1, 1, 0)) turtle.checkmine(turtle.dircoord(1, 0, 0)) turtle.advance() if func then func() end end end local function left_or_right(left) if left then turtle.rotate_left() else turtle.rotate_right() end end local function quarry_clear_liquids() -- puts blocks in front, both sides, and two below where they are liquid -- it does all this one step ahead so no spillage may occur end -- needs to check for liquids (would need to be done in linemine) function turtle.quarry(cstart, cend) -- get a nice cuboid cstart, cend = turtle.rectify(cstart, cend) local start, relend = turtle.relativize(cstart, cend) -- makes it start at the top rather than the bottom cend.y, cstart.y = swapg(cend.y, cstart.y) -- go to the start turtle.moveto(turtle.cadd(cstart, turtle.coord(0, 1, 0))) turtle.rotate_abs(0) -- main loop (zig zag pattern) for height = 0, math.floor(relend.y / 2) do -- go down two blocks turtle.mine(turtle.relcoord(0, -1, 0)) turtle.mine(turtle.relcoord(0, -2, 0)) turtle.descend(2) for width = 0, relend.x do -- swaps left/right rotations each layer and zig zag local leftiness = ((height + width + 1) % 2) == 0 -- actually mine turtle.linemine(relend.z) -- maybe relend.z to make the end inclusive? left_or_right(leftiness) -- dont rotate at the end of the layer if width ~= relend.x then turtle.linemine(1) left_or_right(leftiness) end end -- flip around to start again on the next layer turtle.rotate(180) end end minetest.register_chatcommand("quarry", { func = function() turtle.quarry({x = -60, y = 1, z = -60}, {x = -40, y = -5, z = -40}) end }) turtle.states = {} turtle.states_available = false function turtle.schedule(name, state) if type(name) == "table" then error("turtle.schedule: first parameter should be the task's name") return end turtle.states[#turtle.states + 1] = {name = name, state = state} turtle.states_available = true end function turtle.get_symbolic(name) local dead = {} for i, v in ipairs(turtle.states) do if i == name or v.name == name then dead[#dead + 1] = i end end return dead end function turtle.kill_symbolic(name) local dead = turtle.get_symbolic(name) for i, v in ipairs(dead) do table.remove(turtle.states, v) end end function turtle.pause_symbolic(name) local dead = turtle.get_symbolic(name) for i, v in ipairs(dead) do turtle.states[v].paused = true end end function turtle.resume_symbolic(name) local dead = turtle.get_symbolic(name) for i, v in ipairs(dead) do turtle.states[v].paused = nil end end function turtle.run_states(dtime) if turtle.states_available then local dead = {} for i, v in ipairs(turtle.states) do local ret = tlang.step(v.state) if ret ~= true and ret ~= nil then if type(ret) == "string" then minetest.display_chat_message("Turtle/tlang ERROR in " .. v.name .. ": " .. ret) end dead[#dead + 1] = i end end for i, v in ipairs(dead) do table.remove(turtle.states, v) end turtle.states_available = #turtle.states ~= 0 end end minetest.register_globalstep(turtle.run_states) minetest.register_chatcommand("tlang", { description = "Run a tlang program.", params = "", func = function(params) local state = tlang.get_state(params) turtle.schedule("chat_script", state) end }) minetest.register_chatcommand("tl_list", { description = "List running tlang states.", func = function() for i, v in ipairs(turtle.states) do minetest.display_chat_message(tostring(i) .. " " .. v.name) end end }) minetest.register_chatcommand("tl_kill", { description = "Kill a tlang state.", params = "", func = turtle.kill_symbolic }) minetest.register_chatcommand("tl_pause", { description = "Pause a tlang state.", params = "", func = turtle.pause_symbolic }) minetest.register_chatcommand("tl_resume", { description = "Resume a tlang state.", params = "", func = turtle.resume_symbolic })