-- /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())