Orion09

tuttl control

Mar 24th, 2026 (edited)
27
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.88 KB | None | 0 0
  1. -- turtleremote.lua (CC: Tweaked)
  2. -- Usage:
  3. -- turtleremote turtle (run on the mining turtle)
  4. -- turtleremote control (run on the pocket computer)
  5.  
  6. local args = { ... }
  7. local mode = args[1]
  8.  
  9. -- ─────────────────────────────────────────────
  10. -- Shared: find and open a wireless modem
  11. -- ─────────────────────────────────────────────
  12. local function openWirelessModem()
  13. for _, side in ipairs({"top","bottom","left","right","front","back"}) do
  14. if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  15. local m = peripheral.wrap(side)
  16. if m.isWireless and m.isWireless() then
  17. rednet.open(side)
  18. return side
  19. end
  20. end
  21. end
  22. end
  23.  
  24. local PROTOCOL = "turtleremote"
  25.  
  26. -- ═════════════════════════════════════════════
  27. -- TURTLE MODE
  28. -- ═════════════════════════════════════════════
  29. local function runTurtle()
  30. if not openWirelessModem() then
  31. printError("No wireless modem found! Attach one and retry.")
  32. return
  33. end
  34.  
  35. print("Turtle online (ID: " .. os.getComputerID() .. ")")
  36. print("Waiting for controller…")
  37.  
  38. -- ── Helpers ──────────────────────────────
  39.  
  40. -- Pull any available fuel from inventory into the fuel tank
  41. local function tryRefuel()
  42. for i = 1, 16 do
  43. turtle.select(i)
  44. if turtle.refuel(0) then -- refuel(0) = dry-run; returns true if burnable
  45. turtle.refuel(1)
  46. if turtle.getFuelLevel() > 0 then return true end
  47. end
  48. end
  49. return false
  50. end
  51.  
  52. -- Attempt a movement fn(), auto-refuelling first if empty.
  53. -- Returns "ok:<fuel>" or "blocked:<fuel>" so the controller
  54. -- can update its fuel readout and show a warning.
  55. local function tryMove(fn)
  56. local fuel = turtle.getFuelLevel()
  57. if fuel == 0 then
  58. tryRefuel()
  59. fuel = turtle.getFuelLevel()
  60. end
  61. local ok, reason = fn()
  62. fuel = turtle.getFuelLevel()
  63. if ok then
  64. return "ok:" .. fuel
  65. else
  66. return "blocked:" .. fuel .. (reason and (":" .. reason) or "")
  67. end
  68. end
  69.  
  70. -- Scan inventory for a sword or axe and equip it to the right hand
  71. local function equipBestWeapon()
  72. for i = 1, 16 do
  73. turtle.select(i)
  74. local item = turtle.getItemDetail()
  75. if item then
  76. local n = item.name
  77. if n:find("sword") or n:find("axe") or n:find("knife") then
  78. turtle.equipRight()
  79. return true
  80. end
  81. end
  82. end
  83. return false -- no weapon found; turtle's fist will do
  84. end
  85.  
  86. -- ── GO WILD loop ──────────────────────────
  87. --
  88. -- Vanilla CC:T has no entity scanner — turtle.attack() returning true
  89. -- is the ONLY way to detect a nearby entity. So detection and damage
  90. -- are the same action. We make this work by:
  91. --
  92. -- 1. Respecting the ~0.6 s sword cooldown so every hit does full damage.
  93. -- 2. Using a state machine so the turtle doesn't abandon a chase the
  94. -- instant it misses once (the player is running away, not gone).
  95. --
  96. -- States:
  97. -- SWEEP – no target; rotate 90° each cycle, occasionally lunge
  98. -- CHASE – active lock in a direction; charge hard, tolerate some misses
  99. -- CLOSING – just lost lock; keep charging forward before giving up
  100. --
  101. local ATTACK_COOLDOWN = 0.6 -- seconds; matches vanilla sword cooldown
  102. local CHASE_MISS_LIMIT = 4 -- misses before CHASE → CLOSING
  103. local CLOSING_STEP_LIMIT = 5 -- forward steps before CLOSING → SWEEP
  104.  
  105. local function goWild(senderId)
  106. equipBestWeapon()
  107. rednet.send(senderId, "wild:on", PROTOCOL)
  108.  
  109. local shouldStop = false
  110.  
  111. local function huntLoop()
  112. -- State: "SWEEP" | "CHASE" | "CLOSING"
  113. local state = "SWEEP"
  114. local chaseDir = "F" -- "F" | "U" | "D" (last hit direction)
  115. local missStreak = 0
  116. local closingSteps = 0
  117. local sweepTurns = 0 -- how many right-turns taken in current sweep
  118.  
  119. local function fuel()
  120. if turtle.getFuelLevel() == 0 then tryRefuel() end
  121. end
  122.  
  123. -- Move in a direction, digging through any block in the way
  124. local function advance(dir)
  125. fuel()
  126. if dir == "F" then
  127. if not turtle.forward() then turtle.dig(); turtle.forward() end
  128. elseif dir == "U" then
  129. if not turtle.up() then turtle.digUp(); turtle.up() end
  130. elseif dir == "D" then
  131. if not turtle.down() then turtle.digDown(); turtle.down() end
  132. end
  133. end
  134.  
  135. while not shouldStop do
  136.  
  137. -- ── Attack all three faces ──────────────────────────────────
  138. -- Returns which direction(s) connected this tick.
  139. local hitF = turtle.attack()
  140. local hitU = turtle.attackUp()
  141. local hitD = turtle.attackDown()
  142. local anyHit = hitF or hitU or hitD
  143.  
  144. -- ── State transitions ────────────────────────────────────────
  145.  
  146. if state == "SWEEP" then
  147. if anyHit then
  148. -- Got a contact — lock on and switch to CHASE
  149. chaseDir = hitF and "F" or (hitU and "U" or "D")
  150. missStreak = 0
  151. state = "CHASE"
  152. sweepTurns = 0
  153. advance(chaseDir)
  154. else
  155. -- Rotate 90° and occasionally lunge to cover ground
  156. sweepTurns = sweepTurns + 1
  157. turtle.turnRight()
  158. if sweepTurns % 4 == 0 then
  159. -- Completed a full rotation without a hit; step forward
  160. -- so we actually move around the map instead of spinning on the spot
  161. advance("F")
  162. end
  163. end
  164.  
  165. elseif state == "CHASE" then
  166. if anyHit then
  167. -- Still hitting something — update direction if changed
  168. -- (e.g. player jumped) and keep charging
  169. chaseDir = hitF and "F" or (hitU and "U" or "D")
  170. missStreak = 0
  171. advance(chaseDir)
  172. else
  173. missStreak = missStreak + 1
  174. if missStreak >= CHASE_MISS_LIMIT then
  175. -- Lost them — try CLOSING before giving up
  176. state = "CLOSING"
  177. closingSteps = 0
  178. missStreak = 0
  179. else
  180. -- Still probably just the player backing away;
  181. -- keep charging in the last known direction
  182. advance(chaseDir)
  183. end
  184. end
  185.  
  186. elseif state == "CLOSING" then
  187. if anyHit then
  188. -- Re-acquired — back to CHASE
  189. chaseDir = hitF and "F" or (hitU and "U" or "D")
  190. missStreak = 0
  191. state = "CHASE"
  192. advance(chaseDir)
  193. else
  194. closingSteps = closingSteps + 1
  195. if closingSteps >= CLOSING_STEP_LIMIT then
  196. -- Truly lost; begin sweep from fresh angle
  197. state = "SWEEP"
  198. sweepTurns = 0
  199. -- Turn right once so we don't sweep the same corridor
  200. turtle.turnRight()
  201. else
  202. -- Charge forward in last-known direction
  203. advance(chaseDir)
  204. end
  205. end
  206. end
  207.  
  208. -- Respect sword cooldown — attacking faster wastes damage
  209. sleep(ATTACK_COOLDOWN)
  210. end
  211. end
  212.  
  213. -- Coroutine 2: blocking stop listener — cannot miss the message
  214. local function stopListener()
  215. while true do
  216. local _, msg = rednet.receive(PROTOCOL)
  217. if msg == "stop" then
  218. shouldStop = true
  219. return
  220. end
  221. end
  222. end
  223.  
  224. parallel.waitForAny(huntLoop, stopListener)
  225. rednet.send(senderId, "wild:off", PROTOCOL)
  226. end
  227.  
  228. -- ── Main receive loop ─────────────────────
  229. while true do
  230. local senderId, msg = rednet.receive(PROTOCOL)
  231.  
  232. if msg == "forward" then rednet.send(senderId, tryMove(turtle.forward), PROTOCOL)
  233. elseif msg == "back" then rednet.send(senderId, tryMove(turtle.back), PROTOCOL)
  234. elseif msg == "up" then rednet.send(senderId, tryMove(turtle.up), PROTOCOL)
  235. elseif msg == "down" then rednet.send(senderId, tryMove(turtle.down), PROTOCOL)
  236. elseif msg == "turnLeft" then turtle.turnLeft()
  237. elseif msg == "turnRight" then turtle.turnRight()
  238. elseif msg == "use" then
  239. if not turtle.dig() then turtle.attack() end
  240.  
  241. elseif msg == "goWild" then goWild(senderId)
  242.  
  243. elseif msg == "ping" then
  244. rednet.send(senderId, "pong:" .. turtle.getFuelLevel(), PROTOCOL)
  245. end
  246. end
  247. end
  248.  
  249. -- ═════════════════════════════════════════════
  250. -- CONTROL MODE (pocket computer)
  251. -- ═════════════════════════════════════════════
  252. local function runControl()
  253. if not openWirelessModem() then
  254. printError("No wireless modem found! Attach one and retry.")
  255. return
  256. end
  257.  
  258. -- ── Discover the turtle ──────────────────
  259. term.clear()
  260. term.setCursorPos(1, 1)
  261. print("Scanning for turtle…")
  262.  
  263. local turtleId
  264. local fuelLevel = "?"
  265. local deadline = os.clock() + 6
  266.  
  267. repeat
  268. rednet.broadcast("ping", PROTOCOL)
  269. local id, msg = rednet.receive(PROTOCOL, 1)
  270. if id and msg and msg:sub(1, 4) == "pong" then
  271. turtleId = id
  272. fuelLevel = msg:sub(6) -- "pong:XXXX"
  273. end
  274. until turtleId or os.clock() > deadline
  275.  
  276. if not turtleId then
  277. printError("No turtle found. Is it running?")
  278. return
  279. end
  280.  
  281. print("Connected to turtle #" .. turtleId)
  282. sleep(0.6)
  283.  
  284. -- ── Layout ───────────────────────────────
  285. -- Pocket screen = 26 × 20
  286. --
  287. -- Row 1 title + turtle id
  288. -- Row 2-3 [ UP ]
  289. -- Row 5-6 [TURN L] [ FWD ] [TURN R]
  290. -- Row 8-9 [ DOWN ]
  291. -- Row 11-12 [ BACK ] [ USE! ]
  292. -- Row 14-15 [====== GO WILD! ======]
  293. -- Row 19 fuel bar
  294. -- Row 20 status bar
  295.  
  296. local W, H = term.getSize()
  297. local BW, BH = 7, 2
  298. local CX, LX, RX = 10, 2, 18
  299.  
  300. local buttons = {
  301. { label = " UP ", x = CX, y = 2, cmd = "up" },
  302. { label = "TURN L", x = LX, y = 5, cmd = "turnLeft" },
  303. { label = " FWD ", x = CX, y = 5, cmd = "forward" },
  304. { label = "TURN R", x = RX, y = 5, cmd = "turnRight" },
  305. { label = " DOWN ", x = CX, y = 8, cmd = "down" },
  306. { label = " BACK ", x = LX, y = 11, cmd = "back" },
  307. { label = " USE! ", x = RX, y = 11, cmd = "use" },
  308. }
  309.  
  310. -- GO WILD is wider and has its own draw logic
  311. local WILD = { x = 2, y = 14, w = 22, h = 2 }
  312.  
  313. local wildMode = false
  314.  
  315. -- ── Draw helpers ─────────────────────────
  316. local function drawButton(btn, lit)
  317. local bw = btn.w or BW
  318. local bh = btn.h or BH
  319. term.setBackgroundColor(lit and colors.white or colors.gray)
  320. term.setTextColor(lit and colors.black or colors.white)
  321. for row = 0, bh - 1 do
  322. term.setCursorPos(btn.x, btn.y + row)
  323. if row == math.floor(bh / 2) then
  324. local lbl = btn.label or ""
  325. local pad = math.floor((bw - #lbl) / 2)
  326. term.write(
  327. string.rep(" ", pad) .. lbl ..
  328. string.rep(" ", bw - pad - #lbl)
  329. )
  330. else
  331. term.write(string.rep(" ", bw))
  332. end
  333. end
  334. term.setBackgroundColor(colors.black)
  335. term.setTextColor(colors.white)
  336. end
  337.  
  338. local function drawWildButton(active)
  339. -- Active = red "STOP" state; inactive = orange "GO WILD" state
  340. term.setBackgroundColor(active and colors.red or colors.orange)
  341. term.setTextColor(active and colors.white or colors.black)
  342. local lbl = active and " STOP WILD " or " GO WILD! "
  343. for row = 0, WILD.h - 1 do
  344. term.setCursorPos(WILD.x, WILD.y + row)
  345. if row == math.floor(WILD.h / 2) then
  346. local pad = math.floor((WILD.w - #lbl) / 2)
  347. term.write(
  348. string.rep(" ", pad) .. lbl ..
  349. string.rep(" ", WILD.w - pad - #lbl)
  350. )
  351. else
  352. term.write(string.rep(" ", WILD.w))
  353. end
  354. end
  355. term.setBackgroundColor(colors.black)
  356. term.setTextColor(colors.white)
  357. end
  358.  
  359. local function drawFuelBar()
  360. term.setBackgroundColor(colors.black)
  361. term.setTextColor(colors.yellow)
  362. term.setCursorPos(1, H - 1)
  363. local s = "Fuel: " .. tostring(fuelLevel)
  364. term.write(s .. string.rep(" ", W - #s))
  365. term.setTextColor(colors.white)
  366. end
  367.  
  368. local function setStatus(msg, col)
  369. term.setBackgroundColor(colors.black)
  370. term.setTextColor(col or colors.lime)
  371. term.setCursorPos(1, H)
  372. local line = msg:sub(1, W)
  373. term.write(line .. string.rep(" ", W - #line))
  374. term.setTextColor(colors.white)
  375. end
  376.  
  377. local function redrawAll()
  378. term.setBackgroundColor(colors.black)
  379. term.clear()
  380. -- Title
  381. term.setCursorPos(1, 1)
  382. term.setTextColor(colors.yellow)
  383. term.write(("Turtle #%-4d"):format(turtleId))
  384. term.setTextColor(colors.white)
  385. -- Regular buttons (dim them out in wild mode)
  386. for _, btn in ipairs(buttons) do
  387. if wildMode then
  388. term.setBackgroundColor(colors.gray)
  389. term.setTextColor(colors.black)
  390. for row = 0, BH - 1 do
  391. term.setCursorPos(btn.x, btn.y + row)
  392. term.write(string.rep(" ", BW))
  393. end
  394. term.setBackgroundColor(colors.black)
  395. term.setTextColor(colors.white)
  396. else
  397. drawButton(btn, false)
  398. end
  399. end
  400. -- Wild button
  401. drawWildButton(wildMode)
  402. drawFuelBar()
  403. setStatus(
  404. wildMode and "WILD MODE ACTIVE — turtle is hunting!" or "Connected!",
  405. wildMode and colors.red or colors.lime
  406. )
  407. end
  408.  
  409. redrawAll()
  410.  
  411. -- ── Event loop ───────────────────────────
  412. while true do
  413. local ev = { os.pullEvent() }
  414. local evType = ev[1]
  415.  
  416. -- ── Mouse click ──
  417. if evType == "mouse_click" then
  418. local mx, my = ev[3], ev[4]
  419.  
  420. -- Wild button?
  421. if mx >= WILD.x and mx < WILD.x + WILD.w
  422. and my >= WILD.y and my < WILD.y + WILD.h then
  423. if not wildMode then
  424. rednet.send(turtleId, "goWild", PROTOCOL)
  425. setStatus("Unleashing the turtle…", colors.orange)
  426. else
  427. rednet.send(turtleId, "stop", PROTOCOL)
  428. setStatus("Calling it back…", colors.orange)
  429. end
  430.  
  431. -- Regular movement buttons (only when not in wild mode)
  432. elseif not wildMode then
  433. for _, btn in ipairs(buttons) do
  434. if mx >= btn.x and mx < btn.x + BW
  435. and my >= btn.y and my < btn.y + BH then
  436. drawButton(btn, true)
  437. setStatus("► " .. btn.cmd, colors.cyan)
  438. rednet.send(turtleId, btn.cmd, PROTOCOL)
  439. -- Wait for reply (or short timeout)
  440. local _, reply = rednet.receive(PROTOCOL, 1)
  441. drawButton(btn, false)
  442. if reply then
  443. if reply:sub(1, 2) == "ok" then
  444. fuelLevel = reply:sub(4)
  445. drawFuelBar()
  446. setStatus("Connected!", colors.lime)
  447. elseif reply:sub(1, 7) == "blocked" then
  448. local parts = {}
  449. for p in reply:gmatch("[^:]+") do
  450. parts[#parts+1] = p
  451. end
  452. fuelLevel = parts[2] or fuelLevel
  453. drawFuelBar()
  454. local reason = parts[3] or "obstacle"
  455. setStatus("Blocked! (" .. reason .. ")", colors.red)
  456. end
  457. end
  458. break
  459. end
  460. end
  461. end
  462.  
  463. -- ── Rednet message ──
  464. elseif evType == "rednet_message" then
  465. local id, msg = ev[2], ev[3]
  466. if id == turtleId then
  467. if msg == "wild:on" then
  468. wildMode = true
  469. redrawAll()
  470.  
  471. elseif msg == "wild:off" then
  472. wildMode = false
  473. redrawAll()
  474.  
  475. elseif msg and msg:sub(1, 5) == "pong:" then
  476. fuelLevel = msg:sub(6)
  477. drawFuelBar()
  478. end
  479. end
  480. end
  481. end
  482. end
  483.  
  484. -- ═════════════════════════════════════════════
  485. -- Entry point
  486. -- ═════════════════════════════════════════════
  487. if mode == "turtle" then
  488. runTurtle()
  489. elseif mode == "control" then
  490. runControl()
  491. else
  492. local prog = shell.getRunningProgram()
  493. print("Usage:")
  494. print(" " .. prog .. " turtle (on the turtle)")
  495. print(" " .. prog .. " control (on the pocket computer)")
  496. end
Advertisement
Add Comment
Please, Sign In to add comment