Advertisement
lencH

Rocket Artillery Python

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