CCCPaste

Auto-mapper_MC

-- /startup  (central mapper autostart)  CC:Tweaked + Advanced Peripherals
-- Tracks player via Player Detector, builds free-space voxels from movement, renders on a 5x4 advanced monitor.

-- ======= CONFIG =======
local PLAYER_NAME   = "YourPlayerName"
local MONITOR_NAME  = nil            -- nil = auto-find joined advanced monitor wall
local DETECTOR_NAME = nil            -- nil = auto-find player detector
local SAMPLE_SEC    = 0.2            -- seconds
local VOXEL         = 1              -- blocks per voxel
local RADIUS        = 1              -- voxel radius around path
local MAX_POINTS    = 120000
local BG_COL        = colors.black
local FG_COL        = colors.white
-- ======================

-- Open any modems
rednet.close()
for _,side in ipairs(rs.getSides()) do
  if peripheral.getType(side)=="modem" then rednet.open(side) end
end

-- Peripherals
local mon = MONITOR_NAME and peripheral.wrap(MONITOR_NAME) or peripheral.find("monitor")
assert(mon and peripheral.getType(mon)=="monitor","Monitor not found")
mon.setTextScale(0.5)
mon.setBackgroundColor(BG_COL)
mon.setTextColor(FG_COL)
mon.clear()

local pd = DETECTOR_NAME and peripheral.wrap(DETECTOR_NAME) or peripheral.find("playerDetector")
assert(pd,"Advanced Peripherals Player Detector not found")

-- Confirm GPS present (optional fallback)
local function gpsLocate()
  local x,y,z = gps.locate(2,false)
  return x,y,z
end

-- Screen
local W,H = mon.getSize()

-- Camera
local cam = { yaw=math.rad(45), pitch=math.rad(25), dist=48, cx=0, cy=0, cz=0, panx=0, pany=0, panz=0, mouseRotate=false }

-- Voxel store
local vox, order = {}, {}
local function key(ix,iy,iz) return ix..":"..iy..":"..iz end
local function setVoxel(ix,iy,iz)
  local k = key(ix,iy,iz)
  if not vox[k] then
    vox[k]=true
    order[#order+1]=k
    if #order>MAX_POINTS then local old=table.remove(order,1); vox[old]=nil end
  end
end
local function markFreeSphere(px,py,pz,rad)
  for dx=-rad,rad do
    for dy=-rad,rad do
      for dz=-rad,rad do
        if dx*dx+dy*dy+dz*dz<=rad*rad then setVoxel(px+dx,py+dy,pz+pz and pz or 0 + dz) end
      end
    end
  end
end

-- Math
local function rotatePoint(x,y,z, yaw,pitch)
  local cy,sy = math.cos(yaw), math.sin(yaw)
  local cp,sp = math.cos(pitch), math.sin(pitch)
  local xr =  cy*x + sy*z
  local zr = -sy*x + cy*z
  local yr =  cp*y - sp*zr
  zr =  sp*y + cp*zr
  return xr,yr,zr
end

local function project(ix,iy,iz)
  local x = (ix*VOXEL - cam.cx) + cam.panx
  local y = (iy*VOXEL - cam.cy) + cam.pany
  local z = (iz*VOXEL - cam.cz) + cam.panz
  local xr,yr,zr = rotatePoint(x,y,z, cam.yaw, cam.pitch)
  local f,d = 30, cam.dist
  local denom = (zr + d)
  if denom<=0.2 then return nil end
  local sx = math.floor(W/2 + (xr*f)/denom + 0.5)
  local sy = math.floor(H/2 - (yr*f)/denom + 0.5)
  return sx,sy,denom
end

local function clearMon()
  mon.setBackgroundColor(BG_COL)
  mon.setTextColor(FG_COL)
  mon.clear()
end

-- FIXED: use blit with string colors
local function plot(x,y,c)
  if x>=1 and x<=W and y>=1 and y<=H then
    mon.setCursorPos(x,y)
    mon.blit("\127", colors.toBlit(c), colors.toBlit(BG_COL))
  end
end

local function colorByDepth(den)
  if den<10 then return colors.white end
  if den<20 then return colors.lightGray end
  if den<35 then return colors.gray end
  return colors.black
end

local function drawHUD(points,fps)
  mon.setCursorPos(1,1)
  mon.setTextColor(colors.yellow)
  mon.setBackgroundColor(BG_COL)
  mon.write(("pts:%d fps:%.1f yaw:%d pitch:%d zoom:%d     "):format(points,fps, math.deg(cam.yaw), math.deg(cam.pitch), cam.dist))
end

local function render(fps)
  clearMon()
  local count=0
  local bucketNear, bucketFar = {}, {}
  for k,_ in pairs(vox) do
    local ix,iy,iz = k:match("(-?%d+):(-?%d+):(-?%d+)")
    ix,iy,iz = tonumber(ix),tonumber(iy),tonumber(iz)
    local sx,sy,den = project(ix,iy,iz)
    if sx then
      if den<25 then bucketNear[#bucketNear+1]={sx,sy,den} else bucketFar[#bucketFar+1]={sx,sy,den} end
    end
  end
  local function drawB(b) for i=1,#b do local sx,sy,den=b[i][1],b[i][2],b[i][3]; plot(sx,sy,colorByDepth(den)); count=count+1 end end
  drawB(bucketFar); drawB(bucketNear)
  drawHUD(count,fps or 0)
end

-- Input
local function clamp(v,a,b) if vb then return b else return v end end
local function inputLoop()
  while true do
    local e={os.pullEvent()}
    if e[1]=="key" then
      local k=e[2]
      if k==keys.q then return end
      if k==keys.left then cam.yaw=cam.yaw-math.rad(5) end
      if k==keys.right then cam.yaw=cam.yaw+math.rad(5) end
      if k==keys.up then cam.pitch=clamp(cam.pitch-math.rad(5), math.rad(-80), math.rad(80)) end
      if k==keys.down then cam.pitch=clamp(cam.pitch+math.rad(5), math.rad(-80), math.rad(80)) end
      if k==keys.leftBracket then cam.dist=clamp(cam.dist-2,8,200) end
      if k==keys.rightBracket then cam.dist=clamp(cam.dist+2,8,200) end
      if k==keys.w then cam.pany=cam.pany+1 end
      if k==keys.s then cam.pany=cam.pany-1 end
      if k==keys.a then cam.panx=cam.panx-1 end
      if k==keys.d then cam.panx=cam.panx+1 end
      if k==keys.r then cam.panz=cam.panz+1 end
      if k==keys.f then cam.panz=cam.panz-1 end
      if k==keys.m then cam.mouseRotate=not cam.mouseRotate end
    elseif e[1]=="mouse_drag" and cam.mouseRotate then
      local dx,dy=e[3],e[4]
      cam.yaw=cam.yaw+dx*0.01
      cam.pitch=clamp(cam.pitch+dy*0.01, math.rad(-80), math.rad(80))
    end
  end
end

-- Sampling
local function roundDiv(v,step) return math.floor((v + (v>=0 and step/2 or -step/2))/step) end
local function sampleLoop()
  local last=os.clock()
  while true do
    local pos = pd.getPlayerPos and pd.getPlayerPos(PLAYER_NAME)
    local x,y,z
    if type(pos)=="table" and pos.x then x,y,z=pos.x,pos.y,pos.z else x,y,z=gpsLocate() end
    if x and y and z then
      cam.cx = cam.cx + (x - cam.cx)*0.3
      cam.cy = cam.cy + (y - cam.cy)*0.3
      cam.cz = cam.cz + (z - cam.cz)*0.3
      local ix,iy,iz = roundDiv(x,VOXEL), roundDiv(y,VOXEL), roundDiv(z,VOXEL)
      markFreeSphere(ix,iy,iz,RADIUS)
    end
    local now=os.clock(); local dt=now-last; last=now
    render(1/math.max(dt,1e-3))
    sleep(SAMPLE_SEC)
  end
end

-- Startup screen clear and hint
term.redirect(mon)
clearMon()
mon.setCursorPos(1,2); mon.setTextColor(FG_COL); mon.write("Mapper autostart. Q quits.")
mon.setCursorPos(1,3); mon.write("Arrows rotate  [ ] zoom  WASD pan  R/F elevate  M mouse-rot")

parallel.waitForAny(sampleLoop, inputLoop)

term.redirect(term.native())