Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- turtleremote.lua (CC: Tweaked)
- -- Usage:
- -- turtleremote turtle (run on the mining turtle)
- -- turtleremote control (run on the pocket computer)
- local args = { ... }
- local mode = args[1]
- -- ─────────────────────────────────────────────
- -- Shared: find and open a wireless modem
- -- ─────────────────────────────────────────────
- local function openWirelessModem()
- for _, side in ipairs({"top","bottom","left","right","front","back"}) do
- if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
- local m = peripheral.wrap(side)
- if m.isWireless and m.isWireless() then
- rednet.open(side)
- return side
- end
- end
- end
- end
- local PROTOCOL = "turtleremote"
- -- ═════════════════════════════════════════════
- -- TURTLE MODE
- -- ═════════════════════════════════════════════
- local function runTurtle()
- if not openWirelessModem() then
- printError("No wireless modem found! Attach one and retry.")
- return
- end
- print("Turtle online (ID: " .. os.getComputerID() .. ")")
- print("Waiting for controller…")
- -- ── Helpers ──────────────────────────────
- -- Pull any available fuel from inventory into the fuel tank
- local function tryRefuel()
- for i = 1, 16 do
- turtle.select(i)
- if turtle.refuel(0) then -- refuel(0) = dry-run; returns true if burnable
- turtle.refuel(1)
- if turtle.getFuelLevel() > 0 then return true end
- end
- end
- return false
- end
- -- Attempt a movement fn(), auto-refuelling first if empty.
- -- Returns "ok:<fuel>" or "blocked:<fuel>" so the controller
- -- can update its fuel readout and show a warning.
- local function tryMove(fn)
- local fuel = turtle.getFuelLevel()
- if fuel == 0 then
- tryRefuel()
- fuel = turtle.getFuelLevel()
- end
- local ok, reason = fn()
- fuel = turtle.getFuelLevel()
- if ok then
- return "ok:" .. fuel
- else
- return "blocked:" .. fuel .. (reason and (":" .. reason) or "")
- end
- end
- -- Scan inventory for a sword or axe and equip it to the right hand
- local function equipBestWeapon()
- for i = 1, 16 do
- turtle.select(i)
- local item = turtle.getItemDetail()
- if item then
- local n = item.name
- if n:find("sword") or n:find("axe") or n:find("knife") then
- turtle.equipRight()
- return true
- end
- end
- end
- return false -- no weapon found; turtle's fist will do
- end
- -- ── GO WILD loop ──────────────────────────
- --
- -- Vanilla CC:T has no entity scanner — turtle.attack() returning true
- -- is the ONLY way to detect a nearby entity. So detection and damage
- -- are the same action. We make this work by:
- --
- -- 1. Respecting the ~0.6 s sword cooldown so every hit does full damage.
- -- 2. Using a state machine so the turtle doesn't abandon a chase the
- -- instant it misses once (the player is running away, not gone).
- --
- -- States:
- -- SWEEP – no target; rotate 90° each cycle, occasionally lunge
- -- CHASE – active lock in a direction; charge hard, tolerate some misses
- -- CLOSING – just lost lock; keep charging forward before giving up
- --
- local ATTACK_COOLDOWN = 0.6 -- seconds; matches vanilla sword cooldown
- local CHASE_MISS_LIMIT = 4 -- misses before CHASE → CLOSING
- local CLOSING_STEP_LIMIT = 5 -- forward steps before CLOSING → SWEEP
- local function goWild(senderId)
- equipBestWeapon()
- rednet.send(senderId, "wild:on", PROTOCOL)
- local shouldStop = false
- local function huntLoop()
- -- State: "SWEEP" | "CHASE" | "CLOSING"
- local state = "SWEEP"
- local chaseDir = "F" -- "F" | "U" | "D" (last hit direction)
- local missStreak = 0
- local closingSteps = 0
- local sweepTurns = 0 -- how many right-turns taken in current sweep
- local function fuel()
- if turtle.getFuelLevel() == 0 then tryRefuel() end
- end
- -- Move in a direction, digging through any block in the way
- local function advance(dir)
- fuel()
- if dir == "F" then
- if not turtle.forward() then turtle.dig(); turtle.forward() end
- elseif dir == "U" then
- if not turtle.up() then turtle.digUp(); turtle.up() end
- elseif dir == "D" then
- if not turtle.down() then turtle.digDown(); turtle.down() end
- end
- end
- while not shouldStop do
- -- ── Attack all three faces ──────────────────────────────────
- -- Returns which direction(s) connected this tick.
- local hitF = turtle.attack()
- local hitU = turtle.attackUp()
- local hitD = turtle.attackDown()
- local anyHit = hitF or hitU or hitD
- -- ── State transitions ────────────────────────────────────────
- if state == "SWEEP" then
- if anyHit then
- -- Got a contact — lock on and switch to CHASE
- chaseDir = hitF and "F" or (hitU and "U" or "D")
- missStreak = 0
- state = "CHASE"
- sweepTurns = 0
- advance(chaseDir)
- else
- -- Rotate 90° and occasionally lunge to cover ground
- sweepTurns = sweepTurns + 1
- turtle.turnRight()
- if sweepTurns % 4 == 0 then
- -- Completed a full rotation without a hit; step forward
- -- so we actually move around the map instead of spinning on the spot
- advance("F")
- end
- end
- elseif state == "CHASE" then
- if anyHit then
- -- Still hitting something — update direction if changed
- -- (e.g. player jumped) and keep charging
- chaseDir = hitF and "F" or (hitU and "U" or "D")
- missStreak = 0
- advance(chaseDir)
- else
- missStreak = missStreak + 1
- if missStreak >= CHASE_MISS_LIMIT then
- -- Lost them — try CLOSING before giving up
- state = "CLOSING"
- closingSteps = 0
- missStreak = 0
- else
- -- Still probably just the player backing away;
- -- keep charging in the last known direction
- advance(chaseDir)
- end
- end
- elseif state == "CLOSING" then
- if anyHit then
- -- Re-acquired — back to CHASE
- chaseDir = hitF and "F" or (hitU and "U" or "D")
- missStreak = 0
- state = "CHASE"
- advance(chaseDir)
- else
- closingSteps = closingSteps + 1
- if closingSteps >= CLOSING_STEP_LIMIT then
- -- Truly lost; begin sweep from fresh angle
- state = "SWEEP"
- sweepTurns = 0
- -- Turn right once so we don't sweep the same corridor
- turtle.turnRight()
- else
- -- Charge forward in last-known direction
- advance(chaseDir)
- end
- end
- end
- -- Respect sword cooldown — attacking faster wastes damage
- sleep(ATTACK_COOLDOWN)
- end
- end
- -- Coroutine 2: blocking stop listener — cannot miss the message
- local function stopListener()
- while true do
- local _, msg = rednet.receive(PROTOCOL)
- if msg == "stop" then
- shouldStop = true
- return
- end
- end
- end
- parallel.waitForAny(huntLoop, stopListener)
- rednet.send(senderId, "wild:off", PROTOCOL)
- end
- -- ── Main receive loop ─────────────────────
- while true do
- local senderId, msg = rednet.receive(PROTOCOL)
- if msg == "forward" then rednet.send(senderId, tryMove(turtle.forward), PROTOCOL)
- elseif msg == "back" then rednet.send(senderId, tryMove(turtle.back), PROTOCOL)
- elseif msg == "up" then rednet.send(senderId, tryMove(turtle.up), PROTOCOL)
- elseif msg == "down" then rednet.send(senderId, tryMove(turtle.down), PROTOCOL)
- elseif msg == "turnLeft" then turtle.turnLeft()
- elseif msg == "turnRight" then turtle.turnRight()
- elseif msg == "use" then
- if not turtle.dig() then turtle.attack() end
- elseif msg == "goWild" then goWild(senderId)
- elseif msg == "ping" then
- rednet.send(senderId, "pong:" .. turtle.getFuelLevel(), PROTOCOL)
- end
- end
- end
- -- ═════════════════════════════════════════════
- -- CONTROL MODE (pocket computer)
- -- ═════════════════════════════════════════════
- local function runControl()
- if not openWirelessModem() then
- printError("No wireless modem found! Attach one and retry.")
- return
- end
- -- ── Discover the turtle ──────────────────
- term.clear()
- term.setCursorPos(1, 1)
- print("Scanning for turtle…")
- local turtleId
- local fuelLevel = "?"
- local deadline = os.clock() + 6
- repeat
- rednet.broadcast("ping", PROTOCOL)
- local id, msg = rednet.receive(PROTOCOL, 1)
- if id and msg and msg:sub(1, 4) == "pong" then
- turtleId = id
- fuelLevel = msg:sub(6) -- "pong:XXXX"
- end
- until turtleId or os.clock() > deadline
- if not turtleId then
- printError("No turtle found. Is it running?")
- return
- end
- print("Connected to turtle #" .. turtleId)
- sleep(0.6)
- -- ── Layout ───────────────────────────────
- -- Pocket screen = 26 × 20
- --
- -- Row 1 title + turtle id
- -- Row 2-3 [ UP ]
- -- Row 5-6 [TURN L] [ FWD ] [TURN R]
- -- Row 8-9 [ DOWN ]
- -- Row 11-12 [ BACK ] [ USE! ]
- -- Row 14-15 [====== GO WILD! ======]
- -- Row 19 fuel bar
- -- Row 20 status bar
- local W, H = term.getSize()
- local BW, BH = 7, 2
- local CX, LX, RX = 10, 2, 18
- local buttons = {
- { label = " UP ", x = CX, y = 2, cmd = "up" },
- { label = "TURN L", x = LX, y = 5, cmd = "turnLeft" },
- { label = " FWD ", x = CX, y = 5, cmd = "forward" },
- { label = "TURN R", x = RX, y = 5, cmd = "turnRight" },
- { label = " DOWN ", x = CX, y = 8, cmd = "down" },
- { label = " BACK ", x = LX, y = 11, cmd = "back" },
- { label = " USE! ", x = RX, y = 11, cmd = "use" },
- }
- -- GO WILD is wider and has its own draw logic
- local WILD = { x = 2, y = 14, w = 22, h = 2 }
- local wildMode = false
- -- ── Draw helpers ─────────────────────────
- local function drawButton(btn, lit)
- local bw = btn.w or BW
- local bh = btn.h or BH
- term.setBackgroundColor(lit and colors.white or colors.gray)
- term.setTextColor(lit and colors.black or colors.white)
- for row = 0, bh - 1 do
- term.setCursorPos(btn.x, btn.y + row)
- if row == math.floor(bh / 2) then
- local lbl = btn.label or ""
- local pad = math.floor((bw - #lbl) / 2)
- term.write(
- string.rep(" ", pad) .. lbl ..
- string.rep(" ", bw - pad - #lbl)
- )
- else
- term.write(string.rep(" ", bw))
- end
- end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- end
- local function drawWildButton(active)
- -- Active = red "STOP" state; inactive = orange "GO WILD" state
- term.setBackgroundColor(active and colors.red or colors.orange)
- term.setTextColor(active and colors.white or colors.black)
- local lbl = active and " STOP WILD " or " GO WILD! "
- for row = 0, WILD.h - 1 do
- term.setCursorPos(WILD.x, WILD.y + row)
- if row == math.floor(WILD.h / 2) then
- local pad = math.floor((WILD.w - #lbl) / 2)
- term.write(
- string.rep(" ", pad) .. lbl ..
- string.rep(" ", WILD.w - pad - #lbl)
- )
- else
- term.write(string.rep(" ", WILD.w))
- end
- end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- end
- local function drawFuelBar()
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.yellow)
- term.setCursorPos(1, H - 1)
- local s = "Fuel: " .. tostring(fuelLevel)
- term.write(s .. string.rep(" ", W - #s))
- term.setTextColor(colors.white)
- end
- local function setStatus(msg, col)
- term.setBackgroundColor(colors.black)
- term.setTextColor(col or colors.lime)
- term.setCursorPos(1, H)
- local line = msg:sub(1, W)
- term.write(line .. string.rep(" ", W - #line))
- term.setTextColor(colors.white)
- end
- local function redrawAll()
- term.setBackgroundColor(colors.black)
- term.clear()
- -- Title
- term.setCursorPos(1, 1)
- term.setTextColor(colors.yellow)
- term.write(("Turtle #%-4d"):format(turtleId))
- term.setTextColor(colors.white)
- -- Regular buttons (dim them out in wild mode)
- for _, btn in ipairs(buttons) do
- if wildMode then
- term.setBackgroundColor(colors.gray)
- term.setTextColor(colors.black)
- for row = 0, BH - 1 do
- term.setCursorPos(btn.x, btn.y + row)
- term.write(string.rep(" ", BW))
- end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- else
- drawButton(btn, false)
- end
- end
- -- Wild button
- drawWildButton(wildMode)
- drawFuelBar()
- setStatus(
- wildMode and "WILD MODE ACTIVE — turtle is hunting!" or "Connected!",
- wildMode and colors.red or colors.lime
- )
- end
- redrawAll()
- -- ── Event loop ───────────────────────────
- while true do
- local ev = { os.pullEvent() }
- local evType = ev[1]
- -- ── Mouse click ──
- if evType == "mouse_click" then
- local mx, my = ev[3], ev[4]
- -- Wild button?
- if mx >= WILD.x and mx < WILD.x + WILD.w
- and my >= WILD.y and my < WILD.y + WILD.h then
- if not wildMode then
- rednet.send(turtleId, "goWild", PROTOCOL)
- setStatus("Unleashing the turtle…", colors.orange)
- else
- rednet.send(turtleId, "stop", PROTOCOL)
- setStatus("Calling it back…", colors.orange)
- end
- -- Regular movement buttons (only when not in wild mode)
- elseif not wildMode then
- for _, btn in ipairs(buttons) do
- if mx >= btn.x and mx < btn.x + BW
- and my >= btn.y and my < btn.y + BH then
- drawButton(btn, true)
- setStatus("► " .. btn.cmd, colors.cyan)
- rednet.send(turtleId, btn.cmd, PROTOCOL)
- -- Wait for reply (or short timeout)
- local _, reply = rednet.receive(PROTOCOL, 1)
- drawButton(btn, false)
- if reply then
- if reply:sub(1, 2) == "ok" then
- fuelLevel = reply:sub(4)
- drawFuelBar()
- setStatus("Connected!", colors.lime)
- elseif reply:sub(1, 7) == "blocked" then
- local parts = {}
- for p in reply:gmatch("[^:]+") do
- parts[#parts+1] = p
- end
- fuelLevel = parts[2] or fuelLevel
- drawFuelBar()
- local reason = parts[3] or "obstacle"
- setStatus("Blocked! (" .. reason .. ")", colors.red)
- end
- end
- break
- end
- end
- end
- -- ── Rednet message ──
- elseif evType == "rednet_message" then
- local id, msg = ev[2], ev[3]
- if id == turtleId then
- if msg == "wild:on" then
- wildMode = true
- redrawAll()
- elseif msg == "wild:off" then
- wildMode = false
- redrawAll()
- elseif msg and msg:sub(1, 5) == "pong:" then
- fuelLevel = msg:sub(6)
- drawFuelBar()
- end
- end
- end
- end
- end
- -- ═════════════════════════════════════════════
- -- Entry point
- -- ═════════════════════════════════════════════
- if mode == "turtle" then
- runTurtle()
- elseif mode == "control" then
- runControl()
- else
- local prog = shell.getRunningProgram()
- print("Usage:")
- print(" " .. prog .. " turtle (on the turtle)")
- print(" " .. prog .. " control (on the pocket computer)")
- end
Advertisement
Add Comment
Please, Sign In to add comment