robathome

Untitled

Oct 23rd, 2025
416
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.43 KB | None | 0 0
  1. -- startup.lua — Hypertube catcher (CC:Tweaked + Advanced Peripherals)
  2. -- Stack (bottom→top, adjacent to hypertube exit): Player Detector, Computer, Redstone Integrator, Piston
  3.  
  4. -- ===== CONFIG =====
  5. local CONFIG = {
  6. pistonY = 72, -- absolute Y of the piston face level
  7. side = "up", -- Redstone Integrator side wired to the piston (e.g., "up","north","east"...)
  8. range = 12, -- detector radius to watch below/around the exit
  9. holdSec = 0.30, -- how long to keep piston extended
  10. pistonExtendSec = 0.10, -- piston actuation latency (~2 ticks ≈ 0.10 s)
  11. sampleDt = 0.05, -- sampling interval (seconds). 1 MC tick = 0.05 s
  12. minUpVel = 0.20, -- require at least this upward m/s before arming
  13. fireOncePerPass = true, -- avoid multiple triggers until the player clears the zone
  14. targetPlayer = nil, -- nil=any player; or set a specific player name
  15. }
  16. -- ===================
  17.  
  18. -- Peripherals
  19. local det = peripheral.find("playerDetector") or error("No playerDetector")
  20. local ri = peripheral.find("redstoneIntegrator") or error("No redstoneIntegrator")
  21.  
  22. -- Redstone helpers (boolean API works on AP integrator)
  23. local function piston(on) ri.setOutput(CONFIG.side, on and true or false) end
  24.  
  25. -- Simple per-player tracker
  26. local tracks = {} -- name -> {hist = { {t=.., y=.., v=..} ... max 3}, armed=true/false, cool=false}
  27.  
  28. local function clamp(n,a,b) if n<a then return a elseif n>b then return b else return n end end
  29.  
  30. -- Quadratic arrival solve: y(t) = y0 + v0 t + 0.5 a t^2 → find t where y(t)=Ypiston
  31. local function arrivalTime(y0, v0, a, yTarget)
  32. local dy = yTarget - y0
  33. if math.abs(a) < 1e-5 then
  34. if v0 <= 0 then return nil end
  35. return dy / v0
  36. end
  37. -- 0.5 a t^2 + v0 t - dy = 0
  38. local A = 0.5 * a
  39. local B = v0
  40. local C = -dy
  41. local disc = B*B - 4*A*C
  42. if disc < 0 then return nil end
  43. local sqrtD = math.sqrt(disc)
  44. local t1 = (-B + sqrtD) / (2*A)
  45. local t2 = (-B - sqrtD) / (2*A)
  46. local t = math.huge
  47. if t1 and t1 > 0 then t = math.min(t, t1) end
  48. if t2 and t2 > 0 then t = math.min(t, t2) end
  49. if t == math.huge then return nil end
  50. return t
  51. end
  52.  
  53. -- Update one player's kinematics
  54. local function updateTrack(name, yNow, tNow)
  55. local tr = tracks[name]
  56. if not tr then tr = { hist = {}, armed = true, cool = false } tracks[name] = tr end
  57.  
  58. local h = tr.hist
  59. -- push sample
  60. local vNow = nil
  61. if #h >= 1 then
  62. local dt = math.max(1e-3, tNow - h[#h].t)
  63. vNow = (yNow - h[#h].y) / dt
  64. end
  65. table.insert(h, { t = tNow, y = yNow, v = vNow })
  66. while #h > 3 do table.remove(h, 1) end
  67.  
  68. -- compute a from last two velocities if available
  69. local a = 0
  70. if #h >= 3 and h[#h-1].v and h[#h].v then
  71. local dv = h[#h].v - h[#h-1].v
  72. local dt = math.max(1e-3, h[#h].t - h[#h-1].t)
  73. a = dv / dt
  74. elseif #h >= 2 and h[#h].v then
  75. a = 0 -- fallback linear
  76. end
  77.  
  78. return tr, h[#h].v or 0, a
  79. end
  80.  
  81. -- Fire control with simple pass-based cooldown
  82. local function shouldArm(tr, y, v)
  83. if CONFIG.fireOncePerPass then
  84. if tr.cool then
  85. -- re-arm after player leaves the band around piston
  86. if y > CONFIG.pistonY + 2 or y < CONFIG.pistonY - 6 then
  87. tr.cool = false; tr.armed = true
  88. end
  89. end
  90. return tr.armed
  91. end
  92. return true
  93. end
  94.  
  95. local function markFired(tr)
  96. if CONFIG.fireOncePerPass then
  97. tr.armed = false
  98. tr.cool = true
  99. end
  100. end
  101.  
  102. -- Main loop
  103. print("Hypertube catcher armed. Watching for players...")
  104. while true do
  105. local t0 = os.epoch("utc") / 1000.0
  106.  
  107. -- enumerate players in range (prefer exact name if set)
  108. local names = {}
  109. if CONFIG.targetPlayer then
  110. if det.isPlayerInRange and det.isPlayerInRange(CONFIG.targetPlayer, CONFIG.range) then
  111. names = { CONFIG.targetPlayer }
  112. end
  113. else
  114. if det.getPlayersInRange then
  115. names = det.getPlayersInRange(CONFIG.range) or {}
  116. elseif det.getOnlinePlayers then
  117. -- fallback: filter by distance manually if only positions are available
  118. local online = det.getOnlinePlayers() or {}
  119. for _,n in ipairs(online) do
  120. local p = det.getPlayerPos(n); if p then
  121. local dx = 0 - (p.x or 0) -- unknown computer pos; range check only on Y/Z is fine for a vertical column
  122. local dz = 0 - (p.z or 0)
  123. if math.sqrt(dx*dx + dz*dz) <= CONFIG.range then table.insert(names, n) end
  124. end
  125. end
  126. end
  127. end
  128.  
  129. for _,name in ipairs(names) do
  130. local pos = det.getPlayerPos and det.getPlayerPos(name)
  131. if pos and pos.y then
  132. local tr, v, a = updateTrack(name, pos.y, t0)
  133. -- Only consider players rising toward the piston from below
  134. if pos.y <= CONFIG.pistonY + 1 and v and v > CONFIG.minUpVel and shouldArm(tr, pos.y, v) then
  135. local tArr = arrivalTime(pos.y, v, a, CONFIG.pistonY)
  136. if tArr then
  137. local fireIn = tArr - CONFIG.pistonExtendSec
  138. if fireIn <= 0 then
  139. -- immediate fire
  140. piston(true)
  141. sleep(CONFIG.holdSec)
  142. piston(false)
  143. markFired(tr)
  144. else
  145. -- schedule within this loop tick (bounded by our sampleDt)
  146. local wait = clamp(fireIn, 0, CONFIG.sampleDt)
  147. sleep(wait)
  148. piston(true)
  149. sleep(CONFIG.holdSec)
  150. piston(false)
  151. markFired(tr)
  152. end
  153. end
  154. end
  155. end
  156. end
  157.  
  158. sleep(CONFIG.sampleDt)
  159. end
  160.  
Advertisement
Add Comment
Please, Sign In to add comment