Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- startup.lua — Hypertube catcher (CC:Tweaked + Advanced Peripherals)
- -- Stack (bottom→top, adjacent to hypertube exit): Player Detector, Computer, Redstone Integrator, Piston
- -- ===== CONFIG =====
- local CONFIG = {
- pistonY = 72, -- absolute Y of the piston face level
- side = "up", -- Redstone Integrator side wired to the piston (e.g., "up","north","east"...)
- range = 12, -- detector radius to watch below/around the exit
- holdSec = 0.30, -- how long to keep piston extended
- pistonExtendSec = 0.10, -- piston actuation latency (~2 ticks ≈ 0.10 s)
- sampleDt = 0.05, -- sampling interval (seconds). 1 MC tick = 0.05 s
- minUpVel = 0.20, -- require at least this upward m/s before arming
- fireOncePerPass = true, -- avoid multiple triggers until the player clears the zone
- targetPlayer = nil, -- nil=any player; or set a specific player name
- }
- -- ===================
- -- Peripherals
- local det = peripheral.find("playerDetector") or error("No playerDetector")
- local ri = peripheral.find("redstoneIntegrator") or error("No redstoneIntegrator")
- -- Redstone helpers (boolean API works on AP integrator)
- local function piston(on) ri.setOutput(CONFIG.side, on and true or false) end
- -- Simple per-player tracker
- local tracks = {} -- name -> {hist = { {t=.., y=.., v=..} ... max 3}, armed=true/false, cool=false}
- local function clamp(n,a,b) if n<a then return a elseif n>b then return b else return n end end
- -- Quadratic arrival solve: y(t) = y0 + v0 t + 0.5 a t^2 → find t where y(t)=Ypiston
- local function arrivalTime(y0, v0, a, yTarget)
- local dy = yTarget - y0
- if math.abs(a) < 1e-5 then
- if v0 <= 0 then return nil end
- return dy / v0
- end
- -- 0.5 a t^2 + v0 t - dy = 0
- local A = 0.5 * a
- local B = v0
- local C = -dy
- local disc = B*B - 4*A*C
- if disc < 0 then return nil end
- local sqrtD = math.sqrt(disc)
- local t1 = (-B + sqrtD) / (2*A)
- local t2 = (-B - sqrtD) / (2*A)
- local t = math.huge
- if t1 and t1 > 0 then t = math.min(t, t1) end
- if t2 and t2 > 0 then t = math.min(t, t2) end
- if t == math.huge then return nil end
- return t
- end
- -- Update one player's kinematics
- local function updateTrack(name, yNow, tNow)
- local tr = tracks[name]
- if not tr then tr = { hist = {}, armed = true, cool = false } tracks[name] = tr end
- local h = tr.hist
- -- push sample
- local vNow = nil
- if #h >= 1 then
- local dt = math.max(1e-3, tNow - h[#h].t)
- vNow = (yNow - h[#h].y) / dt
- end
- table.insert(h, { t = tNow, y = yNow, v = vNow })
- while #h > 3 do table.remove(h, 1) end
- -- compute a from last two velocities if available
- local a = 0
- if #h >= 3 and h[#h-1].v and h[#h].v then
- local dv = h[#h].v - h[#h-1].v
- local dt = math.max(1e-3, h[#h].t - h[#h-1].t)
- a = dv / dt
- elseif #h >= 2 and h[#h].v then
- a = 0 -- fallback linear
- end
- return tr, h[#h].v or 0, a
- end
- -- Fire control with simple pass-based cooldown
- local function shouldArm(tr, y, v)
- if CONFIG.fireOncePerPass then
- if tr.cool then
- -- re-arm after player leaves the band around piston
- if y > CONFIG.pistonY + 2 or y < CONFIG.pistonY - 6 then
- tr.cool = false; tr.armed = true
- end
- end
- return tr.armed
- end
- return true
- end
- local function markFired(tr)
- if CONFIG.fireOncePerPass then
- tr.armed = false
- tr.cool = true
- end
- end
- -- Main loop
- print("Hypertube catcher armed. Watching for players...")
- while true do
- local t0 = os.epoch("utc") / 1000.0
- -- enumerate players in range (prefer exact name if set)
- local names = {}
- if CONFIG.targetPlayer then
- if det.isPlayerInRange and det.isPlayerInRange(CONFIG.targetPlayer, CONFIG.range) then
- names = { CONFIG.targetPlayer }
- end
- else
- if det.getPlayersInRange then
- names = det.getPlayersInRange(CONFIG.range) or {}
- elseif det.getOnlinePlayers then
- -- fallback: filter by distance manually if only positions are available
- local online = det.getOnlinePlayers() or {}
- for _,n in ipairs(online) do
- local p = det.getPlayerPos(n); if p then
- local dx = 0 - (p.x or 0) -- unknown computer pos; range check only on Y/Z is fine for a vertical column
- local dz = 0 - (p.z or 0)
- if math.sqrt(dx*dx + dz*dz) <= CONFIG.range then table.insert(names, n) end
- end
- end
- end
- end
- for _,name in ipairs(names) do
- local pos = det.getPlayerPos and det.getPlayerPos(name)
- if pos and pos.y then
- local tr, v, a = updateTrack(name, pos.y, t0)
- -- Only consider players rising toward the piston from below
- if pos.y <= CONFIG.pistonY + 1 and v and v > CONFIG.minUpVel and shouldArm(tr, pos.y, v) then
- local tArr = arrivalTime(pos.y, v, a, CONFIG.pistonY)
- if tArr then
- local fireIn = tArr - CONFIG.pistonExtendSec
- if fireIn <= 0 then
- -- immediate fire
- piston(true)
- sleep(CONFIG.holdSec)
- piston(false)
- markFired(tr)
- else
- -- schedule within this loop tick (bounded by our sampleDt)
- local wait = clamp(fireIn, 0, CONFIG.sampleDt)
- sleep(wait)
- piston(true)
- sleep(CONFIG.holdSec)
- piston(false)
- markFired(tr)
- end
- end
- end
- end
- end
- sleep(CONFIG.sampleDt)
- end
Advertisement
Add Comment
Please, Sign In to add comment