Загрузка данных


-- cannon.lua for CC:Tweaked + CC:CBC
-- v6 — 3D closest-approach tracking (fixes close-range)
--
-- PHYSICS (CBC source):
--   Gravity: 0.05 | Drag: 0.99 multiplicative | Half-step
--
-- CONVENTIONS:
--   Yaw: atan2(dx, -dz) → 0° = -Z, 90° = +X
--   Pitch: 0° = up, 90° = horizontal (sent directly)
--   Mortar: 0.5-44° from vertical

local peripheral = peripheral
local rednet = rednet
local term = term
local io = io
local os = os
local math = math

-- ============ CONFIG ============
local BARREL_LEN = 5
local VELOCITY   = 8
local G          = 0.05
local DRAG       = 0.99
local MAX_TICKS  = 2000
local HIT_RADIUS = 15    -- max 3D distance to count as "hit"

-- ============ FIND MOUNT ============
local mount = nil
for _, name in ipairs(peripheral.getNames()) do
  if peripheral.getType(name) == "cannon_mount" then
    mount = peripheral.wrap(name)
    break
  end
end

if not mount then
  print("ERROR: Cannon Mount not found!")
  for _, name in ipairs(peripheral.getNames()) do
    print("  " .. name .. " = " .. peripheral.getType(name))
  end
  return
end

mount.setComputerControl(true)
print("Cannon Mount connected.")

-- ============ SIMULATE ============
-- Tracks minimum 3D distance to target across ENTIRE flight.
-- No XZ window — just pure closest approach in 3D.
local function simulate(x0, y0, z0, xt, yt, zt, barrelLen, vel, pitchV, dirX, dirZ)
  local pitchRad = math.rad(pitchV)

  local barrelH = barrelLen * math.sin(pitchRad)
  local barrelV = barrelLen * math.cos(pitchRad)
  local px = x0 + barrelH * dirX
  local py = y0 + barrelV
  local pz = z0 + barrelH * dirZ

  local vh = vel * math.sin(pitchRad)
  local vy = vel * math.cos(pitchRad)
  local vx = vh * dirX
  local vz = vh * dirZ

  local minDist = 9999
  local bestT = 0

  for t = 1, MAX_TICKS do
    local ovx, ovy, ovz = vx, vy, vz

    vy = vy - G
    vx = vx * DRAG
    vy = vy * DRAG
    vz = vz * DRAG

    px = px + (ovx + vx) / 2
    py = py + (ovy + vy) / 2
    pz = pz + (ovz + vz) / 2

    -- 3D distance to target
    local dx = px - xt
    local dy = py - yt
    local dz = pz - zt
    local dist = math.sqrt(dx*dx + dy*dy + dz*dz)

    if dist < minDist then
      minDist = dist
      bestT = t
    end

    -- Stop when shell well below target
    if py < yt - 30 then
      return minDist, bestT
    end
  end
  return minDist, bestT
end

-- ============ CALCULATE ============
local function calculate(x0, y0, z0, xt, yt, zt, barrelLen, vel)
  local dx = xt - x0
  local dz = zt - z0

  local apiYaw = math.deg(math.atan2(dx, -dz))
  if apiYaw < 0 then apiYaw = apiYaw + 360 end
  local yawRad = math.rad(apiYaw)
  local dirX = math.sin(yawRad)
  local dirZ = -math.cos(yawRad)

  local bestPitch = 0
  local bestError = 9999
  local bestTime = 0

  -- Phase 1: coarse search
  -- 0.5° steps for 0.5-10° (close range), 1° steps for 10-44°
  for pitch = 1, 88 do
    local pitchVal = pitch * 0.5  -- 0.5° steps
    if pitchVal > 44 then break end
    local err, time = simulate(x0, y0, z0, xt, yt, zt, barrelLen, vel, pitchVal, dirX, dirZ)
    if err < bestError then
      bestError = err
      bestPitch = pitchVal
      bestTime = time
    end
  end

  -- Phase 2: fine search (0.01° steps around best)
  if bestPitch > 0 then
    local lo = (bestPitch - 0.5) * 100
    local hi = (bestPitch + 0.5) * 100
    for p100 = lo, hi do
      local pitch = p100 / 100
      if pitch < 0.5 then pitch = 0.5 end
      if pitch > 44 then pitch = 44 end
      local err, time = simulate(x0, y0, z0, xt, yt, zt, barrelLen, vel, pitch, dirX, dirZ)
      if err < bestError then
        bestError = err
        bestPitch = pitch
        bestTime = time
      end
    end
  end

  return apiYaw, bestPitch, bestError, bestTime
end

-- ============ STATE ============
local lastYaw = nil
local lastPitch = nil

-- ============ MAIN ============
while true do
  term.clear()
  term.setCursorPos(1, 1)
  print("=== CBC Auto-Artillery (v6) ===")
  print("Barrel: " .. BARREL_LEN .. " | Vel: " .. VELOCITY .. " b/t")
  print("Drag: " .. DRAG .. " | Gravity: " .. G)
  print("Pitch: 0=up, 90=horizon | Yaw: 0=-Z, 90=+X")
  print("Search: 0.5-44 deg | 3D closest approach")
  print("")
  print("1 - Fire at target (X Y Z)")
  print("2 - Rednet mode")
  print("f - Fire at last target")
  print("b - Set barrel length")
  print("v - Set velocity")
  print("d - Diagnostics")
  print("t - Test angle (no fire)")
  print("q - Quit")
  io.write("> ")
  local input = io.read()

  if input == "q" then break end

  if input == "b" then
    io.write("Barrel length: ")
    local v = tonumber(io.read())
    if v then BARREL_LEN = v end
    print("Set to " .. BARREL_LEN)
    os.sleep(1)

  elseif input == "v" then
    io.write("Velocity (blocks/tick): ")
    local v = tonumber(io.read())
    if v then VELOCITY = v end
    print("Set to " .. VELOCITY)
    os.sleep(1)

  elseif input == "d" then
    print("\n--- Mount Info ---")
    local info = mount.getInfo()
    if info then
      for k, v in pairs(info) do
        print("  " .. tostring(k) .. " = " .. tostring(v))
      end
    else
      print("  getInfo() = nil")
    end
    print("------------------")
    os.sleep(5)

  elseif input == "t" then
    io.write("Yaw: ")
    local y = tonumber(io.read())
    io.write("Pitch (0=up, 90=horizon): ")
    local p = tonumber(io.read())
    if y and p then
      mount.setTargetAngles(y, p)
      os.sleep(3)
      local info = mount.getInfo()
      print("Current: yaw=" .. tostring(info.yaw) .. " pitch=" .. tostring(info.pitch))
    end
    os.sleep(3)

  elseif input == "f" then
    if lastYaw and lastPitch then
      print("Firing at yaw=" .. lastYaw .. " pitch=" .. lastPitch)
      mount.setTargetAngles(lastYaw, lastPitch)
      os.sleep(3)
      mount.fire(true)
      print("Shot fired!")
    else
      print("No target yet. Use 1 first.")
    end
    os.sleep(3)

  elseif input == "1" then
    io.write("Target X: ") local tx = tonumber(io.read())
    io.write("Target Y: ") local ty = tonumber(io.read())
    io.write("Target Z: ") local tz = tonumber(io.read())

    if tx and ty and tz then
      local info = mount.getInfo()
      local mx, my, mz = info.x, info.y, info.z
      local dist = math.sqrt((tx-mx)^2 + (tz-mz)^2)
      print(string.format("Distance: %.1f blocks (horizontal)", dist))
      print("Calculating...")

      local apiYaw, apiPitch, err, time = calculate(mx, my, mz, tx, ty, tz, BARREL_LEN, VELOCITY)

      if apiPitch > 0 and err < HIT_RADIUS then
        print(string.format("Yaw:   %.2f deg", apiYaw))
        print(string.format("Pitch: %.2f deg (0=up, 90=horizon)", apiPitch))
        print(string.format("3D error: %.2f blocks | Time: %d ticks (%.1fs)", err, time, time / 20))

        lastYaw = apiYaw
        lastPitch = apiPitch

        mount.setTargetAngles(apiYaw, apiPitch)
        print("Waiting for rotation...")
        os.sleep(3)
        mount.fire(true)
        print("Shot fired!")
      else
        print("FAILED: Target out of range.")
        print(string.format("Best 3D error: %.1f blocks", err))
        print("Try: decrease velocity (v) for close targets.")
      end
    else
      print("Invalid coordinates.")
    end
    os.sleep(3)

  elseif input == "2" then
    print("Rednet mode. Listening...")
    rednet.open("back")
    while true do
      local event, id, msg = os.pullEvent("rednet_message")
      if type(msg) == "table" and msg.x and msg.y and msg.z then
        print("Target: " .. msg.x .. ", " .. msg.y .. ", " .. msg.z)
        local info = mount.getInfo()
        local mx, my, mz = info.x, info.y, info.z
        local apiYaw, apiPitch, err, time = calculate(mx, my, mz, msg.x, msg.y, msg.z, BARREL_LEN, VELOCITY)

        if apiPitch > 0 and err < HIT_RADIUS then
          lastYaw = apiYaw
          lastPitch = apiPitch
          mount.setTargetAngles(apiYaw, apiPitch)
          os.sleep(3)
          mount.fire(true)
          rednet.send(id, "OK")
          print("Fired. OK sent.")
        else
          rednet.send(id, "FAIL")
          print("Out of range. FAIL sent.")
        end
      end
    end
  end
end