Requirements:

I'm using ArmA v 1.08, but I the script should work fine with older versions as well.
Also - faster cpu = script will work faster'n'better.

 

 

The mission:

The mission is simple. You just lay low on a hill and let the shells rain down on the troops on the beach. Click on the map where you want the shell to hit and watch the beauty...

 

 

The story behind (and the math):

I always wanted to make an artillery script in OFP, but the limitations in its engine didn't allow me to do that (pretty much because there was no command equivalent to getPosASL). But when ArmA came I started to work on one. I soon noticed that ArmA had a different physics engine where drag plays in. "Fun!" I thought to myself - that makes it more of a challenge!

The real-world drag equation looks like this (jacked from Wikipedia):

where...
Fd is the force of drag,
ρ is the density of the fluid (Note that for the Earth's atmosphere, the density can be found using the barometric formula. (Air is 1.293 kg/m3 at 0 °C and 1 atmosphere),
v is the velocity of the object relative to the fluid,
A is the reference area, and
Cd is the drag coefficient (a dimensionless constant, e.g. 0.25 to 0.45 for a car).

I figured that it would be too complicated for the ArmA-engine to calculate the reference area of the shell, to use different fluid densities depending on the atmospheric pressure etc. I pretty much assumed that the makers of ArmA had chosen to use a drag formula looking something like this: Fd = v² * k (where k is just one big constant representing all the constant elements).

Since F = m * a (F = force, m = mass of object - the shell in this case, a = acceleration) we can set up a formula like this:
m * a = v² * k

So the formula for the (de-)acceleration of the shell is:
a = (v² * k) / m

Since the mass of the shell is constant we might as well let m be a part of k too. Sic:
a = v² * k

Now the only part left was to find out what k was. After some testing using a script, some trial and error and some wild guessing I have concluded that k is equivalent to about (-500 / 1000000).
When I had the physics model ready is was just to start writing the damn script.

 

 

The script:

Now remember that this is just a script and it has its limitations and the physics model I've used isn't perfect, but it works pretty well.

One flaw is that the maximum range is about 5 km - this has to do with the drag-equation in the game which pretty much makes the shell come to a complete stop after a flight of about 5 km and the fact that the shell ceases to exist/"disappears" if it has been in flight for too long.
Another silly thing is that I have to use ¤%#%¤ tanks for beacons since the artillery pieces won't aim at invisible targets. This is the scripts biggest flaw if you ask me, but I honestly do not know what to do about that problem.

The script pretty much works like this:
- You click on the map
- The click is caught by a sensor which executes fired3.sqs
- fired3.sqs checks which artillery pieces that are out there and those who exist executes fired2.sqs which starts the aiming process
- When the aiming is done and the shell is fired fired.sqs is executed by an eventhandler. fired.sqs follows the shell and puts an object (named shell2 for the artillery piece named art2 and so on) where the shell hits, this will allows fired2.sqs to correct the aim by comparing the location of that very object to where the shell should have landed
- Corrections in the aiming is made (if more than one shot is allowed) and the whole process starts over again until the shell has landed within a certain radius (50 m is the default value) or until no more shells are allowed to be fired (two shells is the default).

 

 

The code:

Now, let's see how the script works and what every line of code does...(will hopefully help you to understand how I've thought and it will also hopefully help you to modify/improve the script and make it better). Please do notice that I have not commented any piece of code that begins with ";" since those lines are experimental. Anyways - let's begin.

 

fired.sqs (this part of the script is called by the eventhandler and allows us to follow the shell once it's been fired):

_what_gun = _this select 0 Gives us what gun it is that's fired
_shell = nearestObject [vehicle(_what_gun), _this select 4] Gives us the shell
_what_name = vehicleVarName _what_gun Gives ut the name of the gun that's fired
_Type= typeOf _shell Gives us the type of the shell (this part and some other lines of code in this script is jacked from an old follow-the-bullet-OFP-script made by someone whose name I've forgotten by now - but thanks whoever you are/were)

? _what_name == "art2" : _what_marker = "shell2"
? _what_name == "art2" : _what_shell = shell2

? _what_name == "art3" : _what_marker = "shell3"
? _what_name == "art3" : _what_shell = shell3

? _what_name == "art4" : _what_marker = "shell4"
? _what_name == "art4" : _what_shell = shell4

? _what_name == "art5" : _what_marker = "shell5"
? _what_name == "art5" : _what_shell = shell5

? _what_name == "art6" : _what_marker = "shell6"
? _what_name == "art6" : _what_shell = shell6

? _what_name == "art7" : _what_marker = "shell7"
? _what_name == "art7" : _what_shell = shell7

? _what_name == "art8" : _what_marker = "shell8"
? _what_name == "art8" : _what_shell = shell8

? _what_name == "art9" : _what_marker = "shell9"
? _what_name == "art9" : _what_shell = shell9

? _what_name == "art10" : _what_marker = "shell10"
? _what_name == "art10" : _what_shell = shell10

? _what_name == "art11" : _what_marker = "shell11"
? _what_name == "art11" : _what_shell = shell11

? _what_name == "art12" : _what_marker = "shell12"
? _what_name == "art12" : _what_shell = shell12

? _what_name == "art13" : _what_marker = "shell13"
? _what_name == "art13" : _what_shell = shell13

? _what_name == "art14" : _what_marker = "shell14"
? _what_name == "art14" : _what_shell = shell14

? _what_name == "art15" : _what_marker = "shell15"
? _what_name == "art15" : _what_shell = shell15

? _what_name == "art16" : _what_marker = "shell16"
? _what_name == "art16" : _what_shell = shell16

? _what_name == "art17" : _what_marker = "shell17"
? _what_name == "art17" : _what_shell = shell17

? _what_name == "art18" : _what_marker = "shell18"
? _what_name == "art18" : _what_shell = shell18

? _what_name == "art19" : _what_marker = "shell19"
? _what_name == "art19" : _what_shell = shell19

? _what_name == "art20" : _what_marker = "shell20"
? _what_name == "art20" : _what_shell = shell20

If the name of the gun is this, then the name of the shell and its marker is that
#loop2 Defines a point in the script
? _Type != typeOf _shell : _what_shell setPosASL (_what_shell_pos) ; exit If the shell ceases to exist (aka is destroyed) we shall set an object representing the shell at the shell's last known position (will help us to correct the aim if the shell doesn't hit within allowed hit-radius - see fired2.sqs) and then exit the script
_what_shell_pos = getPosASL _shell Gives us the current position of the shell
_marker_array2 = [(_what_shell_pos select 0), (_what_shell_pos select 1)]
_what_marker setMarkerPos _marker_array2
Set the shell-marker at the shell's position on the map
goto "loop2" Sends the script back to a recent step

 

 

fired2.sqs (this is the aiming-, firing- and correction-part of the script):

_k = -500 / 1000000 Defines the drag-constant (more of that later)
_g = -9.81 Defines gravitation
_t_inc = 0.111 Defines the time-step (in seconds), how often during the simulation we're going to calculate the drag
_vel=1083 Defines the velocity of the shell when it leaves the gun
_number_of_attempts = 0 Set how many attempts we've made so far when the scripts starts
_what_gun = _this select 0 Tells us which gun we're dealing with
_what_beacon = _this select 1 Gives us the name of that gun's beacon/target used to aim the gun at the real target
_pos_target = _this select 2 Gives us the position of the target
_v = _this select 3 Default angle
_what_shell = _this select 4 What's the name of the marker on the map that we use to calculate how close the fired shell's hit the target
_cpe_limit = 150 Defines how far from the target the shell has to hit before we simply increase the angle by 0.5 degrees
_fine_cpe_limit = 50 Defines within what radius of the target that the shell has to hit to consider it a strike (where the gun doesn't try to correct its aim - hence the fire mission's complete)
_pos_gun = getPosASL _what_gun Gives us the position of our gun
_pos_gun_x = _pos_gun select 0 Gives us the x-coordinate of our gun (east/west)
_pos_gun_y = _pos_gun select 1 Gives us the y-coordinate of our gun (north/south)
_pos_gun_z = (_pos_gun select 2) + 1 Gives us the z-coordinate of our gun (sea level), the +1 is because the barrel is like 1 meter over the ground
_pos_target_x = _pos_target select 0 Gives us the x-coordinate of our target (east/west)
_pos_target_y = _pos_target select 1 Gives us the y-coordinate of our target (north/south)
_pos_target_z = _pos_target select 2 Gives us the z-coordinate of our target (sea level)
_tz = _pos_target_z Sets _tz to the target's z-value
_tx = abs(_pos_gun_x - _pos_target_x) What's the distance east-west between our gun and our target?
_ty = abs(_pos_gun_y - _pos_target_y) What's the distance north-south between our gun and our target?
_dist = sqrt((_tx^2) + (_ty^2)) What's the total distance (not counting elevation) between our gun and our target?
_what_gun setCombatMode "BLUE" Makes sure that our gun doesn't fire at enemies if it sees any
? _v != 90 : goto "done_lower" As long as the angle of the gun isn't 90 degrees (pointing upwards) the script shall continue
_text = format ["_shift: %1", _shift]
_what_gun sideChat _text
Let's us know whether the shift-button was pressed or not.

? !_shift : _v = 0; _v_add = 0.1

If shift isn't pressed, start at angle 0 and count upwards with +0.1 degrees.
? _shift : _v = 90; _v_add = -0.1 If shift is pressed, start at angle 90 and count downwards with -0.1 degrees.
#calc_1_lower Defines a point in the script
_v = _v + _v_add Increase the angle by either +0.1 or -0.1
_vel_x_i = cos(_v) * _vel Gives us the initial horizontal velocity of the shell as it leaves the gun
_vel_y_i = sin(_v) * _vel Gives us the initial vertical velocity of the shell as it leaves the gun
_vel_x = _vel_x_i Sets the current horizontal velocity of the shell to the initial horizontal velocity of the shell
_vel_y = _vel_y_i Sets the current vertical velocity of the shell to the initial vertical velocity of the shell
_t = 0 Sets the initial time to 0
_d_x = 0 Sets the initial horizontal position of the shell to 0
_p_y = _pos_gun_z Sets the initial vertical position of the shell to 0
_text = format ["v: %1", _v]
_what_gun sideChat _text
The gun lets us know what the current angle is that it's using to calculate the shell's trajectory
#calc_2_lower Defines a point in the script
_t = _t + _t_inc Increase the time with the time-step
_v_vel = atan(_vel_y / _vel_x) Gives us the angle of the velocity's vector
_vel_tot = sqrt((_vel_x^2) + (_vel_y^2)) Gives us the total velocity (horizontal velocity + vertical velocity)
_vel_tot = _vel_tot + (_k * (_vel_tot^2) * _t_inc) Using the drag-formula we alter the total velocity of the shell
_vel_x = cos(_v_vel) * _vel_tot Gives us the new horizontal velocity
_vel_y = sin(_v_vel) * _vel_tot Gives us the new vertical velocity
_vel_y = _vel_y + _g * _t_inc Using gravitation to alter the vertical velocity of the shell
_d_x = _d_x + (_vel_x * _t_inc) Calculate how far the shell travels horizontally during our time-step
_p_y = _p_y + (_vel_y * _t_inc) Calculate how far the shell travels vertically during our time-step
? _p_y <= 0 : goto "calc_1_lower" If the shell reaches sea-level (zero elevation) then we should increase the angle and do the calculation all over again
? abs(_d_x - _dist) <= _cpe_limit && abs(_p_y - _tz) <= 10 : goto "done_lower" If the shell's within the horizontal hit-radius of the target and the difference in elevation is no more than 10 meters we are ok with it and goes to the next step (firing the gun)
? abs(_d_x - _dist) <= _cpe_limit && abs(_p_y - _tz) > 10 : goto "calc_1_lower" If the shell's within the horizontal hit-radius of the target and the difference in elevation is over 10 meters we are not ok with it; then we should increase the angle and do the calculation all over again
goto "calc_2_lower" Sends the script back to a recent step
goto "calc_1_lower" Sends the script back to a recent step
#done_lower Defines a point in the script
_text = format ["Distance to target: %1 Angle: %2", _dist, _v]
_what_gun sideChat _text
The gun lets us know it's done with the calculation and gives us the distance to the target as well as the angle it's going to use
_aim_dist = 1000 Defines how far away from the gun the beacon (where the gun will aim) is going to be set
_x_factor = 1 This variable is used to define where the beacon is going to be located
_y_factor = 1 This variable is used to define where the beacon is going to be located
_aim_v = atan(abs((_pos_target_y - _pos_gun_y)) / abs((_pos_target_x - _pos_gun_x))) Defines the angle of the gun when it aims at the beacon
? _pos_target_y < _pos_gun_y : _y_factor = -1 This variable is used to define where the beacon is going to be located
? _pos_target_x < _pos_gun_x : _x_factor = -1 This variable is used to define where the beacon is going to be located
_beacon_init_pos = getPosASL _what_beacon Gives us the beacon's initial position (we're going to send the beacon back to this position later when the gun's been fired)
_pos_beacon_x = _pos_gun_x + (cos(_aim_v) * _aim_dist * _x_factor) Gives us the y-coordinate of our beacon (east/west)
_pos_beacon_y = _pos_gun_y + (sin(_aim_v) * _aim_dist * _y_factor) Gives us the y-coordinate of our beacon (north/south)
_pos_beacon_z = (tan(_v) * _aim_dist) + _pos_gun_z Gives us the z-coordinate of our beacon (sea level)
_what_beacon setPosASL [_pos_beacon_x, _pos_beacon_y, _pos_beacon_z ] Puts the beacon in the desired position
_what_gun reveal _what_beacon Let the gun know where the beacon's at
_what_gun doTarget _what_beacon Make the gun aim at the beacon
_y = 0 Using this variable to do some counting (aka "pausing" the script so the gun may load the shell)
#aim_lower Defines a point in the script
_y = _y + 1 Increase the counter with +1
_what_beacon setPosASL [_pos_beacon_x, _pos_beacon_y, _pos_beacon_z] Keeping the beacon in it's position so the gun can aim at it while it's loading
_what_gun doTarget _what_beacon Keep the gun aimed at the beacon while it's loading
? _y == 6000 : _what_gun fire "M119" When the counter has reached 6000 (which should be sufficient to let the gun reload) the gun fires the shell
? _y == 6000 : _what_beacon setPosASL _beacon_init_pos As soon as the gun's fired the shell the beacon is moved back to it's original position
? _y == 6000 : goto "fire_lower" Now let's go to the next step
goto "aim_lower" Sends the script back to a recent step
#fire_lower Defines a point in the script
_number_of_attempts = _number_of_attempts + 1 The gun just made another attempt to hit the target, let's keep track of'em
_text = format ["%1: Fire!", _what_gun]
_what_gun sideChat _text
Let us know the gun's been fired
? _number_of_attempts >= 2 : _what_gun sideChat "Fire mission complete." ; exit If the gun has made 2 attempts we're done and happy and exit the script
_y = 0 Using this variable to do some counting (aka "pausing" the script so the shell may reach its destination)
#wait_a_bit Defines a point in the script
_y = _y + 1 Increase the counter with +1
? _y == 21000 : goto "correct_aim" When the counter has reached 21000 (which should be sufficient to let the shell reach its target) it's time to calculate how close it hit
goto "wait_a_bit" Sends the script back to a recent step
#correct_aim Defines a point in the script
_what_name = vehicleVarName _what_gun What's the name of our gun?

? _what_name == "art2" : _what_marker = "shell2"
? _what_name == "art2" : _what_shell = shell2

? _what_name == "art3" : _what_marker = "shell3"
? _what_name == "art3" : _what_shell = shell3

? _what_name == "art4" : _what_marker = "shell4"
? _what_name == "art4" : _what_shell = shell4

? _what_name == "art5" : _what_marker = "shell5"
? _what_name == "art5" : _what_shell = shell5

? _what_name == "art6" : _what_marker = "shell6"
? _what_name == "art6" : _what_shell = shell6

? _what_name == "art7" : _what_marker = "shell7"
? _what_name == "art7" : _what_shell = shell7

? _what_name == "art8" : _what_marker = "shell8"
? _what_name == "art8" : _what_shell = shell8

? _what_name == "art9" : _what_marker = "shell9"
? _what_name == "art9" : _what_shell = shell9

? _what_name == "art10" : _what_marker = "shell10"
? _what_name == "art10" : _what_shell = shell10

? _what_name == "art11" : _what_marker = "shell11"
? _what_name == "art11" : _what_shell = shell11

? _what_name == "art12" : _what_marker = "shell12"
? _what_name == "art12" : _what_shell = shell12

? _what_name == "art13" : _what_marker = "shell13"
? _what_name == "art13" : _what_shell = shell13

? _what_name == "art14" : _what_marker = "shell14"
? _what_name == "art14" : _what_shell = shell14

? _what_name == "art15" : _what_marker = "shell15"
? _what_name == "art15" : _what_shell = shell15

? _what_name == "art16" : _what_marker = "shell16"
? _what_name == "art16" : _what_shell = shell16

? _what_name == "art17" : _what_marker = "shell17"
? _what_name == "art17" : _what_shell = shell17

? _what_name == "art18" : _what_marker = "shell18"
? _what_name == "art18" : _what_shell = shell18

? _what_name == "art19" : _what_marker = "shell19"
? _what_name == "art19" : _what_shell = shell19

? _what_name == "art20" : _what_marker = "shell20"
? _what_name == "art20" : _what_shell = shell20

If the name of the gun is this, then the name of the shell and its marker is that
_last_x = getPos _what_shell select 0 Last known horizontal position of the shell
_last_y = getPos _what_shell select 1 Last known vertical position of the shell
_dist_shell = sqrt(((_last_x - _pos_gun_x)^2) + ((_last_y - _pos_gun_y)^2)) How far did the shell go?
_dist_target = sqrt(((_pos_gun_x - _pos_target_x)^2) + ((_pos_gun_y - _pos_target_y)^2)) How far away is the target?
_cpe = sqrt(((_last_x - _pos_target_x)^2) + ((_last_y - _pos_target_y)^2)) And how far away from the target did the shell hit?
_new_v = _v Sets the new angle to the old angle
? _cpe > _fine_cpe_limit && _cpe > _cpe_limit && _cpe > 700 && _dist_shell < _dist_target : _new_v = _v + 1.3
? _cpe > _fine_cpe_limit && _cpe > _cpe_limit && _cpe > 700 && _dist_shell > _dist_target : _new_v = _v - 1.3
If the shell is out of the allowed hit-radius and over 700 m away from the target then the angle should be increased/decreased with 1.3 degrees depending on where the shell hit
? _cpe > _fine_cpe_limit && _cpe > _cpe_limit && _cpe > 500 && _cpe < 700 && _dist_shell < _dist_target : _new_v = _v + 1
? _cpe > _fine_cpe_limit && _cpe > _cpe_limit && _cpe > 500 && _cpe < 700 && _dist_shell > _dist_target : _new_v = _v - 1
If the shell is out of the allowed hit-radius and over 500 m away from the target, but within 700 m of the target, then the angle should be increased/decreased with 1 degrees depending on where the shell hit
? _cpe > _fine_cpe_limit && _cpe > _cpe_limit && _cpe < 500 && _dist_shell < _dist_target : _new_v = _v + 0.5
? _cpe > _fine_cpe_limit && _cpe > _cpe_limit && _cpe < 500 && _dist_shell > _dist_target : _new_v = _v - 0.5
If the shell is out of the allowed hit-radius, but within 500 m of the target, then the angle should be increased/decreased with 0.5 degrees depending on where the shell hit
? _cpe > _fine_cpe_limit && _cpe <= _cpe_limit && _dist_shell < _dist_target : _new_v = _v + (_cpe * 0.003)
? _cpe > _fine_cpe_limit && _cpe <= _cpe_limit && _dist_shell > _dist_target : _new_v = _v - (_cpe * 0.003)
If the shell is not a hit, but within the allowed hit-radius then the angle should be increased/decreased using another method
? _cpe > 1500 : _new_v = _v If the shell hit over 1500 m away from the target, then something probably went wrong while firing the gun and so we'll make another attempt using the old angle.
_v = _new_v Sets the angle to the new angle. If no new angle is calculated, aka the shell is a hit, the angle's value will be 0
_text = format ["Shell hit %1 meters off. Trying to correct aim.", _cpe]
? _v != 0 && _cpe > _fine_cpe_limit && _cpe <= 1500 : _what_gun sideChat _text
? _v != 0 : goto "done_lower"
If the angle is not 0 and the shell is out of allowed hit-radius, while obviously not a failed attempt, the gun will tell us how far off it hit and fire a new shell using the new angle
? _v == 0 : _what_gun sideChat "Fire mission complete." If the angle is 0, aka the shell's hit, we're done
exit ...and so it's time to exit the script

 

 

fired3.sqs (this is where we're sent when we click on the map):

_target = _this select 0 Gives us the target
_shift = _this select 1 Let's us know if the shift-key is pressed or not
_alt = _this select 2 Let's us know if the alt-key is pressed or not
_v = _this select 3 Default angle
targ2 setPos _target Set's an object in the game called "targ2" to wherever we're aiming on the map - this will allow us to get the height (z-value) at the current point since a click on the map only gives us x- and y-values.
_pos_target = getPosASL targ2 This is where we want our shell to land
_unit_array = [art2, art3, art4, art5, art6, art7, art8, art9, art10, art11, art12, art13, art14, art15, art16, art17, art18, art19, art20]
_beacon_array = [beacon2, beacon3, beacon4, beacon5, beacon6, beacon7, beacon8, beacon9, beacon10, beacon11, beacon12, beacon13, beacon14, beacon15, beacon16, beacon17, beacon18, beacon19, beacon20]
_shell_array = [shell2, shell3, shell4, shell5, shell6, shell7, shell8, shell9, shell10, shell11, shell12, shell13, shell14, shell15, shell16, shell17, shell18, shell19, shell20]
Three arrays containing the number of units, beacons and shells.
If you want to use more artillery pieces it's just to add more values to the arrays.
_number_of_units = count _unit_array This is the maximum number of artillery pieces we'll be looking for
_i = 0 Sets _i (counter) to 0
_marker_pos = getPos targ2
"target" setMarkerPos _marker_pos
Set's the marker (red X) to where we clicked on the map
#count_loop Defines a point in the script
_unit = (_unit_array select _i)
_beacon = (_beacon_array select _i)
_shell = (_shell_array select _i)
Select the current unit, beacon and shell
? alive _unit : [_unit, _beacon, _pos_target, _v, _shell, _shift] exec "fired2.sqs" If this unit is alive (aka not destroyed or non-existing) send the coordinates, etc. to the unit and start the aiming process (see fired2.sqs)
_unit AddEventHandler ["fired",{_this exec "fired.sqs"}] Then let us add an eventhandler to the unit so we can track its shell once it fires
_i = _i + 1 Increase the counter with +1
?_i < _number_of_units : goto "count_loop" As long as the counter doesn't have a higher value than the number of units - let's keep checking them units..