Big Draco's FP guide
Future Pinball Scripting
Welcome to Code
This area of the website will develop so check back often.
At the moment, the following snippets may be of use...
The Dim statement (short for dimension) is used to declare or tell the program that you want to create a variable. A variable is a place holder that points to an area of memory that the program can use to store a value. Rather than use a complicated memory address (which changes each time the program is run), this variable name can be used instead.
It is important that the program allocates enough memory to store values which is why we need to tell the computer that we want it to allocate space to store a value. In compiled programs (.exe files) it is usual to define not only the name of the variable but also its data type (character, numeric etc) but also how big it is. This is not necessary in VB Script (the language Future Pinball (FP) uses) as the script is interpreted. That means it executes statements 'on the fly' (which is why you do not always get a script error when you start a table up, but can occur when a certain piece of code is run).
This tells the program that we want an area reserved for a value and will reference it within the program as 'myvariable' (Note the the script language is not case sensitive, so MyVariable is the same as myvariable.
In programming it is common to talk about variables and constants but what is the difference? In short, variables do and constants don't! That means that during the execution of a program, a variable's value may change (vary) whilst a constant remains the same value throughout - it never changes. There is no difference in definition of a variable or a constant - which is which is entirely up to you although you can use the Const declaration for constants.
This is an area that can often confuse new programmers and getting this wrong can cause some weird results! I use the terms 'Local' and 'Global' but they can also be known as 'public' and 'private' depending on the language being written.
Local Variables (Private)
These are variables that have been defined within a single subroutine. They are only available to that routine. For example, take the following piece of code that you might find in an FP table script...
bumperhitcount + 1
bumperhitcount + 1
Note that in each of these subroutines there is a variable that has been defined called bumperhitcount. As this variable has been defined within each subroutine it would be classed as a local or private variable. That means that it is only available to that subroutine.
If the player hit bumper 1 two times and bumper 2 three times, each variable would hold '2' and '3' respectively. It would not hold '5' because these local variables are separate from each other. If you wanted a variable to hold the total number of times a bumper has been hit, you would need to Dim a global or public variable.
Global Variables (Public)
These variables are defined outside the subroutine (and within an FP script, the place to do this is immediately beneath the comment box at the start of the script - look at CurrentPlayer as an example). Variables (or constants) defined here can be accessed and change by any subroutine within the whole program - that are not specific to any one subroutine.
Global variables are ideal for 'table wide' values that you want to store (or check) within any subroutine. Such examples might be a total bonus you want to award when the player loses a ball. As the player hits certain things (or achieves certain results), you might want to add an amount to the total bonus which you award when the ball is lost.
Subroutines are a group of code statements that (in FP) are executed when something occurs. The something is known as an event and usually relates to the ball hitting something that reacts (often by adding a score, turning on or off a light etc). Subroutines should stick to a specific task and it is bad practice to have a 'massive' subroutine that carries out a lot of different things.
Here is an example of a subroutine...
bumperhitcount + 1
This subroutine's code is executed when 'bumper1' is hit. In it, a bumper counter is incremented and 100 points are added to the players score (yes - Addscore is another subroutine). You will invariably end up with subroutines far larger and more complex than this but remember to keep the subroutine down to a specific task.
Notice with the start of a subroutine that you have Sub mysub() - note the brackets at the end of the subroutine name. The brackets allow a subroutine to receive an argument. An argument is a variable/constant or value that the subroutine can use. The Addscore subroutine uses an argument and is called when you want to add a set amount of points to the players score. Here's another example that shows how arguments can be used:
... in a subroutine within the script
"Welcome to Code Corner"
dispseg1.queuetext msgstring, seWipeStarRight, 200, 0, FALSE, ""
So what is happening here? I use a subroutine in my own tables called dspmsg (Display Message). Rather than having the dispseg1.queuetext etc everywhere in my program when I want to display a message on a segment display, I use a subroutine. I set the value of msgstring to the text I want to display, call the dspmsg() subroutine and pass msgstring as the value that dspmsg() will work with. This is a way of reusing code that is not only more efficient, but makes my life as a programmer easier!
I can even call dspmsg like this...
but I prefer to use a variable (it's just me!). Apart from having to use _hit() routines, I tend to use subroutines whenever I want to use the same code over and over. For example, suppose I have a table with 3 lanes. Each lane has a star trigger that if hit turns ON or turns OFF a light below it. I also want to rotate the lights if the player presses the flipper keys. Each star trigger will have its own trigger_hit() routine but I would use a separate subroutine to rotate the lights. Here's the code...
= bulbon) then
Now I would have 2 more subroutines like this for lane2trigger_hit() and lane3trigger_hit() (remembering to change the names of the lanelights of course!
In the 2 flipper routines (left and right flipper) I would call my rotate lights subroutine
If (KeyCode =
Now for my rotatelights() subroutine...
So no matter which flipper is depressed, a call to rotatelights() is made which carries out the switch.
Most subroutines are executed when an event occurs. This type of event occurs when a trigger event happens. A trigger event (not a trigger on a pintable) is something that happens. When the event triggers, the relevant section of code is executed. Virtually all Windows programs are event driven. For example, in a Word Processor, the user clicks on the Bold button. The event - click bold button, triggers a section of code that tells the program to produce bold text. In FP, triggers occur (usually) when something (the ball) hits something else.
A lot of controls in FP use a Control_Hit() method. So in the bumper1_hit() example the code is executed when the ball strikes bumper1. This is an important concept to grasp - especially if you have written procedural code. There is an excellent section in the FP manual under Global Methods & Variables that tells you about the existing subroutines that are automatically created when you create a new table. Read this section so that you know what happens and where as this is crucial for the non _hit() routines.
As you know, pinball is a fast and at times furious game with many things seemingly happening at once and every control on the table should "do something" but how do you keep track of this within your script?
One answer is to use flags. Flags are variables that store indicators and are usually ON or OFF (so you can use lights to tell what has happened). For example, in my Winter Olympics table, I created a subroutine that "adds" lights (lights up) un lit OLYMPIC GAMES lights. The subroutine will light the first unlit light it comes across. If it has to light 2 or 3 or 4 lights, it repeats the process, but because (when the subroutine is called), any light(s) may be lit, it has to test each one.
Here's the subroutine...
The flag is the variable Y. On entering the routine, I set Y to zero. I test a light and if I switch it ON I set Y to 1. Note the if statement - if light is OFF AND Y = 0 then turn it on. Of course, if I have already set a light to ON, then Y will be 1 and therefore the light won't be turned on. Flags are very useful for this kind of thing.
I have to admit that when I first started using FP, timers confused me, so in case they confuse you too - here's how they work.
You create a timer (the small clock icon) from the 'Special' button at the bottom of the editor. You need to set a time for it. Note that the time is in milliseconds so setting a timer to 1000 will mean it is set for 1 second. You can either set the timer duration within the editor or within your script. If you are going to vary a timer's duration it is better to set it in the script.
Here's how you use them. Suppose you have a kicker hole. You want the ball to sit in the hole for a brief moment before the kicker kicks the ball out (say 1.5 seconds). Here's the order of events...
1). Enable the timer (turn it on)
2). When the time has elapsed, do something (kick the ball out)
3). Disable the timer (turn it off)
And in script form...
kickertimer.enabled = TRUE ' Turn on the timer having set it to 1500 in the editor
Sub kickertimer_expired() ' The time has run out
kickertimer.enabled = FALSE ' So turn OFF the timer
kicker1.solenoidpulse() ' Kick the ball out
Of course, you can add scores, light lights etc as you see fit. If you remember those 3 steps when using timers, you will find them simple to use.
There have been books written about coding standards so you might not agree with all of these things but I offer these as someone who once programmed for a living and I also offer a reason why.
|Use meaningful variable names||Good||This causes you far less confusion than using 'x1', 'x2', 'x3' etc. In 3 months time you won't remember what the variables represent and (more importantly?) if someone else needs to look at your code, they will have far more difficulty in understanding what is going on|
|Indent Code||Good||This is not just a beautification tool but helps trap errors - particularly in IF statements. Look at the Addlights() routine above and it is easy to see which statements are carried out for each IF (and this is especially true if using IF/ELSE statements)|
|Comment your Code||Good||Not only do comments help you remember what each subroutine is doing (or someone else for that matter!) they offer a great way of finding things in the editor (Using the Find tool)|
|Defining flags||Good||This is a personal preference, but I DO tend to use X, Y, Z etc when defining a flag. When I see X, Y or Z then I KNOW that variable is simply a flag and does not contain 'real data' I use real names for variables that hold 'data'|
|Consistent naming||Good||This is something that can be extremely useful. Say you have 4 lights in a drop target called "dt1". If you use "dt1lt1", "dt1lt2" you can copy and paste lines and change the numbers - far simpler that calling them something like "droptargetleftouterlight", "droptargetleftinnerlight". These names (although potentially more meaningful are pain to copy and paste).|
|Code Obfuscation||Bad||Code obfuscation is the act/art of changing all your variable names to something meaningless (usually done once a program is complete using global find/replace). I've worked on obfuscated code and it was probably easier to ditch it and start from scratch. You may have finished this table but I often find I pinch bits of code from previous tables. If I'd obfuscated it I wouldn't be able to do it.|
|Changing a variables data type||Bad||Although VB Script allows it, it is not a good idea to change the data type of a variable part way through a program. It's confusing - particularly if you need to debug your code. Better to use 2 variables rather than 1 and keep reassigning it|
|Adopting a standard||Good||Try to adopt a standard method of naming variables throughout your table. Do you use "light#" or "lt#"? Whichever, try to stick with it. (I do tend to mix them a bit - I use "light" for 4 or less associated lights and "lt" for more. I also try to use standard naming (like X, Y and Z for flags rather than sometimes use X, Y and Z and other times use A, B and C. It doesn't matter to the program, but it makes my life easier.|
These 'words' are typically used in IF statements. The IF statement is one of the most powerful commands in programming as it allows choice. The syntax is:
IF(condition 1 is true) THEN
Sometimes we want to use more than one condition and this is where AND and OR come into play. Here's an example:
IF(bonuslight1.state = bulbon AND bonuslight2.state = bulbon) THEN
So what we are saying here is that if bonuslight1 is ON and bonuslight2 is ON turn on bonuslight3
We can also use OR and the thing to remember here is that:
Use AND - ALL conditions must
be true for the code to be executed
There is a 3rd option - NOT and in this case NONE of the conditions must be true for the code to be executed.
Handling Drop Targets
At the heart of most pintables are dropped targets. This section will deal with how to set up a bank of 4 targets, each target having a light in front that is turned on as each target is struck. Here's an example taken from my Dragons' Lair table...
E.G. 4 targets with each target having a light in front. As an individual target is struck, the light is turned on.
For this example, we shall assume that 500 points is added to the player's score for each individual target and if all 4 are knocked down, the player is awarded an additional 5000 points. We will flash the lights when all targets have been knocked down.
The code will assume that the group of targets is called "DropTarget1" and the 4 lights are "Dt1Lt1" to "Dt1Lt4"
First of all we need to name the target (as above) and each of the 4 lights in the editor.
Having done that, we can turn our attention to the script side of things.
Here's the section of code...
if(DropTarget1.dropped = TRUE) then
So what is happening here? Lets look at the first section of code - the Select Case statement. Select Case statements are a neat way of having multiple IF statements without having to have, multiple IF statements! The fpEventID is a special variable that is used as the subject of the test (so what we are saying here is "IF fpEventID = 1 THEN turn ON bulb 1". The fpEventID stores which individual target out of a bank of targets has been hit. You can also use your own variables in Case statements. In this example, target 1 or target 2 or target 3 or target 4 has been hit (it must have been hit because this subroutine is only ever run when the the target bank has been hit).
So for each possibility (1 to 4), we turn ON the corresponding light (so make sure light 1 is in front of target 1, light 2 in front of target 2 etc). You can tell in the editor which target is which as each is numbered.
We could at this point add 500 points but this is inefficient because we would need an "addscore(500)" after each statement that turns on a light. We'll add the score in the next section of code.
The 2nd block of code (the 2nd IF statement) handles the situation of when all the targets have been struck. We test to see if the target has been dropped and if it has, we add the 5000 points. We then flash the lights a couple of times "FlashForMs" and when that is complete, the bulb is turned OFF (hence the bulboff at the end of the FlashForMs command). Finally we pulse the target which resets all the individual targets back up again so that the player can hit them again. If you don't do this, the targets will stay down.
If the target HAS NOT been dropped (in other words, a target has been hit but there are still some targets standing), we add 500 points. Notice that by doing the addscore(500) this way, we only need one statement rather than having 4 separate addscore(500) statements.
What else can you do with targets? The above section of code will cover the most usual aspect of handling targets but you can do other things as well. Here are some possible suggestions for when you "drop all targets"
In fact, you can do anything you like!
An attract mode sequence is a light show for when the table is not actually in play (all the flashing lights that occur trying to lure you into playing a game). In FP, one of the ways to do this is by using the .set bulbblink command.
Here's how it works:
Assume we have 4 lights that are laid out on the table vertically (1 bulb beneath another). Bulb 1 is at the top and Bulb4 at the bottom. In the SetAllLightsForAttractMode() subroutine we use a .set bulbblink command to tell the system how we want the bulbs to blink. For this example, we will start at bulb1 then bulb2 then bulb3, bulb4 then go back to bulb3, bulb2 then bulb1. After that we will flash all the bulbs twice. Here's the code using the colours that appear in the script editor:
So what is going on here? The way to "read" what is going on is to look at the purple 1's and 0's in columns. The 1st column is "1000" which means bulb1 is ON whilst bulbs 2, 3 and 4 are OFF. The 2nd column is "0100" which means bulbs 1, 3 and 4 are OFF whilst bulb 2 is ON. following this sequence we can see that the bulbs are turned on in this order: 1 - 2 - 3 - 4 - 4 - 3 - 2 - 1 then all OFF then all ON which is repeated once.
The '200' at the end of the line tells the system how long the bulb should be on in milliseconds so 200 is 1/5th of a second.
To alternate bulbs (1 and 3 ON then 2 and 4 ON) we would use something like this:
You can use all manner of variations to create an attractive light sequence!
Quite often within a pintable, you will want to create a loop that is repeated so many times (perhaps to carry out a bonus sequence). One way to do this is to use a FOR/NEXT loop. In my Winter Olympics table, I used a loop to light a certain number of letters for the OLYMPIC GAMES lights. Players could have up to 6 lights lit depending on what they have achieved and the difficulty level.
Here's the syntax...
For x = 1 to 5
This loop will be executed 5 times (you can have any number here), or you can use a variable in place of the '5'. Here's an example which is a subset of my Olympic Games table.
Note the comments in this code segment. (Shown in Fuchsia)
Dim LoopCounter ' Defined at the top of the script so that it is a global/public variable
' This is the code for
the light a certain number of lights out of a sequence
You can basically ignore the IF statements as this can be any code you want to place inside the FOR/NEXT loop. The key things to remember here are:
1). Make your loop counter (if you are going to use a variable) a global/public variable
2). Initialise the loop counter (this can be done at table load time) but needs to be reinitialised after every ball/game
3). Increment the loop counter when required (perhaps when a bank of targets has been dropped)
4). Use the FOR/NEXT loop to loop through 'loopcounter' times
This is an extremely common pintable feature where the player can use the flippers to rotate a set of lights - often, these lights are at the top of the table (or near the flippers themselves) so that an entire bank of lights can be lit - often employed as a way of getting a bonus multiplier or other game feature.
You can see that the L and A of the word LAIR has already been lit. The player uses the flippers to move the lights so that when the ball goes into a lane, an unlit letter can be lit (as shown in the above screen sample).
How do you allow the player to do this? Here's the code...
As the subroutine is entered, the states of the 4 bulbs (that spell LAIR) are stored in temporary variables (tmp1, tmp2 tmp3 and tmp4)
The state of each actual bulb is reassigned (So light1 takes on light4's state, (the L of LAIR), light2 is assigned light1's state and so on). It does not matter whether any or all bulbs are lit, they will rotate!
The final stage is to call this subroutine from the FuturePinball_KeyPressed subroutine (the easiest place to put the call is immediately after the 'Playsound "Flipper"' command. Don't forget to call the routine from both the left AND right flipper (so the call she be duplicated for each flipper. Of course, you could be clever and rotate the lights one way for left and the opposite direction for right!
Using a Bonus Light Sequence (Code supplied by Brother_B)
We have all played tables where, at the end of a ball, a bonus sequence counts down but how do you do this in Future Pinball without having timers for every single bonus light? Here's how...
This subroutine turns on (and off) the various bonus lights (in this example, up to a maximum of 29) That is lights 1 to 9 plus a 10 and a 20 light)
At the end of the ball you want to collect the bonus. If you have a multiplier, then store everything in temporary variables thus...
Then we have the actual bonus countdown and lights out. The speed of the countdown is controlled by the timer settings. (Note the '150' in the penultimate line of this subroutine)
So it's done by using a single timer over and over until all the lights have been extinguished.
Bonus Light System into your table
Using the previous article as a basis for a bonus system, this section looks at how you embed this code into your own table to get it to work.
Lets start with the declarations. These occur right at the top of the script just before the "Defined Script Events" section.
We have 2 blocks of code here - the Dim Statements that tell the script that these variables can and will be referred to by any subroutine in the script. Then we have a series of Set statements so what is happening here?
Notice the very first Dim statement? It's different from the others because it has a number (29) after it. This means that Bonuslts is in fact an array which means that it is a variable that can contain a list of values. When we access an array we have to state the array name AND which element (or list item) we want to use. Bonuslts has a 29 element array (which relates to 29 bonus lights 1-9, 10 - 19 and 20 - 29)
Look at the 2nd block of statements - the set statements. Here we are assigning individual lights on the table to elements in the array. Now you may be wondering where lights 11 to 19 and 21 to 29 are. We have made this more efficient by re-using the 1-9 lights. When the player has light 9 lit and hits the 10th, lights 1 to 9 are turned off and light ten is lit. When the bonus is increased from 10 to 11, we leave the 10 light on and light the 1 light. When the 10 light and the 1 to 9 lights are lit, the next increase turns off the 10 light and the 1 to 9 lights and turns on the 20 light where the process starts again up to a maximum of 29. Play my Blue Mood table to see this in operation.
So at this state, we have defined the variables and told the array which lights are assigned to which elements.
We now need a way of advancing the bonus and this is carried out when certain objectives have been reached. For example...
Notice the advancebonus(1) statement? (You can ignore the rest of the code in this snippet.) This is how we get the next light on the bonus lit up and you will have an advancebonus(1) statement wherever you want to increase the bonus lights. Want to have differing awards? You could have something like this:
For x = 1 to
This code would increase the number of lights based on the number of balls currently on the playing field which would differ during a multi-ball event.
OK to recap...
We have our variables set up and we have gone through the script adding the "advancebonus(1)" statements everywhere we want to up the bonus lights. Now it's time to deal with collecting the bonus when the ball drains. (You can add this statement right after the (playsound "Drain") statement.
for x = 1 to bonus
Remember all those advancebonus(1) statements? Well every time these statements are called the value in brackets (1) was added to a variable called "bonus". So bonus knows how many lights have been turned on. We create a loop to execute 'bonus' number of times - once for each lit bulb. Bonustemp is called which stores the state of each light and then starts the timer. When the timer expires the _expired subroutine turns off the highest light, adds a score and then leaves. This is repeated "bonus" number of times until all the lights have been extinguished and he bonus has been paid out.
To see this script in action, download and play my Blue Mood table where this code is used.
Within a table, it can be good to include a 'Random Event' that occurs but how is this achieved within FP? To do this, we need 2 things. 1st, we need to generate a random number and 2nd we need to take action based on the random number. Of importance when creating a random number is that we (usually) want to create a random number within a specific range such as "A random number between 1 and 6" (to perhaps simulate the roll of a dice). How is this achieved? The following code can be used...
i = INT(RND(1)*6)+1
Lets breakdown this statement. First of all, the variable 'i' will be the variable that will hold the random number. The INT function tells the system to return only the integer (whole number) portion of the random number. So if the computer generated a random number of 2.308765263421 then only '2' will be returned (the integer part of the number). The RND(1) function is the function that creates the random number. The '*6' means multiply the random number by 6 (the actual random number is between 0 and 1), so we need the * 6 to generate a larger random number. Notice how the * 6 is within the brackets of the INT function so that we will get a whole number only. A crucial part of this statement is the + 1 at the end. Without this, this statement would generate a random number between 0 and 5 but we need a random number between 1 and 6.
So to create a random number between 1 and particular number, replace the *6 with whatever number you want (and yes, you can use a variable here so you could have * My_Variable if you wish). If you want to create a random number between 40 and 50 you could use...
i = INT(RND(1)*11)+39
There are some odd numbers here! We need a random number between 1 and 11 as the range between 40 and 50 is 11 not 10. We add 39 at the end so that 40 can be generated (if the INT function returns '1' this will be added to 39 giving 40).
OK - So we have our random number (using the dice example of a random number between 1 and 6), so what happens now? The easiest way to handle the random number is by using a SELECT statement like this...
Select Case i
Depending on the random number (i), 1 of 6 subroutines (rndevent1() to rndevent6()) will be executed. Of course, you can have any subroutine you like being executed if you wish or even a few lines of code depending on what you want to do. Here are some ideas where you might want to use a random number event (there are plenty more, these are just to give you an initial idea)
I am sure you can think of even more than this list!