Grumman F-14 Tomcat - With Autopilot! [Shade'd]

#1
The Grumman F-14 Tomcat is an American supersonic, twin-engine, two-seat, variable-sweep wing fighter aircraft.




GIFS:


FEATURES:
  • Razgriz Squadron livery
  • Variable geometry wings
  • Faithfully recreated control surfaces
  • Two full-auto 7.0 power guns
  • Two Air-to-Ground missiles
  • Near-lagless negative water cannon engine
  • STOL capability
  • Block count: 375 (80%-90% timescale recommended)
  • Toggleable landing gear, toggleable wing sweep, heatwave exhaust effect, automatic afterburner flame, automatic contrails and a simple AUTOPILOT - these six features are available by installing mods - see Mod Support below for more info!


CONTROLS:
Steering:
R T Y
F G H - Pitch, Roll and Yaw

C - Toggle Engines
X - Afterburner (held)
Z - Airbrake (held)

M / N - Make the plane even more sexy - I mean, sweep the wings (held)

Utility:
Right Alt / . - Landing Gear
Arrow Keys - Ground Controls
V - Cameras (1st person + crosshair, chase close, chase far, landing/missile)

Weaponry:
1, 2 (NumPad) or U, I - Bomb-tipped rockets
. (NumPad) - Semi-auto cannons
Enter (NumPad) or J - Full-auto cannons


MOD SUPPORT (Read for access to additional features!):

This plane supports three mods:
With these installed, you gain access to additional features:
  • (VT) Exhaust heatwave effect
  • (VT + ACM) Afterburner flame + automatic wingtip contrails
  • (ACM) Landing gear and wing sweep become one-key toggles (Right Alt and M, respectively)
  • (Scripter) Autopilot script that will keep the plane level for you (toggle with keypad 8). Flying with autopilot on will overwrite TGFH keys to instead control the offset of roll and pitch angles - for passing over mountains and stuff)
ACM will prompt you to download a library for custom axes upon loading the machine, this is required in order for ACM-associated features to work!

Please note that none of these mods are required for the plane to work. They simply make it easier to use and add purely cosmetic effects.

MODS USED:
Building Tools by TheGuysYouDespise
Easy Scale by Lench
PBP2 by ITR
NoBounds also by ITR

DEV LOG:
Building hit a stop cause it was unstable af. After like a week I realised I did a dumb and fixed it. Building resumed. Then Shado came by:

GG Shade XD
I'm not a huge fan of the livery tbh. Edgy as hell. But I feel that it'd be weird to post it with a different one than in the video, or go with two different versions.

Also I'm a little sad that the blockcount makes it hard to play around with this fighter at 100% timescale. Would have given it more missiles otherwise. Performance took priority over firepower here.

Oh, and hey, I spent a couple weeks learning python and basically adapted Lench's PID controller to planes, with some additional features. The script is pretty unorganized and amateurish, but has a ton of potential for recording cinematic shots. It helped Shade immensly with recording this video, but each scene had to receive a separate version of the autopilot script adapted specifically to it.

I might make a tutorial for the Scripter mod in the future, and will make the code public and go over adapting it to almost any piston-steered aircraft. If you can't wait, the code is right there, inside the .bsg file, or you can just open the spoiler below. Cheers!

AUTOPILOT SCRIPT:
Code:
def clamp(value, minvalue, maxvalue):
  return max(min(value, maxvalue), minvalue)

class AngularController:
  def __init__(self, p, i, d, max, first_piston_group, second_piston_group, int_dampen, der_dampen):
  sum = float(p + i + d)
  self.p = p / sum
  self.i = i / sum
  self.d = d / sum

  self.err = 0.0
  self.int = 0.0
  self.der = 0.0
  self.max = float(max)
  self.int_dampen = int_dampen
  self.der_dampen = der_dampen

  self.first_piston_group = first_piston_group
  self.second_piston_group = second_piston_group

  self.autopilot_toggle = False
  self.first_call = True
  self.last_time = None
  self.last_current = None

  def control(self, current, target):
  time = Besiege.GetTime()
  output = 0.0
  if Input.GetKeyDown(KeyCode.Keypad8):
  self.autopilot_toggle = not self.autopilot_toggle
  self.err = 0.0
  self.int = 0.0
  self.der = 0.0
  if self.autopilot_toggle:
  if self.first_call:
  self.first_call = False
  else:
  self.delta_time = (time - self.last_time)
  self.err = Mathf.DeltaAngle(current, target) / 90
  self.int = self.int + (self.err * self.delta_time) / 2
  if current > (target - 5) or current < (target + 5):
  self.int /= self.int_dampen
  self.der = ((Mathf.DeltaAngle(current, self.last_current)) / self.delta_time) / 180
  if current > (target - 1) or current < (target + 1):
  self.der /= self.der_dampen
  output = (self.err * self.p) + (self.int * self.i) + (self.der * self.d)

  self.last_current = current
  self.last_time = time
  output = clamp(output, self.max * -1, self.max) / self.max
  Besiege.Watch('Output', output)

  if output > 0:
  for block in self.first_piston_group:
  Besiege.GetBlock('PISTON ' + str(block)).SetPosition(output)
  else:
  for block in self.first_piston_group:
  Besiege.GetBlock('PISTON ' + str(block)).SetPosition(0)

  if output < 0:
  for block in self.second_piston_group:
  Besiege.GetBlock('PISTON ' + str(block)).SetPosition(-output)
  else:
  for block in self.second_piston_group:
  Besiege.GetBlock('PISTON ' + str(block)).SetPosition(0)
  else:
  pass


first_piston_group_roll = [2]
second_piston_group_roll = [1]
sbroll = Besiege.GetBlock('BALLAST 24')

first_piston_group_pitch = [8, 10]
second_piston_group_pitch = [9, 7]
sbpitch = Besiege.GetBlock('BALLAST 24')

roll_controller = AngularController(0.5, 0.2, 0.4, 0.4, first_piston_group_roll, second_piston_group_roll, 2, 2)
pitch_controller = AngularController(5, 3.5, 1.5, 0.8, second_piston_group_pitch, first_piston_group_pitch, 1.05, 1.5)


def Update():
  target_pitch = 14
  target_roll = 270
  if Input.GetKey(KeyCode.G):
  target_pitch += 20
  if Input.GetKey(KeyCode.T):
  target_pitch -= 20

  if Input.GetKey(KeyCode.F):
  target_roll -= 50
  if Input.GetKey(KeyCode.H):
  target_roll += 50

  current_roll = sbroll.Rotation.z - 90
  if current_roll < 0 and current_roll > -90:
  current_roll = 360 + current_roll
  Besiege.Watch('Current Roll', current_roll)

  current_pitch = sbpitch.Rotation.x
  Besiege.Watch('Current Pitch', current_pitch)

  roll_autolevel = roll_controller.control(current_roll, target_roll)
  pitch_autolevel = pitch_controller.control(current_pitch, target_pitch)

BONUS:
I had some fun in GIMP and gave the plane a face.


DOWNLOADS:

Steam Workshop page
 

Attachments

Last edited:
Top