Advertisement
lencH

Rocket Artillery Lua

Jun 5th, 2016
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.68 KB | None | 0 0
  1. b = besiege
  2.  
  3. ------ VARIABLES TO BE EDITED ------
  4.  
  5. -- key bindings
  6. TARGET_KEY = KeyCode.T
  7. LAUNCH_KEY = KeyCode.L
  8.  
  9. EXPLOSIVE_CHARGE = 2
  10.  
  11. THRUST_TO_VALUE_RATIO = 60 -- average rocket speed per unit of thrust slider value
  12. TRAJECTORY_CURVATURE = 2 -- more for more curved flight
  13.  
  14. -- turret tuning
  15. TURRET_ELEVATION_TUNING = { p=1, i=3, d=2 } -- proportional-integral-derivative tuning ratio
  16. TURRET_HEADING_TUNING = { p=2, i=0, d=4 }
  17. TURRET_ELEVATION_OUTPUT_LIMIT = 30 -- maximum degrees per second
  18. TURRET_HEADING_OUTPUT_LIMIT = 90
  19.  
  20. ------ PID CONTROLLER CLASS DEFINITION ------
  21. Controller = {}
  22.  
  23. function Controller:new(p, i, d, max)
  24.     local o = {}
  25.     o.sum = p + i + d
  26.     o.p = p / o.sum
  27.     o.i = i / o.sum
  28.     o.d = d / o.sum
  29.     o.err = 0
  30.     o.int = 0
  31.     o.der = 0
  32.     o.max = max
  33.     o.first_call = true
  34.     o.last_time = b:getScaledTime()
  35.     o.last_current = nil
  36.     setmetatable(o, Controller)
  37.     self.__index = self
  38.     return o
  39. end
  40.  
  41. function Controller:control(current, target)
  42.     local time = b:getScaledTime()
  43.     local output = 0
  44.     self.delta_time = (time - self.last_time) / 1000
  45.     self.last_time = time
  46.     if self.first_call then
  47.         self.first_call = false
  48.     else
  49.         self.err = Mathf.Clamp(Mathf.DeltaAngle(target, current), self.p * self.max * -1, self.p * self.max)
  50.         self.int = Mathf.Clamp(self.int + self.err * self.delta_time, self.i * self.max * -1, self.i * self.max)
  51.         self.der = Mathf.DeltaAngle(self.last_current, current) * self.delta_time
  52.         output = (self.err * self.p) + (self.int * self.i) + (self.der * self.d)
  53.     end
  54.     self.last_current = current
  55.     output = Mathf.Clamp(output, self.max * -1, self.max) / self.max
  56.     return output
  57. end
  58.  
  59.  
  60. ------ TURRET CLASS DEFINITION ------
  61. Turret = {}
  62.  
  63. function Turret:new(o)
  64.     local o = o or {}
  65.     o.remaining_rockets = table.getn(ROCKETS)
  66.     o.next_rocket = ROCKETS[o.remaining_rockets]
  67.     o.elevation_controller = Controller:new(
  68.         TURRET_ELEVATION_TUNING.p,
  69.         TURRET_ELEVATION_TUNING.i,
  70.         TURRET_ELEVATION_TUNING.d,
  71.         TURRET_ELEVATION_OUTPUT_LIMIT)
  72.     o.heading_controller = Controller:new(
  73.         TURRET_HEADING_TUNING.p,
  74.         TURRET_HEADING_TUNING.i,
  75.         TURRET_HEADING_TUNING.d,
  76.         TURRET_HEADING_OUTPUT_LIMIT)
  77.     setmetatable(o, Turret)
  78.     self.__index = self
  79.     return o
  80. end
  81.  
  82. function Turret:update()
  83.     local bottom_wheel_speed = 0
  84.     local upper_wheel_speed = 0
  85.  
  86.     -- guide rockets
  87.     for i, rocket in ipairs(ROCKETS) do
  88.         if not rocket.destroyed then
  89.             rocket:update()
  90.             rocket:guide()
  91.         end
  92.     end
  93.  
  94.     if self.next_rocket == nil then
  95.         return
  96.     end
  97.  
  98.     if self.target_set and self.remaining_rockets > 0 then
  99.         -- calculate launch angles
  100.         self.next_rocket:prepare_launch()
  101.  
  102.         -- debug
  103.         b:watch("Launch elevation:", self.next_rocket.launch_elevation)
  104.         b:watch("Rocket elevation:", self.next_rocket.elevation)
  105.         b:watch("Launch heading:", self.next_rocket.launch_heading)
  106.         b:watch("Rocket heading:", self.next_rocket.heading)
  107.  
  108.         -- call PID controllers
  109.         upper_wheel_speed = self.elevation_controller:control(self.next_rocket.elevation, self.next_rocket.launch_elevation)
  110.         bottom_wheel_speed = self.heading_controller:control(self.next_rocket.heading, self.next_rocket.launch_heading)
  111.  
  112.         -- check if ready to fire
  113.         local required_elevation_accuracy = Mathf.Clamp(1 / (Mathf.Pow(self.next_rocket.launch_distance / 100, 2)), 0.5, 1)
  114.         local required_heading_accuracy = Mathf.Clamp(1 / (Mathf.Pow(self.next_rocket.launch_distance / 100, 2)), 0.5, 1)
  115.         local elevation_error = Mathf.Abs(Mathf.DeltaAngle(self.next_rocket.elevation, self.next_rocket.launch_elevation))
  116.         local heading_error = Mathf.Abs(Mathf.DeltaAngle(self.next_rocket.heading, self.next_rocket.launch_heading))
  117.         local accurate = elevation_error < required_elevation_accuracy and heading_error < required_heading_accuracy
  118.         if accurate and not self.next_rocket.armed then
  119.             self.next_rocket:arm(true)
  120.             mark:setColor(Color(0, 1, 0))
  121.         end
  122.         if not accurate and self.next_rocket.armed then
  123.             self.next_rocket:arm(false)
  124.             mark:setColor(Color(1, 0, 0))
  125.         end
  126.     end
  127.     BOTTOM_WHEEL:setSliderValue("SPEED", bottom_wheel_speed)
  128.     LEFT_WHEEL:setSliderValue("SPEED", upper_wheel_speed)
  129.     RIGHT_WHEEL:setSliderValue("SPEED", upper_wheel_speed)
  130. end
  131.  
  132. function Turret:set_target(target)
  133.     self.next_rocket:set_target(target)
  134.     self.target = target
  135.     self.target_set = true
  136. end
  137.  
  138. function Turret:launch_rocket()
  139.     if self.remaining_rockets > 0 and self.next_rocket.armed then
  140.         self.next_rocket:launch()
  141.         self.remaining_rockets = self.remaining_rockets - 1
  142.         if self.remaining_rockets > 0 then
  143.             self.next_rocket = ROCKETS[self.remaining_rockets]
  144.             self.next_rocket:set_target(self.target)
  145.         else
  146.             self.next_rocket = nil
  147.             self.target_set = false;
  148.         end
  149.     end
  150. end
  151.  
  152.  
  153. ------ ROCKET CLASS DEFINITION ------
  154. Rocket = {}
  155.  
  156. function Rocket:new(id, grabber_id)
  157.     local o = {}
  158.     o.rocket = b:getBlock(id)
  159.     o.grabber = b:getBlock(grabber_id)
  160.     o.destroyed = false
  161.     setmetatable(o, Rocket)
  162.     self.__index = self
  163.     return o
  164. end
  165.  
  166. -- to be called when new target is set
  167. function Rocket:set_target(target)
  168.     self.target = target
  169.     self.target_set = true
  170. end
  171.  
  172. -- not to be called from outside of class
  173. function Rocket:update()
  174.     if self.destroyed then
  175.         return
  176.     end
  177.     self.position = self.rocket:getPosition()
  178.     self.velocity = self.rocket:getVelocity()
  179.     self.rotation = self.rocket:getEulerAngles()
  180.     self.angular = self.rocket:getAngularVelocity()
  181.  
  182.     local up = self.rocket:getUp()
  183.     local h = Vector3(up.x, 0, up.z).normalized
  184.     self.elevation = Mathf.Asin(up.y) * Mathf.Rad2Deg
  185.     self.heading = Mathf.Atan2(h.z, h.x) * Mathf.Rad2Deg
  186.     self.heading = mod(self.heading + 360, 360);
  187. end
  188.  
  189. -- to be called every frame when targeting
  190. function Rocket:prepare_launch()
  191.     if not self.target_set then
  192.         return
  193.     end
  194.    
  195.     local target_relative_position = self.target - self.position
  196.     local x = Vector3(target_relative_position.x, 0, target_relative_position.z).magnitude
  197.     local y = - target_relative_position.y
  198.     local g = 10 * TRAJECTORY_CURVATURE
  199.     local v = 45
  200.     while v*v*v*v - g*(g*x*x - 2*y*v*v) < 0 do v = v + 15 end
  201.  
  202.     self.thrust = (v / THRUST_TO_VALUE_RATIO) * 3
  203.     self.rocket:setSliderValue("THRUST", self.thrust)
  204.  
  205.     self.launch_distance = (self.target - self.position).magnitude
  206.     self.launch_elevation = Mathf.Atan((v*v - Mathf.Sqrt(v*v*v*v - g*(g*x*x - 2*y*v*v))) / (g*x)) * Mathf.Rad2Deg
  207.     self.launch_heading = Math.Atan2(target_relative_position.z, target_relative_position.x) * Mathf.Rad2Deg
  208.     self.launch_heading = mod(self.launch_heading + 360, 360)
  209. end
  210.  
  211. -- to be called once when rocket is ready to launch
  212. function Rocket:arm(armed)
  213.     if not self.target_set then
  214.         return
  215.     end
  216.     self.armed = armed
  217. end
  218.  
  219. -- to be called once when launch key is pressed
  220. function Rocket:launch()
  221.     if not self.armed then
  222.         return
  223.     end
  224.     if self.launched then
  225.         return
  226.     end
  227.     self.rocket:action("LAUNCH")
  228.     self.grabber:action("DETACH")
  229.     self.launch_time = b:getScaledTime()
  230.     self.launch_position = self.position
  231.     self.stage = 1
  232.     self.launched = true
  233.     self.grabber:setToggleMode("AUTO-GRAB", false)
  234. end
  235.  
  236. -- to be called on every frame the rocket needs to be guided
  237. function Rocket:guide()
  238.     if not self.rocket:exists() then
  239.         self.destroyed = true
  240.     end
  241.     if not self.launched or self.destroyed then
  242.         return
  243.     end
  244.  
  245.     -- rocket and target info
  246.     local target_relative_vector = self.target - self.position
  247.     local distance = target_relative_vector.magnitude
  248.     local velocity_angle = Mathf.Asin(self.velocity.normalized.y) * Mathf.Rad2Deg
  249.     local target_angle = Mathf.Asin(target_relative_vector.normalized.y) * Mathf.Rad2Deg
  250.  
  251.     local x = Vector3(target_relative_vector.x, 0, target_relative_vector.z).magnitude
  252.     local y = - target_relative_vector.y
  253.     local g = 10 * TRAJECTORY_CURVATURE
  254.     local v = self.velocity.magnitude
  255.  
  256.     b:watch("Velocity:", v)
  257.     b:watch("Velocity angle:", velocity_angle)
  258.     b:watch("Target angle:", target_angle)
  259.  
  260.     -- first stage (launch)
  261.     if self.stage == 1 then
  262.         if v*v*v*v - g*(g*x*x - 2*y*v*v) > 0 then
  263.             self.stage = 2
  264.         end
  265.     end
  266.  
  267.     -- second stage (guide)
  268.     if self.stage == 2 then
  269.         local trajectory_angle = Mathf.Atan((v*v - Mathf.Sqrt(v*v*v*v - g*(g*x*x - 2*y*v*v))) / (g*x)) * Mathf.Rad2Deg
  270.         local deviation = (trajectory_angle - velocity_angle) / (trajectory_angle - target_angle)
  271.  
  272.         -- thrust adjustment
  273.         self.thrust = v / THRUST_TO_VALUE_RATIO
  274.         self.thrust = Mathf.Clamp(self.thrust + self.thrust * deviation, 0, 5)
  275.  
  276.         -- time to impact
  277.         local relative_velocity = Vector3.Project(self.velocity, target_relative_vector.normalized)
  278.         local tti = distance / relative_velocity.magnitude
  279.  
  280.         -- target proximity check
  281.         if distance < 5 then
  282.             self:destroy()
  283.         else
  284.             self.flight_time = b:getScaledTime() - self.launch_time + tti
  285.         end
  286.  
  287.         -- check for miss
  288.         local travel = self.position - self.launch_position
  289.         if travel.magnitude > self.launch_distance then
  290.             self:destroy()
  291.         end
  292.  
  293.         -- set new values
  294.         self.rocket:setSliderValue("THRUST", self.thrust)
  295.         self.rocket:setSliderValue("FLIGHT DURATION", self.flight_time)
  296.  
  297.         -- debug
  298.         b:watch("Trajectory angle:", trajectory_angle)
  299.         b:watch("Deviation:", deviation)
  300.         b:watch("Thrust:", self.thrust)
  301.         b:watch("TTI:", tti)
  302.     end
  303. end
  304.  
  305. function Rocket:destroy()
  306.     self.flight_time = 0
  307.     self.rocket:setSliderValue("FLIGHT DURATION", self.flight_time)
  308.     self.launched = false
  309.     self.armed = false
  310.     self.target_set = false
  311.     self.destroyed = true
  312. end
  313.  
  314. function mod(a, n)
  315.     return a - Mathf.Floor(a/n)*n
  316. end
  317.  
  318. ------ INITIALIZE BLOCK HANDLERS ------
  319.  
  320. ROCKETS = {
  321.     Rocket:new("ROCKET 1", "GRABBER 1"),
  322.     Rocket:new("ROCKET 2", "GRABBER 2"),
  323.     Rocket:new("ROCKET 3", "GRABBER 3"),
  324.     Rocket:new("ROCKET 4", "GRABBER 4")}
  325. BOTTOM_WHEEL = b:getBlock("918ea244-8279-4118-9f7e-ba252fd34f6f")
  326. LEFT_WHEEL = b:getBlock("1902526e-208e-4aa6-ac57-f197f709a435")
  327. RIGHT_WHEEL = b:getBlock("b3cf6f9c-b993-43c3-81bd-1f6f74924ee8")
  328.  
  329. ------ INITIAL MACHINE SETTINGS ------
  330.  
  331. BOTTOM_WHEEL:setToggleMode("AUTOMATIC", true)
  332. LEFT_WHEEL:setToggleMode("AUTOMATIC", true)
  333. RIGHT_WHEEL:setToggleMode("AUTOMATIC", true)
  334.  
  335. BOTTOM_WHEEL:setSliderValue("SPEED", 0)
  336. LEFT_WHEEL:setSliderValue("SPEED", 0)
  337. RIGHT_WHEEL:setSliderValue("SPEED", 0)
  338.  
  339. for i, r in ipairs(ROCKETS) do
  340.     r.rocket:setSliderValue("EXPLOSIVE CHARGE", EXPLOSIVE_CHARGE)
  341.     r.rocket:setSliderValue("FLIGHT DURATION", 10)
  342.     r.rocket:clearKeys("LAUNCH")
  343.     r.grabber:clearKeys("DETACH")
  344. end
  345.  
  346. ------ GLOBAL VARIABLES ------
  347.  
  348. turret = Turret:new()
  349. mark = nil
  350.  
  351. function onUpdate()
  352.     turret:update()
  353. end
  354.  
  355. function onKeyHeld(key)
  356.  
  357. end
  358.  
  359. function onKeyDown(key)
  360.     if key == TARGET_KEY then
  361.         hit = b:getRaycastHit()
  362.         if mark == nil then
  363.             mark = b:createMark(hit)
  364.             turret:set_target(hit)
  365.         else
  366.             mark:move(hit)
  367.             turret:set_target(hit)
  368.         end
  369.     end
  370.     if key == LAUNCH_KEY then
  371.         turret:launch_rocket()
  372.     end
  373. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement