· 9 years ago · Nov 08, 2016, 07:36 AM
1' Müslifresser Softworx proudly presents...
2'
3' B O O M E R A N G
4'
5' a programming example (v1.4)
6'
7'
8'pinball machine design (c)1974 by Bally
9'version for Visual Pinball copyleft 2001 by CYBERYOGI =CO=Windler
10
11'This is seed-ware; with this I spread my knowledge to the mankind though that
12'people can learn to make other great pinball emulations too,to preserve the
13'experience of realizing and playing these beautiful historical games in virtual
14'form and enable everybody to play them again...
15'
16'
17'SOURCE CODE:
18'This program was mostly written by myself but partly inspirated by the horrible
19'spaghetti-code of an egomanic moron who really doesn't deserve to be mentioned
20'here.
21'
22'This source code is a piece of literate programming and should be quite self-
23'explanatory (I hope :-) ) and even be suited as a tutorial example. I hope
24'this will help other people to faithfully port and create many other good
25'pinballs games on Visual Pinball.
26'
27'This program is not really object oriented, but I attempted to make it as
28'human-readable as possible. I therefore added lots of comments, gave most
29'variables and functions understandable names and used functions for all code
30'sequences those have a special meaning or are used multiple times. Although
31'it would be conceptionally even more canonical, I did not allways use 1
32'function per light to encapsulate its state (on or off) and the corresponding
33'variable, because most lights are switched at only few places in the code,
34'which stays understandable also without tons of functions.
35'
36'I don't know how to do object orientation in Micro$ofts crappy Visual Basic,
37'nor if the dialect of Visual Pinball would supports it at all. The last time
38'I programmed in such a BASIC was on my Amiga in the very slow dialect Amiga
39'BASIC 1.2 (yes, from Micro$oft), which definitely included no object orien-
40'tation. But at least I used functions in the style of "setter methods" for
41'all things those have a side effect (e.g. updating an on-screen counter or
42'playing a sound) to achieve a consistent behaviour. To read values of vari-
43'ables, I otherwise did not use functions in the style of "getter methods",
44'but read them directly, because I hope that this works in this BASIC without
45'nasty multi-threading side effects.
46'
47'Notes:This game first jerked a lot on my AMD K6 300MHz because the resting pins
48' of the captive bals were badly placed and surfaces badly aligned, which
49' triggered numerical instabilities of Visual Pinbal those made these
50' balls continuously bounce around and never come entirely to rest. Though
51' accidently the CPU time of a 3er multi-ball play was consumed even when
52' only one ball was moved by the player. Small changes of the pin shape
53' and position reduce this problem dramatically,but any banal modifications
54' of "Wall" objects on the playfield can make it neccessary to re-adjust
55' the resting pins again.On my PC it still runs not perfectly smooth;espe-
56' cially the timing of rhythmic sounds is often quite off.
57'
58' Some sounds are based on another simulation of this games,and additional
59' sounds were added from the Visual PinMAME default sample set. (They are
60' likely inaccurate, but at least similar to a mechanical Bally pinball
61' machine. I don't know if there were bells at all in Bally games of that
62' era.) There are still some state wheel sounds fake and the volume ratios
63' are bad,too. The coin sounds were faked by me.Various additional sounds
64' were donated by Frank Menzler (thanks).
65' The solenoid mains hum and buzz sounds were hand-made by me;I hope I
66' didn't exaggerate them.But remember; an EM pinball hardware is NOT one
67' of these gentle,harmless,noiseless 3.3V CMOS highly integrated microchip
68' thingies where you fearlessly can touch contacts with bare finger during
69' operation. Its nothing cute,but it is a loudly rumbling,mechanical,mains
70' operated monstrosity consisting of rattling,revolving contact wheels and
71' lots of green sparks spitting relays - and now don't come in mind to
72' imagine these relays as those 2cm small,airtight sealed acryl plastic
73' boxes with tiny,gold plated contacts inside you may know from your local
74' electronics store - no,these relays are a bunch of finger-long,corroded
75' looking metal leaf switches without any cover or protection,and inside
76' the machine you can smell the odour of slowly toasting metal,pressboard
77' formaldehyd and a little ozon that lets no occasion untried to make the
78' silver balls less shiny and to attack the playfield rubber parts when
79' they are made from pure noble natural latex. (Sorry for these explicit
80' utterances,but there seem to be many PC users around those can't imagine
81' that there was ever anything digital based on boolean logics besides
82' multi-layer SMD mainboard equipped microprocessor operated micro-elec-
83' tronics.)
84'
85' In this code I often use the term "lamp" rather then "light" to name
86' objects,because "light" can als be a verb which could sometimes result in
87' confusion. With "light" I usually mean rather the actual state of a
88' lamp or group of lamps than the physical lamp itself.
89'
90' This program includes 2 switchable game rule sets - one is the authentic
91' version from a real Bally "Boomerang" machine,the 2nd is a more challen-
92' ging rule set created by me.The reason for this is that I originally in-
93' tended to emulate the original pinball game as close as possible,but by
94' the lack of access to the real machine I had partly defined my own rules.
95' Later I was contacted by Jeff Stuecheli who explained me the original
96' rules and behaviour of his "Boomerang" machine,just to discover that
97' these authentic rules sounded very boring and way too easy to me.Though
98' I decided to implement 2 switchable rule sets to make it possible to keep
99' my old rules and despite provide an authentic emulation of the original
100' game rules by Bally.I hope this doesn't complicate the source code too
101' much;the program switches by a key press between both rule sets using the
102' variable "ruleSet".Its value RS_authentic (=0) selects the authentic
103' Bally rules,while the value RS_enhanced (=1) selects my own rule set.(If
104' you intend to use source code parts for other games without rule selec-
105' tion,you can remove the conditional statements those test the "ruleSet"
106' value and simply use unconditioned statements for your game rules instead.)
107'
108' Various options of the game can be adjusted in the "operator settings"
109' section below.Many additional game machine dependant parameters can be
110' changed in the "pinball machine constants" section;some depend on actual
111' given game configurations (number of score displays on the backbox etc.)
112' but others (especially boolean flags) often can be changed easily without
113' modifying anything else. These are especially interesting when some
114' generic behaviour details of a pinball machine are currently unknown and
115' shall be adjusted later when anybody else finds them out.
116'
117' On the table a huge decal that simulates the rims of the acryl parts,
118' hinders the view onto the playfield.If you want to learn how this
119' table is built (or just modify it),best first write down its coordinates
120' etc. and remove the thing(resize to 10*10 or so works best and also speeds
121' up rendering time - the original values in "Boomerang" are width 927.313,
122' height 1503.13,rotation 0.) To adapt this program to a different pinball
123' game,I strongly recommend to copy the new parts into this program and not
124' vice versa because Visual Pinball 3 yet supports no export of graphics/
125' sounds and (worse?) it is impossible to copy playfield objects with keeping
126' their names,which makes modifications very awkward.
127'
128'TECH INFO: (This is the world first EM pinball emulator!)
129'
130'This piece of software is the possibly world first open source attempt to
131'EMULATE the inner working of an electro-mechanical pinball machine. (The
132'commercial PC game Amtex "Royal Flush" also looks like an emulation,but I am
133'not sure.)
134'
135'Electro-mechanical pinball machines contain neither ROMs nor anything computer-
136'like,but my idea was to simulate at least the behaviour of the score motor (the
137'camshaft-based main command sequencing unit in such a machine) to reproduce the
138'general timing behaviour of the circuit. When correctly implemented,this
139'automatically should e.g. play all sounds of the mechanism with the right timing
140'(except on my slow PC :S ) and also reproduce flaws like that certain targets
141'don't score points when they are hit while the motor is still running.Though
142'every mechanical sound you hear in this game is caused by an action performed
143'by the internal mechanism (and not just because "it sounds cool" to play a pre-
144'recorded sample of the entire reset sequence before the game starts).Listen
145'carefully e.g. to the state wheel reset noises - they really depend on what the
146'previous wheel rotation angle was.
147'
148'(If you have no clue of what I talk about,then urgently take a look at the
149'great pinball repair site at http://marvin3m.com to learn how a real electro-
150'mechanical pinball machine works internally.)
151'
152'This emulation is certainly not very accurate,because I have neither access to
153'pinball schematics nor to the real pinball machine nor I have time to work
154'myself through EM pinball schematics (those are said to be really cryptic).
155'Though I did not emulate every single relay,although most of them certainly
156'have an equivalent in my code.Also the dependencies among the relays are
157'not modelled when unknown or not important,because I don't know how to easily
158'express in BASIC that when e.g. a relay turns a lamp off but a closed switch
159'is wired in parallel it still supplies the lamp,that then the lamp must
160'not turn off. My relays are just binary flags,though everything that shall be
161'done by them must be done by hand in the code whereever their state is changed.
162'
163'Certainly I know well about boolean logics in BASIC,but for making a perfect
164'emulation it would be necessary to write somekind of digital circuit simulator
165'(like PSPICE) which can be fed with the relay wiring table from the original
166'pinball machine schematics, and then would simulate the states of all relays
167'and voltages on every single line when the states of the input lines change.
168'Such an approach would be generally possible. (In the book "Structure
169'and Interpretation of Computer Programs" by Abelson and Sussman is a Scheme
170'(LISP) program listing explained that does something similar.) But I have
171'not the slightest imagination how the network-like wiring dependencies among
172'relays could be described in a non-objectoriented BASIC program without ending
173'up in a terrible chaos of a thousand of arrays.
174'
175'The game logics description in an exact circuit emulator would also become a
176'very abstract and badly understandable piece of code that would be almost
177'useless for hobby programmers without schematics. Also because I wanted to
178'write an understandable program that everybody can modify to add game rules
179'or to simulate completely different mechanical pinball games with,I did not
180'try to code such a number table driven monstrosity.
181'
182'I saw now that it is possible to use classes in this BASIC and that there
183'is even an "eval" command to implement a thunk concept (executing strings as
184'code),which may be enough to port Abelson and Sussman's digital circuit
185'emulator,but I have no time at the moment.If anybody else wants to implement
186'a physically perfect emulation program - you are welcome to write it!
187'
188'
189'Notes:I have not much time to answer questions about programming,nor to become
190' coordinator of an open source team.I wrote this because it needed to be
191' done,but now I have to prepare myself again for my thesis(development of
192' a gesture and sing controlled VR music synthesizer program).I hope you
193' can understand this source code without too much e-mail questions.
194'
195' And for people those still don't get it: All the "TM" and "®" signs for
196' fancy named features of this souce code etc. are a HOAX to kid capitalism.
197' This is an open source project and free for everybody.Although it is
198' almost unbelievable,some people indeed took this for real and flamed me
199' because they thought it would be a nasty anti-open-source measure.In re-
200' ality registering a trademark is (at least in Germany) a costly bureau-
201' cratic act that takes at least some months and (by my knowledge) costs
202' a fee of multiple hundred (when not thousands) of dollars. Thus trademar-
203' king random words for code features of a free open source program is such
204' a ridiculously absurd idea that I never came in mind that it was even re-
205' motely possible that anybody could take this for real.I am a strict anti-
206' capitalist and fight against any approaches to patent software algorithms
207' or human genes or to trademark commonly used words to beat profit out of
208' them and the like. The only genuine trademarks in my VP games are the
209' ones those were made by the pinball machine manufacturers themselves.
210'
211'
212'CREDITS:
213'
214'Many thanks to Randy Davis,who created Visual Pinball and though made my work
215'and the revival of all these great historical pinball games possible.(What a
216'pity that there is no Linux version...) Also thanks to PeBo,who find out the
217'trick how to simulate a realistic looking mechanical reel score counter,to
218'Kurt Herman for the idea of round voxel graphics 3D targets and thanks to Boris
219'(?) for the highscore save and load routine.Thanks to Frank Gigliotti for the
220'tip and picture for the shiny ball.Also thanks to the Visual PinMAME team for
221'some sounds and especially very much thanks to Frank Menzler who donated the
222'great Bally mechanism sound sample set from his nice "Fireball" VP game and
223'also helped me with details about the Bally hardware.Thanks to Jeff Stuecheli
224'for the detailed explanation of the original "Boomerang" game rules.
225'
226'No thanks at all to an egomanic moron who refuses any programming help and
227'flamed me instead by his disgustingly self-rightous false pride.(Though
228'never forget - boomerangs always come back... ;-) )
229'
230'
231'LICENCE: ("Seedware licence v1.1 for Visual Pinball games")
232'
233'This is seed-ware; with this I spread my knowledge to the mankind though that
234'people can learn to make other great pinball emulations too,to preserve the
235'experience of realizing and playing these beautiful historical games in virtual
236'form and enable everybody to play them again.
237'
238'For video arcade games there is MAME,for home computers and consoles there is
239'MESS and also computerized pinball machines have been emulated with PinMAME,but
240'yet there was no solution for electro-mechanical pinball machines.With this
241'work I hope I can help to close the gap by providing a sort of framework for
242'building other emulations from.
243'
244'You are permitted and encouraged to use parts of this source code or other
245'parts of this game in other Visual Pinball tables and publish them for non-
246'commercial purposes, so far you give me (CYBERYOGI =CO=Windler) credits and
247'include the URL of my site (see below) into the derived work.
248'
249'You are also permitted to port this code to other programming languages and
250'hardware platforms to hopefully soon get rid of that "MS"-desease it is yet
251'still bound to.If you publish a modified version of a seedware game that was
252'created by someone else,you must use a different file name than the original
253'work to prevent confusion,and you also must include your e-mail adress and a
254'download URL or other description to enable people to obtain the unmodified
255'version of it.
256'
257'All source code parts must be documented using code comments (in the style of
258'literate programming) in a way that other programmers can understand them.When
259'you change code parts,then you must also update the documentation to explain
260'the changes.
261'
262'If you emulate a pinball machine that has a certain feature (e.g. playfield
263'illumination) but you are too lazy to implement it in your playfield and
264'backbox model,then you must NOT delete the corresponding routines from this
265'source code,but instead implement them as empty,commented dummy routine to
266'permit everybody to easily add the missing features later.
267'
268'This program and its components are distributed "as is" and come with absolute-
269'ly no warranties.
270'
271'All copyleft and copyright messages of authors and co-authors must be kept in-
272'tact.
273'
274'This seedware and its derived works can NOT be sold for profit without my ex-
275'plicit written permission and the permission of all co-authors.
276'
277'All new written program code that includes portions of this code or is based
278'on it (so far published) must also be releases as open source under this
279'license to follow the universal law of software and let the seed grow and
280'multiply.
281'
282'(If you make such works,please inform me by e-mail because I would like to
283'receive a copy of your work too.But never(!) e-mail to me a VPT file itself
284'or other huge data without my prior explicit request,because huge files
285'could result in a mail box crash which could endanger me to loose e-mails.
286'Instead better e-mail me a download URL of your VPT file.)
287'
288'When you make a new pinball game derived from this seedware,you have the right
289'to exclude art components of your own work from open source in a way that they
290'may not be modified and/or not removed from your pinball game and/or not in-
291'cluded into different pinball games,so far these components are mainly copyrigh-
292'ted by you and NOT substancially based on the artwork of a given pinball ma-
293'chine.The here described "art components" cover only pictures,text and music
294'elements - they can never cover technical parts of the source code nor technical
295'playfield objects nor sound samples taken from a pinball machine.
296'
297'So far you use seedware code to upgrade a given Visual Pinball game that initi-
298'ally was created by 3rd persons (not by you) under a different open source li-
299'cence,then you must mention the game's original author and explain in the merged
300'code that the original parts of the game are not seedware and provide a source
301'(e.g. download URL) to enable people (e.g. other programmers) to obtain the un-
302'modified version under its original licence unless the initial creator of the VP
303'game accepts the upgraded version as the new official release of his game and
304'allows to change the new version's licence entirely into seedware.
305'
306'It is FORBIDDEN to use parts of seedware for purposes or products those severely
307'glorify brutality or senseless brain destruction(e.g. alcohol,glutamate products
308'or other drugs),cruelty,environmental destruction,machismo or other socially
309'harmful things.
310'
311'If you intend to remake a historical pinball game that deals with such a topic,
312'you may possibly get my permission by e-mail, but in this case you must
313'include a visible warning into the game that its topic is ok only within a
314'piece of pinball game history but not in reality.
315'
316'The following messages and sayings must not be modified or removed from this
317'game:
318'
319'-the copyleft message including "by CYBERYOGI =CO=Windler,teachmaster of
320' Logologie - the 1st cyberage-religion!"
321'-"ABANDON THE BRUTALITY!"
322'-the formula "V.R. HERE!" (hidden on the backdrop picture).
323'-the disclaimer text on the intro screen and that I respect shamanic cultures
324' and love this game's artwork.
325'-the use of "washers" instead of coins in texts of this game,including:
326' "Washers® - the arcade operator's least beloved foreign currency...".
327'
328'The Cyberyoga icon (the green,drawn line being with heart and smily) in this
329'game is copyrighted by me (=CO= Windler) and may not be modified or spread in
330'modified state without my permission. :)-E-8
331'
332'
333' MAY THE SOFTWARE BE WITH YOU!
334'
335'*============================================================================*
336'I CYBERYOGI Christian Oliver(=CO=) Windler I
337'I (teachmaster of LOGOLOGIE - the first cyberage-religion!) I
338'I ! I
339'*=============================ABANDON=THE=BRUTALITY==========================*
340' {http://www.informatik.fh-hamburg.de/~windle_c/e_index.html}
341'
342'e-mail: windle_c@informatik.fh-hamburg.de
343
344
345option explicit
346'(This option makes declarations of global variables strictly neccessary - it
347'needs a little more typing work but prevents awful to find bugs by misspelled
348'variable names.Due to all undeclared variables in BASIC are initialized with 0
349'or false before use,without this you otherwise would NEVER get errors by mis-
350'spelled variables those were intended to be anyway false or 0 - only the
351'program would behave wrong whithout any obvious reason."option explicit" is
352'the only protection against this.)
353
354On Error Resume Next
355ExecuteGlobal GetTextFile("controller.vbs")
356If Err Then MsgBox "You need the controller.vbs in order to run this table, available in the vp10 package"
357On Error Goto 0
358
359Const cGameName = "boomerang_1974"
360
361'operator settings
362'=================
363
364'for authentic game rules mode:
365const ballsPerGame0 =3 'max. 5 balls (The counter can't display more.)
366const specialWinsCredit0=false 'true: hitting the special wins a free credit
367const specialShootAgain0=true 'true: hitting the special wins an extra ball
368const specialWinsPoints0=false 'amount of points won by hitting a special (false=none)
369
370'for enhanced game rules mode:
371const ballsPerGame1 =3 'max. 5 balls
372const specialWinsCredit1=true 'true: hitting the special wins a free credit
373const specialShootAgain1=false 'true: hitting the special wins an extra ball
374const specialWinsPoints1=10000 'amount of points won by hitting a special (false=none)
375const topHoleShootAgain =15000 'bonus value to make tophole give a "shoot again" (false=off)
376const bonusClearsBumper =true 'true: bonus at topHoleShootAgain value turns center bumper
377 'light off.
378'common:
379const coinChute1Credits=1 'credits for inserting a small washer
380const coinChute2Credits=2 'credits for mid-sized washers
381const coinChute3Credits=12 'credits for large washers (=false "removes" a slot)
382const maxBonus =15000 'According to rules flyer the machine can only count to 15000.
383const specialLitValue =15000 'bonus value to light the captive ball lane specials(false=off)
384const knockerEnabled =true 'sounds a loud separate knocker when winning extra credits
385const tiltEnabled =true
386const tiltSensitivity =4 'higher value = lower sensitivity
387const tiltSwingDecay =0.2 'higher value = faster pendulum decay
388const initBonusIs1000 =true 'true: each round starts with bonus 1000.
389const digitMatchEnabled =true 'true: score digit matching can win free credits
390const extraDigitsEnabled =true 'true: small numbers display missing higher score digits
391const chimeForExtraDigits =true 'true: a gong is played for extra (overflow) digits
392const lossyScoreCounter =false'true: score counter skips recognizing too fast points to
393 'simulate mechanical limitations. (This simulates a nasty
394 'technical flaw - PLEASE keep this turned off!)
395const ballRollNoisesEnabled=true'If you don't like the ball roll noises or the game runs
396 'too jerky on a slow PC,set this false to disable.
397const highScoreSoundEnabled=true'plays sound effect after highscore
398const highScoreFileName ="Boomerang_highscore.txt"
399 'name of highscore save file,or false to disable save/load
400'-----------------------------------------------------------------------------------------------
401
402'Some general Visual Pinball tips:
403'
404'Before you design a Visual Pinball playfield,check that the picture is not
405'distorted;otherwise use a graphics program to remove the distortion.Due to
406'parallax effects higher playfield parts on photos tend to appear closer to
407'rims than low parts.Though e.g. the playfield bottom area(near the outhole)
408'which should be a vertical wall can look like a slanted floor area.Cut this
409'piece out and paste the rest of the photo together again to make the playfield
410'shorter.(Though nudging the ball up again will work better.)
411'In Visual Pinball the "Table" width and height parameters can be used to fix a
412'wrong width to height ratio.Also the relative playfield size in ratio to the
413'ball is determined this way.Adjust these values so that round bumper tops on
414'the photo will match with the shape of a real bumper.Use a Kicker hole to see
415'how large a ball is in ratio to the photo.
416'
417'Decals can be used on lamps or to display a high resolution detail on a low-
418'res background to save memory.To scale a decal for use as a playfield part or
419'backbox part,use the following formula:
420'
421'scaledLength = areaLength*(decalLength/pictureLength), where
422'
423' length = a height or width
424' areaLength = length of the entire playfield or backdrop in the editor
425' decalLength = length of the decal (in pixels)
426' pictureLength= length of the backdrop or playfield picture the decal was
427' cut out from (in pixels)
428'
429'(When you use a decal with n times the picture resolution,you have to devide
430'the result by n afterwards.)
431
432'In a finished pinball game at least the picture of the flippers,spinners and
433'bumpers should always be removed from the playfield floor photo,because they
434'make the game look really stupid.To stay capable to adjust these objects
435'correctly,set the Table picture to an unretouched "testFloor" version of the
436'picture file while editing the playfield.Use JPEG pictures instead of BMP
437'for all non-transparent textures to reduce the VPT file size.
438'
439'When you start making a playfield,never use the "outerWall" object for model-
440'ling playfield "buildings" unless you are entire sure that their height matches
441'the rim and that they indeed consist of a single block that is not separated
442'from the rim,because doing so makes later changes of these buildings extremely
443'difficult and also side colours can not be set separately when everything was
444'made from 1 piece.
445'
446'Normal buildings on the playfield should not be made of one solid block but
447'instead consist of at least 2 levels (middle and top) of those the middle
448'level has a lightbulbish beige colour while the top level has the plastic
449'texture on it.In its simplest form this is really easy to do;simply copy the
450'building with cut and paste to the same place,and then change the height
451'of the top part to about from bottom 48 to top 50 and the height of the middle
452'part to about bottom 23 to top 27.Set the top colour of the resulting pane to
453'about RGB 238,205,142.This simple trick makes playfields look much more
454'realistic without much effort.
455'
456'It looks even better to also add cylindrical posts between these panes,but
457'don't use excessively many control points for them,because they can slow down
458'Visual Pinball.The modelling of playfield objects those can neither be seen nor
459'hit with the ball should be avoided or kept simple to keep the count of control
460'points low for better performance.
461'
462'For further important tips about playfield colour adjustment and lighting see
463'resetLights() and lightCenterBumper().
464'
465'For normal playfield "Wall" parts use elasticity 0.3,for fixed rubbers 0.4 and
466'to the free,straight part of long rubbers add a slingshot property with sling-
467'shot force 3 to get correct bouncing behaviour.Also targets should have an
468'elasticity value about 0.35 to simulate the rebounce force of the leaf spring
469'behind them.
470'
471'To make a playfield appear realistic,a view angle about "Inclination 31" and
472'"Field Of View 66" should be used,because only this way the length of the
473'playfield stays realizable.Many VP games try to make the playfield area on the
474'screen bigger by choosing values those let the front and back edge of the table
475'appear with the same width.But this makes the table look almost square and the
476'spacial depth of the playfield gets completely lost.When in the opposit the
477'front edge of the playfield occupies the entire screen to maximize the width
478'by a very flat angle,the game becomes badly overviewable and looks like watched
479'by a child.(This may remind your childhood pinball memories,but playing is dif-
480'ficult this way.) Also top view should be avoided,because it is very unrealistic
481'and uses only a tiny fraction of the screen for the playfield which makes it
482'look very small and unspectacular.When a player wants to take an undistorted
483'top view look onto the playfield,he should download a pinball flyer photo
484'instead or watch the playfield in the editor. The playfield view angle during
485'game is intended for playing and not for watching it in all details.
486'To simulate a left and right playfield rim makes the machine look much more
487'realistic;unfortunately a Visual Pinball bug displays any objects closely left
488'or right outside the playfield bounds only in black.To define the table size
489'wider would be possible,but this way all table texture pictures would need to
490'be extended with a blank left and right area too.
491'
492'The captive ball lanes in this game have a kink in their outer wall - this
493'is a trick to make it more difficult to shoot their ball up to the top,because
494'these lanes are intended as skill shots and though the the kink slows down the
495'captive ball when it isn't hit at a particular angle.
496'
497'The way a backglass shall be displayed depends very much on its motive.
498'Important is that the score counters etc. must not become too small.In this
499'game("Boomerang") I devides the backglass landscape into 2 halfs and addeded
500'some missing graphics in the middle to create a rather impressionistic than
501'realistic appearance of the game.But for other backglasses with more central
502'motives this concept can also look really awful (look e.g. at the terribly
503'mutilated graphics of VP "Magnotron" v1.1 by Hugh).For such backglasses the
504'central picture should rather stay in 1 piece,but the score counters etc.
505'in this case should not be displayed simply on the backglass,because this
506'would make them way too small.Important is not that the backglass appearance
507'stays completely authentic,but rather that the athmosphere of the game is
508'kept intact.Way too small displays disturb this much more than a reasonably
509're-arranged backglass layout.
510'
511'When you use textboxes,always check how they look at different screen
512'resolutions;due to the font size can slightly vary at different resolutions,
513'sometimes characters or even entire words can disappear when they don't fit
514'into their textbox at certain resolutions.Therefore make textboxes better a
515'bit too large than too narrow.
516
517
518'pinball machine constants: (for code portability)
519'--------------------------
520const maxCredits=25 'My Bally "Champ" could count credits up to 25(?).
521const maxPlayers =4 'player count of this machine
522const maxBalls =5 'range of the ball counter on this machine
523const matchedScoreDigit=1 'digit that is compared during matching
524const bonusStepWidth=1000 'amount by that the bonus ladder advances
525const coinMech1Time=500 'delay time for inserted coin (must be >0)
526const coinMech2Time=500
527const coinMech3Time=500
528const spinnerResetTime=7 'speed of the spinnner state wheel reset.
529const playerResetTime=14 'speed of the player state wheel reset.
530const playerUpResetTime=12 'speed of the player-up state wheel reset.
531const ballCounterResetTime=16 'speed of the ball counter state wheel reset.
532const creditAddTime=69 'speed of the credit counter wheel
533const ballTexture="shinyBall" 'picture for the balls
534const knockerKnocksAlways =false 'true: knocker knocks also by insert coin
535const smallCoinStartsGame =true 'true: inserting a 1 credit coin behaves like start button press
536const overPayBlocksChutes =true 'true: at maximum credit coins are rejected
537const startButtonQuitsGame =true 'true: a running game can be reset by start button presses
538const highScoreEnabled =true 'when the game contains a highscore display
539const shootAgainStaysLit =true 'true: after next ball "shoot again" lamps stay lit
540 'until scoring points,else they switch off immediately
541const restartWhen0Pts =true 'true: player shoots again when ball scored no points
542const tiltClearsPilotLamps =true 'true: tilt clears the playfield pilot lamps (typical for Bally)
543const tiltClearsIllumination=true 'true: illumination switches off when tilt
544 '(this is not authentic Bally,but looks great)
545const tiltPreventsExtraBall =true 'true: during tilt no new extra ball can be won
546const tiltVoidsExtraBall =false 'true: like above,but a won extra ball is void by tilt
547const tiltVoids0PtsRestart =true 'Normally any player can shoot again when the round
548 'scored no points. When true a tilt prevents this.
549const tiltClearsDoubleBonus =true 'true: counts down bonus ladder faster when tilt
550const tiltPreventsAddBonus =true 'true: bonus ladder can't increment during tilt
551const noTiltDuringBonus =false 'true prevents tilt during bonus ladder countdown
552const credit0UnlightsCounter=true 'true: credit counter light switches off when credit=0
553const creditOverpayBounce =false 'true: credit counter reel rattles by overpaying
554
555'score counter mechanism:
556const minCounterNr =1 'set this 0 when an additional score counter 0 exists;
557 'e.g. as a highscore display
558const reelsPerCounter=5 'digits per score counter
559const reelSpinTime =55 'normal score counter reel rotation speed (was 55)
560const reelCarryTime =5 'faster score counter reel speed for carry (was 5)
561const scoreMotorTime =24 'speed with that the score motor advances
562 '(I don't know if this is mains frequency dependant,because many
563 'sound samples in VP games click with different speeds.)
564const motorStepWidth =10 'degree with that the simulated score motor wheels
565 'turn by each timer step (Never change this unless you
566 'adapt the camshaft simulations too!)
567const reelChimeForCarry=false 'true: play score chime also for digits flipped by carry
568const maxScoreReelNoises=9 'number of sound samples for clicks of multiple reels
569
570'game rule sets: '(These are the values for ruleSet.)
571const RS_authentic=0 'the original Bally "Boomerang" rule set (too easy =>boring)
572const RS_enhanced =1 'enhanced game rules by =CO=Windler (more challenging)
573
574
575'declarations of global variables:
576'---------------------------------
577' Variables defined within procedures stay local. Local variables for internal
578' use within procedures must never be declared here, because this would make
579' them global and though could result in ugly side effect bugs due to a pinball
580' game uses massive multi-threading.Variables become global when they are either
581' declared or first used (assigned) outside all procedures.
582
583'mechanical score counter variables:
584dim scoreDigit (4,5) 'holds the digit values;highest index stores all extra digits
585dim carry (4,5) 'carries are 1 when 1 will be added to next digit,else 0
586dim stuckScoreReel(4,5) 'counting semaphores for modelling stuck counter reels
587dim scoreReelTop (4,5)
588dim scoreReelMain (4,5)
589dim scoreReelLow (4,5)
590
591 'These arrays must have the numerical dimension values of (maxPlayers,
592 'reelsPerCounter);inserting the constant names directly doesn't work in this
593 'crappy Micro$oft BASIC.
594
595dim scoreReelBorder(4,2)
596 'Array must have dimensions scoreReelBorder(maxPlayers,2).
597
598dim scoreReelChime(5)
599 'Array must have dimensions of reelsPerCounter.
600
601dim scoreReelNoise(9) 'sample name strings for 1 to 10 simultaneous reels clicks
602 'Array must have dimensions of maxScoreReelNoises
603
604dim scoreAddTimer(4)
605dim scoreSpinTimer(4)
606dim reelTime(4) 'with this speed the counter reels rotate
607dim unaddedScore(4) 'this buffers the remaining score to add
608dim scoreIsCounting(4) 'flag signals that a player's counter has not finished counting yet
609 'These arrays must have dimensions of maxPlayers.
610
611dim scoreResetNumber 'will contain as many "1" as value of reelsPerCounter
612
613dim reelClicks 'actual count of counter click noises to play
614dim stuckReelsBuzz 'actual count of stuck reels to simulate solenoid buzz
615
616'relays:
617
618dim gameOver 'true when game over
619dim gameIsResetting '"reset relay", true when the counters etc. are resetting
620dim tilt 'true when game in tilt state
621dim newRound 'true when bonusses are collected by score motor
622dim matching 'true during end game digit matching
623dim creditTaken '"credit relay", true when a credit from the counter is
624 'used to start the game)
625dim illumination 'true when the general illumination(plastics etc.) is lit
626dim centerBumperLit
627dim doubleBonus
628dim specialHit
629dim extraBallWon
630dim shootAgain 'when the actual round is a shoot again
631dim add500 'relays for points to be added by the score motor
632dim add3000
633
634dim A 'target A..D hit
635dim B
636dim C
637dim D
638dim A_lock 'these prevent adding Bonus twice when captive ball rolls back
639dim B_lock
640dim C_lock
641dim D_lock
642
643'other flags:
644
645dim reelChimeEnabled 'signals that counter digits play a chime sound
646dim playfieldLightsEnabled 'true enables the various pilot lamps on playfield
647dim backboxLightsEnabled 'true enables various display lamps in the backbox
648dim creditsAddable 'true when credit counter is ready to add new credits
649dim ballInTopHole
650dim ballInDrain
651dim keepMotorRunning 'lets the score motor turn another round
652dim leftExtraBallLit
653dim rightExtraBallLit
654dim rollover1kLit
655dim flippersEnabled
656dim leftFlipButton 'true when button pressed
657dim rightFlipButton 'true when button pressed
658dim leftFlipperUp 'true when the flipper is up
659dim rightFlipperUp
660dim ballBefore10p1 'true when ball lies in front of the center rubbers
661dim ballBefore10p2
662dim ballBefore10p3
663dim ballBefore10p4
664
665'stuck score reel semaphores: (1 per rollover switch with changeable score)
666
667dim triggerLeft1kStuck
668dim triggerRight1kStuck
669
670'other state variables:
671dim scoreMotorAngle 'position of the score motor camshaft wheels
672 '(0 when motor is at stand-still position)
673dim highScore(2) 'highscores for both rule sets
674dim rulesSwitchable 'true while rules can be switched (not during game)
675dim ruleSet 'the currently active rule set (0=authentic,1=advanced)
676dim bonus
677dim tiltSwing
678dim tiltHasEffect
679dim spins
680dim ballCount
681dim credits
682dim specialLit
683dim currentPlayer
684dim players
685dim unaddedCredits
686dim matchNumber 'actual pseudo random number of the matcher state wheel
687dim knockerActive 'false when inserting coin to disable the separate (loud) knocker
688dim ballCreated
689
690'program initialization:
691'-----------------------
692highScore(RS_authentic)=0
693highScore(RS_enhanced )=0
694ruleSet=RS_authentic
695scoreMotorAngle=0
696keepMotorRunning=false
697triggerLeft1kStuck =0
698triggerRight1kStuck=0
699credits=0
700unaddedCredits=0
701creditsAddable=true
702creditTaken=false
703knockerActive=true
704matching=false
705illumination=true 'else setIllumination false will be ignored
706tiltSwing=0
707tiltHasEffect=false
708tilt=false
709If B2SOn Then
710Controller.B2SSetTilt 33,0
711Controller.b2ssetdata 1,1
712End If
713leftFlipButton =false
714rightFlipButton=false
715flippersEnabled=false
716gameOver=true
717players=1
718currentPlayer=1
719ballCount=1
720ballBefore10p1=false
721ballBefore10p2=false
722ballBefore10p3=false
723ballBefore10p4=false
724
725ballInDrain=true
726ballCreated=false 'no ball on playfield created yet.
727
728matchNumber=int(10*rnd())
729spins=int(11*rnd())
730'In a real pinball machine these values stem from the previous game.
731
732
733sub theTable_init()
734 LoadEM
735' This is called by the pinball table at program start to generate the 2
736' captive balls etc.
737 setIllumination false
738 scoreMotor.interval=scoreMotorTime
739 clickTimer.interval=2
740 spinnerReset.interval =spinnerResetTime
741 playerCounterReset.interval=playerResetTime
742 playerUpReset.interval =playerUpResetTime
743 ballCounterReset.interval =ballCounterResetTime
744 creditAdder.interval =creditAddTime
745 coinMech1.interval=0
746 coinMech2.interval=0
747 coinMech3.interval=0
748
749 initializeScoreCounters
750
751 leftCBallKicker.createBall.image=ballTexture
752 leftCBallKicker.kick 90,6
753 rightCBallKicker.CreateBall.image=ballTexture
754 rightCBallKicker.kick -100,6
755
756 tiltTimer.interval=200
757 tiltTimer.enabled=tiltEnabled 'true or false
758
759 showKeySettings
760 shootAgainText.text=""
761 If B2SOn Then Controller.B2SSetShootAgain 36, 0
762 setEndMatchLights(-1)
763 If B2SOn Then Controller.B2SSetBallInPlay 32, 0
764 if highScoreFileName<>false then LoadGameStats UserDirectory,highScoreFileName
765 spinCreditReel
766 doGameOver
767 setGameRules ruleSet 'updates the rules display
768
769 showIntro.enabled=true 'timer to pop up the intro text requester
770end sub
771
772
773'Button press and release routines:
774'----------------------------------
775
776sub theTable_KeyDown(ByVal keycode)
777
778' keysBox.text=keycode 'TEST!!!
779
780 'Coin inserted:
781 if keycode = 2 and coinChute1Credits<>false then coinMech1.enabled=true 'key "5"
782 if keycode = 3 and coinChute2Credits<>false then coinMech2.enabled=true 'key "6"
783 if keycode = 4 and coinChute3Credits<>false then coinMech3.enabled=true 'key "7"
784
785 'By setting a coinChuteXCredits constant false,a coin slot can be "removed" to
786 'simulate a machine with less coin slots.
787
788 'Start button pressed:
789
790 if (keycode = 2) and credits > 0 and not gameIsResetting then
791 'A start button press starts a new game after game over or adds a player
792 'to an already started game so far it is still playing the 1st ball.For
793 'both actions a credit is needed,that will be taken off the credit counter.
794
795 if startButtonQuitsGame or gameOver or (ballCount=1 and players<maxPlayers) then
796 'When startButtonQuitsGame is set false,a running game can be canceled by
797 'pressing the start button again when the maximum number of players already
798 'has been added or the ball counter has advanced beyond the 1st ball.
799
800 setRelay creditTaken,true
801 creditButtonRoutine
802 end if
803 end if
804
805 'select game rules:
806 if (keycode=50) and rulesSwitchable then 'key "M"
807 if ruleSet=RS_authentic then
808 setGameRules RS_enhanced
809 else
810 setGameRules RS_authentic
811 end if
812
813 playSoundMonophonic "diverter"
814 saveGame
815 end if
816
817 'rule sheet: (game rules)
818 if keycode = 19 then showRuleSheet 'key "R"
819
820
821 if keycode = plungerKey then
822 plunger.PullBack
823 end if
824
825 if keycode = leftFlipperKey then
826 leftFlipButton=true
827 if flippersEnabled then moveLeftFlipper true
828 if gameOver then setIllumination true
829 end if
830
831 if keycode = rightFlipperKey then
832 rightFlipButton=true
833 if flippersEnabled then moveRightFlipper true
834 end if
835
836 if keycode = LeftTiltKey then
837 Nudge 90, 2
838 nudged
839 end if
840
841 if keycode = RightTiltKey then
842 Nudge 270, 2
843 nudged
844 end if
845
846 if keycode = CenterTiltKey then
847 Nudge 0, 2
848 nudged
849 end if
850end sub
851
852
853sub theTable_KeyUp(ByVal keycode)
854
855 if keycode = plungerKey then
856 playSoundMonophonic "plunger"
857 plunger.fire
858
859 'Hint: When you have a combined sound sample of the plunger and ball rolling
860 ' noise,please split it up into 2 samples and only play the plunger sound
861 ' when no ball is in the firing chute - it sounds so silly when the plunger
862 ' always starts a ball rolling noise when there is no ball in the chute.
863 ' Instead place an overroll trigger in the chute that activates the rolling
864 ' noise sample only when touched by the ball itself.
865 end if
866
867
868 if keycode = leftFlipperKey then
869 leftFlipButton=false
870 if flippersEnabled then moveLeftFlipper false
871 end if
872
873 if keycode = rightFlipperKey then
874 rightFlipButton=false
875 if flippersEnabled then moveRightFlipper false
876 end if
877end sub
878
879
880'target hit routines: (called when a ball hits playfield objects)
881'--------------------
882
883sub centerBumper_Hit()
884 playSound SoundFXDOF("Bumper",107,DOFPulse,DOFContactors)
885 DOF 108, DOFPulse
886 if centerBumperLit then
887' playSound "Bell100"
888 addScore 100
889 end if
890end sub
891
892
893sub topHoleKicker_Hit()
894' triggered by top hole
895
896 stopBallRollNoises
897
898 rollover1kLit = true
899 setLight lampLeft1k ,true
900 setLight lampRight1k,true
901
902 lightCenterBumper true
903 'This is just a guess from Bally "Champ",because on the bumper stands "when
904 'lit", therefore there must be anything in the game to light it.
905 '
906 'I was told later that on the original pinball machine this bumper stays
907 'always lit,but I kept this in my enhanced rule set,because it is much more
908 'fun this way.
909
910 if ruleSet=RS_enhanced and topHoleShootAgain<>false and bonus>=topHoleShootAgain then setExtraBall true 'Shoot Again!
911
912 'This rule was created by me from Bally "Champ",because otherwise there would
913 'be no "real" extra balls in the game. The outlane lamps basically do not more
914 'than a kind of ball saver;they just temporably disable the outlane danger
915 'but never give an extra ball without loosing the current ball in play.
916 '
917 'The real pinball machine can instead make the captive ball lane specials
918 'earn an extra ball,therefore I use topHoleShootAgain only in my enhanced
919 'game rule set.
920
921 ballInTopHole=true
922 scoreMotor.enabled=true '3000 points are added by the score motor
923end sub
924
925
926sub leftSlingshot_slingshot()
927' playSound "bell10"
928 playSound SoundFXDOF("bigSlingshot",103,DOFPulse,DOFContactors)
929 DOF 105, DOFPulse
930 addScore (10)
931end sub
932
933sub rightSlingshot_slingshot()
934' playSound "bell10"
935 playSound SoundFXDOF("bigSlingshot",104,DOFPulse,DOFContactors)
936 DOF 106, DOFPulse
937 addScore (10)
938end sub
939
940
941sub spinner1_Spin()
942' playSound "bell100"
943 playSound "soloff2"
944 DOF 109, DOFPulse
945 if centerBumperLit then addScore 100 else addScore 10 'this rule is a guess
946 incrementSpinner
947end sub
948
949' The disc-shaped targets on the playfield are composite objects consisting of
950' multiple vertically intersecting rectangular blocks.The blocks intersect each-
951' other to keep the count of controll points reasonable low,because too many of
952' them can slow down the game by too much CPU load. Unlike round decal props
953' stuck on the backdrop over square targets in other games,these ones are ge-
954' nuine 3 dimensional objects those can be viewed at any angle.The targets have
955' a higher elasticity value than normal walls to simulate the rebounce of the
956' metal leaf spring contact.
957'
958' Due to the active part of these targets is as well their horizontal middle
959' stripe as the black center spot(bolt),there are 2 hit subroutines for each
960' target neccessary to recognize hits on both parts.In the below code I
961' therefore each time call the subroutine of the main part of the same target
962' when its center spot is hit,to make maintainance easier.The center spot
963' has always the same name as the target but with the prefix "target2" instead
964' of "target".
965'
966' Tip: You can copy these targets into other Visual Pinball games using copy
967' and paste.
968
969sub targetCenter1_hit()
970 msgbox("test")
971 playSound SoundFXDOF("soloff",112,DOFPulse,DOFTargets)
972 call hitCenterTarget
973end sub
974sub target2Center1_hit()
975 call targetCenter1_hit
976end sub
977
978sub targetCenter2_hit()
979 playSound SoundFXDOF("soloff",112,DOFPulse,DOFTargets)
980 call hitCenterTarget
981end sub
982
983sub target2Center2_hit()
984 call targetCenter2_hit
985end sub
986
987sub targetCenter3_hit()
988 playSound SoundFXDOF("soloff",113,DOFPulse,DOFTargets)
989 call hitCenterTarget
990end sub
991
992sub target2Center3_hit()
993 call targetCenter3_hit
994end sub
995
996sub targetCenter4_hit()
997 playSound SoundFXDOF("soloff",113,DOFPulse,DOFTargets)
998 call hitCenterTarget
999end sub
1000
1001sub target2Center4_hit()
1002 call targetCenter4_hit
1003end sub
1004
1005sub hitCenterTarget()
1006 addScore (100)
1007 if ruleSet=RS_authentic or centerBumperLit then incrementSpinner
1008end sub
1009
1010'These memorize at which side the rubber bands in the tribal center are hit:
1011
1012sub before10p1_hit() : ballBefore10p1=true end sub
1013sub before10p1_unhit(): ballBefore10p1=false end sub
1014
1015sub before10p2_hit() : ballBefore10p2=true end sub
1016sub before10p2_unhit(): ballBefore10p2=false end sub
1017
1018sub before10p3_hit() : ballBefore10p3=true end sub
1019sub before10p3_unhit(): ballBefore10p3=false end sub
1020
1021sub before10p4_hit() : ballBefore10p4=true end sub
1022sub before10p4_unhit(): ballBefore10p4=false end sub
1023
1024
1025sub rubber10p1_hit()
1026 if ballBefore10p1 and centerBumperLit then addScore 10
1027end sub
1028sub rubber10p1_slingshot() call rubber10p1_hit end sub
1029
1030sub rubber10p2_hit()
1031 if ballBefore10p2 and centerBumperLit then addScore 10
1032end sub
1033sub rubber10p2_slingshot() call rubber10p2_hit end sub
1034
1035sub rubber10p3_hit()
1036 if ballBefore10p3 and centerBumperLit then addScore 10
1037end sub
1038sub rubber10p3_slingshot() call rubber10p3_hit end sub
1039
1040sub rubber10p4_hit()
1041 if ballBefore10p4 and centerBumperLit then addScore 10
1042end sub
1043sub rubber10p4_slingshot() call rubber10p4_hit end sub
1044
1045
1046sub targetLeftCLaneEnd_hit()
1047 playSound SoundFXDOF("soloff",112,DOFPulse,DOFTargets)
1048 call hitSpecial
1049end sub
1050
1051sub target2LeftCLaneEnd_hit() call targetLeftCLaneEnd_hit end sub
1052
1053sub targetRightCLaneEnd_hit()
1054 playSound SoundFXDOF("soloff",113,DOFPulse,DOFTargets)
1055 call hitSpecial
1056end sub
1057
1058sub target2RightCLaneEnd_hit() call targetRightCLaneEnd_hit end sub
1059
1060
1061sub hitSpecial()
1062' This lets the player win an extra credit.Per round only one credit can be
1063' won. (This rule is a guess.)
1064 if specialLit then
1065
1066 select case ruleSet
1067 case RS_authentic
1068 if specialWinsPoints0>0 then addScore specialWinsPoints0
1069 if specialWinsCredit0 then addCredit
1070 if specialShootAgain0 then setExtraBall true
1071 case RS_enhanced
1072 if specialWinsPoints1>0 then addScore specialWinsPoints1
1073 if specialWinsCredit1 then addCredit
1074 if specialShootAgain1 then setExtraBall true
1075 end select
1076
1077 setLight lampLeftCLaneEnd ,false
1078 setLight lampRightCLaneEnd,false
1079 specialLit=false
1080 setRelay specialHit,true
1081 end if
1082end sub
1083
1084
1085sub targetRight500_hit()
1086 playSound "soloff"
1087' playSound "bell100-5"
1088 addScore500
1089end sub
1090sub target2Right500_hit()
1091 DOF 115, DOFPulse
1092 call targetRight500_hit
1093end sub
1094
1095sub targetLeft500_hit()
1096 playSound "soloff"
1097' playSound "bell100-5"
1098 addScore500
1099end sub
1100sub target2Left500_hit()
1101 DOF 114, DOFPulse
1102 call targetLeft500_hit
1103end sub
1104
1105
1106sub triggerLeft1k_hit()
1107 DOF 122, DOFOn
1108 playSound "soloff2"
1109 addscore 1000
1110 if rollover1kLit then addScoreDigitStuckBySema 3,triggerLeft1kStuck
1111 'This adds 1 to the 4th score counter digit in a way that the digit can get
1112 'stuck and hum so long a ball lies on the switch (see reel-a-listic score
1113 'counter description below).
1114
1115 if ruleSet=RS_authentic then incrementBonus
1116end sub
1117
1118sub triggerLeft1k_unhit()
1119 DOF 122, DOFOff
1120 playBallRollSound "ballRoll2"
1121 releaseScoreDigitBySema 3,triggerLeft1kStuck
1122 'This releases the stuck counter digit again.
1123end sub
1124
1125
1126sub triggerRight1k_hit()
1127 DOF 123, DOFOn
1128 addscore 1000
1129 playSound "soloff2"
1130 if rollover1kLit then addScoreDigitStuckBySema 3,triggerRight1kStuck
1131 if ruleSet=RS_authentic then incrementBonus
1132end sub
1133
1134sub triggerRight1k_unhit()
1135 DOF 123, DOFOff
1136 playBallRollSound "ballRoll2"
1137 releaseScoreDigitBySema 3,triggerRight1kStuck
1138end sub
1139
1140
1141sub triggerLeftCLaneMid_hit()
1142 DOF 117, DOFOn
1143 playSound "soloff2"
1144 select case ruleSet
1145 case RS_authentic
1146 addScoreDigitStuck 3
1147 incrementBonus
1148 case RS_enhanced
1149 addScore500
1150 end select
1151end sub
1152
1153sub triggerLeftCLaneMid_unhit()
1154 DOF 117, DOFOff
1155 if ruleSet=RS_authentic then releaseScoreDigit 3
1156end sub
1157
1158
1159sub triggerRightCLaneMid_Hit()
1160 DOF 120, DOFOn
1161 playSound "soloff2"
1162 select case ruleSet
1163 case RS_authentic
1164 addScoreDigitStuck 3
1165 incrementBonus
1166 case RS_enhanced
1167 addScore500
1168 end select
1169end sub
1170
1171sub triggerRightCLaneMid_unhit()
1172 DOF 120, DOFOff
1173 if ruleSet=RS_authentic then releaseScoreDigit 3
1174end sub
1175
1176
1177sub triggerLeftInLane_hit()
1178 DOF 125, DOFOn
1179 addscore 1000
1180' playSound "bell1000"
1181 playSound "soloff"
1182 incrementBonus
1183 addScoreDigitStuck(3)
1184end sub
1185
1186sub triggerLeftInLane_unhit()
1187 DOF 125, DOFOff
1188 releaseScoreDigit(3)
1189end sub
1190
1191
1192sub triggerRightInLane_hit()
1193 DOF 126, DOFOn
1194 addscore 1000
1195' playSound "bell1000"
1196 playSound "soloff"
1197 incrementBonus
1198 addScoreDigitStuck(3)
1199end sub
1200
1201sub triggerRightInLane_unhit()
1202 DOF 126, DOFOff
1203 releaseScoreDigit(3)
1204end sub
1205
1206
1207sub triggerLeftOutLane_Hit()
1208 DOF 124, DOFPulse
1209 playSound "soloff"
1210' playSound "bell100-5"
1211 checkExtraBall
1212 if leftExtraBallLit then
1213' lampLeft_XB.State = LightStateOff 'I doubt that the bonus wheel can do this
1214 setExtraBall true 'Shoot Again!
1215 end if
1216 addScore500
1217end sub
1218
1219
1220sub triggerRightOutLane_Hit()
1221 DOF 127, DOFPulse
1222 playSound "soloff"
1223' playSound "bell100-5"
1224 checkExtraBall
1225 if rightExtraBallLit then
1226' lampRight_XB.State = LightStateOff 'I doubt that the bonus wheel can do this
1227 setExtraBall true 'Shoot Again!
1228 end if
1229 addScore500
1230end sub
1231
1232
1233sub checkExtraBall()
1234 if bonus >= 15000 then
1235 leftExtraBallLit = true
1236 rightExtraBallLit = true
1237 end if
1238 if bonus = 8000 or bonus = 10000 or bonus = 12000 or bonus = 14000 then
1239 leftExtraBallLit = true
1240 end if
1241 if bonus = 9000 or bonus = 11000 or bonus = 13000 then
1242 rightExtraBallLit = true
1243 end if
1244end sub
1245
1246'A-B-C-D targets:
1247
1248sub target_A_hit()
1249 playSound SoundFXOF("soloff",114,DOFPulse,DOFTargets)
1250 addScore 1000
1251' playSound "bell1000"
1252 if not A then
1253 setLight lampTarget_A ,false
1254 setLight lampLeftCLane_A,true
1255 setRelay A,true
1256 checkDoubleBonus
1257 end if
1258end sub
1259sub target2_A_hit() call target_A_hit end sub
1260
1261
1262sub target_B_hit()
1263 playSound SoundFXDOF("soloff",114,DOFPulse,DOFTargets)
1264 addScore 1000
1265' playSound "bell1000"
1266
1267 if not B then
1268 setLight lampTarget_B ,false
1269 setLight lampLeftCLane_B,true
1270 setRelay B,true
1271 checkDoubleBonus
1272 end if
1273end sub
1274sub target2_B_hit() call target_B_hit end sub
1275
1276
1277sub target_C_hit()
1278 playSound SoundFXDOF("soloff",115,DOFPulse,DOFTargets)
1279 addScore 1000
1280' playSound "bell1000"
1281
1282 if not C then
1283 setLight lampTarget_C ,false
1284 setLight lampRightCLane_C,true
1285 setRelay C,true
1286 checkDoubleBonus
1287 end if
1288end sub
1289sub target2_C_hit() call target_C_hit end sub
1290
1291
1292sub target_D_hit()
1293 playSound "soloff"
1294 addScore 1000
1295' playSound "bell1000"
1296
1297 if not D then
1298 setLight lampTarget_D ,false
1299 setLight lampRightCLane_D,true
1300 setRelay D,true
1301 checkDoubleBonus
1302 end if
1303end sub
1304sub target2_D_hit() call target_D_hit end sub
1305
1306
1307sub checkDoubleBonus()
1308 if A and B and C and D then
1309 if tiltClearsDoubleBonus and tilt then exit sub
1310
1311 setRelay doubleBonus,true
1312 setLight lamp_DB ,true
1313 end if
1314end sub
1315
1316
1317sub triggerLeftCLane_A_hit()
1318 DOF 116, DOFOn
1319 playSound "soloff2"
1320 addscore 1000
1321 select case ruleSet
1322 case RS_authentic
1323 addScoreDigitStuck 3
1324 if A then incrementBonus
1325 case RS_enhanced
1326 if A and not A_lock then
1327 setRelay A_lock,true 'A_lock prevents twice bonus when ball rolls back
1328 incrementBonus
1329 end if
1330 end select
1331end sub
1332
1333sub triggerLeftCLane_A_unhit()
1334 DOF 116, DOFOff
1335 if ruleSet=RS_authentic then releaseScoreDigit 3
1336end sub
1337
1338
1339sub triggerLeftCLane_B_hit()
1340 DOF 118, DOFOn
1341 playSound "soloff2"
1342addscore 1000
1343 select case ruleSet
1344 case RS_authentic
1345 addScoreDigitStuck 3
1346 if B then incrementBonus
1347 case RS_enhanced
1348 if B and not B_lock then
1349 setRelay B_lock,true
1350 incrementBonus
1351 end if
1352 end select
1353end sub
1354
1355sub triggerLeftCLane_B_unhit()
1356 DOF 118, DOFOff
1357 if ruleSet=RS_authentic then releaseScoreDigit 3
1358end sub
1359
1360
1361sub triggerRightCLane_C_hit()
1362 DOF 121, DOFOn
1363 playSound "soloff2"
1364 addscore 1000
1365 select case ruleSet
1366 case RS_authentic
1367 addScoreDigitStuck 3
1368 if C then incrementBonus
1369 case RS_enhanced
1370 if C and not C_lock then
1371 setRelay C_lock,true
1372 incrementBonus
1373 end if
1374 end select
1375end sub
1376
1377sub triggerRightCLane_C_unhit()
1378 DOF 121, DOFOff
1379 if ruleSet=RS_authentic then releaseScoreDigit 3
1380end sub
1381
1382
1383sub triggerRightCLane_D_hit()
1384 DOF 119, DOFOn
1385 playSound "soloff2"
1386addscore 1000
1387 select case ruleSet
1388 case RS_authentic
1389 addScoreDigitStuck 3
1390 if D then incrementBonus
1391 case RS_enhanced
1392 if D and not D_lock then
1393 setRelay D_lock,true
1394 incrementBonus
1395 end if
1396 end select
1397end sub
1398
1399sub triggerRightCLane_D_unhit()
1400 DOF 119, DOFOff
1401 if ruleSet=RS_authentic then releaseScoreDigit 3
1402end sub
1403
1404
1405sub targetLeftCLaneStart_hit()
1406' triggered by left pin of left captive ball lane.
1407
1408 setRelay A_lock,false
1409 setRelay B_lock,false
1410 'The ball has rolled back and though the triggers are active again.
1411end sub
1412
1413sub targetRightCLaneStart_hit()
1414' triggered by right pin of right captive ball lane.
1415
1416 setRelay C_lock,false
1417 setRelay D_lock,false
1418end sub
1419
1420
1421'pseudo-targets: (these only simulate playfield noises)
1422'---------------
1423'Note: All ball rolling and -hit noises in this game are only an approximation
1424' simulated with invisible rollover targets.They therefore don't behave
1425' very realistic because volume and timbre neither react on the ball speed
1426' nor hit angle.
1427' For a proper simulation of these sounds it would be neccessary to give
1428' the floor and all other playfield materials a sound property depending on
1429' the actual ball speed and roll direction,which (by my knowledge) is yet
1430' impossible in Visual Pinball because there are no ways to measure the
1431' actual ball rolling speed and direction. At least the volume of hit noises
1432' should depend on the hit speed.
1433'
1434' Also the volume of hit/bump noises is unrealistic because there is no way
1435' to determine with how much force an object was actually hit.Though they
1436' either sound at their fixed volume or not at all,instead of sounding louder
1437' when hit harder.The "Hit Treshold" parameter adjusts at which ball speed
1438' a hit event shall cause a (sound) reaction.
1439'
1440'TIP: You can digitally re-sample sounds from other Visual Pinball games without
1441' external devices by using the Windoze audio recorder and the freeware
1442' wave loopback driver program "Virtual Audio Cable" by Eugene Muzychenko.
1443
1444
1445sub ejectChute_hit() playSound SoundFXDOF("ballin",129,DOFPulse,DOFContactors) end sub
1446
1447sub firingChute_hit() playSound "tinRoll":DOF 130, DOFPulse:end sub
1448 'should be different?
1449
1450sub beforePlunger_hit()
1451' playSound "soloff" 'should click differently?
1452 stopBallRollNoises
1453end sub
1454
1455sub beforeLeftCLane_hit() playSound "ballKlick" end sub
1456sub beforeRightCLane_hit() playSound "ballKlick" end sub
1457
1458sub metal1_hit() playSound "tinHit3" end sub
1459sub metal2_hit() playSound "tinHit3" end sub
1460sub metal3_hit() playSound "tinHit3" end sub
1461sub metal4_hit() playSound "tinHit3" end sub
1462sub metal5_hit() playSound "tinHit3" end sub
1463sub metal6_hit() playSound "tinHit3" end sub
1464
1465sub spinnerMetal1_hit() playSound "tinHit3" end sub
1466sub spinnerMetal2_hit() playSound "tinHit3" end sub
1467
1468sub protector_hit() playSound "tinHit3" end sub
1469sub gateTrigger_hit() playSound "tinHit3" end sub
1470
1471sub tin1_hit() playSound "tinHit2":call undropGuideWires end sub
1472sub tin2_hit() playSound "tinHit2":call undropGuideWires end sub
1473
1474sub damper_hit():stopBallRollNoises:playSound "bump5":end sub
1475
1476sub post1_hit() playSound "bump2" end sub
1477sub post2_hit() playSound "bump2" end sub
1478sub post3_hit() playSound "bump2" end sub
1479sub post4_hit() playSound "bump2" end sub
1480sub post5_hit() playSound "bump2" end sub
1481sub post6_hit() playSound "bump2" end sub
1482
1483sub postL1_hit() playSound "bump5" end sub
1484sub postL2_hit() playSound "bump5" end sub
1485sub postL3_hit() playSound "bump5" end sub
1486sub postL4_hit() playSound "bump5" end sub
1487sub postR1_hit() playSound "bump5" end sub
1488sub postR2_hit() playSound "bump5" end sub
1489sub postR3_hit() playSound "bump5" end sub
1490sub postR4_hit() playSound "bump5" end sub
1491
1492sub leftSlingshot_hit() playSound "bump5" end sub
1493sub rightSlingshot_hit() playSound "bump5" end sub
1494
1495sub tinRoll1_hit() playBallRollSound "tinRoll" end sub
1496sub tinRoll2_hit() playBallRollSound "tinRoll" end sub
1497
1498sub roll1_hit() playBallRollSound "ballRoll1" end sub
1499sub roll2_hit() playBallRollSound "ballRoll1" end sub
1500sub roll3_hit() playBallRollSound "ballRoll1" end sub
1501sub roll4_hit() playBallRollSound "ballRoll1" end sub
1502sub roll5_hit() playBallRollSound "ballRoll1" end sub
1503sub roll6_hit() playBallRollSound "ballRoll1" end sub
1504sub roll7_hit() playBallRollSound "ballRoll1" end sub
1505sub roll8_hit() playBallRollSound "ballRoll1":call undropGuideWires end sub
1506
1507sub rollDown1_hit() playBallRollSound "ballRoll2" end sub
1508sub rollDown2_hit() playBallRollSound "ballRoll2" end sub
1509sub rollDown3_hit() playBallRollSound "ballRoll2" end sub
1510sub rollDown4_hit() playBallRollSound "ballRoll2" end sub
1511sub rollDown5_hit() playBallRollSound "ballRoll2" end sub
1512
1513
1514sub playBallRollSound(aSoundString)
1515' This plays a sound sample so far ballRollNoisesEnabled=true.
1516
1517 if ballRollNoisesEnabled then playSound aSoundString
1518end sub
1519
1520
1521sub stopBallRollNoises()
1522' This routine stops all still sounding ball roll samples.It should be called
1523' at all places where the ball is known to stand still (e.g. in the outhole
1524' or tophole etc.) to prevent confusion of the player.
1525'
1526' Note: In a multi-ball game this would need to be modelled more complex (using
1527' counting semaphores?). The easiest modelling there would be to stop these
1528' noises only when all balls stand still.
1529'
1530' TIP: A routine like this could also be used in a simulation of an electronic
1531' pinball machine to prevent a certain type of sounds(e.g. speech) to be
1532' played polyphonic when the simulated sound hardware could only play them
1533' monophonic.
1534
1535 stopSound "tinRoll"
1536 stopSound "ballRoll1"
1537 stopSound "ballRoll2"
1538end sub
1539
1540'The following prevents the ball getting stuck under a flipper by temporary
1541'dropping the guiding wire below it.Without this trick an ugly buzz was audible
1542'and it ate that much CPU load that it might crash VP on very slow PCs.
1543
1544sub leftGuide_hit()
1545 playSound "tinHit3"
1546 if not leftFlipperUp then leftGuide.isDropped=true
1547end sub
1548
1549sub rightGuide_hit()
1550 playSound "tinHit3"
1551 if not rightFlipperUp then rightGuide.isDropped=true
1552end sub
1553
1554sub undropGuideWires()
1555' This restores the guide wires below the flippers so far they were dropped
1556' by a stuck ball.
1557 leftGuide.isDropped =false
1558 rightGuide.isDropped=false
1559end sub
1560
1561
1562'misc game related routines:
1563'---------------------------
1564
1565sub drain_hit()
1566 DOF 128, DOFPulse
1567' This function is called when a ball falls into the outhole.
1568'
1569' TIP: The outhole should always be placed outside the viewfield of the player
1570' (except when the real pinball had a visible one) because it looks really
1571' ugly when the ball just disappears below the flippers.It is very easy
1572' to form a mould and place a flat cover tin above it to place the outhole
1573' (and possibly eject hole) away from the player's sight.(Setting the
1574' kicker type "invisible" may safe a little CPU time because it avoids
1575' drawing it.)
1576'
1577' When you want to make a visible fall-through hole,then don't call
1578' .destroyBall immediately but start a timer for this with a small delay.
1579' Though the ball stays visible in the hole for a fraction of a second,
1580' which looks much more realistic.
1581' (This game doesn't destroy the ball at all,but re-ejects it like a
1582' real pinball machine.)
1583
1584 stopBallRollNoises
1585 playSound "Ballout"
1586
1587 ballInDrain=true
1588 'When points were scorend,then this will add the actual bonus from the bonus
1589 'ladder to the score and start the next round. Else the ball will be just
1590 're-ejected.
1591
1592 undropGuideWires
1593 'When a ball got stuck under a flipper,this restores the dropped guide wire.
1594
1595 if noTiltDuringBonus then tiltHasEffect=false
1596 'prevents tilt happening while collecting bonus
1597
1598 scoreMotor.enabled=true
1599end sub
1600
1601
1602sub ejectBall()
1603
1604 if ballInDrain then
1605 if not ballCreated then
1606 drain.createBall.image=ballTexture
1607 ballCreated=true
1608 end if
1609
1610 'Like a real pinball machine,this game does not delete and re-create
1611 'the ball again and again,but re-uses the same ball (after it is
1612 'created a single time).
1613 'By a Visual Pinball bug drain.createBall works only when the ball is
1614 'kicked out afterward, therefore it can not be done in theTable_init()
1615 'without also kicking it out there,which would result in the drain_hit()
1616 'noise when the program starts.
1617 '
1618 'Important: Most Visual Pinball games hang/become unplayable when the player
1619 ' nudges the ball back into the eject hole near the plunger.This
1620 ' is a very annoying flaw.To prevent this,a hit event on the eject
1621 ' kicker should always be sensed and cause a re-eject of the ball,
1622 ' no matter if the game uses a separate eject kicker or the drain
1623 ' kicker to eject the ball.
1624
1625 playSound "ballrel" 'eject coil sound
1626 drain.kick 80,15
1627 ballInDrain=false
1628 end if
1629end sub
1630
1631'tilt routines:
1632
1633sub nudged()
1634' This is a crude approximation of a nudge against a tilt pendulum. Only an
1635' amplitude is simulated - neither sine swinging nor different directions. The
1636' decay of the pendulum is simulated by tiltTimer.
1637
1638 if tiltEnabled then
1639 tiltSwing=tiltSwing+1
1640
1641 if tiltSwing>=tiltSensitivity then
1642 tiltSwing=tiltSensitivity
1643 if not gameOver and tiltHasEffect then doTilt
1644 end if
1645
1646 end if
1647end sub
1648
1649
1650sub tiltTimer_timer()
1651' This timer periodically decreases tiltSwing to approximate pendulum decay.
1652 if tiltSwing>0 then subtractUntil0 tiltSwing,tiltSwingDecay
1653end sub
1654
1655
1656sub doTilt()
1657' This sets the machine into tilt state.
1658'
1659' Hint: For simulating a pinball machine with "tilt disqualifies player"
1660' behaviour it would be necessary to make one tilt relay per player
1661' (i.e. replace tilt variable by an array) and in startNextRound()
1662' instead of calling unTilt() players with a set tilt relay would need
1663' to be skipped by executing the section that calls nextPlayer() again
1664' (using the score motor) until tilt(currentPlayer)=false.This way a
1665' tilted players would be skipped until game over.
1666
1667 setRelay tilt,true
1668 tiltText.Text = "TILT"
1669 setBumpers false
1670 setFlippers false
1671 If B2SOn Then
1672 Controller.B2SSetTilt 33,1
1673 Controller.b2ssetdata 1,0
1674 End If
1675 if tiltClearsPilotLamps then
1676 setPlayfieldLights false 'typical for Bally
1677 lightCenterBumper false
1678 end if
1679
1680 if tiltClearsIllumination then setIllumination false
1681 'this one may be not authentic for Bally,but looks great
1682
1683 if tiltClearsDoubleBonus then setRelay doubleBonus,false 'counts down faster
1684 if tiltVoids0PtsRestart then newRound=false
1685
1686 if tiltVoidsExtraBall then
1687 setExtraBall false
1688 setShootAgain false
1689 '(In setExtraBall() also the win of a new extra ball is disabled during tilt
1690 ' when const tiltVoidsExtraBall=true.)
1691 end if
1692end sub
1693
1694
1695sub unTilt()
1696' When the game was tilted, then this ends tilt state.
1697
1698 setRelay tilt,false
1699 tiltText.Text = " "
1700 setBumpers true
1701 setFlippers true
1702 If B2SOn Then
1703 Controller.B2SSetTilt 33,0
1704 Controller.b2ssetdata 1,1
1705 End If
1706end sub
1707
1708'extra balls:
1709
1710sub setExtraBall(aBoolean)
1711' When aBoolean=true this lets the player win an extra ball (by setting the
1712' extraBallWon relay) and lights the "shoot again" lamps. When aBoolean=false,
1713' the extraBallWon relay is cleared.(The "shoot again" lamps must be cleared
1714' elsewhere by calling setShootAgain false.)
1715'
1716' During tilt no extra ball can be won when const tiltPreventsExtraBall or
1717' tiltVoidsExtraBall is set true.
1718
1719 if aBoolean then
1720 if not (tilt and (tiltPreventsExtraBall or tiltVoidsExtraBall)) then
1721 setRelay extraBallWon,true
1722 setShootAgain true
1723 end if
1724 else
1725 setRelay extraBallWon,false
1726 end if
1727end sub
1728
1729
1730sub setShootAgain(aBoolean)
1731' This sets the shootAgain relay and the shootAgain lamps on playfield and
1732' backbox to the value of aBoolean.
1733'
1734' In Bally EM pinball machines the "shoot again" lamp on the playfield and the
1735' backbox are wired in parallel and therefore always switch simulataneously.
1736'
1737' Unlike other playfield lamps, these lamps stays also lit when
1738' playfieldLightsEnabled=false,because this way also after a tilt the player
1739' can see if he still has a shoot again. (I believe to remember this from Bally
1740' "Champ";is this correct?)
1741'
1742' Important: The extraBallWon relay must be set to tell the program that the next
1743' ball shall be a shoot again.The shootAgain relay itself does nothing
1744' except holding its own lamps lit. (I don't know if real Bally machines
1745' handle this differently.)
1746
1747 setRelay shootAgain,aBoolean
1748
1749 if aBoolean then
1750 lampShootAgain.state=lightStateOn
1751 shootAgainText.Text = "SHOOT AGAIN"
1752 If B2SOn Then Controller.B2SSetShootAgain 36, 1
1753 else
1754 lampShootAgain.state=lightStateOff
1755 shootAgainText.Text =""
1756 If B2SOn Then Controller.B2SSetShootAgain 36, 0
1757 end if
1758end sub
1759
1760
1761'move flippers:
1762
1763sub moveLeftFlipper(aBoolean)
1764' This moves the left flipper up when aBoolean=true or down when false with the
1765' corresponding sound so far the flipper was not already in that position.
1766
1767 if aBoolean and not leftFlipperUp then
1768 leftFlipper.rotateToEnd
1769 stopSound "leftFlipperDown"
1770 'The stopSound reduces CPU load when someone chatters with the flippers and
1771 'it also makes it sound more realistic.
1772 playSound SoundFXDOF("leftFlipperUp",101,DOFOn,DOFFlippers)
1773 playSound "leftFlipperHum",-1 'endless loop sample of solenoid hum
1774 leftFlipperUp=true
1775 elseIf not aBoolean and leftFlipperUp then
1776 leftFlipper.RotateToStart
1777 stopSound "leftFlipperUp"
1778 playSound SoundFXDOF("leftFlipperDown",101,DOFOff,DOFFlippers)
1779 stopSound "leftFlipperHum"
1780 leftFlipperUp=false
1781 end if
1782end sub
1783
1784sub moveRightFlipper(aBoolean)
1785' This moves the right flipper up when aBoolean=true or down when false with the
1786' corresponding sound so far the flipper was not already in that position.
1787
1788 if aBoolean and not rightFlipperUp then
1789 rightFlipper.rotateToEnd
1790 stopSound "rightFlipperDown"
1791 playSound SoundFXDOF("rightFlipperUp",102,DOFOn,DOFFlippers)
1792 playSound "rightFlipperHum",-1 'endless loop sample of solenoid hum
1793 rightFlipperUp=true
1794 elseIf not aBoolean and rightFlipperUp then
1795 rightFlipper.RotateToStart
1796 stopSound "rightFlipperUp"
1797 playSound SoundFXDOF("rightFlipperDown",102,DOFOff,DOFFlippers)
1798 stopSound "rightFlipperHum"
1799 rightFlipperUp=false
1800 end if
1801end sub
1802
1803
1804'enable/disable playfield stuff:
1805
1806sub setFlippers(aBoolean)
1807' This switches the flippers on or off and regards (with correct sound) when
1808' a flipper button is held down.
1809' (In a real pinball machine the flippers react when the button is held and
1810' then the current to the flippers is switched on.)
1811
1812 flippersEnabled=aBoolean
1813
1814 if flippersEnabled then
1815 moveLeftFlipper leftFlipButton
1816 moveRightFlipper rightFlipButton
1817 else
1818 moveLeftFlipper false
1819 moveRightFlipper false
1820 end if
1821end sub
1822
1823
1824sub setBumpers(aBoolean)
1825' This shall turn the bumping of all bumpers on or off.
1826' BUG: this doesn't work yet (a Visual Pinball bug?). Bumpers remain always on.
1827
1828 'TIP: Beside slingshots (triangular "bumpers") also the straight part of long
1829 ' rubber bands(where they don't touch the post) should be set as slingshots
1830 ' with a low slingshot force(about 2) to get a more realistic ball bounce
1831 ' behaviour in Visual Pinball.
1832 ' (Note: In Visual Pinball 3 slingshot areas of "Wall" objects must not be
1833 ' convex,because otherwise their slingshot part(black line in editor) can
1834 ' not be hit with the ball.Instead make such areas as multiple small straight
1835 ' lines. Concave slingshot areas make no problems.)
1836 '
1837 ' To simulate bumpers with a non-round or flat top cover (like e.g. in
1838 ' Interflip "Alaska"),I recommend to use the Visual Pinball bumper only for
1839 ' the round center part,and add a flat disc of "wall" material to draw the
1840 ' top. If the top shall be lightable,use the same trick I did with the
1841 ' playfield plastics.
1842
1843 if aBoolean=true then
1844 centerBumper.threshold =1
1845 leftSlingshot.threshold =1
1846 rightSlingshot.threshold=1
1847 else
1848 centerBumper.threshold =1000
1849 leftSlingshot.threshold =1000
1850 rightSlingshot.threshold=1000
1851 end If
1852end sub
1853
1854'coin chutes:
1855
1856sub creditButtonRoutine
1857' This routine adds a player to a running game when it is still at the 1st ball,
1858' or otherwise resets the current game and starts a new one with 1 player.
1859'
1860' This is called when the start button is pressed and credits are present or
1861' when a valid washer was inserted into a 1-credit coin chute.
1862
1863 rulesSwitchable=false 'During a game the rules must stay the same.
1864
1865 if gameOver or ballCount>1 or players=maxPlayers then
1866 'starting a new game with 1 player
1867 resetGame
1868 else
1869 if creditTaken then decrementCredit
1870 'When this was called by a start button press,a credit is consumed from the
1871 'credit counter (decrementCredit also clears the creditTaken relay).
1872
1873 addPlayer
1874 'Players can be added to a running game as long the 1st ball is in play.
1875 'After a player lost a ball, it would become unfair to permit this, because
1876 'the new player would get less balls for his credit due to these old Bally
1877 'machines have only a single ball counter wheel shared among all players.
1878 end if
1879end sub
1880
1881
1882'The following timers delay inserted coins to simulate the coin mech sound
1883'correctly.
1884'
1885'Important: The constants coinMechXTime must be >0,because Timer.interval is abused
1886' here as a state variable. The coinMechX timers must be initialized with
1887' .interval=0 to work correctly.
1888
1889sub coinMech1_timer()
1890 if coinMech1.interval=0 then
1891 coinMech1.interval=coinMech1Time
1892 playSound "coin1"
1893 playSound "coinMech1"
1894 else
1895 coinMech1.enabled=false
1896 coinMech1.interval=0
1897 insertCoin coinChute1Credits
1898 end if
1899end sub
1900
1901sub coinMech2_timer()
1902 if coinMech2.interval=0 then
1903 coinMech2.interval=coinMech2Time
1904 playSound "coin1"
1905 playSound "coinMech1"
1906 else
1907 coinMech2.enabled=false
1908 coinMech2.interval=0
1909 insertCoin coinChute2Credits
1910 end if
1911end sub
1912
1913sub coinMech3_timer()
1914 if coinMech3.interval=0 then
1915 coinMech3.interval=coinMech3Time
1916 playSound "coin1"
1917 playSound "coinMech1"
1918 else
1919 coinMech3.enabled=false
1920 coinMech3.interval=0
1921 insertCoin coinChute3Credits
1922 end if
1923end sub
1924
1925
1926sub insertCoin(someCredits)
1927' This simulates inserting a coin. The coins are added using the score motor.
1928' Therefore when the score motor is still running (e.g. by a previous coin), the
1929' then the coin is rejected. Coins with a single credit can be inserted at any
1930' time, because these don't need the score motor to be counted.
1931'
1932' (A real mechanical pinball machine can not memorize the credits of multiple
1933' inserted coins,therefore a mechanism in the coindoor re-directs inserted coins
1934' to the coin return chute when the counter is still counting. But so far I know,
1935' the machine does not recognize overpaying the credit counter.)
1936
1937 if creditsAddable then
1938
1939 select case int(3*rnd())
1940 case 0: playSound "cashbox2"
1941 case 1: playSound "cashbox9"
1942 case 2: playSound "cashbox15"
1943 'This plays randomly 1 of multiple different cashbox noises,but they are
1944 'anyway badly audible due to the loud rest of the mechanism.
1945 end select
1946
1947 setIllumination true 'a coin activates illumination
1948
1949 if someCredits=1 then
1950 'For adding a single credit the score motor is not involved.
1951
1952 if smallCoinStartsGame then
1953 'When true,it behaves much like a start button press,except that no credit
1954 'is subtracted from the counter and that a running game can always be reset
1955 'by this.
1956 creditButtonRoutine
1957 else
1958 if not knockerKnocksAlways then knockerActive=false
1959 'disables the separate(loud) knocker coil
1960 addCredit
1961 knockerActive=true
1962 end if
1963 else
1964 if not knockerKnocksAlways then knockerActive=false
1965 addCredits(someCredits)
1966 '(The score motor re-enables the separate knocker after credits are added.)
1967 end if
1968 else
1969 'here the coin was refused by the rejector because creditsAddable=false.
1970 playSound "coinEject4" 'coin reject sound.
1971 end if
1972end sub
1973
1974
1975sub setCoinLockOut(aBoolean)
1976' A real mechanical pinball machine can not memorize the credits of multiple
1977' inserted coins,therefore a latch in the coindoor re-directs inserted coins to
1978' the coin return chute when the counter is still counting or the score motor
1979' is occupied with other stuff.
1980'
1981' This routine is called by processAddCredits() to emulate this feature,because it
1982' makes a characteristic noise when the score motor starts and stops.True means
1983' no credits can be added,false means that credits can be added.(Don't call this
1984' routine elsewhere. The value of creditsAddable is also important for the
1985' proper function of the end game matching routine.)
1986'
1987' (I am not sure if this is correctly emulated,but I remember that a mechanism
1988' in the coin door of my Bally "Champ" clicked very often during many kinds of
1989' actions the machine did.)
1990
1991 if not creditsAddable and not aBoolean then
1992 creditsAddable=true
1993 playSound "lockOutOn"
1994 elseIf creditsAddable and aBoolean then
1995 creditsAddable=false
1996 playSound "lockOutOff"
1997 end if
1998end sub
1999
2000'switch game rule set:
2001
2002sub setGameRules(aValue)
2003' This swithes the game rule set between authentic and enhanced rules and
2004' updates the displays accordingly.
2005'
2006' Attention: This should not be called while a game is running,because it
2007' would mess up the saved highscore.
2008
2009 ruleSet=aValue
2010
2011 if aValue=RS_authentic then
2012 rulesBox.text="authentic game rules"
2013 elseIf aValue=RS_enhanced then
2014 rulesBox.text="enhanced mode"
2015 end if
2016
2017 releaseStuckScoreReels 'so far captive balls can make mess here
2018 displayHighScore
2019 updatePlayfieldLights
2020end sub
2021
2022'reset:
2023
2024sub resetGame
2025' This routine starts the reset sequence so far the gameOver relay is set.
2026
2027' Below are some lines of test code with that you can experiment to see how the
2028' emulated reset routine reacts on unusual conditions.Especially try to
2029' uncomment "ejectBall" and/or "bonus=..." to see what the simulated mechanism
2030' does when the pinball machine was switched off during a game in such a situ-
2031' ation.
2032'
2033' setScore 1,12121 'TEST!!!
2034' setScore 2,91010 'TEST!!!
2035' setScore 3,70000 'TEST!!!
2036' setScore 4,12345 'TEST!!!
2037
2038' debugScoreDisplayAlignment 'TEST!!!
2039' bonus=12000 'TEST!!!
2040' ejectBall 'TEST!!!
2041
2042 if not gameOver then doGameOver
2043 'The gameOver relay must be set because it also controlls the reset sequence.
2044
2045 setRelay newRound,false
2046 'This is important to reset the per-round stuff (spinner etc.) when
2047 'we start a new game.Also processCollectBonus() needs this to reset
2048 'the bonus ladder.
2049
2050 setIllumination true
2051 gameIsResetting =true
2052 reelChimeEnabled=false 'prevents a racket when score reels reset
2053 scoreMotor.enabled=true
2054end sub
2055
2056
2057'general light related routines:
2058'-------------------------------
2059
2060sub setLight(aLamp,aBoolean)
2061' This sets a lamp on or off depending on the value of aBoolean (useful for
2062' setting a lamp according to a given boolean state variable).
2063
2064 if aBoolean and playfieldLightsEnabled then
2065 aLamp.state=lightStateOn
2066 else
2067 aLamp.state=lightStateOff
2068 end if
2069end sub
2070
2071
2072sub resetLights()
2073' This resets playfield lights and their variables to their default state
2074' when a round begins.
2075'
2076' Hint: To make a pinball game look realistic, the 'colourless' lights must not
2077' be white but instead have a quite orange, lightbulbish hue. RGB 254,235,
2078' 175 is a good value to use. Due to the brightness of this colour is lower
2079' than cranking R+G+B fully up to 255 (as white light could do), the entire
2080' playfield and backglass picture needs to be darkened by a few percent with
2081' a graphics program to make the lamps appear bright enough. Usually bright-
2082' ness and contrast need to be reduced by the same amount. The brightness
2083' also should be set in a way that the black parts of a playfield look
2084' really almost black and not just grey.
2085'
2086' Generally the key for a realistic looking playfield is the use of lightbulb
2087' light effects. For these transparent round spots of yellowish orange with
2088' fading rims should be painted onto the playfield picture everywhere where
2089' under plastic parts light bulbs can be expected. (RGB 255,204,17 looks
2090' good.)
2091'
2092' With my German "Micrografx Picture Publisher 8" version the best looking
2093' painting mode for drawing light spots is "Schicht"(i.e. layer) with a
2094' transparency about 42% and 100% "Verwischen" (fading brush rims).
2095
2096 setRelay A,false
2097 setRelay B,false
2098 setRelay C,false
2099 setRelay D,false
2100 setRelay A_lock,false
2101 setRelay B_lock,false
2102 setRelay C_lock,false
2103 setRelay D_lock,false
2104
2105 rollover1kLit=(ruleSet=RS_authentic) 'test on true or false
2106 specialLit =false
2107 setRelay doubleBonus,false
2108
2109 updatePlayfieldLights
2110 lightCenterBumper (ruleSet=RS_authentic) 'test on true or false
2111 'The center bumper is always lit on the original pinball machine,though
2112 'this is standard for the authentic rule set.
2113end sub
2114
2115
2116sub updatePlayfieldLights()
2117' This routine shall update all the pilot lamps on the playfield according to
2118' their variables. It is ment as an initialization routine and it is also very
2119' good for debugging states of the playfield variables.But it is not a good
2120' idea to update a single changed light by calling this after setting its
2121' variable. When single variables change,by performance reasons instead only its
2122' corresponding lamp should be updated by hand using setLight().
2123'
2124' Note: Although possible, this routine should NOT be called by a fast timer to
2125' periodically update lights when the variables change,because this would
2126' waste much CPU time.
2127'
2128' When playfieldLightsEnabled=false,this routine switches all lamps off
2129' (implemented in setLight() ). The "shoot again" lamp is independent from this
2130' (see setShootAgain() ).
2131'
2132' When playfield lights are initialized,then this routine must be the central
2133' place to do this,instead of setting some lamps here and others there in the
2134' code.
2135
2136 setLight lampLeftCLaneMid ,true
2137 setLight lampRightCLaneMid,true
2138 setLight lampLeft1k ,rollover1kLit
2139 setLight lampRight1k ,rollover1kLit
2140 setLight lamp_DB ,doubleBonus
2141 setLight lampLeftCLaneEnd ,specialLit
2142 setLight lampRightCLaneEnd,specialLit
2143
2144 setLight lampTarget_A ,not A
2145 setLight lampTarget_B ,not B
2146 setLight lampTarget_C ,not C
2147 setLight lampTarget_D ,not D
2148 setLight lampLeftCLane_A ,A
2149 setLight lampLeftCLane_B ,B
2150 setLight lampRightCLane_C,C
2151 setLight lampRightCLane_D,D
2152
2153 setSpinnerLights spins
2154 setBonusLights bonus 'this also updates extra ball lights
2155end sub
2156
2157
2158sub setPlayfieldLights(aBoolean)
2159' This switches the pilot lamps on the playfield on or off.Important is that switching it
2160' on again during a game relies on a complete implementation of updatePlayfieldLights().
2161
2162 playfieldLightsEnabled=aBoolean
2163 updatePlayfieldLights
2164end sub
2165
2166
2167sub setBackboxLights(aBoolean)
2168' This switches various display lamps in the backbox on or off. To make this
2169' work correctly,they must stay dark when they become set and
2170' backboxLightsEnabled=false.
2171'
2172' The "game over" lamp is wired independently and therefore not affected by
2173' this.
2174
2175 backboxLightsEnabled=aBoolean
2176 updateBackboxLights
2177end sub
2178
2179
2180sub updateBackboxLights()
2181' This routine shall update various display lamps in the backbox according to
2182' their variables.
2183
2184 setBallCounterLights ballCount
2185 setPlayerLights players
2186 setPlayerUpLights currentPlayer
2187end sub
2188
2189
2190sub setIllumination(aBoolean)
2191' This switches the general illumination (playfield plastics illumination and
2192' some backbox illumination lights) on or off.
2193
2194 if illumination=aBoolean then exit sub
2195
2196 If aBoolean then
2197 captLaneLLit.isDropped =false
2198 captLaneRLit.isDropped =false
2199 bankLLit.isDropped =false
2200 bankRLit.isDropped =false
2201 slingshotLLit.isDropped=false
2202 slingshotRLit.isDropped=false
2203
2204 lampHead.state =lightStateOn
2205 lampKang1.state =lightStateOn
2206 lampKang2.state =lightStateOn
2207 lampTitle1.state=lightStateOn
2208 lampTitle2.state=lightStateOn
2209 'These are the translucent objects on the backglass.Sorry that they
2210 'have an ugly black rim;a Visual Pinball bug draws a black rectangle
2211 'around every lit lamp behind a decal.
2212 else
2213 captLaneLLit.isDropped =true
2214 captLaneRLit.isDropped =true
2215 bankLLit.isDropped =true
2216 bankRLit.isDropped =true
2217 slingshotLLit.isDropped=true
2218 slingshotRLit.isDropped=true
2219 lampHead.state =lightStateOff
2220 lampKang1.state =lightStateOff
2221 lampKang2.state =lightStateOff
2222 lampTitle1.state=lightStateOff
2223 lampTitle2.state=lightStateOff
2224 end if
2225
2226 setRelay illumination,aBoolean
2227end sub
2228
2229
2230sub lightCenterBumper(aBoolean)
2231' This switches the light of the center bumper on or off.
2232
2233 setRelay centerBumperLit,aBoolean
2234 If aBoolean=true then
2235 centerBumper.state =lightStateOn
2236 litCenterFloor.isDropped=false
2237 else
2238 centerBumper.state =lightStateOff
2239 litCenterFloor.isDropped=true
2240 end if
2241
2242 'Note:
2243 'To simulate an illuminated floor part, a flat wall object with the illuminated
2244 'floor texture is risen or dropped here. Due to even a dropped object has still
2245 'a height above the playfield, its bottom height must be set below the playfield
2246 'to become capable to sink completely.
2247
2248 'Due to unlit bumpers look awfully dark on Visual Pinball, for the center
2249 'bumper a white texture with a checkered pixel pattern is used of that
2250 'every 2nd pixel its pixels is set to the transparent colour; though the light
2251 'of the lit bumper can shine through the transparent pixels while the rest
2252 'looks white - very much like light shining through a white gaze curtain at
2253 'your window. When the daylight turns dark, you can still see the white of the
2254 'curtain by room light from the other side, and not just the black of the night
2255 'behind it.
2256 'Also here, the lamp of the bumper was set to a lightbulbish orange tint, and
2257 'not to the awful xenon-tube white that is default in Visual Pinball.
2258 '
2259 'TIP: Before you "perforate" a picture this way for transparency,I recommend to
2260 ' resize it to the twice resolution to make it look less gritty.
2261
2262 'BUG: Since I added playfield illumination,due to a Visual Pinball bug the spinner
2263 ' is now partly invisible when the lit floor is not dropped. This seems to be
2264 ' a Z-buffer rendering problem.I therefore placed a triangle with Can Drop
2265 ' property directly under the playfield at the spinner in a way that both
2266 ' spinner posts stand on it. Despite it is invisible,its bare existence seems
2267 ' to convince the Z-Buffer to display the spinner correctly. (The less control
2268 ' points an object has,the less CPU time is needed, therefore a triangular
2269 ' object burdens the CPU the least.)
2270 '
2271 ' The surface order recognition algorithm of Visual Pinball seems to be as
2272 ' lousy as the one in the terrible "Hard Drivin'" (3D car racing) home computer
2273 ' versions.
2274end sub
2275
2276
2277'small counters and state wheel emulations:
2278'------------------------------------------
2279
2280'credit counter:
2281'
2282'This credit display employs the "reel-a-listic" technology to simulate counter
2283'reel rotation.(See score counter section for more explanations of the used
2284'technical tricks.)
2285
2286sub addCredits(anInteger)
2287' This adds multiple credits to the credit counter using the score motor,which
2288' calls addCredit() multiple times.
2289'
2290' Warning: This routine must only be called when scoreMotorAngle=0,otherwise
2291' it would count wrong. (Test on "if creditsAddable=true" for this.)
2292
2293 unaddedCredits=unaddedCredits+anInteger
2294 scoreMotor.enabled=true
2295 If unaddedCredits > 0 Then
2296 DOF 132, DOFOn
2297 Else
2298 DOF 132, DOFOff
2299 End If
2300end sub
2301
2302
2303sub addCredit()
2304' This adds 1 credit to the credit counter and updates the counter display.
2305' (This routine does not depend on the score motor but is called from there.)
2306
2307 if credits=maxCredits and not creditOverpayBounce then exit sub
2308
2309 playSoundMonophonic "creditSolenoid"
2310
2311 if knockerEnabled and knockerActive then playSound SoundfXDOF("knocker",131,DOFPulse,DOFKnocker):DOF 111, DOFPulse
2312 'This triggers a loud separate knocker when the credit was added by anything
2313 'else than inserting a coin.
2314
2315 if credits<maxCredits then credits = credits+1
2316 'I increment the credit counter variable already here because between the
2317 'creditAdder timer and the scoreMotor timer exists a race condition that in
2318 'rare cases (only on slow PCs?) sometimes made the counter count wrong.
2319 'For a physically correct behaviour this instead should be done in
2320 'creditAdder_timer().
2321
2322 setCreditDisplay credits
2323 creditAdder.enabled=true
2324 If B2SOn Then Controller.B2ssetcredits Credits
2325end sub
2326
2327sub creditAdder_timer()
2328 'This timer updates the credit display after a small delay to simulate a
2329 'rotation at the new stable position.It also plays mechanical sounds after
2330 'the delay to achieve a more realistic sound timing for the credit counter.
2331
2332 creditAdder.enabled=false
2333 playSound "creditCounter" 'tinny knock/clang of the credit wheel
2334 'I remember that the credit counter knocks allways,and not only by
2335 'replay credits.
2336
2337 spinCreditReel
2338end sub
2339
2340
2341sub decrementCredit()
2342' This subtracts 1 credit from the credit counter.
2343
2344 if credits>0 then
2345 credits=credits-1
2346 setCreditDisplay credits
2347 If B2SOn Then Controller.B2ssetcredits Credits
2348 creditAdder.enabled=true
2349
2350 if credits=maxCredits-1 and unaddedCredits=0 and scoreMotorAngle=0 then setCoinLockOut false
2351 'When overpaying blocked the coin chutes,this will free them again because
2352 'now there will be at least 1 credit less.
2353 end if
2354
2355 setRelay creditTaken,false 'credit relay cleared
2356 saveGame 'save new credit counter value
2357end sub
2358
2359
2360sub setCreditDisplay(aValue)
2361' This displays on the credit counter reel the number aValue halfway rotated
2362' to the next value.
2363
2364 dim top
2365 if aValue<maxCredits then top=aValue+1 else top=""
2366
2367 creditLow.text =credits 'print bottom number
2368 creditClr1.text="" 'clear its lower half
2369 creditTop.text =top 'print top number
2370 creditClr0.text="" 'clear its upper half
2371end sub
2372
2373
2374sub spinCreditReel()
2375' This updates the credit display and its lamp.This routine must also be called
2376' during init of this program.
2377
2378 if credits=0 and credit0UnlightsCounter then
2379 lampCreditBox.state=lightStateOff
2380 else
2381 lampCreditBox.state=lightStateOn
2382 end if
2383 'This darkens credit counter illumination at 0 credit. (I am not sure if
2384 'this is correct.)
2385
2386 creditClr1.text=""
2387 creditClr0.text=""
2388 creditTop.text =""
2389 creditLow.text =""
2390 creditMid.text =credits
2391 If B2SOn Then Controller.B2SSetcredits Credits
2392end sub
2393
2394
2395'ball count assembly:
2396
2397sub ballCounterReset_timer
2398' Enabling this timer resets the ball counter and buzzes with the correct
2399' length to simulate the state wheel mechanism behaviour.
2400'
2401' Warning: to function properly,ballCount must be initialized with 1 before use.
2402
2403 if ballCount>1 then
2404 ballCount=ballCount-1
2405 playSound "diverter"
2406 else
2407 ballCounterReset.enabled=false
2408 end if
2409
2410 setBallCounterLights ballCount
2411end sub
2412
2413
2414function isBallCounterResetted()
2415' just a useless feature to emulate the score motor driven reset sequence
2416 if ballCount=1 then isBallCounterResetted=true else isBallCounterResetted=false
2417end function
2418
2419
2420sub incrementBallCounter()
2421' This lets the ball counter count 1 ball forward and displays the new value.
2422'
2423' Note: In these old Bally machines there is only one ball counter wheel for all
2424' players, though the players can never habe different numbers of balls
2425' per game. Due to all players are strictly served in the sequence 1,2,3,4,
2426' 1,2..., it can never happen that a 'later' player has more balls left than
2427' an earlier player.(A 'shoot again' only prevents loosing a ball, but never
2428' adds a new ball to the counter, because otherwise the later players would
2429' get the additional ball too, because the ball counter state wheel can not
2430' distinguish to whom the added ball would belong.)
2431'
2432' In the "Boomerang" flyer stands that these machines could be modified to
2433' support "add-a-ball", i.e. that a player can launch multiple of his balls
2434' into play at any time during a game. Due to there is only one ball counter,
2435' I can only imagine that for this the machine was re-wired in a way that
2436' every player finishes all his balls and only after he goes game over, the
2437' next player could play (i.e. after the modification players can't play
2438' their rounds alternatingly anymore, what I find quite stupid.
2439
2440 if ballCount<=maxBalls+1 then ballCount = ballCount+1
2441
2442 'The counter in a real machine can step 1 position further than maxBalls.
2443
2444 playSound "diverter"
2445 setBallCounterLights ballCount
2446end sub
2447
2448
2449sub setBallCounterLights(aValue)
2450' This routine simulates a row of 5 number-shaped lamps on the backbox to
2451' display the ball counter.Use -1 (or 0) to clear all lamps.
2452
2453 ball1Text.text=" "
2454 ball2Text.text=" "
2455 ball3Text.text=" "
2456 ball4Text.text=" "
2457 ball5Text.text=" "
2458 If B2SOn Then Controller.B2SSetBallInPlay 32, 0
2459 if not backboxLightsEnabled then exit sub
2460
2461 if aValue=1 then
2462 ball1Text.text="1"
2463 elseIf aValue=2 then ball2Text.text="2"
2464 elseIf aValue=3 then ball3Text.text="3"
2465 elseIf aValue=4 then ball4Text.text="4"
2466 elseIf aValue=5 then ball5Text.text="5"
2467 end if
2468 If B2SOn Then Controller.B2SSetBallInPlay 32, ballcount
2469 'At game over the ball counter advances 1 beyond ballsPerGame (also in a real
2470 'Bally machine),but the lamps stay off in this case.
2471end sub
2472
2473'player counter assembly: (Bally "coin unit")
2474
2475sub playerCounterReset_timer
2476' Enabling this timer resets the player counter and buzzes with the correct
2477' length to simulate the state wheel mechanism behaviour.
2478'
2479' Warning: to function properly,players must be initialized with 1 before use.
2480
2481 if players>1 then
2482 players=players-1
2483 playSound "diverter"
2484 else
2485 playerCounterReset.enabled=false
2486 end if
2487
2488 setPlayerLights players
2489end sub
2490
2491
2492function isPlayerCounterResetted()
2493' just a useless feature to emulate the score motor driven reset sequence
2494 if players=1 then isPlayerCounterResetted=true else isPlayerCounterResetted=false
2495end function
2496
2497
2498sub addPlayer()
2499 'This adds a player to the game and updates the player counter display.
2500
2501 playSound "diverter" 'loud click when counter wheel steps
2502 if players<maxPlayers then players=players+1
2503 setPlayerLights players
2504end sub
2505
2506
2507sub setPlayerLights(aValue)
2508' This sets the player counter display and lights the score counter according to
2509' the number of players.Use -1 (or 0) to clear all lamps.
2510
2511 dim t
2512
2513 if backboxLightsEnabled then t=aValue else t=-1
2514
2515 if t>=1 then
2516 lamp1CanPlay.state=lightStateOn
2517 lampScoreP1.state =lightStateOn
2518 If B2SOn Then Controller.B2SSetCanPlay 31, t
2519 else
2520 lamp1CanPlay.state=lightStateOff
2521 lampScoreP1.state =lightStateOff
2522 end if
2523
2524 if t>=2 then
2525 lamp2CanPlay.state=lightStateOn
2526 lampScoreP2.state =lightStateOn
2527 If B2SOn Then Controller.B2SSetCanPlay 31, t
2528 else
2529 lamp2CanPlay.state=lightStateOff
2530 lampScoreP2.state =lightStateOff
2531 end if
2532
2533 if t>=3 then
2534 lamp3CanPlay.state=lightStateOn
2535 lampScoreP3.state =lightStateOn
2536 If B2SOn Then Controller.B2SSetCanPlay 31, t
2537 else
2538 lamp3CanPlay.state=lightStateOff
2539 lampScoreP3.state =lightStateOff
2540 end if
2541
2542 if t>=4 then
2543 lamp4CanPlay.state=lightStateOn
2544 lampScoreP4.state =lightStateOn
2545 If B2SOn Then Controller.B2SSetCanPlay 31, t
2546 else
2547 lamp4CanPlay.state=lightStateOff
2548 lampScoreP4.state =lightStateOff
2549 end if
2550
2551 updateScoreDisplays
2552end sub
2553
2554'player-up unit:
2555
2556sub playerUpReset_timer
2557' Enabling this timer resets the playerUp counter and buzzes with the correct
2558' length to simulate the state wheel mechanism behaviour.
2559'
2560' Warning: to function properly,currentPlayer must be initialized with 1 before
2561' use.
2562
2563 if currentPlayer>1 then
2564 currentPlayer=currentPlayer-1
2565 playSound "diverter"
2566 else
2567 playerUpReset.enabled=false
2568 end if
2569
2570 setPlayerUpLights currentPlayer
2571end sub
2572
2573
2574function isPlayerUpResetted()
2575' just a useless feature to emulate the score motor driven reset sequence
2576 if currentPlayer=1 then isPlayerUpResetted=true else isPlayerUpResetted=false
2577end function
2578
2579
2580sub nextPlayerUp()
2581' This sets the next player as the current player,depending on the number of
2582' players per game stored in the player counter state wheel.
2583
2584 playSound "diverter" 'loud click when player wheel steps
2585
2586 if currentPlayer<players then
2587 setCurrentPlayer currentPlayer+1
2588 else
2589 setCurrentPlayer 1
2590 'I don't know if the state wheel should be resetted here or if it just steps
2591 'around and uses different contacts for different numbers of players
2592 'or anything like that.
2593 end if
2594end sub
2595
2596
2597sub setCurrentPlayer(aPlayerNumber)
2598' This sets the player (1 to 4) that is actually playing and lights its lamp
2599' on the backbox.
2600
2601 if aPlayerNumber<>currentPlayer then
2602 releaseStuckScoreReels 'so far any are stuck
2603 currentPlayer=aPlayerNumber
2604 end if
2605 setPlayerUpLights currentPlayer
2606end sub
2607
2608
2609sub setPlayerUpLights(aValue)
2610' This lights the player-up lamp on backbox.Use -1 (or 0) to clear all lamps.
2611
2612 dim t
2613
2614 if backboxLightsEnabled then t=aValue else t=-1
2615 If B2SOn Then Controller.B2SSetPlayerUp 30,0
2616 if t=1 then lamp1Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp1Up.state=lightStateOff
2617 if t=2 then lamp2Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp2Up.state=lightStateOff
2618 if t=3 then lamp3Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp3Up.state=lightStateOff
2619 if t=4 then lamp4Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp4Up.state=lightStateOff
2620end sub
2621
2622'spinner lights state wheel assembly:
2623
2624sub spinnerReset_timer
2625' Enabling this timer resets the spinner state wheel.
2626'
2627' Note:I was told that the spinner state wheel of the original pinball machine
2628' can not reset,though I use this only in my enhanced rule set but not
2629' in the authentic rule set one.
2630
2631 if spins>0 then
2632 spins=spins-1
2633 playSound "diverter"
2634 else
2635 spinnerReset.enabled=false
2636 end if
2637 setSpinnerLights spins
2638end sub
2639
2640
2641function isSpinnerResetted()
2642' another dummy for emulation
2643 if spins=0 then isSpinnerResetted=true else isSpinnerResetted=false
2644end function
2645
2646
2647sub incrementSpinner()
2648 spins=spins+1
2649
2650 if spins>10 then
2651 spins=0
2652 incrementBonus 'Bonus added and spinner lights go off again
2653 end if
2654
2655 if spins=0 and ruleSet=RS_authentic then spins=1
2656
2657 'Is this rule correct? (Returning only to lamp 1 would waste a lamp,
2658 'and also psychologically it makes more sense when the bonus increases
2659 'while all lights zap away.But otherwise the walking dot in authentic
2660 'mode looks more correct to me when there stays always a light visible,
2661 'similar like a hand on a clock face.)
2662
2663 if spins=0 or not playfieldLightsEnabled then
2664 lampSpin1.State = LightStateOff
2665 lampSpin2.State = LightStateOff
2666 lampSpin3.State = LightStateOff
2667 lampSpin4.State = LightStateOff
2668 lampSpin5.State = LightStateOff
2669 lampSpin6.State = LightStateOff
2670 lampSpin7.State = LightStateOff
2671 lampSpin8.State = LightStateOff
2672 lampSpin9.State = LightStateOff
2673 lampSpin10.State = LightStateOff
2674 else
2675 setSpinnerLights spins
2676 end if
2677end sub
2678
2679
2680sub setSpinnerLights(aValue)
2681' This lights on playfield the spinner lamps for aValue.Use -1 (or 0) to clear
2682' all lamps.
2683'
2684' I was told that the original pinball machine only lights 1 light at a time,
2685' but I kept my previous light row implementation in my enhanced rule set be-
2686' cause it looks much better.
2687
2688 dim t
2689
2690 if playfieldLightsEnabled then t=aValue else t=-1
2691
2692 if ruleSet=RS_authentic then
2693 'This lights only the lamp with the number t (authentic Bally).
2694
2695 if t=1 then lampSpin1.State = LightStateOn else lampSpin1.State = LightStateOff
2696 if t=2 then lampSpin2.State = LightStateOn else lampSpin2.State = LightStateOff
2697 if t=3 then lampSpin3.State = LightStateOn else lampSpin3.State = LightStateOff
2698 if t=4 then lampSpin4.State = LightStateOn else lampSpin4.State = LightStateOff
2699 if t=5 then lampSpin5.State = LightStateOn else lampSpin5.State = LightStateOff
2700 if t=6 then lampSpin6.State = LightStateOn else lampSpin6.State = LightStateOff
2701 if t=7 then lampSpin7.State = LightStateOn else lampSpin7.State = LightStateOff
2702 if t=8 then lampSpin8.State = LightStateOn else lampSpin8.State = LightStateOff
2703 if t=9 then lampSpin9.State = LightStateOn else lampSpin9.State = LightStateOff
2704 if t=10 then lampSpin10.State= LightStateOn else lampSpin10.State= LightStateOff
2705
2706 else
2707 'With enhanced game rules this displays a row of t lights (looks better).
2708
2709 if t>=1 then lampSpin1.State = LightStateOn else lampSpin1.State = LightStateOff
2710 if t>=2 then lampSpin2.State = LightStateOn else lampSpin2.State = LightStateOff
2711 if t>=3 then lampSpin3.State = LightStateOn else lampSpin3.State = LightStateOff
2712 if t>=4 then lampSpin4.State = LightStateOn else lampSpin4.State = LightStateOff
2713 if t>=5 then lampSpin5.State = LightStateOn else lampSpin5.State = LightStateOff
2714 if t>=6 then lampSpin6.State = LightStateOn else lampSpin6.State = LightStateOff
2715 if t>=7 then lampSpin7.State = LightStateOn else lampSpin7.State = LightStateOff
2716 if t>=8 then lampSpin8.State = LightStateOn else lampSpin8.State = LightStateOff
2717 if t>=9 then lampSpin9.State = LightStateOn else lampSpin9.State = LightStateOff
2718 if t =10 then lampSpin10.State= LightStateOn else lampSpin10.State= LightStateOff
2719 end if
2720end sub
2721
2722'bonus ladder assembly: ("bonus unit")
2723
2724sub incrementBonus()
2725' This routine increments the bonus ladder by const bonusStepWidth.
2726
2727 if bonus=maxBonus or tilt and tiltPreventsAddBonus then exit sub
2728
2729 bonus=bonus+bonusStepWidth
2730 playSound "diverter" 'loud click sound when bonus wheel steps
2731
2732 if ruleSet=RS_enhanced and bonusClearsBumper and bonus=topHoleShootAgain then lightCenterBumper false
2733 'This unlights the center bumper in enhanced game rule mode when the bonus
2734 'reached the topHoleShootAgain value as a hint to shoot the tophole.
2735 '(I don't do this in setBonusLights() because it shall be triggered only
2736 'once and not when the lights get updated by any other reasons.)
2737
2738 setBonusLights bonus
2739end sub
2740
2741
2742sub decrementBonus()
2743' This routine steps down the bonus ladder by const bonusStepWidth.
2744
2745 if bonus>0 then playSound "diverter" 'loud click sound when bonus wheel steps
2746 subtractUntil0 bonus,bonusStepWidth
2747 setBonusLights bonus
2748end sub
2749
2750
2751sub setBonusLights(aValue)
2752' This routine displays the lights of the bonus ladder according to the actual
2753' value of the argument variable. This is an approximation of what the bonus
2754' state wheel in a real mechanical pinball machine does.
2755'
2756' Due to both extra ball lights and specials are also controlled by contacts
2757' on the wheel (a guess from Bally "Champ"),these are also set here depending
2758' on the actual bonus value.
2759'
2760' (This routine is game rule dependant and though must be alterated for
2761' emulating other pinball machines.)
2762
2763 dim x,value
2764
2765 'set extra ball lamps: (At bonus >=15000 both lamps are lit.)
2766
2767 if playfieldLightsEnabled then value=aValue else value=0
2768 'This disables all bonus lights when playfieldLightsEnabled=false.
2769 '(Note: As a side effect then also the extraBallLit variables become false.)
2770
2771 leftExtraBallLit = false
2772 rightExtraBallLit = false
2773 lampLeft_XB.State = LightStateOff
2774 lampRight_XB.State = LightStateOff
2775
2776 if value=8000 or value=10000 or value=12000 or value=14000 or value>=15000 then
2777 lampLeft_XB.State = LightStateOn
2778 leftExtraBallLit = true
2779 end if
2780 if value=9000 or value=11000 or value=13000 or value>=15000 then
2781 lampRight_XB.State = LightStateOn
2782 rightExtraBallLit = true
2783 end if
2784
2785 'set bonus ladder lamps:
2786
2787 x=value mod 10000
2788
2789 lampBonus1k.State = LightStateOff 'all bonus lamps off
2790 lampBonus2k.State = LightStateOff
2791 lampBonus3k.State = LightStateOff
2792 lampBonus4k.State = LightStateOff
2793 lampBonus5k.State = LightStateOff
2794 lampBonus6k.State = LightStateOff
2795 lampBonus7k.State = LightStateOff
2796 lampBonus8k.State = LightStateOff
2797 lampBonus9k.State = LightStateOff
2798 lampBonus10k.State= LightStateOff
2799
2800 if ruleSet=RS_authentic then
2801
2802 'This lights only the lamp with the number x (authentic Bally).
2803
2804 select case x
2805 case 1000: lampBonus1k.State=lightStateOn
2806 case 2000: lampBonus2k.State=lightStateOn
2807 case 3000: lampBonus3k.State=lightStateOn
2808 case 4000: lampBonus4k.State=lightStateOn
2809 case 5000: lampBonus5k.State=lightStateOn
2810 case 6000: lampBonus6k.State=lightStateOn
2811 case 7000: lampBonus7k.State=lightStateOn
2812 case 8000: lampBonus8k.State=lightStateOn
2813 case 9000: lampBonus9k.State=lightStateOn
2814 end select
2815
2816 else
2817 'With enhanced game rules this displays a vertical bar of x lights (looks better).
2818
2819 if x>=1000 then lampBonus1k.State = LightStateOn
2820 if x>=2000 then lampBonus2k.State = LightStateOn
2821 if x>=3000 then lampBonus3k.State = LightStateOn
2822 if x>=4000 then lampBonus4k.State = LightStateOn
2823 if x>=5000 then lampBonus5k.State = LightStateOn
2824 if x>=6000 then lampBonus6k.State = LightStateOn
2825 if x>=7000 then lampBonus7k.State = LightStateOn
2826 if x>=8000 then lampBonus8k.State = LightStateOn
2827 if x =9000 then lampBonus9k.State = LightStateOn
2828 end if
2829
2830 if value>=10000 then lampBonus10k.State = LightStateOn
2831
2832 'set specials:
2833
2834 if specialLitValue<>false and bonus>=specialLitValue and not specialHit then
2835 'light both specials when enough bonus
2836 lampLeftCLaneEnd.State = LightStateOn
2837 lampRightCLaneEnd.State = LightStateOn
2838 specialLit=true
2839 end if
2840end sub
2841
2842'end game digit matching assembly: (Bally "00-90 unit")
2843
2844sub advanceMatcher()
2845' This advances the simulated state wheel of the pseudo random number generator
2846' for the end game digit matching feature of the mechanical pinball machine.
2847
2848 playSound "rflipper" 'quiet click
2849
2850 if matchNumber>=9 then
2851 matchNumber=0
2852 else
2853 matchNumber=matchNumber+1
2854 end if
2855end sub
2856
2857
2858sub setEndMatchLights(aValue)
2859' This lights a number on the 00-90 score end digit matching lamp row on the
2860' backglass. aValue must be between 0 and 9 to light one of the lamps,or -1
2861' to light none.
2862
2863 dim i,t
2864
2865 t="" & aValue 'string of aValue
2866 for i=0 to matchedScoreDigit
2867 t=t&"0"
2868 'concatenates the correct number of zeroes behind the digit
2869 next
2870
2871 if aValue=0 then match0Text.text=t else match0Text.text=""
2872 if aValue=1 then match1Text.text=t else match1Text.text=""
2873 if aValue=2 then match2Text.text=t else match2Text.text=""
2874 if aValue=3 then match3Text.text=t else match3Text.text=""
2875 if aValue=4 then match4Text.text=t else match4Text.text=""
2876 if aValue=5 then match5Text.text=t else match5Text.text=""
2877 if aValue=6 then match6Text.text=t else match6Text.text=""
2878 if aValue=7 then match7Text.text=t else match7Text.text=""
2879 if aValue=8 then match8Text.text=t else match8Text.text=""
2880 if aValue=9 then match9Text.text=t else match9Text.text=""
2881end sub
2882
2883'relay
2884'-----
2885
2886sub setRelay(aRelayName,aBoolean)
2887' This sets the boolean variable aRelayName to the value aBoolean and clicks
2888' when the variable had not already the same value to simulate a relay click.
2889
2890 if not aRelayName and aBoolean then
2891 aRelayName=true
2892 playSound "relayOn"
2893 elseIf aRelayName and not aBoolean then
2894 aRelayName=false
2895 playSound "relayOff"
2896 end if
2897end sub
2898
2899
2900'the score motor (main game logics sequencing unit)
2901'~~~~~~~~~~~~~~~
2902
2903sub scoreMotor_timer()
2904' This simulates the score motor,which is the central main command sequencing
2905' unit in an electro-mechanical Bally pinball machine.
2906'
2907' The score motor consists of a motor that rotates a camshaft with multiple
2908' toothed wheels on it.Above each wheel is a stack of leaf switch contacts
2909' those get opened and or closed by the teeth(cams) when the wheels turn.
2910' The camshaft can only rotate a full turn, because only at its 0 position
2911' is a contact that can stop the motor when it has completed a job.
2912'
2913' You can roughly compare the assembly with a mechanical wind-up music box
2914' clockwork,which is designed always to plays the melody to the end before
2915' the cylinder stops at fixed position. Like the tines on the cylinder play
2916' their tones in a fixed sequence, also the leaf switch stacks above the
2917' camshaft move in the same sequence no matter what the relays do.
2918'
2919' A Bally-specific special feature of the score motor is that all cam wheels
2920' on the camshaft have 2 equal hemispheres with each the full set of cams,
2921' and therefore the capstan turns instead of full 360 degree rotations
2922' physically only 180 degree until it stops. Though what I modelled as a full
2923' logical 360 degree rotation would be technically only a half rotation. To
2924' make adaptions for other score motor brands easier,I despite devided each
2925' rotation into 360 degree.
2926'
2927'
2928' This subroutine is called by a timer again and again, and though can be
2929' imagined as the inner part of a for-loop with the running variable
2930' scoreMotorAngle. Like the score motor in a real pinball machine,it serves
2931' many different purposes and therefore contradicts the paradigms of modern
2932' software engineering. What it does depending on which boolean variables
2933' (equivalent to relays in a real pinball machine) are actually set.
2934
2935 if scoreMotorAngle=0 then
2936 stopSound "motorHum1"
2937 playSound "motorHum1",-1
2938 '(re-)starts endless loop sample of Bally score motor hum
2939 end if
2940
2941 processBallInTopHole
2942 processResetSequence
2943 processCollectBonus
2944 processEndGameMatching
2945 processAddCredits
2946 processAddScore
2947
2948 'Note: The order of the above commands is crucial for the correct behaviour
2949 ' of this emulation,because unlike with real in series wired relay
2950 ' switches, the if-statements of this BASIC programs can't recognize when
2951 ' the sequence of testing and setting a boolean variable is reversed.
2952 ' (The real circuit would behave rather like when all if-statements in the
2953 ' above routines would be continuously checked simultaneously. A circuit
2954 ' simulation program based on constraint logics could simulate this,but
2955 ' I doubt that this can be done in this poor BASIC,and it also would likely
2956 ' consume even more CPU capacity than this already quite CPU intensive
2957 ' piece of code takes.)
2958
2959 scoreMotorAngle=scoreMotorAngle+motorStepWidth
2960
2961 if scoreMotorAngle>359 then '360 degree shall not happen
2962 'now check how to start the next camshaft round:
2963
2964 if not keepMotorRunning then
2965 stopSound "motorHum1" 'stops motor rotation sound
2966 scoreMotor.enabled=false
2967 else
2968 keepMotorRunning=false
2969 end if
2970
2971 'The motor stops after one complete round of the camshaft so far the flag
2972 'keepMotorRunning isn't set true. Otherwise the motor continues spinning the
2973 'next cycle,but keepMotorRunning turns false by this,though the flag each time
2974 'needs to be set true again to extend the motor spin time by another round.
2975
2976 scoreMotorAngle=0
2977 end if
2978end sub
2979
2980' The following boolean functions simulate the cams(teeth) on the wheels rotated
2981' by the score motor. Depending on how many teeth a wheel has and where they
2982' are,its contacts are triggered at different times depending on the actual
2983' camshaft rotation angle. In this simulation the rotation angle is modelled by
2984' the scoreMotorAngle variable.
2985'
2986' Note: Important is that the modulo values for the cams can indeed be reached
2987' with the current motorStepWidth setting. Otherwise the function will be
2988' triggered too seldomly or not at all. The reason why I don't use a single
2989' function here is because with multiple functions the exact tooth position
2990' of each wheel can be easier changed by hand. The actual ones are only
2991' guesses. Each function shall correspond to a wheel, but as well the names
2992' as the count of teeth per wheel were choosen by reason and have not
2993' neccessarily equivalents in Bally schematics. I test on
2994' (scoreMotorAngle mod x)=y instead on "...mod x)=0" because this way the
2995' 1st cam of each wheel is simulated slightly(by an angle of y) after the 0
2996' position,which sounds more realistic than when the 1st pulse always starts
2997' already at 0.
2998'
2999'When a routine shall wait on a certain cam that may ocure only in the next camshaft
3000'round,then keepMotorRunning must be set true to prevent the motor from stopping
3001'before reaching it.(The real pinball circuit would use parallel wired switches for
3002'this.)
3003
3004'multi-cam wheels: (good for counting)
3005
3006function is2StepsCamOn()
3007 is2StepsCamOn=(scoreMotorAngle=110 or scoreMotorAngle=290)
3008 'This one must not come before the 2nd step of is4StepsCamOn() - otherwise
3009 'the bonus ladder adds 1000 points to little during double bonus.
3010end function
3011
3012function is3StepsCamOn()
3013 is3StepsCamOn=((scoreMotorAngle mod 40)=20 and scoreMotorAngle<140)
3014end function
3015
3016function is4StepsCamOn()
3017 is4StepsCamOn=((scoreMotorAngle mod 90)=20 and scoreMotorAngle<=290)
3018end function
3019
3020function is5StepsCamOn()
3021 is5StepsCamOn=((scoreMotorAngle mod 50)=20 and scoreMotorAngle<270)
3022end function
3023
3024function is6StepsCamOn()
3025 is6StepsCamOn=((scoreMotorAngle mod 50)=20 and scoreMotorAngle>30)
3026 'The position of these cams may be inaccurate.There should be 0.5s pause before
3027 'the 1st and after the 6th cam.
3028end function
3029
3030'single-cam wheels: (good for sequencing)
3031
3032function isZeroCamOn()
3033' This is true when the motor stands still or is passing its default position.
3034' Note: To directly test on "scoreMotorAngle=0" in the code may be clearer.
3035 isZeroCamOn=(scoreMotorAngle=0)
3036end function
3037
3038function isEarlyCamOn()
3039 isEarlyCamOn=(scoreMotorAngle=30)
3040end function
3041
3042function isMiddleCamOn()
3043 isMiddleCamOn=(scoreMotorAngle=170)
3044end function
3045
3046function isLateCamOn()
3047' This cam must come later than all others to work correctly.
3048 isLateCamOn=(scoreMotorAngle=350)
3049end function
3050
3051function isCamBeforeEnd()
3052' This means that the camshaft is about to reach the 0 position again (where the
3053' score motor will stop when keepMotorRunning=false).Due to after the motor timer
3054' stands still any procedures of the score motor can not be executed anymore until
3055' the motor is started again,this function must be used instead of isZeroCamOn()
3056' to do a job that is intended to be completed when the motor reaches 0 again.
3057'
3058' Note: The value of this function may overleap with isLateCamOn().
3059
3060 isCamBeforeEnd=((scoreMotorAngle+motorStepWidth)>359)
3061end function
3062
3063
3064' The following procedures are repeatedly called by the scoreMotor_timer()
3065' rountine and can be imagined as the inner part of a for-loop that has
3066' the running variable scoreMotorAngle.What they do depends on which camshaft
3067' cam is active at the moment, and this depends on the value of scoreMotorAngle.
3068
3069sub processAddScore()
3070' This adds points to the score using the score motor. In a real Bally pinball
3071' machine this is neccessary when digits need to be flipped multiple times to
3072' score other point amounts than 10,100,1000 and so on.
3073
3074 if add500 and is5StepsCamOn then addScore(100)
3075 if add3000 and is3StepsCamOn then addScore(1000)
3076
3077' These relays are set by the addScore...() procedures,because on a real machine
3078' they can only be set when the score motor is in 0 position. Though a target
3079' only scores points when the motor is not running, because when the relay would
3080' be set in between, less points than the value of the target would be added
3081' depending on the actual score motor position.
3082'
3083' For emulating other pinball machines,relays with other values could be added
3084' here.
3085
3086 if isLateCamOn then
3087 'At the end of a camshaft round the score relays have to be reset to prevent
3088 'endless counting:
3089 setRelay add500 ,false
3090 setRelay add3000,false
3091 end if
3092end sub
3093
3094
3095sub processAddCredits()
3096' This adds credits using the score motor. Important is that new credits must only
3097' be added to unaddedCredits when scoreMotorAngle=0.Otherwise it would count wrong.
3098'
3099' This routine must be called after processResetSequence() and processCollectBonus()
3100' to set the coin rejector correctly.
3101
3102 if isZeroCamOn then setCoinLockOut true 'sets creditsAddable=false
3103
3104 'New credits can only be added when the score motor camshaft has rotated
3105 'entirely to its end(i.e. the motor will stop now so far keepMotorRunning
3106 '= false) and the credit counter is not occupied with counting old credits
3107 'anymore.
3108
3109 if unaddedCredits>0 then
3110
3111 'First I had used different cam wheels for adding different number of credits,
3112 'but Frank Menzler told me that credits are always added by a 6 cam wheel of
3113 'the score motor.But I don't know how it works in detail that the number of
3114 'credits per coin can be adjusted for different coin chutes.
3115 '
3116 'Possibly it uses boolean comparisons among multiple cam wheel switch outputs
3117 'to generate an adjustable count of pulses.I just remember that a Bally "Champ"
3118 'had tons of jumper plugs to set the credits per coin,and that in some odd
3119 'positions (or by bent relay contacts?) it sometimes gave something like 13
3120 'credits per coin.
3121 '
3122 'Frank Menzler claimed that the maximum number of credits per coin would be
3123 '12 because this would be a full physical rotation of the camshaft (i.e. 2
3124 'logical rotations in my model).I don't know if for adding more than 6 credits
3125 'an additional relay was set or if there was a special cam wheel that marks
3126 'by a long cam the current hemisphere of the otherwise perfectly symmetrical
3127 'Bally camshaft.
3128 '
3129 'I just simulated the credit counter behaviour phenomenologically by adding
3130 '1 credit during each cam of the 6 cam wheel and counting down unaddedCredits
3131 'to memorize the number of credits to count,but this has likely nothing to
3132 'do with the inner working of the real machine.
3133
3134 if is6StepsCamOn then
3135 addCredit
3136 unaddedCredits=unaddedCredits-1
3137 if unaddedCredits=0 then saveGame 'save new credit counter value
3138 end if
3139
3140 if isLateCamOn and unaddedCredits>0 then keepMotorRunning=true
3141 'for adding the remaining credits
3142 end if
3143
3144 if isCamBeforeEnd and unaddedCredits=0 and not gameIsResetting then
3145 knockerActive=true 'when it was disabled by inserting a coin
3146 if not(overPayBlocksChutes and credits=maxCredits) then setCoinLockOut false
3147 end if
3148end sub
3149
3150
3151sub processBallInTopHole()
3152' This routine adds 3000 points and ejects the ball from the tophole.
3153' This must be called before processAddScore() to work correctly.
3154
3155 if not ballInTopHole then exit sub
3156
3157 if scoreMotorAngle=0 then
3158 setRelay add3000,true
3159 'The relay must only be set at angle 0 to count the correct amount of points.
3160 else
3161 keepMotorRunning=true
3162 end if
3163
3164 if add3000 and isLateCamOn then
3165 'The test for add3000 prevents that the ball will be kicked out without
3166 'points when the motor is still running from previous activation.
3167 playSound SoundfXDOF("Saucer",110,DOFPulse,DOFContactors)
3168 DOF 111, DOFPulse
3169 topHoleKicker.Kick (85+(120*rnd())),8
3170 setRelay ballInTopHole,false
3171 end if
3172end sub
3173
3174
3175sub processEndGameMatching()
3176' This simulates the end digit matching feature with that players can win
3177' free credits depending on the last score digits.
3178'
3179' I know very little how this works internally. Though this is rather a dummy
3180' than anything emulated. The only thing I know is that a state wheel is
3181' stepped by various events to simulate a random number generator.
3182'
3183' This procedure must be called before processAddCredits to work correctly.
3184
3185 if not digitMatchEnabled or gameIsResetting then exit sub
3186
3187 dim i,n
3188
3189 if matching then
3190
3191 if creditsAddable then
3192 setEndMatchLights(matchNumber)
3193 n=0
3194 for i=1 to players
3195 if scoreDigit(i,matchedScoreDigit)=matchNumber then n=n+1
3196 next
3197
3198 addCredits(n)
3199 setRelay matching,false
3200 else
3201 keepMotorRunning=true
3202 end if
3203
3204 else
3205 if isEarlyCamOn and not gameOver then advanceMatcher
3206 'just a guess - I don't know in which situations the random number state wheel
3207 'should advance.
3208 end if
3209end sub
3210
3211
3212sub processResetSequence()
3213' The following is an attempt to emulate the start-up sequence of a mechanical
3214' Bally pinball machine. I don't know how exact my emulation is.My only source of
3215' information about the reset sequence is based on the great pinball repair site
3216' at http://marvin3m.com and some newsgroup articles.
3217'
3218' This procedure must be called before processCollectBonus() to work correctly.
3219'
3220' Important:
3221'
3222' This is a BALLY reset sequence.EM pinball machines of other manufacturers
3223' process their reset commands in different orders,though if you want to
3224' faithfully emulate machines of other companies(Gottlieb,Chicago Coins etc.),
3225' then the below code must be modified to correctly model their behaviour.
3226
3227 if not gameIsResetting then exit sub
3228
3229 dim i
3230
3231 if gameOver then
3232 keepMotorRunning=true
3233
3234 if isZeroCamOn and not isScoreResetted then
3235 for i=1 to maxPlayers
3236 scoreDigit(i,reelsPerCounter)=0
3237 if extraDigitsEnabled then scoreReelMain(i,reelsPerCounter).text=""
3238 next
3239 'this clears higher surplus digits
3240 end if
3241
3242 if is5StepsCamOn then
3243 if not isScoreResetted then
3244 for i=1 to maxPlayers
3245 unaddedScore(i)=scoreResetNumber
3246 scoreAddTimer(i).enabled=true
3247 next
3248 '(the reset relay noise is part of the counter clicks)
3249 else
3250 playSoundMonophonic "resetClick10Relay"
3251 'let the score reset relay continue clicking
3252 end if
3253 end if
3254
3255 'The score reset mechanism advances all reels of all counter until they
3256 'have reached 0. When gameIsResetting is true, the counters don't step
3257 'further than 0, and also carries are ignored to speed up resetting.
3258 '
3259 'A real pinball machine does this the same way,and each reel has a switch
3260 'in its 0 position,though after all reels are at 0, a signal current can
3261 'flow through all the switches to continue the reset sequence.
3262 '
3263 'I don't know if it is correct that I advance all reels of all counters
3264 'simultaneously.Possibly a real pinball machines devided them into multiple
3265 'groups those are advanced alternatingly (to limit the electric peak current
3266 'that would be likely quite high when about 20 solenoid coils switch simul-
3267 'taneously?) Also my "Royal Flush" PC game advances the digits in groups
3268 'instead,but possibly this is just Gottlieb specific.
3269
3270 if isScoreResetted then 'after all digits reached 0
3271
3272 if isBallCounterResetted and isPlayerCounterResetted and isPlayerUpResetted then
3273
3274 'Here the machine waits until all state wheels are resetted and that the
3275 'ball has returned to the outhole.When it is still in the firing chute,the
3276 'machine starts the new game without the need of ejecting a ball.
3277 '(The nested if-statement keeps the CPU load low in this case,because star-
3278 'ting the state wheel reset timers unneccessary wasted much CPU time.)
3279
3280 if isMiddleCamOn and bonus=0 then
3281 'after bonus ladder "resetted" itself by counting down...
3282
3283 if creditTaken then decrementCredit 'also clears creditTaken relay
3284 setRelay gameOver,false
3285 gameOverText.Text = "" 'counters are resetted now.
3286 If B2SOn Then
3287 Controller.B2SSetGameOver 35,0
3288 Controller.B2SSetMatch 34,0
3289 End If
3290 displayHighScore 'removes "new" after a previous highscore
3291 setEndMatchLights -1 'clears end match number display
3292 setBackboxLights true
3293 playfieldLightsEnabled=true
3294 resetLights '(Here the playfield lights get updated.)
3295Dim Fuckingcode:fuckingcode=0
3296 If B2SOn Then
3297 Controller.B2ssetscore 1, fuckingcode
3298 Controller.B2ssetscore 2, fuckingcode
3299 Controller.B2ssetscore 3, fuckingcode
3300 Controller.B2ssetscore 4, fuckingcode
3301 End If
3302 'The game will now be started by processCollectBonus() because the
3303 'gameOver relay was cleared.
3304 end if
3305 elseIf isEarlyCamOn then
3306 'This resets the state wheels on those we waited above.
3307
3308' setBackboxLights true 'TEST!!! this shows the state wheel resets
3309 playerCounterReset.enabled=true
3310 playerUpReset.enabled =true
3311 ballCounterReset.enabled =true
3312 end if
3313 end if
3314
3315 else
3316 'When we are here,after a successful reset the gameOver relay was cleared...
3317 keepMotorRunning=true
3318 'This waits until processCollectBonus() can eject the ball.
3319 end if
3320end sub
3321
3322
3323sub processCollectBonus()
3324' This routine is started by the score motor.It steps down the bonus ladder and
3325' adds the bonus to the score so far there is no tilt.
3326
3327 if not (ballInDrain or gameIsResetting) then exit sub
3328
3329 'Reset must count down the bonus even without ball in drain.
3330
3331 if bonus>0 and not newRound then
3332 keepMotorRunning=true
3333
3334 select case ruleSet
3335 case RS_authentic
3336 if (not doubleBonus and is2StepsCamOn) or (doubleBonus and is4StepsCamOn) then addScore bonusStepWidth
3337 if is2StepsCamOn then decrementBonus
3338
3339 'This makes the bonus step down with a fixed speed and during double bonus
3340 'adds twice as fast const bonusStepWidth to the score.(In "Boomerang"
3341 'bonusStepWidth=1000 points.)
3342
3343 case RS_enhanced
3344 if is4StepsCamOn then addScore bonusStepWidth
3345 if (doubleBonus and is2StepsCamOn) or (not doubleBonus and is4StepsCamOn) then decrementBonus
3346 'In the enhanced mode I use the old version (counts faster,sounds better).
3347 end select
3348
3349 'is4StepsCamOn comes not before the 2nd step of is4StepsCamOn() - otherwise
3350 'we would empty the bonus counter to soon and though add 1x bonusStepWidth to
3351 'little(?) during double bonus.
3352 '
3353 'In an old version of this program I added 4x bonusStepWidth per camshaft
3354 'round,because otherwise the bonus ladder descended very slowly,but I was
3355 'told that the bonus must count faster and not longer during double bonus,
3356 'therefore I modified this for the authentic mode.
3357 '
3358 'Is my new speed and working principle assumption more correct?
3359 else
3360 if not gameOver then startNextRound
3361 end if
3362end sub
3363
3364
3365sub startNextRound()
3366' This routine is called after bonus collecting was finished.
3367' It checks if we have game-over or how the game continues. This routine is
3368' also called at game start by processCollectBonus().
3369
3370 if isMiddleCamOn and not newRound then
3371
3372 if not extraBallWon and not gameIsResetting then
3373 'Not gameIsResetting prevents mess when the pinball machine comes up and
3374 'the ball is still in the firing chute during reset. (E.g. after overpaying
3375 'the 1st coin chute.)
3376
3377 setShootAgain false
3378 nextPlayer 'this can set game over
3379 end if
3380
3381 setExtraBall false
3382 setRelay specialHit,false
3383
3384 if gameOver then exit sub
3385 'The game over relay would prevent the rest of this procedure.
3386
3387 if not shootAgainStaysLit then setShootAgain false '("shoot again" lamps off)
3388
3389 if not ruleSet=RS_authentic then
3390 spinnerReset.enabled=true
3391 else
3392 if spins=0 then spins=1
3393 'In authentic mode the spinner never resets,but it can not have the
3394 'value 0.
3395 end if
3396
3397 unTilt 'also activates flippers & bumpers
3398 tiltHasEffect=true
3399 setPlayfieldLights true
3400 setIllumination true
3401 resetLights
3402 if initBonusIs1000 then incrementBonus
3403
3404' setScore 1,96000 'TEST!!!
3405' bonus=13000 'TEST!!!
3406' doubleBonus=true 'TEST!!!
3407' updatePlayfieldLights 'TEST!!!
3408
3409 setRelay newRound,true
3410
3411 'This relay stays true until points are scored. Though as long no points
3412 'are scored in a round,the ball will be only re-ejected without collecting
3413 'bonus or changing any lights on the playfield. This is known as the
3414 'famous "fairplay interlock" feature.It can be disabled by setting const
3415 'restartWhen0Pts=false.
3416 '
3417 'Note: The behaviour that even a tilt voids no ball in the above state is
3418 ' realistic, because it was invented to prevent the loose of 2 balls
3419 ' when by too much nudging the freshly ejected ball plummeted back into
3420 ' the eject hole without scoring a single point.
3421 '
3422 ' By setting const tiltVoids0PtsRestart=true the behaviour at tilt can
3423 ' be changed.
3424 end if
3425
3426 if isLateCamOn and newRound and (isSpinnerResetted or ruleSet=RS_authentic) then
3427 '(newRound was set above...)
3428
3429 setRelay gameIsResetting,false 'this is a guess to exit reset loop
3430
3431 reelChimeEnabled=true
3432 releaseStuckScoreReels 'so far any are stuck
3433 ejectBall 'so far in outhole
3434 if not restartWhen0Pts then setRelay newRound,false 'fairplay interlock
3435 end if
3436end sub
3437
3438
3439sub nextPlayer()
3440' This switches to the next player or does game over depending on how many
3441' balls are left and players in the game.
3442
3443 if currentPlayer>=players then
3444 incrementBallCounter
3445
3446 if (ruleSet=RS_authentic and ballCount>ballsPerGame0) or (ruleSet=RS_enhanced and ballCount>ballsPerGame1) then
3447 doGameOver
3448 if digitMatchEnabled then
3449 setRelay matching,true 'starts end digit matching
3450 keepMotorRunning=true
3451 end if
3452 exit sub
3453
3454 end if
3455 end if
3456
3457 nextPlayerUp 'This increments currentPlayer or sets it 1 when >players.
3458end sub
3459
3460
3461sub doGameOver()
3462 setRelay gameOver,true
3463 gameOverText.text = "GAME OVER"
3464 If B2SOn Then
3465 Controller.B2SSetGameOver 35,1
3466 Controller.B2SSetBallInPlay 32, 0
3467 End If
3468 setPlayfieldLights false
3469 tiltHasEffect=false
3470 if tilt then untilt 'clears tilt sign
3471 setIllumination true
3472 setPlayfieldLights false
3473 lightCenterBumper false
3474 setShootAgain false 'so far a running game was canceled by creditButtonRoutine()
3475 setBackboxLights false
3476
3477 setBumpers false
3478 setFlippers false
3479 releaseStuckScoreReels 'so far any are stuck
3480 if digitMatchEnabled then setEndMatchLights(matchNumber)
3481 If B2SOn Then Controller.B2SSetMatch 34,matchnumber
3482 updateHighscore
3483 saveGame 'saving highscore etc.
3484 rulesSwitchable=true
3485end sub
3486
3487
3488' The following routines simulate the way a mechanical Bally pinball machine
3489' adds score amounts those multiple times advance digits using the score
3490' motor. Note that these routines only add score when the camshaft is in its 0
3491' position (i.e. e.g. when it is not turning).
3492
3493sub addScore500()
3494 if scoreMotorAngle=0 then setRelay add500,true
3495 scoreMotor.enabled=true 'rest does the score motor
3496end sub
3497
3498sub addScore3000()
3499 if scoreMotorAngle=0 then setRelay add3000,true
3500 scoreMotor.enabled=true 'rest does the score motor
3501end sub
3502
3503
3504'the reel-a-listic(tm) ;-) score counter assembly:
3505'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3506'This code simulates a great looking,backlighted mechanical reel score counter
3507'assembly;the reels can even get stuck and hum when a ball lies on a playfield
3508'switch. When the counter flows over,the missing score digits don't get lost but
3509'are displayed outside the reel counter using lamp-like looking extra digits(so
3510'far extraDigitsEnabled is set true).The extra digits can display even multi-
3511'digit overflow values - the digit count of them is only limited by the used
3512'textBox width.
3513
3514sub initializeScoreCounters()
3515' This initializer must be called at program start before the score counter can
3516' be used.
3517
3518
3519 set scoreReelTop(1,0)=scoreTopP1D0
3520 set scoreReelTop(1,1)=scoreTopP1D1
3521 set scoreReelTop(1,2)=scoreTopP1D2
3522 set scoreReelTop(1,3)=scoreTopP1D3
3523 set scoreReelTop(1,4)=scoreTopP1D4
3524
3525 set scoreReelMain(1,0)=scoreMidP1D0
3526 set scoreReelMain(1,1)=scoreMidP1D1
3527 set scoreReelMain(1,2)=scoreMidP1D2
3528 set scoreReelMain(1,3)=scoreMidP1D3
3529 set scoreReelMain(1,4)=scoreMidP1D4
3530 set scoreReelMain(1,5)=scoreP1Extra
3531
3532 set scoreReelLow(1,0)=scoreLowP1D0
3533 set scoreReelLow(1,1)=scoreLowP1D1
3534 set scoreReelLow(1,2)=scoreLowP1D2
3535 set scoreReelLow(1,3)=scoreLowP1D3
3536 set scoreReelLow(1,4)=scoreLowP1D4
3537
3538 set scoreReelBorder(1,0)=scoreClr0P1
3539 set scoreReelBorder(1,1)=scoreClr1P1
3540
3541 set scoreReelTop(2,0)=scoreTopP2D0
3542 set scoreReelTop(2,1)=scoreTopP2D1
3543 set scoreReelTop(2,2)=scoreTopP2D2
3544 set scoreReelTop(2,3)=scoreTopP2D3
3545 set scoreReelTop(2,4)=scoreTopP2D4
3546
3547 set scoreReelMain(2,0)=scoreMidP2D0
3548 set scoreReelMain(2,1)=scoreMidP2D1
3549 set scoreReelMain(2,2)=scoreMidP2D2
3550 set scoreReelMain(2,3)=scoreMidP2D3
3551 set scoreReelMain(2,4)=scoreMidP2D4
3552 set scoreReelMain(2,5)=scoreP2Extra
3553
3554 set scoreReelLow(2,0)=scoreLowP2D0
3555 set scoreReelLow(2,1)=scoreLowP2D1
3556 set scoreReelLow(2,2)=scoreLowP2D2
3557 set scoreReelLow(2,3)=scoreLowP2D3
3558 set scoreReelLow(2,4)=scoreLowP2D4
3559
3560 set scoreReelBorder(2,0)=scoreClr0P2
3561 set scoreReelBorder(2,1)=scoreClr1P2
3562
3563 set scoreReelTop(3,0)=scoreTopP3D0
3564 set scoreReelTop(3,1)=scoreTopP3D1
3565 set scoreReelTop(3,2)=scoreTopP3D2
3566 set scoreReelTop(3,3)=scoreTopP3D3
3567 set scoreReelTop(3,4)=scoreTopP3D4
3568
3569 set scoreReelMain(3,0)=scoreMidP3D0
3570 set scoreReelMain(3,1)=scoreMidP3D1
3571 set scoreReelMain(3,2)=scoreMidP3D2
3572 set scoreReelMain(3,3)=scoreMidP3D3
3573 set scoreReelMain(3,4)=scoreMidP3D4
3574 set scoreReelMain(3,5)=scoreP3Extra
3575
3576 set scoreReelLow(3,0)=scoreLowP3D0
3577 set scoreReelLow(3,1)=scoreLowP3D1
3578 set scoreReelLow(3,2)=scoreLowP3D2
3579 set scoreReelLow(3,3)=scoreLowP3D3
3580 set scoreReelLow(3,4)=scoreLowP3D4
3581
3582 set scoreReelBorder(3,0)=scoreClr0P3
3583 set scoreReelBorder(3,1)=scoreClr1P3
3584
3585 set scoreReelTop(4,0)=scoreTopP4D0
3586 set scoreReelTop(4,1)=scoreTopP4D1
3587 set scoreReelTop(4,2)=scoreTopP4D2
3588 set scoreReelTop(4,3)=scoreTopP4D3
3589 set scoreReelTop(4,4)=scoreTopP4D4
3590
3591 set scoreReelMain(4,0)=scoreMidP4D0
3592 set scoreReelMain(4,1)=scoreMidP4D1
3593 set scoreReelMain(4,2)=scoreMidP4D2
3594 set scoreReelMain(4,3)=scoreMidP4D3
3595 set scoreReelMain(4,4)=scoreMidP4D4
3596 set scoreReelMain(4,5)=scoreP4Extra
3597
3598 set scoreReelLow(4,0)=scoreLowP4D0
3599 set scoreReelLow(4,1)=scoreLowP4D1
3600 set scoreReelLow(4,2)=scoreLowP4D2
3601 set scoreReelLow(4,3)=scoreLowP4D3
3602 set scoreReelLow(4,4)=scoreLowP4D4
3603
3604 set scoreReelBorder(4,0)=scoreClr0P4
3605 set scoreReelBorder(4,1)=scoreClr1P4
3606
3607 set scoreAddTimer(1)=scoreAddTimer1
3608 set scoreAddTimer(2)=scoreAddTimer2
3609 set scoreAddTimer(3)=scoreAddTimer3
3610 set scoreAddTimer(4)=scoreAddTimer4
3611
3612 set scoreSpinTimer(1)=scoreSpinTimer1
3613 set scoreSpinTimer(2)=scoreSpinTimer2
3614 set scoreSpinTimer(3)=scoreSpinTimer3
3615 set scoreSpinTimer(4)=scoreSpinTimer4
3616
3617 dim i,j
3618 for i=0 to maxScoreReelNoises
3619 scoreReelNoise(i)=false
3620 next
3621
3622 scoreReelNoise(0)="resetClick10Relay"
3623 scoreReelNoise(1)="resetClick9"
3624 scoreReelNoise(2)="resetClick8"
3625 scoreReelNoise(3)="resetClick7"
3626 scoreReelNoise(4)="resetClick6"
3627 scoreReelNoise(5)="resetClick5"
3628 scoreReelNoise(6)="resetClick4"
3629 scoreReelNoise(7)="resetClick3"
3630 scoreReelNoise(8)="resetClick2"
3631 scoreReelNoise(9)="resetClick1"
3632
3633 'These samples shall simulate the click of n simultaneous score counter reels.
3634 '
3635 'Note: These are just samples taken from the n-th counter click of a Bally
3636 ' "Fireball" reset sequence.I don't know how many counters really turned
3637 ' at each click.
3638
3639 for i=0 to reelsPerCounter
3640 scoreReelChime(i)=false
3641 next
3642
3643 scoreReelChime(1)="bell10"
3644 scoreReelChime(2)="bell100"
3645 scoreReelChime(3)="bell1000"
3646
3647 'Each counter digit can have a chime sound that is played whenever the counter
3648 'reel of the digit advances. The chime sound can be disabled by setting
3649 'reelChimeEnabled=false. When only for certain digits chime sounds exist,the
3650 'remaining array indices of scoreReelChime() must be set false.
3651 '
3652 'Note: This chime emulation needs a little extra CPU time and it may sound
3653 ' less accurate than a simulation by a long sample of multiple clangs
3654 ' that is directly started each time a ball hits a trigger. But as a
3655 ' benefit the number of clangs with the emulation allways exactly matches
3656 ' the earned score.
3657
3658 if chimeForExtraDigits then scoreReelChime(reelsPerCounter)="gong"
3659 'This sound is played when an extra digit advances by a reel counter overflow.
3660
3661 for i=minCounterNr to maxPlayers
3662 scoreAddTimer(i).interval=reelCarryTime
3663 reelTime(i)=reelSpinTime
3664
3665 for j=0 to reelsPerCounter 'the last element contains extra digits
3666 scoreDigit (i,j)=0
3667 stuckScoreReel(i,j)=0
3668 next
3669 next
3670
3671 scoreResetNumber=0
3672 for i=0 to reelsPerCounter-1
3673 scoreResetNumber=scoreResetNumber+(10^i)
3674 'Generates a number of reelsPerCounter times "1" (like 11111) which is used
3675 'in the counter reset emulation.
3676 next
3677
3678 reelChimeEnabled=true
3679end sub
3680
3681
3682sub addScore(points)
3683' This routine adds points to the score counter of the current player.
3684'
3685' (This behaviour reflects quite well the hardware architecture of the
3686' mechanical Bally pinball machine, which can add points also only to the
3687' currently selected player.)
3688'
3689' Important:
3690' This routine can basically add any amount of points at any time. A real
3691' mechanical pinball machine counter can only add 1 to any digit and though
3692' can only add point values consisting of 1 and 0 (e.g. 100,1000,110 etc.),
3693' though it needs the score motor to add other values. To emulate the behaviour
3694' correct, therefore routines in the style of addScore500() and addScore3000()
3695' (see there) should be used to get realistic timing. (E.g. while these are
3696' counting,another hit of a target with the same value has no effect.)
3697
3698 setRelay newRound,false
3699
3700 'This relay stays true until points are scored. Though as long no points
3701 'are scored in a round,the ball will be only re-ejected without collecting
3702 'bonus or changing any lights on the playfield. This is known as the
3703 'famous "fairplay interlock" feature.
3704
3705 if shootAgain and not extraBallWon then setShootAgain false
3706
3707 'When we had an extra ball and the next round has begun,the extraBallWon relay
3708 'has been cleared,but the shootAgain relay (and though lamps) stay on until
3709 'points are scored. (When shootAgainStaysLit=false,it will have been cleared
3710 'already when the new round started.)
3711
3712 if not gameOver and not tilt and currentPlayer>0 then addScoreToPlayer currentPlayer,points
3713
3714end sub
3715
3716dim ascore
3717sub addScoreToPlayer(aPlayer,points)
3718' This adds points to the score counter of a Player.
3719 if lossyScoreCounter then unaddedScore(aPlayer)=0
3720 'A real mechanical score counter can't memorize multiple scores to
3721 'add,though when there is too little time left between score events,
3722 'the counter skips counting the rest of the previous score.
3723 '
3724 'This is an approximation of this behaviour. (In reality it would
3725 'possibly affect only certain digits and digit values.)
3726
3727 unaddedScore(aPlayer)=unaddedScore(aPlayer)+points
3728 If B2SOn Then Controller.B2ssetscore (aplayer), getscore(aplayer)+unaddedscore(aplayer)
3729
3730 scoreAddTimer(aPlayer).enabled=true
3731
3732 'The digits of unaddedScore are counted down while the counter adds them
3733 'to the score.
3734end sub
3735
3736
3737sub addScoreDigitStuck(aDigit)
3738' This advances a score digit reel by 1 and locks it until releaseScoreDigit()
3739' for that digit will be called.
3740'
3741' Call this procedure when a ball hits a rollover trigger that has a fixed score
3742' value that advances a digit by 1 directly (without the score motor).Call
3743' releaseScoreDigit() for the digit in the _unhit() procedure of the same target
3744' to enable the digit again when the ball leaves the switch.
3745' Important is that the currentPlayer must not be changed so long a digit is
3746' stuck. After a different player is up, releaseStuckScoreReels() should be
3747' called to free any stuck digits (setCurrentPlayer() does this).
3748'
3749' (Internally a counting semaphore memorizes how often this procedure is called,
3750' therefore to release the digit,releaseScoreDigit() must be called for it
3751' the same number of times.)
3752
3753 setRelay newRound,false
3754 if shootAgain and not extraBallWon then setShootAgain false
3755
3756 if not gameOver and not tilt and currentPlayer>0 then
3757 incrementScoreDigit currentPlayer,aDigit,true
3758 end if
3759end sub
3760
3761
3762sub releaseScoreDigit(aDigit)
3763' This frees a score digit that was stuck with addScoreDigitStuck().
3764 releaseScoreReel currentPlayer,aDigit
3765end sub
3766
3767
3768sub addScoreDigitStuckBySema(aDigit,aSema)
3769' This does the above but additionally uses an external semaphore.
3770'
3771' This routine should be used for rollover triggers those score value can
3772' change(e.g. become 0) during the game,because releaseScoreDigit() must
3773' only be called for a digit that was previously stuck by the same rollover
3774' trigger to get realistic behaviour in multi-ball games. Each such rollover
3775' trigger must use an own semaphore (global variable initialized with 0) per
3776' involved digit to work correctly (i.e. use an array when the rollover can
3777' stick different digits depending on its actual score value.)
3778
3779 if aSema<0 then aSema=0 'would be out of range
3780 if aSema=0 then addScoreDigitStuck(aDigit)
3781 aSema=aSema+1
3782
3783 'We use here a counting semaphore,because Visual Pinball can(according to the
3784 'docs) call the _hit() procedure of a switch multiple times before calling
3785 '_unhit() when multiple balls touch the same switch simultaneously.
3786end sub
3787
3788
3789sub releaseScoreDigitBySema(aDigit,aSema)
3790' This does the above but additionally uses an external semaphore
3791' (see addScoreDigitStuckBySema() ).
3792
3793 if aSema=1 then releaseScoreDigit(aDigit) 'only when sema switches from 1 to 0
3794 if aSema>=1 then aSema=aSema-1
3795end sub
3796
3797
3798sub scoreAddTimer1_timer()
3799 scoreAddTimerRoutine 1
3800end sub
3801
3802sub scoreAddTimer2_timer()
3803 scoreAddTimerRoutine 2
3804end sub
3805
3806sub scoreAddTimer3_timer()
3807 scoreAddTimerRoutine 3
3808end sub
3809
3810sub scoreAddTimer4_timer()
3811 scoreAddTimerRoutine 4
3812end sub
3813
3814sub scoreAddTimerRoutine(aPlayer)
3815' To add some points to the score counter of a player, the points first need
3816' to be added to the variable unaddedScore(aPlayer) and then the timer
3817' scoreAddTimer(aPlayer) for the player must be enabled to start counting, which
3818' is done in addScore().
3819'
3820' This reel counter simulation code is capable to run multiple score displays at
3821' the same time, and they even have independent timers, which enables them to
3822' rotate (and though click) out of phase to each other when started this way.
3823'
3824' Notes:
3825' To implement a mechanical highscore display (like in "Kewpie Doll"),the unused
3826' player 0 postion in the arrays could be used.
3827
3828' This routine can not count backwards,because this would need a completely
3829' different carry processing logics. But so far I know,no mechanical pinball
3830' machine uses countdown counters, and also the normal Bally counters could
3831' not do this. If anybody needs such counters, it would certainly be possible to
3832' re-write this code with a backward stepping routine, but it would take likely
3833' twice as much effort.
3834
3835 if unaddedScore(aPlayer)=0 then
3836 scoreAddTimer(aPlayer).enabled=false
3837 scoreAddTimer(aPlayer).interval=reelCarryTime 'makes counter start faster
3838 scoreIsCounting(aPlayer)=false
3839 else
3840 scoreAddTimer(aPlayer).interval=reelSpinTime
3841 scoreIsCounting(aPlayer)=true
3842 end if
3843
3844 'The scoreAddTimer is not delayed by the carry routine,but it always expects
3845 'that the carry routine will be faster. This theoretically might make trouble
3846 'on stupidly slow PCs or when the reelCarryTime would be set ridicoulously slow,
3847 'but it has the benefit that the counter clicks much more rhythmical this way
3848 'and especially the reel chimes sound way better because they don't get out of
3849 'sync by the varying reelSpinTime speed.
3850
3851 dim i,t
3852 if extraDigitsEnabled then t=reelsPerCounter else t=reelsPerCounter-1
3853
3854 for i=0 to t
3855 if getDigit(unaddedScore(aPlayer),i)>0 then
3856 unaddedScore(aPlayer)=unaddedScore(aPlayer)-(10^i)
3857 incrementScoreDigit aPlayer,i,false
3858 end if
3859 next
3860
3861 'This decrements each non-zero digit of unaddedScore(aPlayer) by 1 while it adds the
3862 '1 to the digit of the score counter.
3863end sub
3864
3865
3866sub incrementScoreDigit(aPlayer,digit,isStuck)
3867' This increments a score counter reel(a digit) of the current player by 1 and
3868' displays it halfway rotated to its next value. The digits are numbered from
3869' right to left starting with 0.
3870'
3871' The trick how this works was discovered by PeBo.It is based on the use of
3872' 3 vertically overleaping text boxes per score display digit. To draw a
3873' counter reel in a halfway rotated position,the old cipher is drawn in the
3874' top text box while the coming cipher is drawn in the lower text box.(The
3875' cipher in the middle text box is erased by overdrawing from the other
3876' text boxes.) To get rid of the top half of the top cipher and the bottom
3877' half of the bottom cipher,2 bar-shaped empty textboxes are drawn over them.
3878' (Printing "" into a textbox displays nothing but overwrites the contents
3879' of other text boxes with blanks at the overleaped area.) Then the
3880' scoreSpinTimer is started,which after a fraction of a second clears the top
3881' and the bottom text box and instead writes the new cipher into the middle
3882' ("main") text box.
3883'
3884' To make this counter work correctly,it is crucial that the drawing order of
3885' the overleaping textboxes (changable with "draw in front","draw in back") in
3886' the editor must be the same as intended in the game;otherwise the overdraw-
3887' ing trick fails. The layers should be (from back to front):
3888'
3889' 1.counter reel decale,frame decale and backlight (order is not that important)
3890' 2.top and bottom cipher text boxes
3891' 3.main cipher text boxes
3892' 4.upper and lower clearing bar text boxes
3893'
3894' Note: The way how the counter reels jerk into an intermediate position and
3895' then further to the next cipher look not that unrealistic. In a real
3896' mechanical pinball counter the reels also don't rotate smoothly,
3897' because they are not turned by a motor,but instead a coil quickly
3898' pulls each reel quickly into the intermediate position by a short
3899' current pulse, and when the pulse ends, a spring makes the reel flip
3900' further to its next stable position.
3901'
3902' When the boolean isStuck is set true,the reel will stay in this position and
3903' refuse any further counting until releaseScoreReel() for this digit will be
3904' called to simulate a blocked solenoid like e.g. when a ball laying on a
3905' playfield overroll switch. This is implemented with counting semaphores,
3906' therefore when incrementScoreDigit(...,...,true) is called multiple times,
3907' releaseScoreReel() must be called the same times to enable it again.The
3908' purpose of this is to simulate multiple switches wired in series those e.g.
3909' in a multi-ball game could be blocked by multiple balls simultaneously.
3910'
3911' To simulate the counter backlight,all the mentioned textboxes are transparent
3912' and behind them is rectangular lamp. Due to lights in visual pinball look
3913' extremely dark when they are off, between the lamp and the textboxes there
3914' is a white decal,of that every 2nd pixel is black;due to black was set to
3915' its transparent colour, though the light of the lamp can shine trough it like
3916' daylight through a white gaze curtain. (Also see lightCenterBumper().)
3917'
3918' Due to a Visual Pinball bug the background of text boxes gets not updated when
3919' a lamp behind it is switched on or off,therefore the routine
3920' updateScoreDisplays() must be called every time these lamps are switched.
3921
3922 if isStuck then
3923 stuckScoreReel(aPlayer,digit)=stuckScoreReel(aPlayer,digit)+1
3924 if stuckScoreReel(aPlayer,digit)=1 then
3925 if stuckReelsBuzz=0 then playSound "reelStuckBuzz",-1
3926 'starts endless loop of solenoid buzz
3927 stuckReelsBuzz=stuckReelsBuzz+1
3928 end if
3929 end if
3930
3931 if not(stuckScoreReel(aPlayer,digit)=0 or isStuck and stuckScoreReel(aPlayer,digit)=1) then exit sub
3932
3933 'This executes the rest of this procedure only when the stuckScoreReel()
3934 'semaphore of a digit is 0 or when it is set to 1 the first time.
3935
3936 if gameIsResetting and scoreDigit(aPlayer,digit)=0 then exit sub
3937 'During reset any reels rotate only until 0.
3938
3939 scoreIsCounting(aPlayer)=true
3940
3941 'This flag signals that the counter is still running. As long it is true,
3942 'the game should not be switched to a different player.
3943 '(A real mechanical pinball machine would count wrong when the player is
3944 'switched at the wrong moment. My algorithm may prevent this,but I am not
3945 'sure if it works reliable,nor would this be realistic.)
3946
3947 dim top
3948 top=scoreDigit(aPlayer,digit)
3949 scoreDigit(aPlayer,digit)=scoreDigit(aPlayer,digit)+1
3950
3951 if digit<reelsPerCounter then 'when not an extra digit,display it as rotated reel
3952
3953 if scoreDigit(aPlayer,digit)=10 then
3954 scoreDigit(aPlayer,digit)=0
3955 top=9
3956 if not gameIsResetting then carry(aPlayer,digit)=1
3957 'sets a carry for the next digit
3958 end if
3959
3960 scoreReelLow(aPlayer,digit).text=scoreDigit(aPlayer,digit) 'print bottom cipher
3961 scoreReelBorder(aPlayer,1).text ="" 'clear its lower half
3962 scoreReelTop(aPlayer,digit).text=top 'print top cipher
3963 scoreReelBorder(aPlayer,0).text ="" 'clear its upper half
3964
3965 if isStuck then playSound "reelStuck2" else playReelClick
3966 'This is the mechanical counter click for a digit,that is played always
3967 'when the digit switches.
3968 end if
3969
3970 if reelChimeEnabled and scoreReelChime(digit)<>false and aPlayer>0 then
3971 'aPlayer=0 can only be a highscore counter that shall remain mute.
3972 if not(reelChimeForCarry=false and digit>0 and carry(aPlayer,max(digit-1,0))=1) or digit=reelsPerCounter then
3973 'so far this digit was not flipped by a carry and reelChimeForCarry=false
3974 'or when this is an extra digit.
3975 if digit = 5 Then
3976 playSoundMonophonic SoundFXDOF(scoreReelChime(digit),145,DOFPulse,DOFBell)
3977 else
3978 playSoundMonophonic SoundFXDOF(scoreReelChime(digit),140 + digit,DOFPulse,DOFChimes)
3979 end if
3980
3981 'This is an optional chime sound for each digit,that can be disabled e.g. when
3982 'the counter is in reset mode.
3983 '
3984 'In a real mechanical pinball machine it depends on how the chimes are wired,
3985 'whether they sound also by carry. When they are directly connected with the
3986 'counter coils, they unavoidably will. When they are instead connected with a
3987 'score motor switch or relay that steps the counter forward, then they won't
3988 'notice carries and though sound only when the score reel advances by adding
3989 'points. The constant reelChimeForCarry makes both behaviours selectable.
3990 '
3991 'The chime for extra digits(digit=reelsPerCounter) always reacts on carries,
3992 'because in a proper designed simulation they could anyway only be trigger by
3993 'the "carry" from an overflowing score counter,though otherwise it would
3994 'sound never.
3995 end if
3996 end if
3997
3998 scoreSpinTimer(aPlayer).interval=reelTime(aPlayer)
3999 scoreSpinTimer(aPlayer).enabled=true 'the rest does spinScoreReels()
4000end sub
4001
4002
4003sub scoreSpinTimer1_timer()
4004 spinScoreReels 1
4005end sub
4006
4007sub scoreSpinTimer2_timer()
4008 spinScoreReels 2
4009end sub
4010
4011sub scoreSpinTimer3_timer()
4012 spinScoreReels 3
4013end sub
4014
4015sub scoreSpinTimer4_timer()
4016 spinScoreReels 4
4017end sub
4018
4019sub spinScoreReels(aPlayer)
4020' This is called by the scoreSpinTimers completes rotation of the reels to their
4021' stable position.This routine is also called by setScore() to update the
4022' display.
4023'
4024' Note: Actually all digits are flipped simultaneously when this counter gets
4025' active. This in some cases can look unrealistic.Better would be the use
4026' of an agenda (time job list) in that all reels write their intended
4027' flip time to flip independently by using a timer that repeatedly
4028' executes the next pending job. An agenda would help a lot also with other
4029' problems of this emulation, but I don't know any easy way to do something
4030' like this in BASIC.
4031
4032' gameOverText.text=getScore(aPlayer) 'TEST!!!
4033
4034 dim i
4035 scoreSpinTimer(aPlayer).enabled=false
4036 for i=0 to reelsPerCounter-1
4037 if stuckScoreReel(aPlayer,i)=0 then scoreReelMain(aPlayer,i).text=scoreDigit(aPlayer,i)
4038 'print cipher in digit middle
4039 next
4040
4041 if extraDigitsEnabled then
4042 'This displays the missing digits when the score counter overflows in a
4043 '(sort of) "light ciphers" style:
4044
4045 if scoreDigit(aPlayer,reelsPerCounter)>0 then
4046 scoreReelMain(aPlayer,reelsPerCounter).text=scoreDigit(aPlayer,reelsPerCounter)
4047 else
4048 scoreReelMain(aPlayer,reelsPerCounter).text=""
4049 'prevents a "0" when counter is long enough
4050 end if
4051 end if
4052
4053 carryForward aPlayer
4054end sub
4055
4056
4057sub carryForward(aPlayer)
4058' This checks if a carry value must ripple upward to a higher digit.
4059
4060' I use here one carry array per counter. This may appear unneccessary,but when
4061' aPlayer is switched at the wrong moment,this may be safer than sharing
4062' a single array for all score counters.
4063
4064 dim i
4065 for i=0 to reelsPerCounter-1
4066 if carry(aPlayer,i)=1 then
4067 reelTime(aPlayer)=reelCarryTime 'a digit moved by carry shall flip faster
4068 incrementScoreDigit aPlayer,i+1,false
4069 'The highest array cell accumulates all missing higher digits.
4070 carry(aPlayer,i)=0
4071 end if
4072 next
4073
4074 reelTime(aPlayer)=reelSpinTime
4075
4076' if unaddedScore(aPlayer)>0 then 'a stricter synchronization method (looks jerky)
4077' scoreAddTimer(aPlayer).enabled=true
4078' else
4079' scoreIsCounting(aPlayer)=false
4080' end if
4081end sub
4082
4083
4084sub releaseScoreReel(aPlayer,digit)
4085' This releases a stuck scoring reel digit of a player
4086' (see IncrementScoreDigit() ).
4087
4088 if stuckScoreReel(aPlayer,digit)=0 then exit sub
4089
4090 subtractUntil0 stuckScoreReel(aPlayer,digit),1
4091
4092 if stuckScoreReel(aPlayer,digit)=0 then
4093 subtractUntil0 stuckReelsBuzz,1
4094 if stuckReelsBuzz=0 then stopSound "reelStuckBuzz" 'stops solenoid buzzing
4095
4096 scoreReelMain(aPlayer,digit).text=scoreDigit(aPlayer,digit)
4097 playSound "reelRelease"
4098 end if
4099end sub
4100
4101
4102sub releaseStuckScoreReels()
4103' This is a safety command that releases all stuck score reels for the case
4104' that anything goes wrong with the counting semaphores.
4105
4106 dim i,j
4107
4108 for i=minCounterNr to maxPlayers
4109 for j=0 to reelsPerCounter
4110 if stuckScoreReel(i,j)<>0 then
4111 stuckScoreReel(i,j)=1
4112 releaseScoreReel i,j
4113 end if
4114 next
4115 next
4116
4117 stuckReelsBuzz=0
4118end sub
4119
4120
4121sub updateScoreDisplays()
4122' This must be called after switching the score backlight on or off to fix a
4123' graphical bug in Visual Pinball (lighted areas behind textboxes otherwise
4124' don't get updated).
4125
4126 dim i,j
4127
4128 for i=minCounterNr to maxPlayers
4129 for j=0 to reelsPerCounter-1
4130 scoreReelLow(i,j).text=scoreReelLow(i,j).text
4131 scoreReelBorder(i,1).text=""
4132 scoreReelTop(i,j).text=scoreReelTop(i,j).text
4133 scoreReelBorder(i,0).text=""
4134 scoreReelMain(i,j).text=scoreReelMain(i,j).text
4135 next
4136 if extraDigitsEnabled then scoreReelMain(i,reelsPerCounter).text=scoreReelMain(i,reelsPerCounter).text
4137 next
4138end sub
4139
4140
4141sub setScore(aPlayer,aNumber)
4142' This sets a score counter directly to a value (possibly useful for loading
4143' a highscore into a counter).
4144
4145 dim i
4146 for i=0 to reelsPerCounter-1
4147 scoreDigit(aPlayer,i)=getDigit(aNumber,i)
4148 carry(aPlayer,i)=0
4149 next
4150 scoreDigit(aPlayer,reelsPerCounter)=int(aNumber/(10^reelsPerCounter))
4151
4152 spinScoreReels aPlayer 'this updates the score display
4153end sub
4154
4155
4156function getScore(aPlayer)
4157' This returns as an integer the actually displayed score value of a player,
4158' including all missing higher digits.
4159'
4160' Note: This routine does NOT care about the still unadded (buffered) score
4161' the counter is about to add.Therefore it must be waited until
4162' scoreIsCounting(aPlayer)=false to get the completed score value of a
4163' player (e.g. for saving highscores etc.).
4164
4165 dim i,aScore
4166
4167 aScore=0
4168 for i=0 to reelsPerCounter
4169 aScore=aScore+(scoreDigit(aPlayer,i)*10^i)
4170
4171 next
4172
4173 getScore=aScore
4174end function
4175
4176
4177function getDigit(aNumber,n)
4178' This returns the n-th digit from aNumber; aNumber can be a number string or
4179' an integer number.
4180
4181 if len(aNumber)>n then
4182 getDigit=mid(aNumber,len(aNumber)-n,1)
4183 else
4184 getDigit="0"
4185 end if
4186 'return values look like a function name assignment in this silly BASIC.
4187end function
4188
4189
4190function isScoreResetted()
4191' This returns true when all score reels of all score counters are 0.
4192
4193 dim i
4194 for i=minCounterNr to maxPlayers
4195 if getScore(i)<>0 then
4196 isScoreResetted=false
4197 exit function
4198 end if
4199 next
4200 isScoreResetted=true
4201end function
4202
4203
4204sub debugScoreDisplayAlignment
4205' This is a test routine that can be used to align the text boxes of the score
4206' counters on the backdrop picture correctly.
4207' (Don't fear, although the result looks nasty, these counter solenoids are
4208' purely virtual and won't be toasted by this procedure. ;-) )
4209'
4210' This routine is not intended to be used within a finished game program.
4211
4212 dim i,j
4213
4214 for i=1 to maxPlayers
4215 unaddedScore(i)=0
4216
4217 for j=0 to reelsPerCounter-1
4218 scoreDigit(i,j)=8
4219 carry(i,j)=0
4220 incrementScoreDigit i,j,true
4221 'this sticks all score reels between 8 and 9
4222 next
4223 next
4224end sub
4225
4226
4227'sound effect routines:
4228'----------------------
4229
4230sub playReelClick()
4231 'This is the mechanical counter click for a score reel,that is played always
4232 'when the digit switches.
4233
4234 reelClicks=reelClicks+1
4235 clickTimer.enabled=true
4236end sub
4237
4238
4239sub clickTimer_timer
4240
4241 dim n
4242
4243 if reelClicks>0 then
4244 n=reelClicks
4245 if n>maxScoreReelNoises then n=maxScoreReelNoises
4246
4247 if n>3 then n=n-int(3*rnd()) 'a little random makes sound more vivid
4248 subtractUntil0 reelClicks,n
4249
4250 playSound scoreReelNoise(n)
4251
4252 'This plays now a sample of n simultaneous reel clicks.The remaining clicks
4253 'will be played the next times this timer re-starts.
4254
4255 clickTimer.interval=1+2*rnd()
4256
4257 'This adds a little random to the timer delay to prevent phasing and avoid
4258 'overdrive distortion of the output during counter reset.
4259 else
4260 clickTimer.enabled=false
4261 end if
4262end sub
4263
4264
4265sub playSoundMonophonic(aSoundString)
4266' This plays a sound only on a single voice at a time by shutting down all old
4267' still playing samples of the same name.This can dramatically reduce the
4268' polyphony demand when a sound plays in a loop,and though saves a lot of CPU
4269' time and also prevents overdriving the maximum volume.
4270'
4271' Attention: This works only well with sounds that start immediately.When the
4272' loop is too fast and there is a delay in the sample then the
4273' main sound won't be audible at all.Don't use this routine when
4274' the same sample is used for multiple playfield objects those shall
4275' be capable to sound independantly,because their sound would cancel
4276' out each other with this routine.A memory wasting workaround would
4277' be to load multiple copies of the same samples under a different
4278' name.
4279'
4280 stopSound aSoundString
4281 playSound aSoundString
4282end sub
4283
4284'highscore routines:
4285'-------------------
4286
4287sub updateHighScore()
4288' This calculates the new highscore from the scores of all players and displays it.
4289
4290 if not highScoreEnabled then exit sub
4291
4292 dim i,t,updated
4293
4294 updated=false
4295
4296 for i=1 to maxPlayers
4297 if getScore(i)>highScore(ruleSet) then
4298 highScore(ruleSet)=getScore(i)
4299 updated=true
4300 end if
4301 next
4302
4303 if updated then t="new " else t=""
4304 t=t+"high score:" & chr(13) & highScore(ruleSet)
4305 highScoreText.text=t
4306
4307 if updated then
4308 if highScoreSoundEnabled then playSound "didge-a-redo"
4309 'This sample I found on a very old "Romware" freeware CD-ROM under the
4310 'name SHIPHORN.WAV and immediately I thought whow - what a racket!
4311 'It's so incredible lo-fi and crackling that it sounds much rather like a
4312 '(kind of) mechanically played didgerido,though I immediately knew where
4313 'to use it... All attempts to de-click it made it sound just boring,thus
4314 'I kept it in all its grainy beauty like it is.
4315 end if
4316end sub
4317
4318
4319sub displayHighScore()
4320' This displays the actual highscore. (used when a new game starts to remove
4321' the "new" before the word "high score:",or when the game rules get
4322' switched.)
4323
4324 highScoreText.text="high score:" & chr(13) & highScore(ruleSet)
4325end sub
4326
4327
4328' This was copied from Mr.Do!,where it was copied from the brilliant Eight Ball
4329' Deluxe but I(we?) believe credit and thanks should go to boris for the script.
4330
4331Sub loadGameStats(dirFile, nameFile)
4332 Dim fso, ts
4333 Dim TestFolder, files, file, f
4334 Dim temp, pos
4335 Dim prm, value
4336
4337 Set fso = CreateObject("Scripting.FileSystemObject")
4338 If Not fso.FolderExists(dirFile) Then Exit Sub
4339 Set TestFolder = fso.GetFolder(dirFile)
4340 Set files = TestFolder.Files
4341
4342 If (files.Count <> 0) Then
4343 For Each file in files
4344 If nameFile=file.Name Then
4345 Set f = fso.GetFile(dirFile & nameFile)
4346 Set ts = f.OpenAsTextStream(1,0)
4347 Do While (ts.AtEndOfStream <> True)
4348 temp = ts.ReadLine
4349 pos = instr(1,temp,"=")
4350 If (pos > 1) Then
4351 prm = Left(temp,pos-1)
4352 value = mid(temp,pos+1)
4353 Select Case prm
4354 case "highscore_authentic"
4355 highScore(RS_authentic)=CDbl(value)
4356 case "highscore_enhanced"
4357 highScore(RS_enhanced) =CDbl(value)
4358 case "currentRuleSet"
4359 ruleSet=CDbl(value)
4360 case "credits"
4361 credits=CDbl(value)
4362 case "matchNumber"
4363 matchNumber=CDbl(value)
4364 case "spinnerState"
4365 spins=CDbl(value)
4366 case "score1"
4367 setScore 1,CDbl(value)
4368 case "score2"
4369 setScore 2,CDbl(value)
4370 case "score3"
4371 setScore 3,CDbl(value)
4372 case "score4"
4373 setScore 4,CDbl(value)
4374 End Select
4375 If B2SOn Then Controller.B2SSetcredits Credits
4376
4377 End If
4378 Loop
4379 ts.close
4380 Set f = Nothing
4381 End If
4382 Next
4383 End If
4384 Set fso = Nothing
4385End Sub
4386
4387
4388Sub saveGameStats(fileName)
4389 Dim fso, f
4390
4391 Set fso = CreateObject("Scripting.FileSystemObject")
4392 If Not fso.FolderExists(UserDirectory) Then Exit Sub
4393 Set f = fso.CreateTextFile(fileName, True)
4394 f.writeLine "highscore_authentic=" & highScore(RS_authentic)
4395 f.writeLine "highscore_enhanced=" & highScore(RS_enhanced)
4396 f.writeLine "currentRuleSet=" & ruleSet
4397 f.writeLine "credits=" & credits
4398 f.writeLine "matchNumber=" & matchNumber
4399 f.writeLine "spinnerState=" & spins
4400 f.writeLine "score1=" & getScore(1)
4401 f.writeLine "score2=" & getScore(2)
4402 f.writeLine "score3=" & getScore(3)
4403 f.writeLine "score4=" & getScore(4)
4404 f.close
4405 Set f = Nothing
4406 Set fso = Nothing
4407End Sub
4408
4409
4410sub saveGame()
4411' This saves the game status so far the highScoreFileName variable is set.
4412 if highScoreFileName<>false then saveGameStats UserDirectory & "\" & highScoreFileName
4413end sub
4414
4415
4416'general arithmetics:
4417'--------------------
4418function max(value1,value2)
4419' returns the bigger of 2 values
4420 if value2>value1 then max=value2 else max=value1
4421end function
4422
4423function min(value1,value2)
4424' returns the smaller of 2 values
4425 if value2<value1 then min=value2 else min=value1
4426end function
4427
4428
4429sub subtractUntil0(aVariable,aNumber)
4430' This subtracts aNumber from aVariable so far the result is >0;else aVariable
4431' is set 0 without getting negative in between.
4432' (This is intended to prevents race conditions with counting semaphores and
4433' other state variables those can be simultaneously set and decremented by
4434' different tasks.)
4435
4436 if aVariable>aNumber then aVariable=aVariable-aNumber else aVariable=0
4437end sub
4438
4439'instruction messages:
4440
4441sub showIntro_timer()
4442' This displays the intro text requester.
4443 showIntro.enabled=false
4444
4445 dim t
4446
4447
4448end sub
4449
4450
4451sub showRuleSheet()
4452' This displays a brief summary of the game rules.
4453
4454 dim t
4455
4456 t= "RULE SHEET: "
4457 select case ruleSet
4458 case RS_authentic: t=t+"(authentic Bally)"
4459 case RS_enhanced : t=t+"(enhanced mode - more challenging)"
4460 end select
4461 t=t+chr(13)&chr(13)
4462 if ruleSet=RS_enhanced then t=t&"Tophole lights center bumper and upper 1k rollovers."& chr(13)
4463 t=t&"A,B,C,D targets light A,B,C,D rollovers in captive ball lanes."& chr(13)
4464 t=t&"Hit A+B+C+D targets to win double bonus."& chr(13)
4465 t=t&"Left & right inlanes, spinner hits and lit "
4466 if ruleSet=RS_enhanced then t=t&"A,B,C,D "
4467 t=t&"rollovers advance bonus."& chr(13)
4468 t=t&"Maximum bonus="& maxBonus &".High bonusses light outlane extra ball & special."& chr(13)
4469
4470 select case ruleSet
4471 case RS_authentic
4472 if specialLitValue<>false then
4473 if specialShootAgain0 then t=t&"Special wins extra ball."& chr(13)
4474 if specialWinsCredit0 then t=t&"Special wins 1 free credit."& chr(13)
4475 if specialWinsPoints0>0 then t=t&"Special scores "&specialWinsPoints0&" points."&chr(13)
4476 end if
4477
4478 case RS_enhanced
4479 if topHoleShootAgain<>false then
4480 t=t&"At bonus "& topHoleShootAgain &" tophole wins extra ball."& chr(13)
4481 end if
4482 if specialLitValue<>false then
4483 if specialShootAgain1 then t=t&"Special wins extra ball."& chr(13)
4484 if specialWinsCredit1 then t=t&"Special wins 1 free credit."& chr(13)
4485 if specialWinsPoints1>0 then t=t&"Special scores "&specialWinsPoints1&" points."&chr(13)
4486 end if
4487 end select
4488
4489 t=t&"A tilt does not disqualify a player."& chr(13)
4490 t=t&chr(13)
4491 t=t&"Push credit button to start game or add 2nd,3rd or 4th player."& chr(13)
4492 t=t&chr(13)
4493 t=t&"Insert "
4494 if coinChute1Credits<>false then
4495 t=t&"small washer for "& coinChute1Credits &" credit"
4496 if coinChute1Credits>1 then t=t&"s" else t=t&" (starts game)"
4497 t=t&chr(13)
4498 end if
4499 if coinChute2Credits<>false then
4500 t=t&"mid size washer for "& coinChute2Credits &" credits"& chr(13)
4501 end if
4502 if coinChute3Credits<>false then
4503 t=t&"large washer for "& coinChute3Credits &" credits"& chr(13)
4504 end if
4505 t=t&chr(13)
4506 t=t&"Washers® - the arcade operator's least beloved foreign currency..."
4507 msgBox t
4508end sub
4509
4510
4511sub showKeySettings()
4512' This simply fills a static text box with instruction text.
4513
4514 dim t
4515
4516 t= "M = Select Game Mode"& chr(13)
4517 t=t&"5,6,7 = Insert Washer"& chr(13)
4518 t=t&"S = Credit Button"& chr(13)
4519 t=t&"R for Rule Sheet"& chr(13)
4520
4521 keysBox.text=t
4522end sub
4523
4524 Sub LeftFlipper_Collide(parm)
4525 PlaySound "rubber"
4526 End Sub
4527
4528 Sub RightFlipper_Collide(parm)
4529 PlaySound "rubber"
4530 End Sub
4531
4532 Sub Rubbers_Hit(IDX)
4533 PlaySound "rubber"
4534 End Sub
4535
4536Sub theTable_Exit
4537 If B2SOn Then Controller.Stop
4538End Sub