Well...
There are a number of limitations if you try to attach the camera to the object. The main problem is the script relies on the camera height being kept almost constant from one frame to another. If you tie the height of the camera to the height of the plane then it will falsely trigger the weapon selection and firing. Fortunately there is a solution which will work for single player (and multiplayer as long as the pilot is AI), since the AI pilots always fly at or very near their set FlyInHeight (default 100) then the script can simply glue to their X and Y positions while following their ideal AGL flight path.
Usage for this "glued" version is [plane,plane width, plane flyInHeight] exec "ac130glued.sqs".
So for an example mission set an AI cessna named plane flying in a loose circle anti-clockwise around a village (six cycled waypoints should do). Then add a radio trigger with [plane,3,100] exec "ac130glued.sqs".
For larger planes you'll need to adjust the width setting so that the camera remains "outside" of the aircraft.
Code:
; AC-130 Glue script
; move forward/move backward/move left/move right pans camera
; move up changes weapon (105mm M102, 40mm Bofors L/60, 25mm GAU-12)
; move down fires weapon
; plane to tie camera to
_plane = _this select 0
; distance from centre of plane to place camera
_planeWidth = _this select 1
; flyinheight of the plane
_planeFlyInHeight = _this select 2
; sea level reference for manual ASL calculation
_sealevelref = "logic" camcreate [0,0,0]
; selected weapon number
_currentWeapon = 0
; on screen weapon names
_weaponNames = ["105mm","40mm","25mm"]
; sound class names defined by descrition.ext or config cfgSounds
_sounds = ["m102","bofors","gau12"]
; cfgammo class names
_weaponClasses = ["shell125","shell73","Bullet4x23"]
; FOVs
_zoom = [0.4,0.15,0.05]
; Pan/tilt sensitivity to compensate for change in FOV
_panGain = [60,30,15]
; muzzle velocities in m/s
_MuzzleVelocity = [737,881,1040]
; time between bursts/seconds
_BurstReloadTime = [5,4,3]
; timers for weapon burst cooldown
_BurstReload = [0,0,0]
; rounds per burst
_burst = [1,4,20]
; rounds left in burst
_roundsLeft = [1,4,20]
; time between shots in a burst
_ReloadTime = [0,0.5,0.15]
; timers for reloading
_reload = [0,0,0]
; dead time timer for switching weapon
_switchDeadTime = 0
; on screen weapon status
_reloading = " "
; Laptop opening animation defined by description.ext
;cutrsc["RscLaptopOpen","PLAIN"]
; remember if the player had NVGs
_removeGogs = false
?((Daytime < 17) and (Daytime > 7)) : goto "start"
; night time, force night vision by giving player goggles
?!(player hasweapon "NVGoggles"): _removeGogs = true;
player removeweapon "NVGoggles"
player addweapon "NVGoggles"
player action["NVGoggles"]
#start
~0.5
; distance to initial target
_r = 500
; get plane position and direction
_direction = getdir _plane
_campos = [(getpos _plane select 0) + (_planeWidth * sin (_direction - 90)),(getpos _plane select 1) + (_planeWidth * cos (_direction - 90)),_planeFlyInHeight]
; set target position
_pos = [(_campos select 0) + (_r*sin (_direction - 90)),(_campos select 1)+(_r*cos (_direction-90)),0]
; target marker for manual ASL calculation
_target = "logic" camcreate _pos
; setup camera
_camera = "camera" camCreate _campos
_camera cameraEffect ["internal","back"]
_camera camSetTarget _pos
_camera camSetFOV 0.7
_camera camCommit 0
@camCommitted _camera
; remove laptop cutrsc
cutrsc["Default","PLAIN"]
_campos = GetPos _camera
; time on station
_timeout = _time + 40
; camera target coordinates
_tx = _pos select 0
_ty = _pos select 1
; frame counter for on screen text
_i = 0
#orbit
; if weapon burst cooldown time over, reset burst counter and update on screen status
?((_roundsLeft select _currentWeapon == 0) and (_BurstReload select _currentWeapon < _time)): _roundsLeft set[_currentWeapon, _burst select _currentWeapon]; _reloading = " "
; calculate new positions
_direction = getdir _plane
_campos = [(getpos _plane select 0) + (_planeWidth * sin (_direction - 90)),(getpos _plane select 1) + (_planeWidth * cos (_direction - 90)),_planeFlyInHeight]
_pos = [(_campos select 0) + (_r*sin (_direction - 90)),(_campos select 1)+(_r*cos (_direction-90)),0]
; update camera position
_camera camSetPos _campos
; update camera target
_camera camSetTarget [_tx,_ty,0]
; update FOV
_camera camsetFOV (_zoom select _currentWeapon)
_camera camcommit 0
@camCommitted _camera
; switch to manual
_camera camCommand "Manual on"
; wait for next frame
_campos = getpos _camera
~0.02
; increment frame counter
_i = _i + 1
; display on screen text every ten frames
?(_i == 10): _i = 0; titletext[Format["\n%1 %2 %3\nAGL: %4 %5", _WeaponNames select _currentWeapon,_reloading,(_timeout - _time),getpos plane select 2],"PLAIN DOWN",0.05]
; detect if V pressed (camera killed), if so, restart
?(_camera != _camera): goto "restart"
; switch manual mode off
_camera camCommand "Manual off"
; check for changes in camera position due to user input
_cx = (_panGain select _currentWeapon)*((getpos _camera select 0)-(_campos select 0))
_cy = (_panGain select _currentWeapon)*((getpos _camera select 1)-(_campos select 1))
_cz = ((getpos _camera select 2)-(_campos select 2))
; calculate distance of camera target to orbit datum
_rx = (_pos select 0)-(_tx+_cx)
_ry = (_pos select 1)-(_ty+_cy)
_distance = sqrt((_rx^2)+(_ry^2))
; limit pan and tilt of camera
?(_distance < 300): _tx = _tx + _cx; _ty = _ty + _cy
?(_distance > 350): titletext["","BLACK IN",0.1]; _tx = _pos select 0; _ty = _pos select 1
; check for increase in camera AGL due to "move up" pressed
?((_cz > 0.05) and (_switchDeadTime < _time) and (_cx == 0) and (_cy == 0)): goto "switchWeapon"
; check for decrease in camera AGL due to "move down" pressed
?((_cz < -0.1) and (_cx == 0) and (_cy == 0)): goto "fire"
?((_timeout > _time) and (alive player) and (alive plane)) : goto "orbit"
#end
; if player didn't have goggles remove them
?(_removeGogs) : player removeweapon "NVGoggles"
_camera cameraEffect ["Terminate","back"]
_camera camcommit 0
@camcommitted _camera
camDestroy _camera
; Laptop closing animation defined in description.ext, requires custom .rtm file
;cutrsc["RscLaptopClose","PLAIN"]
~0.5
; destroy local objects
cutrsc["Default","PLAIN"]
camdestroy _sealevelref
camdestroy _target
exit
#switchWeapon
_currentWeapon = _currentWeapon + 1
; clip index
?(_currentWeapon == 3): _currentWeapon = 0
; set deadtime timer
_switchDeadTime = _time + 0.5
; update on screen status
_reloading = " "
?((_roundsLeft select _currentWeapon == 0) or (_time < _reload select _currentWeapon)): _reloading = "RELOADING"
goto "orbit"
#fire
; check if any rounds are left in the gun
?((_roundsLeft select _currentWeapon == 0) or (_time < _reload select _currentWeapon)): goto "orbit"
; update target position
_target setpos [_tx,_ty,0]
_reloading = " "
; set the reload timer (time between shots)
_reload set[_currentWeapon, _time + (_ReloadTime select _currentWeapon)]
; decrement rounds left in burst
_roundsLeft set[_currentWeapon,(_roundsLeft select _currentWeapon) - 1]
; calculate ASL (manually for v1.96 compatiblility)
_distance = _camera distance _sealevelref
_cameraHeight = sqrt((_distance^2) - (((_campos select 0)^2) + ((_campos select 1)^2)))
_distance = _target distance _sealevelref
_targetHeight = sqrt((_distance^2) - ((_tx^2) + (_ty^2)))
; calculate distance to target
_rx = (_campos select 0)-_tx
_ry = (_campos select 1)-_ty
_distance = (sqrt((_rx^2)+(_ry^2)))
; calculate polar angle of target to camera
_gunPhi = ((_cameraHeight - _targetHeight) atan2 _distance)
; calculate azimuth of target to camera
_gunTheta = _rx atan2 _ry
; calculate velocity vector
_vx = (_MuzzleVelocity select _currentWeapon)*(cos _gunPhi)*-(sin _gunTheta)
_vy = (_MuzzleVelocity select _currentWeapon)*(cos _gunPhi)*-(cos _gunTheta)
_vz = (_MuzzleVelocity select _currentWeapon)*-(sin _gunPhi)
; spawn round
_round = (_WeaponClasses select _currentWeapon) createvehicle _campos
; "fire" the round
_round setdir _gunTheta
_round setvelocity [_vx,_vy,_vz]
; play the weapon sound
;playsound (_sounds select _currentWeapon)
; check burst status
?(_roundsLeft select _currentWeapon != 0): goto "orbit"
; burst completed update on screen status and set cooldown timer
_reloading = "RELOADING"
_BurstReload set[_currentWeapon, _time + (_BurstReloadTime select _currentWeapon)]
goto "orbit"
#restart
titletext["","BLACK IN",10];
~0.1
titletext["","BLACK IN",0.1];
?(alive _camera): camDestroy _camera
_camera = "camera" camCreate _campos
_camera cameraEffect ["internal","back"]
_camera camSetTarget [_tx,_ty,0]
_camera camsetFOV (_zoom select _currentWeapon)
_camera camcommit 0
@camcommitted _camera
goto "orbit"
You can see how closely the plane sticks to it's flyinheight from the AGL display. A human pilot would need to do the same for this version of the script to work on a player piloted aircraft. I wrote this version in about two hours so consider it a fudge. There are many ways it could be enhanced, for example; using the aircraft's bank angle to set the target position and running the plane's height through a low pass filter, then use the filtered version of the height to glue the camera to the plane.
Addendum: I forgot to mention about muzzle velocities. Since the script does not compensate for bullet drop, using a (realistic) muzzle velocity of 472ms
-1 (as done in the version shown in the first post) you will find the howitzer requires significant adjustment when attacking targets. To eliminate this, increase it to something like 700ms
-1. Also, the 40mm really needs a custom cfgAmmo entry, since the 73mm heat round is over-powered and doesn't provide tracers (I think the real gun uses 40mm HEI-T).