code is doing, documentation and comments are redundant. The goal for this article is to explain what a script is
doing. Once you understand that, scripting a table for Visual PinMAME is easy! As far as scripting technique goes,
first and foremost, a script must be accurate, and it must work - preferably with no errors. With that main criteria
satisfied, everything else is an added bonus. If you need help in a pinch, check out vbsdoc.html - it comes with the
vbs scripting files download.
Disclaimer - for every rule and recommendation there are exceptions. Visual Pinball and Visual PinMAME will only do
what they are told to, and there are limitless ways of accomplishing the same task. This initial article is just one
such method available. Feel free to experiment on your own until you find a system that works best for you.
In general, every Visual PinMAME table requires the following items:
1) Software -- Visual Pinball, Visual PinMAME, VBS Script Files, the ROM code file(s) for the game
2) A Visual Pinball table file
3) A Visual Pinball object named "PinMAMETimer" and a Visual Pinball object names "PulseTimer"
I recommended initially setting the PinMAMETimer object to an interval of 100, and the PulseTimer to an interval of
40, and leave both of them enabled (as is default).
In order to comprehend how this all works, a short paragraph or two is appropriate on this subject, before going
further. Visual Pinball recreates the visual table layout, graphics, ball physics, and provides an interface layer
to Visual PinMAME. Visual PinMAME emulates the game roms from the real machine and handles the display, sound, and
rules. The VBS Script Files provide common code routines, as well as individual game settings for Visual Pinball to
utilize.
The PinMAMETimer on the table runs a polling routine for your game that checks the light states, general illumination
states, solenoid states, and playfield motors. The PulseTimer, as far as I know, closes and reopens the switch when
the time interval has expired. Core.vbs lists conTimerPulse=40 so it runs at 25Hz, 25 times/second. 1000/40=25
These timing intervals are important. The PinMAMETimer interval is usually reset to 1, to get improved
synchronization between Visual Pinball and Visual PinMAME. However, be careful about resetting or changing the
PulseTimer value, as it might not work as expected if switch pulses are missed or lost (faster), or slower than the
expected interval. Switch events are queued to prevent misses up to 5 in a row for the same switch number.
One of the primary reasons to even create a Visual PinMAME table is, the rom handles the game logic, rules, and
sounds for you, the vbs files have common routines prewritten for your use, and it is possible to get an entire table
scripted in less than an hour. It is not uncommon to have a fully working basic product with less than 300 lines of
coding and it will work. I think my personal best is 60 lines. Once you have a working table, you can go back and
spend more time on presentation and added value features if you choose to do so.
With that out of the way, we will examine the basic script for Gottlieb Chicago Cubs Triple Play:
Click here or see attached script at the bottom of this post.
---------------------------------
I selected this game to start with because it is a simple single ball machine, there are no difficult conversion
tables to work with between the game manual and Visual PinMAME's emulation layer, and there are no solenoid
expanders. I'll get to those later in a future article.
Option Explicit - while not required, is highly recommended. This enforces the rule that all variables must be
declared before use. The benefits clearly outweigh the extra line of code to have this. If any variables are
mistyped, the editor will let you know about them with a line number to fix it. http://msdn.microsof.....28VS.80).aspx
The next Const lines set up default constants for the game, such as the romset name Visual PinMAME will use, whether
to use the default routine for solenoids, lamps, and General Illumination, and set up default sound sample names for
various events. If demand exists for extra definition of these lines, let me know. They are basically the same for
all games I work on, so I leave them alone and just copy from table to table unless there is a need to change them.
The LoadVPM line executes the routine "Sub LoadVPM" directly below it, passing in the required values for VPMver,
VBSfile, and VBSVer. This routine was written originally by WPCMAME to allow error checking during the setup process
and loading of external files. If a user is missing the vbs file required, or it is an older version than the one
required, or if Visual PinMAME is too old to be used for this game, this routine will create an error window with a
bunch of error messages. Nearly all Visual PinMAME tables have this routine in them, or something similar to this
routine. If the files pass these checks, Visual Pinball is able to run the table.
LoadVPM loads the required main vbs file - sys80.vbs, and that one loads core.vbs and vpmkeys.vbs as well. These are
just text files so you can open them with notepad and check them out line by line if you like.
For this game I put the keycode routines next. If a user presses a key, program execution jumps to the KeyDown
routine. if a user releases a key, execution jumps to the KeyUp routine. I think this is fairly self explanatory.
If the left flipperkey is pressed, Visual Pinball signals the romset to close switch 6. Once Visual PinMame is
active and running, visual pinball sends a message to visual pinmame that switch 6 is closed. SYS80.vbs also sends
other predefined flipper switches for the left flipper (switch 114). If the right flipper key is pressed, Visual
Pinball does the same thing, only this time it closes switch 16, and the sys80.vbs sends the message again, with
switch 112. Visual PinMAME itself also could be activating switches in the background - typically for flippers those
are 84 and 82. You don't need to be fully aware of every step that takes place, just know more happens than is
readily apparent sometimes.
The KeyUp routine in this table opens the switches, letting the game know the switches should be released/cleared/and
opened. Any keys not implicity defined in these two routines, will be handled by the 'if vpmkeydown(keycode) then exit sub'
line. This simply passes the keypress to the vbs files to handle. This would include pressing 5 to insert a coin,
pressing 1 to start a game, pressing the nudge keys, entering the dipswitch menu options, resetting the emulation,
etc etc. If the vbs files do not handle the key that was pressed, and if nothing exists in the table script for the
key that was pressed, then nothing will happen. Because the plunger key is NOT handled by the vbs files, we need a
line to do that for us here.
Next I set up some more constant declarations for reference, from the manual for Triple Play. This game has one
saucer, one drop target bank, a knocker solenoid, and a ball release/outhole solenoid. The numbers match the manual,
so this is quite easy. You do not need to use constants, you can refer to them directly by number, but if anyone
else is modifying your code, it's easy to read and understand this way.
Next is the SolCallback section. This is short for "Solenoid Callback". Whenever a solenoid activates on the
machine, and whenever a solenoid deactivates/turns off, these routines are called by core.vbs. This is because we
have 'usesolenoids=1' at the top of the script. When the romset fires solenoid 2, it kicks a ball out of the saucer
and clears the switch for it. When it wants to reset the drop target bank it executes dtL.SolDropUp to raise the
targets and play a sound and clear the switches for those. When it fires the knocker it plays the knocker sound.
sLLFlipper and sLRFlipper move the left and right flippers, when the game wants them to move. If your game only has
two flippers, you can substitute the word "nothing" in place of "Flipper1" and "Flipper2". Moving the actual
flippers and handling their default switches is handled by sys80.vbs and core.vbs.
The first code that is executed when a table is played is option explicit. Any direct commands that are not within
subroutines are called next - as in DIM, CONST, and variable assignments such as N3=0. Because LoadVPM is called
directly at the top of the script, the vbs files are checked and loaded. Those are required before the compiler for
VP can check the script commands for obvious errors. If it passes the error check (misspellings, playfield objects,
and names) then the table is rendered. As soon as the table is rendered and on the screen, Tablename_Init is
executed. As events happen in the Player window, their corresponding routines are called by Visual Pinball, and the
table begins to actually do interesting stuff. :) There are some ways to change the execution order, but that is not
the focus of this article. Realize the entire program is 'event driven' - if a timer expires it automatically looks
for a routine to execute for the timer, if a wall is hit and the has hit event is checked, it searches for a routine
to execute that matches the wall name.
So, in Table1_Init, I set up some default options for the visual pinmame window. I set the red color to 7, green
color to 194, and blue color to 78. This will provide a teal color to match the real machine for the score display.
The cGameName variable was set at the top of the script as "triplay". It is imperative that the name is correct, or
visual pinmame will not be able to find the registry value for these settings. It also won't be able to run the game
if it is not found in the game list for visual pinmame.
On Error Resume Next allows you to continue running the program if it encounters errors at this point.
http://msdn.microsof...y/ms998260.aspx
Then I set up some basic options for visual pinmame. I give it a gamename for a romset to load, the vpinmame splash
screen will have two lines of text at the bottom - "Triple Play,Gottlieb 1985" and "Table by Destruk & TAB", the game
has no mechanics handlers built into visual pinmame, so that is set to 0, I want Visual Pinball to handle all the
keyboard input instead of the visual pinmame window itself so that is 0, I just want to see the score displays and no
diagnostic information in the visual pinmame window so that is 0, and I don't want to see a frame or title on the
visual pinmame window so those are 0 as well.
At this point, if there are any problems with the commands I gave so far, it will show errors again, and on error
goto 0 will terminate the program and leave me back at the editor screen if it did find an error.
For some Gottlieb System 80 games, as soon as the machine is turned on, it will automatically send a surge of power
to some of the solenoids. So right before starting the emulation, I give the controller.solmask(0)=0 command. This
disables Visual Pinball from acting on any solenoid callbacks. Then I add a timer to re-enable the solenoid
callbacks after 2 seconds, and then the emulation of the romset starts in visual pinmame. Without this routine, when
the game starts up it will play a loud knocker sound.
The next four lines are the game dipswitches. This was the original method to set up the game options. It can
handle 32 dipswitches, and switch 25 controls the number of balls. I kept it here for reference. This method can be
used for nearly all games that have dips. Most games in this case, have a custom menu created/provided by
Inkochnito. Switch 25 is the first value in the Controller.Dip(3) line, so the 1*1 sets the game to 3 balls. 0*1
sets it to off, which is 5 balls.
PinMAMETimer.Interval=PinMAMEInterval -- this line grabs the value of PinMAMEInterval from core.vbs and resets the
PinMAMETimer to the new interval value. By default this is 1.
After that I set the tilt switch to 57, set the sensitivity to a conservative 5, and tell it what objects on the
playfield to disable if the machine tilts.
The game has 1 ball, so the Trough is switch 67, it kicks balls from the ball release kicker at 90 degrees at a force
of 3. When it kicks a ball it plays the ballrel sound, when it kicks and there is no ball available it plays the
solon sound. When the table initializes, this preloads 1 ball and closes switch 67. When the ball release/outhole
solenoid fires, it kicks the ball out and opens the switch.
Then I set up the saucer code. The saucer will be Kicker 1, switch 43, and it kicks balls out at 180 degrees with a
force of 5. It plays the popper sound regardless if there is a ball present or not.
Next is the drop target bank. It is made up of 6 targets with 6 corresponding switches. It plays FlapClos when a
target is hit, and FlapOpen when the bank is reset by the solenoid.
There is absolutely no reason to have to reinvent the wheel, so eventually, you'll have a few tables scripted that
you can copy and paste routines from with ease. Need a drop target bank? Grab it from a table with this routine,
change some names and numbers, and you're done. It's faster than referring to the vbsdoc.html file, and faster than
retyping it in by hand. Unless you need something unique that doesn't fit into an existing solution, there is no
problem with it.
Now this is the extremely repetitive section. Switch handling and light setup. Copy and paste saves a lot of time
here. I selected this table so you would have examples, rather than shortcuts.
When the left slingshot's slingshot is hit, it plays the sound "sling" and pulses switch 40. This is a quick closure
of the switch, followed by opening the switch. The visual pinball script passes the event to core.vbs, which The rom
notices the change of state and awards points and plays extra sounds based on this event.
The next line of significance would be when Kicker1 is hit, it adds the ball to the saucer and the saucer definition
closes switch 43 for us. The game emulation will notice the switch has been closed, it will award points and/or play
a sound, and eventually, because it's a single ball table, has no choice but to kick the ball back into play.
Walls 1, 2, 3, 4, 5 and 6 are the drop target bank. When one of those walls is hit, we send the number of the target
to the drop target bank definition. Wall1 is listed first, so it is #1, Wall 6 is last, so that is #6 in the
definition from left to right. You then hear the FlapClos sound, the corresponding switch is closed, and the rom
sees the switch closure to award points and play its own sound for that event. The rom also controls when to raise
the targets and clear the switches.
When a trigger is hit, you close a switch, and when unhit you clear the switch. While you can just as easily use
PulseSw or PulseSwitch switchnumber, delay, callback to do the same thing, it isn't nearly as accurate as a
pulseswitch will hold it closed for a much shorter length of time. While it might not impact gameplay for this game,
other games do check how long a rollover switch is held down - like one of the skill shots for Creature from the
Black Lagoon.
The last switch statement of note is Sub Drain_Hit. When the ball is lost in the drain kicker, it is added to the
tough and the switch 67 is closed again. The rom notices the switch closure and runs the end of ball routine.
Next is light setup. By default, core.vbs initializes an array for lights up to 201 members (0-200). This is enough
for most of the available emulated systems for Visual PinMAME. For Triple Play it uses less than 50. Here I just
look at the manual page and place lights on the table and give them matching names to the number the manual provides.
Lamp 3 is L3, lamp 10 is L10, etc etc. If there are more than a single lamp with the same number, it can be handled
without the set command. Lamp 20 in the manual shows up as controlling two bulbs. So for that one, I use Lights
(20)=Array(L20A,L20B)
Core.VBS handles the lamp updates because at the top of the script we had Use Lamps=1, and lamps are updated 60
times/second.
Now, I also wanted to handle the backglass lamps for this machine. There is a game over lamp and an 'extra ball'
lamp on the backglass. Because I chose to use emreels to handle those instead of light objects in vp, I have a
callback line next. There are three extra callback types that are available by default for your use. If
LampCallback exists, it will be executed whenever a lamp state changes in the update cycle of the emulation (up to a
maximum of 60 times/second), motorcallback is executed if it exists, every time a solenoid state changes, and
GICallback is called whenever GI states change.
As soon as a lamp changes state, visual pinball updates the display of the corresponding lamp (if it exists), and
then executes the routine specified to the right of GetRef -- get reference, in this case UpdateMultipleLamps. First
I check the state of controller.lamp(2) -- If the current state of that lamp isn't the same as the last known state
of the lamp, then if lamp 2 is on, I set emreel1 to the on frame, which is frame 1, and if it is off, I set it to 0
for the off frame. I had initialized the O2 value to 0 (off), and EMReels are always rendered by default with Frame
0 visible, which just so happens to be off for my graphic image. Finally, I save the current value of lamp 2 to the
new last read value of lamp 2, and then run the check for lamp 3.





Top
Contributor











are all trademarks of VPFORUMS.