robathome

Auto-mapper_MC

Oct 12th, 2025 (edited)
790
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.56 KB | None | 0 0
  1. -- /startup  (central mapper autostart, no blit, in-situ commands, nil-safe)
  2. -- CC:Tweaked + Advanced Peripherals
  3.  
  4. -- ======= CONFIG =======
  5. local PLAYER_NAME   = "Robathome"
  6. local MONITOR_NAME  = nil            -- nil = auto-find joined advanced monitor wall
  7. local DETECTOR_NAME = nil            -- nil = auto-find player detector
  8. local SAMPLE_SEC    = 0.2
  9. local VOXEL         = 1
  10. local RADIUS        = 2
  11. local MAX_POINTS    = 120000
  12. local BG_COL        = colors.black
  13. local FG_COL        = colors.white
  14. local SCALE         = 1.5            -- 0.5 .. 5.0
  15. -- ======================
  16.  
  17. -- ===== Utilities =====
  18. local function N(v,def) v = tonumber(v); if v==nil then return def end; return v end
  19. local function D(v) return math.floor(N(v,0)+0.5) end
  20. local function clamp(v,a,b) if v<a then return a elseif v>b then return b else return v end end
  21.  
  22. -- Open any modems
  23. rednet.close()
  24. for _,side in ipairs(rs.getSides()) do
  25.   if peripheral.getType(side)=="modem" then rednet.open(side) end
  26. end
  27.  
  28. -- Peripherals
  29. local mon = MONITOR_NAME and peripheral.wrap(MONITOR_NAME) or peripheral.find("monitor")
  30. assert(mon and peripheral.getType(mon)=="monitor","Monitor not found")
  31. mon.setTextScale(SCALE)
  32. mon.setBackgroundColor(BG_COL)
  33. mon.setTextColor(FG_COL)
  34. mon.clear()
  35.  
  36. local pd = DETECTOR_NAME and peripheral.wrap(DETECTOR_NAME) or peripheral.find("playerDetector")
  37. assert(pd,"Advanced Peripherals Player Detector not found")
  38.  
  39. -- GPS fallback
  40. local function gpsLocate() local x,y,z=gps.locate(2,false); return x,y,z end
  41.  
  42. -- Screen
  43. local W,H = mon.getSize()
  44. local function recalcLayout()
  45.   W,H = mon.getSize()
  46. end
  47. recalcLayout()
  48. local ROW_STATUS = H           -- bottom
  49. local ROW_CMD    = H-1         -- second-last
  50. local RENDER_MAX_Y = H-2       -- leave bottom two rows free
  51.  
  52. -- Camera
  53. local cam = { yaw=math.rad(45), pitch=math.rad(25), dist=28, cx=0, cy=0, cz=0, panx=0, pany=0, panz=0, mouseRotate=false }
  54.  
  55. -- Tracking toggle
  56. local tracking_enabled = true
  57.  
  58. -- ===== Status line (bottom row reserved) =====
  59. local status_phase, status_data, status_dirty = "INIT", {}, true
  60. local function setStatus(phase, data) status_phase=phase or status_phase; status_data=data or status_data; status_dirty=true end
  61. local function fmtStatus()
  62.   local parts={("phase:%s"):format(status_phase), ("track:%s"):format(tracking_enabled and "on" or "off")}
  63.   for k,v in pairs(status_data) do parts[#parts+1]=("%s=%s"):format(k,tostring(v)) end
  64.   local s=table.concat(parts,"  ")
  65.   if #s>N(W,0) then s=s:sub(1,N(W,0)) end
  66.   return s
  67. end
  68. local function drawStatus()
  69.   if not status_dirty then return end
  70.   mon.setBackgroundColor(colors.gray); mon.setTextColor(colors.black)
  71.   mon.setCursorPos(1,N(ROW_STATUS,1)); mon.write(("%-"..N(W,1).."s"):format(fmtStatus()))
  72.   mon.setBackgroundColor(BG_COL); mon.setTextColor(FG_COL)
  73.   status_dirty=false
  74. end
  75.  
  76. -- ===== Command line (H-1) =====
  77. local cmd_text, cmd_dirty = "", true
  78. local function drawCmd()
  79.   if not cmd_dirty then return end
  80.   mon.setBackgroundColor(colors.blue); mon.setTextColor(colors.white)
  81.   mon.setCursorPos(1,N(ROW_CMD,1)); mon.write(("%-"..N(W,1).."s"):format((":%s"):format(cmd_text)))
  82.   mon.setBackgroundColor(BG_COL); mon.setTextColor(FG_COL)
  83.   cmd_dirty=false
  84. end
  85. local function clearCmd()
  86.   cmd_text=""; cmd_dirty=true; drawCmd()
  87. end
  88.  
  89. -- ===== Voxel store =====
  90. local vox, order = {}, {}
  91. local function key(ix,iy,iz) return ix..":"..iy..":"..iz end
  92. local function setVoxel(ix,iy,iz)
  93.   ix,iy,iz = D(ix),D(iy),D(iz)
  94.   local k=key(ix,iy,iz)
  95.   if not vox[k] then
  96.     vox[k]=true; order[#order+1]=k
  97.     if #order>MAX_POINTS then local old=table.remove(order,1); vox[old]=nil end
  98.   end
  99. end
  100. local function markFreeSphere(px,py,pz,rad)
  101.   px,py,pz,rad = D(px),D(py),D(pz),N(rad,0)
  102.   for dx=-rad,rad do
  103.     for dy=-rad,rad do
  104.       for dz=-rad,rad do
  105.         if dx*dx+dy*dy+dz*dz<=rad*rad then setVoxel(px+dx,py+dy,pz+dz) end
  106.       end
  107.     end
  108.   end
  109. end
  110.  
  111. -- Highlights (drawn last)
  112. local highlights = {}  -- { {x=..,y=..,z=..,r=..,color=..}, ... }
  113. local function addHighlight(x,y,z,r,c)
  114.   highlights[#highlights+1]={x=N(x,0),y=N(y,0),z=N(z,0),r=N(r,0),color=c or colors.red}
  115.   setStatus("CMD",{added="highlight"})
  116. end
  117. local function clearHighlights()
  118.   highlights = {}
  119.   setStatus("CMD",{highlights="cleared"})
  120. end
  121.  
  122. -- ===== Math / projection =====
  123. local function rotatePoint(x,y,z,yaw,pitch)
  124.   x,y,z = N(x,0),N(y,0),N(z,0)
  125.   local cy,sy=math.cos(N(yaw,0)),math.sin(N(yaw,0))
  126.   local cp,sp=math.cos(N(pitch,0)),math.sin(N(pitch,0))
  127.   local xr =  cy*x + sy*z
  128.   local zr = -sy*x + cy*z
  129.   local yr =  cp*y - sp*zr
  130.   zr =  sp*y + cp*zr
  131.   return xr,yr,zr
  132. end
  133.  
  134. local function project(ix,iy,iz)
  135.   local x=(N(ix,0)*VOXEL - N(cam.cx,0))+N(cam.panx,0)
  136.   local y=(N(iy,0)*VOXEL - N(cam.cy,0))+N(cam.pany,0)
  137.   local z=(N(iz,0)*VOXEL - N(cam.cz,0))+N(cam.panz,0)
  138.   local xr,yr,zr=rotatePoint(x,y,z,cam.yaw,cam.pitch)
  139.   local f,d=30,N(cam.dist,30)
  140.   local denom=zr+d; if denom<=0.2 then return nil end
  141.   local sx=D(W/2 + (xr*f)/denom)
  142.   local sy=D(H/2 - (yr*f)/denom)
  143.   return sx,sy,denom
  144. end
  145.  
  146. local function clearMon()
  147.   mon.setBackgroundColor(BG_COL); mon.setTextColor(FG_COL); mon.clear()
  148.   drawCmd(); drawStatus()
  149. end
  150.  
  151. -- Pixel fill without blit
  152. local function plot(x,y,c)
  153.   x,y = D(x),D(y)
  154.   if x>=1 and x<=N(W,1) and y>=1 and y<=N(RENDER_MAX_Y,1) then
  155.     mon.setCursorPos(x,y)
  156.     mon.setBackgroundColor(c or FG_COL)
  157.     mon.write(" ")
  158.     mon.setBackgroundColor(BG_COL)
  159.   end
  160. end
  161.  
  162. local function colorByDepth(den)
  163.   den=N(den,0)
  164.   if den < 12 then return colors.white end
  165.   if den < 24 then return colors.lightGray end
  166.   if den < 48 then return colors.gray end
  167.   return colors.darkGray
  168. end
  169.  
  170. local function drawHUD(points,fps)
  171.   points = N(points,0); fps = N(fps,0)
  172.   local yaw   = D(math.deg(N(cam.yaw,0)))
  173.   local pitch = D(math.deg(N(cam.pitch,0)))
  174.   local dist  = D(N(cam.dist,0))
  175.   local cx,cy,cz = D(cam.cx), D(cam.cy), D(cam.cz)
  176.   mon.setCursorPos(1,1)
  177.   mon.setTextColor(colors.yellow)
  178.   mon.setBackgroundColor(BG_COL)
  179.   local hud = ("pts:%d  fps:%.1f  yaw:%d  pitch:%d  zoom:%d  cx:%d cy:%d cz:%d")
  180.               :format(points,fps,yaw,pitch,dist,cx,cy,cz)
  181.   if #hud>N(W,0) then hud=hud:sub(1,N(W,0)) end
  182.   mon.write(("%-"..N(W,1).."s"):format(hud))
  183. end
  184.  
  185. -- render: guard bad keys and nil projects
  186. local function render(fps)
  187.   clearMon()
  188.   setStatus("RENDER",{voxels=#order})
  189.   local count=0
  190.   local bucketNear,bucketFar={},{}
  191.  
  192.   for k,_ in pairs(vox) do
  193.     local ix,iy,iz = k:match("(-?%d+):(-?%d+):(-?%d+)")
  194.     if ix and iy and iz then
  195.       ix,iy,iz = tonumber(ix),tonumber(iy),tonumber(iz)
  196.       local sx,sy,den = project(ix,iy,iz)
  197.       if sx and sy and den then
  198.         if den<25 then bucketNear[#bucketNear+1]={sx,sy,den}
  199.         else bucketFar[#bucketFar+1]={sx,sy,den} end
  200.       end
  201.     else
  202.       vox[k] = nil
  203.     end
  204.   end
  205.  
  206.   local function drawB(b)
  207.     for i=1,#b do
  208.       local sx,sy,den = b[i][1],b[i][2],b[i][3]
  209.       if sx and sy then plot(sx,sy,colorByDepth(den or 0)); count=count+1 end
  210.     end
  211.   end
  212.   drawB(bucketFar); drawB(bucketNear)
  213.  
  214.   -- Draw highlights last
  215.   for _,h in ipairs(highlights) do
  216.     local r=N(h.r,0)
  217.     for dx=-r,r do for dy=-r,r do for dz=-r,r do
  218.       if dx*dx+dy*dy+dz*dz <= r*r then
  219.         local sx,sy,den = project(h.x+dx,h.y+dy,h.z+dz)
  220.         if sx then plot(sx,sy,h.color or colors.red) end
  221.       end
  222.     end end end
  223.   end
  224.  
  225.   drawHUD(count, fps or 0)
  226.   drawCmd(); drawStatus()
  227. end
  228.  
  229. -- ===== Command parsing =====
  230. local function split(s) local t={} for w in tostring(s):gmatch("%S+") do t[#t+1]=w end; return t end
  231. local function toNum(s) return tonumber(s) end
  232.  
  233. local function execCommand(line)
  234.   local t=split(line)
  235.   if #t==0 then return end
  236.   local cmd=t[1]:lower()
  237.  
  238.   if cmd=="help" or cmd=="?" then
  239.     setStatus("CMD",{help="track on|off; highlight x y z [r]; clear highlights; cam yaw|pitch|dist N; cam set yaw pitch dist; center x y z"})
  240.     return
  241.  
  242.   elseif cmd=="track" and t[2] then
  243.     local v=t[2]:lower()
  244.     if v=="on" then tracking_enabled=true elseif v=="off" then tracking_enabled=false else return setStatus("CMD",{error="track on/off"}) end
  245.     setStatus("CMD",{track=tracking_enabled and "on" or "off"}); return
  246.  
  247.   elseif cmd=="highlight" and t[2] and t[3] and t[4] then
  248.     local x,y,z=toNum(t[2]),toNum(t[3]),toNum(t[4]); if not(x and y and z) then return setStatus("CMD",{error="coords"}) end
  249.     local r=toNum(t[5]) or 0
  250.     addHighlight(x,y,z,r, colors.red)
  251.     return
  252.  
  253.   elseif cmd=="clear" and t[2] and t[2]:lower()=="highlights" then
  254.     clearHighlights(); return
  255.  
  256.   elseif cmd=="cam" and t[2] then
  257.     local sub=t[2]:lower()
  258.     if sub=="yaw" and t[3] then cam.yaw=math.rad(clamp(toNum(t[3]) or 0,-180,180)); setStatus("CMD",{yaw=D(math.deg(cam.yaw))})
  259.     elseif sub=="pitch" and t[3] then cam.pitch=math.rad(clamp(toNum(t[3]) or 0,-80,80)); setStatus("CMD",{pitch=D(math.deg(cam.pitch))})
  260.     elseif sub=="dist" and t[3] then cam.dist=clamp(toNum(t[3]) or cam.dist,8,200); setStatus("CMD",{dist=D(cam.dist)})
  261.     elseif sub=="set" and t[3] and t[4] and t[5] then
  262.       cam.yaw=math.rad(clamp(toNum(t[3]) or 45,-180,180))
  263.       cam.pitch=math.rad(clamp(toNum(t[4]) or 25,-80,80))
  264.       cam.dist=clamp(toNum(t[5]) or 48,8,200)
  265.       setStatus("CMD",{yaw=D(math.deg(cam.yaw)),pitch=D(math.deg(cam.pitch)),dist=D(cam.dist)})
  266.     else
  267.       setStatus("CMD",{error="cam yaw|pitch|dist N  / cam set yaw pitch dist"})
  268.     end
  269.     return
  270.  
  271.   elseif (cmd=="center" or cmd=="lookat") and t[2] and t[3] and t[4] then
  272.     local x,y,z=toNum(t[2]),toNum(t[3]),toNum(t[4]); if not(x and y and z) then return setStatus("CMD",{error="center x y z"}) end
  273.     cam.cx,cam.cy,cam.cz=x,y,z
  274.     setStatus("CMD",{center=("%d,%d,%d"):format(D(x),D(y),D(z))})
  275.     return
  276.   end
  277.  
  278.   setStatus("CMD",{error="unknown; try :help"})
  279. end
  280.  
  281. -- ===== Command-mode prompt (triggered by ':' key) =====
  282. local function commandPrompt()
  283.   cmd_text=""; cmd_dirty=true; drawCmd()
  284.   while true do
  285.     local e={os.pullEvent()}
  286.     if e[1]=="char" then
  287.       local ch=e[2]
  288.       if #cmd_text < math.max(1,N(W,1)-2) then cmd_text=cmd_text..ch; cmd_dirty=true; drawCmd() end
  289.     elseif e[1]=="key" then
  290.       local k=e[2]
  291.       if k==keys.enter then
  292.         local line=cmd_text
  293.         clearCmd()
  294.         execCommand(line)
  295.         return
  296.       elseif k==keys.backspace then
  297.         cmd_text=cmd_text:sub(1,math.max(0,#cmd_text-1)); cmd_dirty=true; drawCmd()
  298.       elseif k==keys.escape then
  299.         clearCmd(); setStatus("CMD",{cancel="ok"}); return
  300.       end
  301.     elseif e[1]=="monitor_resize" then
  302.       recalcLayout(); ROW_STATUS=H; ROW_CMD=H-1; RENDER_MAX_Y=H-2
  303.       drawCmd(); drawStatus()
  304.     end
  305.   end
  306. end
  307.  
  308. -- ===== Input loop =====
  309. local function inputLoop()
  310.   while true do
  311.     local e={os.pullEvent()}
  312.     if e[1]=="char" then
  313.       if e[2]==":" then setStatus("CMD",{mode="prompt"}); commandPrompt() end
  314.     elseif e[1]=="key" then
  315.       local k=e[2]
  316.       if k==keys.q then return end
  317.       if k==keys.left then cam.yaw=cam.yaw-math.rad(5) end
  318.       if k==keys.right then cam.yaw=cam.yaw+math.rad(5) end
  319.       if k==keys.up then cam.pitch=clamp(cam.pitch-math.rad(5),math.rad(-80),math.rad(80)) end
  320.       if k==keys.down then cam.pitch=clamp(cam.pitch+math.rad(5),math.rad(-80),math.rad(80)) end
  321.       if k==keys.leftBracket then cam.dist=clamp(cam.dist-2,8,200) end
  322.       if k==keys.rightBracket then cam.dist=clamp(cam.dist+2,8,200) end
  323.       if k==keys.w then cam.pany=cam.pany+1 end
  324.       if k==keys.s then cam.pany=cam.pany-1 end
  325.       if k==keys.a then cam.panx=cam.panx-1 end
  326.       if k==keys.d then cam.panx=cam.panx+1 end
  327.       if k==keys.r then cam.panz=cam.panz+1 end
  328.       if k==keys.f then cam.panz=cam.panz-1 end
  329.       if k==keys.m then cam.mouseRotate=not cam.mouseRotate end
  330.       setStatus("INPUT",{key=k})
  331.     elseif e[1]=="mouse_drag" and cam.mouseRotate then
  332.       local dx,dy=e[3],e[4]
  333.       cam.yaw=cam.yaw+dx*0.01
  334.       cam.pitch=clamp(cam.pitch+dy*0.01,math.rad(-80),math.rad(80))
  335.       setStatus("INPUT",{drag=("%d,%d"):format(D(dx),D(dy))})
  336.     elseif e[1]=="monitor_touch" then
  337.       local _side,x,y=e[2],e[3],e[4]
  338.       if y==ROW_STATUS then
  339.         cam.mouseRotate=not cam.mouseRotate
  340.         setStatus("TOUCH",{toggle_mouseRotate=cam.mouseRotate})
  341.       else
  342.         local third = math.floor(N(W,1)/3)
  343.         if x<=third then
  344.           cam.yaw=cam.yaw-math.rad(10); setStatus("TOUCH",{action="yaw -10"})
  345.         elseif x>2*third then
  346.           cam.yaw=cam.yaw+math.rad(10); setStatus("TOUCH",{action="yaw +10"})
  347.         else
  348.           if y < math.floor(N(RENDER_MAX_Y,1)/2) then
  349.             cam.pitch=clamp(cam.pitch-math.rad(10),math.rad(-80),math.rad(80)); setStatus("TOUCH",{action="pitch -10"})
  350.           else
  351.             cam.pitch=clamp(cam.pitch+math.rad(10),math.rad(-80),math.rad(80)); setStatus("TOUCH",{action="pitch +10"})
  352.           end
  353.         end
  354.       end
  355.     elseif e[1]=="monitor_resize" then
  356.       recalcLayout(); ROW_STATUS=H; ROW_CMD=H-1; RENDER_MAX_Y=H-2
  357.       mon.setTextScale(SCALE)
  358.       setStatus("RESIZE",{w=W,h=H,scale=SCALE})
  359.       clearMon()
  360.     end
  361.   end
  362. end
  363.  
  364. -- ===== Sampling =====
  365. local function roundDiv(v,step) return math.floor((N(v,0) + (v and v>=0 and step/2 or -step/2))/step) end
  366. local function sampleLoop()
  367.   local last=os.clock()
  368.   while true do
  369.     local x,y,z
  370.     if tracking_enabled then
  371.       setStatus("SAMPLE",{src="detector"})
  372.       local pos=pd.getPlayerPos and pd.getPlayerPos(PLAYER_NAME)
  373.       if type(pos)=="table" and pos.x then
  374.         x,y,z=pos.x,pos.y,pos.z
  375.       else
  376.         setStatus("SAMPLE",{src="gps"})
  377.         x,y,z=gpsLocate()
  378.       end
  379.     else
  380.       setStatus("IDLE",{track="off"})
  381.     end
  382.  
  383.     if x and y and z then
  384.       setStatus("UPDATE",{x=D(x),y=D(y),z=D(z)})
  385.       cam.cx = cam.cx + (x - cam.cx)*0.3
  386.       cam.cy = cam.cy + (y - cam.cy)*0.3
  387.       cam.cz = cam.cz + (z - cam.cz)*0.3
  388.       local ix,iy,iz=roundDiv(x,VOXEL),roundDiv(y,VOXEL),roundDiv(z,VOXEL)
  389.       markFreeSphere(ix,iy,iz,RADIUS)
  390.     end
  391.  
  392.     local now=os.clock(); local dt=now-last; last=now
  393.     render(1/math.max(dt,1e-3))
  394.     sleep(SAMPLE_SEC)
  395.   end
  396. end
  397.  
  398. -- ===== Startup =====
  399. term.redirect(mon)
  400. mon.setBackgroundColor(BG_COL); mon.setTextColor(FG_COL); mon.clear()
  401. mon.setCursorPos(1,2); mon.setTextColor(FG_COL); mon.write("Mapper autostart. Q quits.")
  402. mon.setCursorPos(1,3); mon.write("Arrows rotate  [ ] zoom  WASD pan  R/F elevate  M mouse-rot")
  403. mon.setCursorPos(1,4); mon.write("Type ':' for commands. ':help' for syntax.")
  404. setStatus("INIT",{scale=SCALE}); drawCmd(); drawStatus()
  405.  
  406. parallel.waitForAny(sampleLoop, inputLoop)
  407. term.redirect(term.native())
  408.  
Advertisement