minecraft_storm

CBC control

Aug 14th, 2025 (edited)
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 25.35 KB | None | 0 0
  1. --!/rom/programs/startup
  2. -- Main program for controlling a Create Big Cannon with ComputerCraft.
  3. -- This script uses CC: Create Bridge to interact with Sequenced Gearshifts
  4. -- for aiming, and bundled cables for building and firing.
  5.  
  6. -- Configuration Parameters (YOU MUST EDIT THESE!)
  7. -- These values will depend on your specific cannon build and Minecraft world.
  8. local config = {
  9.     -- Peripheral names for your Sequenced Gearshifts
  10.     -- Use the name given when you wrap it, e.g., "Create_SequencedGearshift_3"
  11.     yawGearshiftSide = "Create_SequencedGearshift_3",
  12.     pitchGearshiftSide = "Create_SequencedGearshift_2",
  13.  
  14.     -- Bundled cable side for ALL Redstone signals (build/fire/etc.)
  15.     bundledCableSide = "left",
  16.  
  17.     -- Bundled cable colors (channels) for specific actions
  18.     buildSignalColor = colors.black, -- The color of the channel for building/resetting (on/off)
  19.     fireSignalColor = colors.red,    -- The color of the channel for firing (pulse)
  20.  
  21.     -- Cannon specific parameters
  22.     barrelLength = 2,           -- Length of the cannon barrel in blocks
  23.  
  24.     -- Gear Ratio: How many degrees the gearshift needs to rotate for 1 degree of cannon rotation.
  25.     -- If 1 gearshift degree = 0.125 cannon degrees, then 1 cannon degree = 1 / 0.125 = 8 gearshift degrees.
  26.     gearRatio = 8.0,             -- (1.0 / 0.125) Every 1 degree of cannon movement requires 8 degrees of gearshift rotation.
  27.  
  28.     -- Default cannon orientation offset (for built-in direction)
  29.     -- This value should be the yaw angle (in degrees, 0-360) that your cannon faces
  30.     -- when it's in its default, 'built' orientation (e.g., 0 for North, 90 for East, etc.)
  31.     defaultYawOffset = 180,        -- Example: 0 for North, 90 for East, 180 for South, 270 for West
  32.  
  33.     -- Offsets in blocks from the COMPUTER'S position to the CANNON'S BARREL TIP
  34.     -- These are crucial for accurate GPS-based trajectory calculations.
  35.     xOffset = 0.0,               -- X-offset (East/West)
  36.     yOffset = 1.0,               -- Y-offset (Up/Down)
  37.     zOffset = 0.0,               -- Z-offset (North/South)
  38.  
  39.     -- --- PHYSICS PARAMETERS (REQUIRED FOR AUTOMATIC AIMING) ---
  40.     -- You MUST fill these in based on your Create Big Cannons setup.
  41.     -- These are crucial for accurate trajectory calculation.
  42.     projectileVelocity = 40.0,   -- 💥 Initial speed of the projectile in blocks per tick (e.g., 50)
  43.                                  --    (SET THIS VALUE TO YOUR CANNON'S ACTUAL MUZZLE VELOCITY!)
  44.     gravity = 0.08,              -- ⬇️ Minecraft's default gravity, blocks per tick^2 (0.08)
  45.                                  --    Adjust if you have mods changing gravity.
  46.     airResistance = 0.00,        -- 🌬️ Simple drag coefficient (e.g., 0.01-0.05). Set to 0 for no drag.
  47.                                  --    (SET THIS VALUE based on your observation, or leave 0 for no drag!)
  48.     maxPitch = 90.0,             -- ⬆️ Maximum physical pitch angle your cannon can achieve (degrees)
  49.     minPitch = -30,              -- ⬇️ Minimum physical pitch angle your cannon can achieve (degrees)
  50. }
  51.  
  52. -- Global peripheral variables
  53. local yawGearshift = nil
  54. local pitchGearshift = nil
  55. -- 'redstone' is a global API in CC:Tweaked, no need to peripheral.wrap it.
  56.  
  57. -- Internal state to track current angles
  58. -- IMPORTANT: These values are tracked by the program, the physical gearshifts
  59. -- will rotate relatively. It's critical that your physical cannon is aligned
  60. -- to its 'defaultYawOffset' and 0 pitch when the program starts or `resetcannon` is used.
  61. local currentYaw = 0.0
  62. local currentPitch = 0.0
  63.  
  64. -- Internal state for build status
  65. local isCannonBuilt = false
  66.  
  67. --- Initializes all required peripherals.
  68. -- @return boolean True if all peripherals are found, false otherwise.
  69. local function initializePeripherals()
  70.     print("Initializing peripherals...")
  71.     yawGearshift = peripheral.wrap(config.yawGearshiftSide)
  72.     pitchGearshift = peripheral.wrap(config.pitchGearshiftSide)
  73.  
  74.     if not yawGearshift then print("Error: Yaw Gearshift '" .. config.yawGearshiftSide .. "' not found. Check name/side and connection.") end
  75.     if not pitchGearshift then print("Error: Pitch Gearshift '" .. config.pitchGearshiftSide .. "' not found. Check name/side and connection.") end
  76.  
  77.     if yawGearshift and pitchGearshift then
  78.         print("All required peripherals initialized successfully.")
  79.         return true
  80.     else
  81.         print("Failed to initialize one or both gearshift peripherals. Please check your config and in-game setup.")
  82.         return false
  83.     end
  84. end
  85.  
  86. --- Sends a brief pulse on a specific bundled cable channel.
  87. -- This function uses bitwise operations to safely toggle a single color channel
  88. -- without affecting others on the same bundled cable.
  89. -- @param color The color constant (e.g., `colors.red`) for the channel.
  90. local function pulseBundled(color)
  91.     -- 'redstone' is a global API, so no need for a 'redstonePeripheral' variable.
  92.     local currentOutput = redstone.getBundledOutput(config.bundledCableSide)
  93.     redstone.setBundledOutput(config.bundledCableSide, bit.bor(currentOutput, color))
  94.     os.sleep(0.2) -- Keep the signal active briefly to ensure it registers in Create
  95.     currentOutput = redstone.getBundledOutput(config.bundledCableSide) -- Re-read in case others changed
  96.     redstone.setBundledOutput(config.bundledCableSide, bit.band(currentOutput, bit.bnot(color)))
  97. end
  98.  
  99. --- Sets the state of a bundled cable channel (on/off).
  100. -- This is used for the 'build' signal which is not a pulse.
  101. -- @param color The color constant for the channel.
  102. -- @param state True for on, false for off.
  103. local function setBundledState(color, state)
  104.     local currentOutput = redstone.getBundledOutput(config.bundledCableSide)
  105.     if state then
  106.         redstone.setBundledOutput(config.bundledCableSide, bit.bor(currentOutput, color))
  107.     else
  108.         redstone.setBundledOutput(config.bundledCableSide, bit.band(currentOutput, bit.bnot(color)))
  109.     end
  110. end
  111.  
  112. --- Normalizes an angle to be within the -180 to 180 degree range.
  113. -- This helps in calculating the shortest rotation path.
  114. -- @param angle The angle in degrees.
  115. -- @return The normalized angle.
  116. local function normalizeAngle(angle)
  117.     angle = angle % 360
  118.     if angle > 180 then
  119.         angle = angle - 360
  120.     elseif angle <= -180 then
  121.         angle = angle + 360
  122.     end
  123.     return angle
  124. end
  125.  
  126. --- Gets the cannon's current GPS location, applying configured offsets.
  127. -- The returned coordinates represent the theoretical firing point (barrel tip).
  128. -- @return table A table with x, y, z coordinates, or nil if GPS fails.
  129. local function getCannonLocation()
  130.     print("Attempting to get cannon GPS location...")
  131.     local x, y, z = gps.locate()
  132.     if x then
  133.         -- Apply configured offsets to the base GPS location for precise barrel position
  134.         x = x + config.xOffset
  135.         y = y + config.yOffset
  136.         z = z + config.zOffset
  137.         print(string.format("Cannon Location (with offsets): X:%.2f Y:%.2f Z:%.2f", x, y, z))
  138.         return {x = x, y = y, z = z}
  139.     else
  140.         print("Could not get GPS location. Ensure wireless modems are set up and in range.")
  141.         return nil
  142.     end
  143. end
  144.  
  145. --- Sets the yaw (horizontal) angle of the cannon using the Sequenced Gearshift.
  146. -- Calculates the relative rotation needed from the current tracked angle to the target.
  147. -- Compensates for the gear ratio: target_cannon_angle * config.gearRatio = gearshift_angle_to_rotate.
  148. -- This function is blocking and waits for the gearshift to stop moving.
  149. -- @param targetCannonAngle The desired absolute yaw angle in degrees (for the cannon).
  150. -- @return boolean True if the command was sent successfully and completed, false otherwise.
  151. local function setYaw(targetCannonAngle)
  152.     if not yawGearshift then
  153.         print("Yaw Gearshift not initialized. Cannot set yaw.")
  154.         return false
  155.     end
  156.  
  157.     -- Adjust target angle by the cannon's fixed orientation offset
  158.     local adjustedTargetAngle = normalizeAngle(targetCannonAngle - config.defaultYawOffset)
  159.  
  160.     local deltaCannonAngle = adjustedTargetAngle - currentYaw
  161.     -- Adjust deltaCannonAngle for the shortest path if crossing the -180/180 boundary
  162.     if math.abs(deltaCannonAngle) > 180 then
  163.         deltaCannonAngle = -(360 - math.abs(deltaCannonAngle)) * math.sign(deltaCannonAngle)
  164.     end
  165.    
  166.     local modifier = 1 -- Default forward rotation for gearshift
  167.     if deltaCannonAngle < 0 then
  168.         modifier = -1 -- Reverse rotation for gearshift
  169.     end
  170.  
  171.     local cannonAngleToRotate = math.abs(deltaCannonAngle)
  172.    
  173.     -- If the cannon angle to rotate is very small, we consider it already at target.
  174.     if cannonAngleToRotate < 0.01 then -- Using a small epsilon for floating point comparison
  175.         print(string.format("Yaw already at target %.2f degrees (actual cannon angle).", targetCannonAngle))
  176.         currentYaw = adjustedTargetAngle -- Ensure internal state is precise
  177.         return true
  178.     end
  179.  
  180.     -- Apply the gear ratio to get the required gearshift rotation
  181.     local gearshiftAngleToRotate = cannonAngleToRotate * config.gearRatio
  182.    
  183.     -- The 'rotate' method expects a positive integer angle.
  184.     -- Round to the nearest integer, ensuring a minimum of 1 if rotation is needed.
  185.     local roundedGearshiftAngleToRotate = math.max(1, math.floor(gearshiftAngleToRotate + 0.5))
  186.  
  187.     print(string.format("Setting yaw from %.2f (relative) to %.2f (relative) (rotating gearshift by %d with modifier %d)...",
  188.                         currentYaw, adjustedTargetAngle, roundedGearshiftAngleToRotate, modifier))
  189.    
  190.     yawGearshift.rotate(roundedGearshiftAngleToRotate, modifier) -- No return value expected
  191.  
  192.     -- Wait for the gearshift to finish rotating (blocking call)
  193.     while yawGearshift.isRunning() do
  194.         os.sleep(0.1) -- Small delay to avoid busy-waiting
  195.     end
  196.     print("Yaw Gearshift stopped.")
  197.  
  198.     currentYaw = adjustedTargetAngle -- Update internal state after physical movement is done
  199.     return true
  200. end
  201.  
  202. --- Sets the pitch (vertical) angle of the cannon using the Sequenced Gearshift.
  203. -- Compensates for the gear ratio. Clamps the target angle to physical min/max pitch.
  204. -- This function is blocking and waits for the gearshift to stop moving.
  205. -- @param targetCannonAngle The desired absolute pitch angle in degrees (for the cannon).
  206. -- @return boolean True if the command was sent successfully and completed, false otherwise.
  207. local function setPitch(targetCannonAngle)
  208.     if not pitchGearshift then
  209.         print("Pitch Gearshift not initialized. Cannot set pitch.")
  210.         return false
  211.     end
  212.  
  213.     -- Clamp pitch angle to the configured physical range
  214.     targetCannonAngle = math.max(config.minPitch, math.min(config.maxPitch, targetCannonAngle))
  215.  
  216.     local deltaCannonAngle = targetCannonAngle - currentPitch
  217.    
  218.     local modifier = 1
  219.     if deltaCannonAngle < 0 then
  220.         modifier = -1
  221.     end
  222.  
  223.     local cannonAngleToRotate = math.abs(deltaCannonAngle)
  224.  
  225.     if cannonAngleToRotate < 0.01 then
  226.         print(string.format("Pitch already at target %.2f degrees (actual cannon angle).", targetCannonAngle))
  227.         currentPitch = targetCannonAngle
  228.         return true
  229.     end
  230.  
  231.     -- Apply the gear ratio to get the required gearshift rotation
  232.     local gearshiftAngleToRotate = cannonAngleToRotate * config.gearRatio
  233.  
  234.     local roundedGearshiftAngleToRotate = math.max(1, math.floor(gearshiftAngleToRotate + 0.5))
  235.  
  236.     print(string.format("Setting pitch from %.2f to %.2f (rotating gearshift by %d with modifier %d)...",
  237.                         currentPitch, targetCannonAngle, roundedGearshiftAngleToRotate, modifier))
  238.    
  239.     pitchGearshift.rotate(roundedGearshiftAngleToRotate, modifier) -- No return value expected
  240.  
  241.     -- Wait for the gearshift to finish rotating (blocking call)
  242.     while pitchGearshift.isRunning() do
  243.         os.sleep(0.1) -- Small delay to avoid busy-waiting
  244.     end
  245.     print("Pitch Gearshift stopped.")
  246.  
  247.     currentPitch = targetCannonAngle -- Update internal state after physical movement is done
  248.     return true
  249. end
  250.  
  251. --- Resets the program's internal angle tracking and physically resets the cannon.
  252. -- This function will toggle the 'build' signal off then on, which you've stated
  253. -- causes the cannon to reset to the default position.
  254. local function resetCannon()
  255.     print("Performing full cannon reset sequence...")
  256.     -- First, set the build signal OFF (to un-build/reset state)
  257.     setBundledState(config.buildSignalColor, false)
  258.     isCannonBuilt = false
  259.     os.sleep(1.0) -- Give Minecraft/Create time to process the un-build/reset
  260.  
  261.     -- Then, set the build signal ON (to build state, which defaults position)
  262.     setBundledState(config.buildSignalColor, true)
  263.     isCannonBuilt = true
  264.     os.sleep(1.0) -- Give Minecraft/Create time to process the build
  265.  
  266.     -- Reset internal angle tracking based on the cannon's default orientation
  267.     currentYaw = normalizeAngle(0.0) -- Relative to defaultYawOffset, so 0 is the baseline
  268.     currentPitch = 0.0 -- Assuming 0 pitch is default for reset
  269.  
  270.     print(string.format("Cannon reset sequence complete. Internal angles reset to Yaw: %.2f (relative), Pitch: %.2f.", currentYaw, currentPitch))
  271.     print("Ensure your physical cannon is aligned with its default (initial) orientation.")
  272. end
  273.  
  274. --- Toggles the cannon's 'built' state (on/off).
  275. -- If the cannon is currently built, it will un-build it. If not built, it will build it.
  276. -- Toggling from un-built to built should reset its position.
  277. local function toggleBuildState()
  278.     if isCannonBuilt then
  279.         print("Un-building cannon (Black Bundled Cable channel OFF)...")
  280.         setBundledState(config.buildSignalColor, false)
  281.         isCannonBuilt = false
  282.         print("Cannon un-built.")
  283.     else
  284.         print("Building cannon (Black Bundled Cable channel ON)...")
  285.         setBundledState(config.buildSignalColor, true)
  286.         isCannonBuilt = true
  287.         print("Cannon built.")
  288.         -- When building, we assume it snaps to default position
  289.         currentYaw = normalizeAngle(0.0) -- Relative to defaultYawOffset, so 0 is the baseline
  290.         currentPitch = 0.0
  291.         print(string.format("Internal angles reset to Yaw: %.2f (relative), Pitch: %.2f after building.", currentYaw, currentPitch))
  292.     end
  293. end
  294.  
  295. --- Triggers the cannon to fire.
  296. -- This sends a brief pulse on the red bundled cable channel.
  297. -- @return boolean True if the signal was sent.
  298. local function fireCannon()
  299.     print("Activating cannon fire signal (Red Bundled Cable channel)...")
  300.     pulseBundled(config.fireSignalColor)
  301.     print("Fire signal sent. Expect explosion!")
  302.     return true
  303. end
  304.  
  305. --- Calculates the required yaw and pitch to hit a target.
  306. -- This is where the physics-based trajectory calculation happens.
  307. -- Uses the provided physics parameters from the config.
  308. -- @param targetX Target X coordinate.
  309. -- @param targetY Target Y coordinate.
  310. -- @param targetZ Target Z coordinate.
  311. -- @return table A table {yaw = angle, pitch = angle} if successful, nil otherwise.
  312. local function calculateTrajectory(targetX, targetY, targetZ)
  313.     local cannonLoc = getCannonLocation()
  314.     if not cannonLoc then
  315.         print("Cannot calculate trajectory: Cannon location unknown.")
  316.         return nil
  317.     end
  318.  
  319.     local dx = targetX - cannonLoc.x
  320.     local dy = targetY - cannonLoc.y
  321.     local dz = targetZ - cannonLoc.z
  322.  
  323.     print(string.format("Target relative to cannon (DX:%.2f, DY:%.2f, DZ:%.2f)", dx, dy, dz))
  324.  
  325.     -- --- ADVANCED PHYSICS CALCULATION ---
  326.     -- Yaw Calculation: Simple arctan2 for horizontal aim.
  327.     -- Assuming Minecraft's coordinate system where +X is East, +Z is South.
  328.     -- Yaw 0 is North, 90 is East, 180 is South, 270 (or -90) is West, increasing clockwise.
  329.     -- math.atan2(y, x) returns angle in radians from positive X axis counter-clockwise.
  330.     -- For Minecraft yaw (0=North, clockwise):
  331.     -- atan2(dx, dz) where dx is targetX-originX (East/West diff), dz is targetZ-originZ (North/South diff)
  332.     -- This gives angle from +Z (South), positive towards +X (East). Need to adjust for North.
  333.     -- angle from North clockwise: if dz > 0 (South), use atan2(dx, dz)
  334.     -- if dz < 0 (North), use atan2(-dx, -dz) + 180 or handle sign change.
  335.     -- Simpler: map dx, dz to standard math.atan2 arguments to get desired orientation.
  336.     -- If 0 degrees is North, 90 East, 180 South, 270 West:
  337.     -- atan2(east_component, north_component) where east is positive X, north is positive Z (if 0 north).
  338.     -- If your map's +Z is South, and +X is East, and 0 yaw is North:
  339.     local calculatedYaw = math.deg(math.atan2(dx, dz)) -- Angle from Z-axis, positive towards X.
  340.     -- Adjust to make 0 degrees true North, increasing clockwise:
  341.     -- math.atan2(y, x) gives angle from +x axis counter-clockwise.
  342.     -- If +Z is North, +X is East, then (X,Z) plane maps to (y,x) in atan2.
  343.     -- To get angle from +Z (North) increasing clockwise: use atan2(dx, dz) and then adjust.
  344.     calculatedYaw = 90 - calculatedYaw -- Adjust for 0 being North and clockwise
  345.     calculatedYaw = normalizeAngle(calculatedYaw) -- Ensure it's in -180 to 180 range for setYaw
  346.  
  347.     -- Pitch Calculation: Projectile motion in 2D (horizontal distance vs vertical distance)
  348.     local horizontalDistance = math.sqrt(dx^2 + dz^2)
  349.    
  350.     local calculatedPitch = nil -- Will store the pitch in degrees
  351.  
  352.     if horizontalDistance < 0.1 then -- Too close, pitch straight up or down (handle edge case)
  353.         if dy > 0 then calculatedPitch = 90.0 else calculatedPitch = config.minPitch end -- Or a negative pitch if cannon allows
  354.     else
  355.         -- Check if projectileVelocity is set to a non-zero value
  356.         if config.projectileVelocity == 0 then
  357.             print("Error: projectileVelocity is 0. Cannot calculate trajectory with physics. Please set 'config.projectileVelocity'.")
  358.             return nil
  359.         end
  360.  
  361.         -- Quadratic formula for projectile motion (simplified, ignoring air resistance for the first pass):
  362.         -- dy = v0y * t - 0.5 * g * t^2
  363.         -- dx_horizontal = v0x * t
  364.         -- v0y = v0 * sin(theta)
  365.         -- v0x = v0 * cos(theta)
  366.         -- horizontalDistance = v0 * cos(theta) * t  =>  t = horizontalDistance / (v0 * cos(theta))
  367.  
  368.         -- Substitute t into dy equation:
  369.         -- dy = v0 * sin(theta) * (horizontalDistance / (v0 * cos(theta))) - 0.5 * g * (horizontalDistance / (v0 * cos(theta)))^2
  370.         -- dy = horizontalDistance * tan(theta) - (0.5 * g * horizontalDistance^2) / (v0^2 * cos(theta)^2)
  371.         -- dy = horizontalDistance * tan(theta) - (g * horizontalDistance^2) / (2 * v0^2 * (1/cos(theta)^2))  (since 1/cos^2 = sec^2 = 1 + tan^2)
  372.         -- dy = horizontalDistance * tan(theta) - (g * horizontalDistance^2 / (2 * v0^2)) * (1 + tan(theta)^2)
  373.  
  374.         -- Rearrange into a quadratic equation in terms of tan(theta):
  375.         -- (g * horizontalDistance^2 / (2 * v0^2)) * tan(theta)^2 - horizontalDistance * tan(theta) + dy + (g * horizontalDistance^2 / (2 * v0^2)) = 0
  376.  
  377.         -- Let A = (config.gravity * horizontalDistance^2) / (2 * config.projectileVelocity^2)
  378.         -- Let B = -horizontalDistance
  379.         -- Let C = dy + (config.gravity * horizontalDistance^2) / (2 * config.projectileVelocity^2)
  380.  
  381.         -- A_quad * tan(theta)^2 + B_quad * tan(theta) + C_quad = 0
  382.         local A_quad = config.gravity * horizontalDistance^2 / (2 * config.projectileVelocity^2)
  383.         local B_quad = -horizontalDistance
  384.         local C_quad = dy + A_quad -- This is the corrected C from the derivation
  385.  
  386.         local discriminant = B_quad^2 - 4 * A_quad * C_quad
  387.  
  388.         if discriminant < 0 then
  389.             print("Error: Target unreachable with current velocity/gravity. Try adjusting target or velocity.")
  390.             return nil
  391.         else
  392.             local tan_theta_1 = (-B_quad + math.sqrt(discriminant)) / (2 * A_quad)
  393.             local tan_theta_2 = (-B_quad - math.sqrt(discriminant)) / (2 * A_quad)
  394.  
  395.             local pitch1 = math.deg(math.atan(tan_theta_1))
  396.             local pitch2 = math.deg(math.atan(tan_theta_2))
  397.  
  398.             local validPitches = {}
  399.             if pitch1 >= config.minPitch and pitch1 <= config.maxPitch then
  400.                 table.insert(validPitches, pitch1)
  401.             end
  402.             if pitch2 >= config.minPitch and pitch2 <= config.maxPitch then
  403.                 table.insert(validPitches, pitch2)
  404.             end
  405.  
  406.             if #validPitches == 0 then
  407.                 print("Error: No valid pitch found within cannon's physical limits for this trajectory.")
  408.                 return nil
  409.             elseif #validPitches == 1 then
  410.                 calculatedPitch = validPitches[1]
  411.             else -- Two valid pitches, prefer the lower (flatter) trajectory
  412.                 calculatedPitch = math.min(validPitches[1], validPitches[2])
  413.             end
  414.         end
  415.     end
  416.  
  417.     -- --- END PHYSICS CALCULATION ---
  418.  
  419.     print(string.format("Calculated Trajectory (Cannon Angles): Yaw: %.2f, Pitch: %.2f", calculatedYaw, calculatedPitch))
  420.  
  421.     -- Return the calculated angles
  422.     return {yaw = calculatedYaw, pitch = calculatedPitch}
  423. end
  424.  
  425.  
  426. --- Main program execution loop.
  427. local function main()
  428.     -- Attempt to initialize peripherals. Exit if critical ones are missing.
  429.     if not initializePeripherals() then
  430.         return -- Program stops if peripherals cannot be wrapped
  431.     end
  432.  
  433.     -- Initialize build state by reading the bundled cable on startup
  434.     isCannonBuilt = (bit.band(redstone.getBundledOutput(config.bundledCableSide), config.buildSignalColor) ~= 0)
  435.  
  436.     print("\n--- Cannon Control Program Initialized ---")
  437.     print("Current tracked Yaw (relative to cannon's default): " .. string.format("%.2f", currentYaw) .. " degrees")
  438.     print("Current tracked Pitch: " .. string.format("%.2f", currentPitch) .. " degrees")
  439.     print("Cannon Built Status: " .. (isCannonBuilt and "BUILT" or "NOT BUILT"))
  440.     print("Type 'help' for available commands.")
  441.  
  442.     -- Main command processing loop
  443.     while true do
  444.         term.write("> ")
  445.         local input = read()
  446.        
  447.         local args = {}
  448.         for s in string.gmatch(input, "%S+") do
  449.             table.insert(args, s)
  450.         end
  451.         local command = args[1] and string.lower(args[1])
  452.  
  453.         if command == "help" then
  454.             print("Commands:")
  455.             print("  location                     - Get current cannon GPS location (with configured offsets).")
  456.             print("  getangles                    - Display the program's current tracked yaw and pitch angles.")
  457.             print("  setyaw <angle>               - Set the absolute yaw angle of the cannon (degrees, -180 to 180).")
  458.             print("  setpitch <angle>             - Set the absolute pitch angle of the cannon (degrees, clamped " .. config.minPitch .. "-" .. config.maxPitch .. ").")
  459.             print("  aim <x> <y> <z>              - Calculate and set yaw/pitch to hit target XYZ coordinates.")
  460.             print("  fire                         - Trigger the cannon to fire (via Red bundled cable).")
  461.             print("  togglebuild                  - Toggle cannon build/un-build state (via Black bundled cable).")
  462.             print("  resetcannon                  - Perform a full reset sequence (un-build -> build) which aligns cannon to default.")
  463.             print("  exit                         - Exit the program.")
  464.         elseif command == "location" then
  465.             getCannonLocation()
  466.         elseif command == "getangles" then
  467.             print(string.format("Current Program-Tracked Angles: Yaw: %.2f (relative to default), Pitch: %.2f", currentYaw, currentPitch))
  468.         elseif command == "setyaw" then
  469.             local angle = tonumber(args[2])
  470.             if angle ~= nil then
  471.                 setYaw(angle)
  472.             else
  473.                 print("Usage: setyaw <angle> (angle must be a number)")
  474.             end
  475.         elseif command == "setpitch" then
  476.             local angle = tonumber(args[2])
  477.             if angle ~= nil then
  478.                 setPitch(angle)
  479.             else
  480.                 print("Usage: setpitch <angle> (angle must be a number)")
  481.             end
  482.         elseif command == "aim" then
  483.             local targetX = tonumber(args[2])
  484.             local targetY = tonumber(args[3])
  485.             local targetZ = tonumber(args[4])
  486.             if targetX and targetY and targetZ then
  487.                 print(string.format("Attempting to aim at target: X:%d Y:%d Z:%d", targetX, targetY, targetZ))
  488.                 local angles = calculateTrajectory(targetX, targetY, targetZ)
  489.                 if angles then
  490.                     if setYaw(angles.yaw) and setPitch(angles.pitch) then
  491.                         print("Cannon aimed successfully!")
  492.                     else
  493.                         print("Failed to set cannon angles.")
  494.                     end
  495.                 else
  496.                     print("Could not calculate trajectory.")
  497.                 end
  498.             else
  499.                 print("Usage: aim <x> <y> <z> (coordinates must be numbers)")
  500.             end
  501.         elseif command == "fire" then
  502.             fireCannon()
  503.         elseif command == "togglebuild" then
  504.             toggleBuildState()
  505.         elseif command == "resetcannon" then
  506.             resetCannon()
  507.         elseif command == "exit" then
  508.             print("Exiting program. Goodbye!")
  509.             break
  510.         else
  511.             print("Unknown command. Type 'help' for available commands.")
  512.         end
  513.         print("")
  514.     end
  515. end
  516.  
  517. -- Run the main program when the script is executed
  518. main()
  519.  
Advertisement
Add Comment
Please, Sign In to add comment