Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ' Müslifresser Softworx proudly presents...
- '
- ' B O O M E R A N G
- '
- ' a programming example (v1.4)
- '
- '
- 'pinball machine design (c)1974 by Bally
- 'version for Visual Pinball copyleft 2001 by CYBERYOGI =CO=Windler
- 'This is seed-ware; with this I spread my knowledge to the mankind though that
- 'people can learn to make other great pinball emulations too,to preserve the
- 'experience of realizing and playing these beautiful historical games in virtual
- 'form and enable everybody to play them again...
- '
- '
- 'SOURCE CODE:
- 'This program was mostly written by myself but partly inspirated by the horrible
- 'spaghetti-code of an egomanic moron who really doesn't deserve to be mentioned
- 'here.
- '
- 'This source code is a piece of literate programming and should be quite self-
- 'explanatory (I hope :-) ) and even be suited as a tutorial example. I hope
- 'this will help other people to faithfully port and create many other good
- 'pinballs games on Visual Pinball.
- '
- 'This program is not really object oriented, but I attempted to make it as
- 'human-readable as possible. I therefore added lots of comments, gave most
- 'variables and functions understandable names and used functions for all code
- 'sequences those have a special meaning or are used multiple times. Although
- 'it would be conceptionally even more canonical, I did not allways use 1
- 'function per light to encapsulate its state (on or off) and the corresponding
- 'variable, because most lights are switched at only few places in the code,
- 'which stays understandable also without tons of functions.
- '
- 'I don't know how to do object orientation in Micro$ofts crappy Visual Basic,
- 'nor if the dialect of Visual Pinball would supports it at all. The last time
- 'I programmed in such a BASIC was on my Amiga in the very slow dialect Amiga
- 'BASIC 1.2 (yes, from Micro$oft), which definitely included no object orien-
- 'tation. But at least I used functions in the style of "setter methods" for
- 'all things those have a side effect (e.g. updating an on-screen counter or
- 'playing a sound) to achieve a consistent behaviour. To read values of vari-
- 'ables, I otherwise did not use functions in the style of "getter methods",
- 'but read them directly, because I hope that this works in this BASIC without
- 'nasty multi-threading side effects.
- '
- 'Notes:This game first jerked a lot on my AMD K6 300MHz because the resting pins
- ' of the captive bals were badly placed and surfaces badly aligned, which
- ' triggered numerical instabilities of Visual Pinbal those made these
- ' balls continuously bounce around and never come entirely to rest. Though
- ' accidently the CPU time of a 3er multi-ball play was consumed even when
- ' only one ball was moved by the player. Small changes of the pin shape
- ' and position reduce this problem dramatically,but any banal modifications
- ' of "Wall" objects on the playfield can make it neccessary to re-adjust
- ' the resting pins again.On my PC it still runs not perfectly smooth;espe-
- ' cially the timing of rhythmic sounds is often quite off.
- '
- ' Some sounds are based on another simulation of this games,and additional
- ' sounds were added from the Visual PinMAME default sample set. (They are
- ' likely inaccurate, but at least similar to a mechanical Bally pinball
- ' machine. I don't know if there were bells at all in Bally games of that
- ' era.) There are still some state wheel sounds fake and the volume ratios
- ' are bad,too. The coin sounds were faked by me.Various additional sounds
- ' were donated by Frank Menzler (thanks).
- ' The solenoid mains hum and buzz sounds were hand-made by me;I hope I
- ' didn't exaggerate them.But remember; an EM pinball hardware is NOT one
- ' of these gentle,harmless,noiseless 3.3V CMOS highly integrated microchip
- ' thingies where you fearlessly can touch contacts with bare finger during
- ' operation. Its nothing cute,but it is a loudly rumbling,mechanical,mains
- ' operated monstrosity consisting of rattling,revolving contact wheels and
- ' lots of green sparks spitting relays - and now don't come in mind to
- ' imagine these relays as those 2cm small,airtight sealed acryl plastic
- ' boxes with tiny,gold plated contacts inside you may know from your local
- ' electronics store - no,these relays are a bunch of finger-long,corroded
- ' looking metal leaf switches without any cover or protection,and inside
- ' the machine you can smell the odour of slowly toasting metal,pressboard
- ' formaldehyd and a little ozon that lets no occasion untried to make the
- ' silver balls less shiny and to attack the playfield rubber parts when
- ' they are made from pure noble natural latex. (Sorry for these explicit
- ' utterances,but there seem to be many PC users around those can't imagine
- ' that there was ever anything digital based on boolean logics besides
- ' multi-layer SMD mainboard equipped microprocessor operated micro-elec-
- ' tronics.)
- '
- ' In this code I often use the term "lamp" rather then "light" to name
- ' objects,because "light" can als be a verb which could sometimes result in
- ' confusion. With "light" I usually mean rather the actual state of a
- ' lamp or group of lamps than the physical lamp itself.
- '
- ' This program includes 2 switchable game rule sets - one is the authentic
- ' version from a real Bally "Boomerang" machine,the 2nd is a more challen-
- ' ging rule set created by me.The reason for this is that I originally in-
- ' tended to emulate the original pinball game as close as possible,but by
- ' the lack of access to the real machine I had partly defined my own rules.
- ' Later I was contacted by Jeff Stuecheli who explained me the original
- ' rules and behaviour of his "Boomerang" machine,just to discover that
- ' these authentic rules sounded very boring and way too easy to me.Though
- ' I decided to implement 2 switchable rule sets to make it possible to keep
- ' my old rules and despite provide an authentic emulation of the original
- ' game rules by Bally.I hope this doesn't complicate the source code too
- ' much;the program switches by a key press between both rule sets using the
- ' variable "ruleSet".Its value RS_authentic (=0) selects the authentic
- ' Bally rules,while the value RS_enhanced (=1) selects my own rule set.(If
- ' you intend to use source code parts for other games without rule selec-
- ' tion,you can remove the conditional statements those test the "ruleSet"
- ' value and simply use unconditioned statements for your game rules instead.)
- '
- ' Various options of the game can be adjusted in the "operator settings"
- ' section below.Many additional game machine dependant parameters can be
- ' changed in the "pinball machine constants" section;some depend on actual
- ' given game configurations (number of score displays on the backbox etc.)
- ' but others (especially boolean flags) often can be changed easily without
- ' modifying anything else. These are especially interesting when some
- ' generic behaviour details of a pinball machine are currently unknown and
- ' shall be adjusted later when anybody else finds them out.
- '
- ' On the table a huge decal that simulates the rims of the acryl parts,
- ' hinders the view onto the playfield.If you want to learn how this
- ' table is built (or just modify it),best first write down its coordinates
- ' etc. and remove the thing(resize to 10*10 or so works best and also speeds
- ' up rendering time - the original values in "Boomerang" are width 927.313,
- ' height 1503.13,rotation 0.) To adapt this program to a different pinball
- ' game,I strongly recommend to copy the new parts into this program and not
- ' vice versa because Visual Pinball 3 yet supports no export of graphics/
- ' sounds and (worse?) it is impossible to copy playfield objects with keeping
- ' their names,which makes modifications very awkward.
- '
- 'TECH INFO: (This is the world first EM pinball emulator!)
- '
- 'This piece of software is the possibly world first open source attempt to
- 'EMULATE the inner working of an electro-mechanical pinball machine. (The
- 'commercial PC game Amtex "Royal Flush" also looks like an emulation,but I am
- 'not sure.)
- '
- 'Electro-mechanical pinball machines contain neither ROMs nor anything computer-
- 'like,but my idea was to simulate at least the behaviour of the score motor (the
- 'camshaft-based main command sequencing unit in such a machine) to reproduce the
- 'general timing behaviour of the circuit. When correctly implemented,this
- 'automatically should e.g. play all sounds of the mechanism with the right timing
- '(except on my slow PC :S ) and also reproduce flaws like that certain targets
- 'don't score points when they are hit while the motor is still running.Though
- 'every mechanical sound you hear in this game is caused by an action performed
- 'by the internal mechanism (and not just because "it sounds cool" to play a pre-
- 'recorded sample of the entire reset sequence before the game starts).Listen
- 'carefully e.g. to the state wheel reset noises - they really depend on what the
- 'previous wheel rotation angle was.
- '
- '(If you have no clue of what I talk about,then urgently take a look at the
- 'great pinball repair site at http://marvin3m.com to learn how a real electro-
- 'mechanical pinball machine works internally.)
- '
- 'This emulation is certainly not very accurate,because I have neither access to
- 'pinball schematics nor to the real pinball machine nor I have time to work
- 'myself through EM pinball schematics (those are said to be really cryptic).
- 'Though I did not emulate every single relay,although most of them certainly
- 'have an equivalent in my code.Also the dependencies among the relays are
- 'not modelled when unknown or not important,because I don't know how to easily
- 'express in BASIC that when e.g. a relay turns a lamp off but a closed switch
- 'is wired in parallel it still supplies the lamp,that then the lamp must
- 'not turn off. My relays are just binary flags,though everything that shall be
- 'done by them must be done by hand in the code whereever their state is changed.
- '
- 'Certainly I know well about boolean logics in BASIC,but for making a perfect
- 'emulation it would be necessary to write somekind of digital circuit simulator
- '(like PSPICE) which can be fed with the relay wiring table from the original
- 'pinball machine schematics, and then would simulate the states of all relays
- 'and voltages on every single line when the states of the input lines change.
- 'Such an approach would be generally possible. (In the book "Structure
- 'and Interpretation of Computer Programs" by Abelson and Sussman is a Scheme
- '(LISP) program listing explained that does something similar.) But I have
- 'not the slightest imagination how the network-like wiring dependencies among
- 'relays could be described in a non-objectoriented BASIC program without ending
- 'up in a terrible chaos of a thousand of arrays.
- '
- 'The game logics description in an exact circuit emulator would also become a
- 'very abstract and badly understandable piece of code that would be almost
- 'useless for hobby programmers without schematics. Also because I wanted to
- 'write an understandable program that everybody can modify to add game rules
- 'or to simulate completely different mechanical pinball games with,I did not
- 'try to code such a number table driven monstrosity.
- '
- 'I saw now that it is possible to use classes in this BASIC and that there
- 'is even an "eval" command to implement a thunk concept (executing strings as
- 'code),which may be enough to port Abelson and Sussman's digital circuit
- 'emulator,but I have no time at the moment.If anybody else wants to implement
- 'a physically perfect emulation program - you are welcome to write it!
- '
- '
- 'Notes:I have not much time to answer questions about programming,nor to become
- ' coordinator of an open source team.I wrote this because it needed to be
- ' done,but now I have to prepare myself again for my thesis(development of
- ' a gesture and sing controlled VR music synthesizer program).I hope you
- ' can understand this source code without too much e-mail questions.
- '
- ' And for people those still don't get it: All the "TM" and "®" signs for
- ' fancy named features of this souce code etc. are a HOAX to kid capitalism.
- ' This is an open source project and free for everybody.Although it is
- ' almost unbelievable,some people indeed took this for real and flamed me
- ' because they thought it would be a nasty anti-open-source measure.In re-
- ' ality registering a trademark is (at least in Germany) a costly bureau-
- ' cratic act that takes at least some months and (by my knowledge) costs
- ' a fee of multiple hundred (when not thousands) of dollars. Thus trademar-
- ' king random words for code features of a free open source program is such
- ' a ridiculously absurd idea that I never came in mind that it was even re-
- ' motely possible that anybody could take this for real.I am a strict anti-
- ' capitalist and fight against any approaches to patent software algorithms
- ' or human genes or to trademark commonly used words to beat profit out of
- ' them and the like. The only genuine trademarks in my VP games are the
- ' ones those were made by the pinball machine manufacturers themselves.
- '
- '
- 'CREDITS:
- '
- 'Many thanks to Randy Davis,who created Visual Pinball and though made my work
- 'and the revival of all these great historical pinball games possible.(What a
- 'pity that there is no Linux version...) Also thanks to PeBo,who find out the
- 'trick how to simulate a realistic looking mechanical reel score counter,to
- 'Kurt Herman for the idea of round voxel graphics 3D targets and thanks to Boris
- '(?) for the highscore save and load routine.Thanks to Frank Gigliotti for the
- 'tip and picture for the shiny ball.Also thanks to the Visual PinMAME team for
- 'some sounds and especially very much thanks to Frank Menzler who donated the
- 'great Bally mechanism sound sample set from his nice "Fireball" VP game and
- 'also helped me with details about the Bally hardware.Thanks to Jeff Stuecheli
- 'for the detailed explanation of the original "Boomerang" game rules.
- '
- 'No thanks at all to an egomanic moron who refuses any programming help and
- 'flamed me instead by his disgustingly self-rightous false pride.(Though
- 'never forget - boomerangs always come back... ;-) )
- '
- '
- 'LICENCE: ("Seedware licence v1.1 for Visual Pinball games")
- '
- 'This is seed-ware; with this I spread my knowledge to the mankind though that
- 'people can learn to make other great pinball emulations too,to preserve the
- 'experience of realizing and playing these beautiful historical games in virtual
- 'form and enable everybody to play them again.
- '
- 'For video arcade games there is MAME,for home computers and consoles there is
- 'MESS and also computerized pinball machines have been emulated with PinMAME,but
- 'yet there was no solution for electro-mechanical pinball machines.With this
- 'work I hope I can help to close the gap by providing a sort of framework for
- 'building other emulations from.
- '
- 'You are permitted and encouraged to use parts of this source code or other
- 'parts of this game in other Visual Pinball tables and publish them for non-
- 'commercial purposes, so far you give me (CYBERYOGI =CO=Windler) credits and
- 'include the URL of my site (see below) into the derived work.
- '
- 'You are also permitted to port this code to other programming languages and
- 'hardware platforms to hopefully soon get rid of that "MS"-desease it is yet
- 'still bound to.If you publish a modified version of a seedware game that was
- 'created by someone else,you must use a different file name than the original
- 'work to prevent confusion,and you also must include your e-mail adress and a
- 'download URL or other description to enable people to obtain the unmodified
- 'version of it.
- '
- 'All source code parts must be documented using code comments (in the style of
- 'literate programming) in a way that other programmers can understand them.When
- 'you change code parts,then you must also update the documentation to explain
- 'the changes.
- '
- 'If you emulate a pinball machine that has a certain feature (e.g. playfield
- 'illumination) but you are too lazy to implement it in your playfield and
- 'backbox model,then you must NOT delete the corresponding routines from this
- 'source code,but instead implement them as empty,commented dummy routine to
- 'permit everybody to easily add the missing features later.
- '
- 'This program and its components are distributed "as is" and come with absolute-
- 'ly no warranties.
- '
- 'All copyleft and copyright messages of authors and co-authors must be kept in-
- 'tact.
- '
- 'This seedware and its derived works can NOT be sold for profit without my ex-
- 'plicit written permission and the permission of all co-authors.
- '
- 'All new written program code that includes portions of this code or is based
- 'on it (so far published) must also be releases as open source under this
- 'license to follow the universal law of software and let the seed grow and
- 'multiply.
- '
- '(If you make such works,please inform me by e-mail because I would like to
- 'receive a copy of your work too.But never(!) e-mail to me a VPT file itself
- 'or other huge data without my prior explicit request,because huge files
- 'could result in a mail box crash which could endanger me to loose e-mails.
- 'Instead better e-mail me a download URL of your VPT file.)
- '
- 'When you make a new pinball game derived from this seedware,you have the right
- 'to exclude art components of your own work from open source in a way that they
- 'may not be modified and/or not removed from your pinball game and/or not in-
- 'cluded into different pinball games,so far these components are mainly copyrigh-
- 'ted by you and NOT substancially based on the artwork of a given pinball ma-
- 'chine.The here described "art components" cover only pictures,text and music
- 'elements - they can never cover technical parts of the source code nor technical
- 'playfield objects nor sound samples taken from a pinball machine.
- '
- 'So far you use seedware code to upgrade a given Visual Pinball game that initi-
- 'ally was created by 3rd persons (not by you) under a different open source li-
- 'cence,then you must mention the game's original author and explain in the merged
- 'code that the original parts of the game are not seedware and provide a source
- '(e.g. download URL) to enable people (e.g. other programmers) to obtain the un-
- 'modified version under its original licence unless the initial creator of the VP
- 'game accepts the upgraded version as the new official release of his game and
- 'allows to change the new version's licence entirely into seedware.
- '
- 'It is FORBIDDEN to use parts of seedware for purposes or products those severely
- 'glorify brutality or senseless brain destruction(e.g. alcohol,glutamate products
- 'or other drugs),cruelty,environmental destruction,machismo or other socially
- 'harmful things.
- '
- 'If you intend to remake a historical pinball game that deals with such a topic,
- 'you may possibly get my permission by e-mail, but in this case you must
- 'include a visible warning into the game that its topic is ok only within a
- 'piece of pinball game history but not in reality.
- '
- 'The following messages and sayings must not be modified or removed from this
- 'game:
- '
- '-the copyleft message including "by CYBERYOGI =CO=Windler,teachmaster of
- ' Logologie - the 1st cyberage-religion!"
- '-"ABANDON THE BRUTALITY!"
- '-the formula "V.R. HERE!" (hidden on the backdrop picture).
- '-the disclaimer text on the intro screen and that I respect shamanic cultures
- ' and love this game's artwork.
- '-the use of "washers" instead of coins in texts of this game,including:
- ' "Washers® - the arcade operator's least beloved foreign currency...".
- '
- 'The Cyberyoga icon (the green,drawn line being with heart and smily) in this
- 'game is copyrighted by me (=CO= Windler) and may not be modified or spread in
- 'modified state without my permission. :)-E-8
- '
- '
- ' MAY THE SOFTWARE BE WITH YOU!
- '
- '*============================================================================*
- 'I CYBERYOGI Christian Oliver(=CO=) Windler I
- 'I (teachmaster of LOGOLOGIE - the first cyberage-religion!) I
- 'I ! I
- '*=============================ABANDON=THE=BRUTALITY==========================*
- ' {http://www.informatik.fh-hamburg.de/~windle_c/e_index.html}
- '
- 'e-mail: [email protected]
- option explicit
- '(This option makes declarations of global variables strictly neccessary - it
- 'needs a little more typing work but prevents awful to find bugs by misspelled
- 'variable names.Due to all undeclared variables in BASIC are initialized with 0
- 'or false before use,without this you otherwise would NEVER get errors by mis-
- 'spelled variables those were intended to be anyway false or 0 - only the
- 'program would behave wrong whithout any obvious reason."option explicit" is
- 'the only protection against this.)
- On Error Resume Next
- ExecuteGlobal GetTextFile("controller.vbs")
- If Err Then MsgBox "You need the controller.vbs in order to run this table, available in the vp10 package"
- On Error Goto 0
- Const cGameName = "boomerang_1974"
- 'operator settings
- '=================
- 'for authentic game rules mode:
- const ballsPerGame0 =3 'max. 5 balls (The counter can't display more.)
- const specialWinsCredit0=false 'true: hitting the special wins a free credit
- const specialShootAgain0=true 'true: hitting the special wins an extra ball
- const specialWinsPoints0=false 'amount of points won by hitting a special (false=none)
- 'for enhanced game rules mode:
- const ballsPerGame1 =3 'max. 5 balls
- const specialWinsCredit1=true 'true: hitting the special wins a free credit
- const specialShootAgain1=false 'true: hitting the special wins an extra ball
- const specialWinsPoints1=10000 'amount of points won by hitting a special (false=none)
- const topHoleShootAgain =15000 'bonus value to make tophole give a "shoot again" (false=off)
- const bonusClearsBumper =true 'true: bonus at topHoleShootAgain value turns center bumper
- 'light off.
- 'common:
- const coinChute1Credits=1 'credits for inserting a small washer
- const coinChute2Credits=2 'credits for mid-sized washers
- const coinChute3Credits=12 'credits for large washers (=false "removes" a slot)
- const maxBonus =15000 'According to rules flyer the machine can only count to 15000.
- const specialLitValue =15000 'bonus value to light the captive ball lane specials(false=off)
- const knockerEnabled =true 'sounds a loud separate knocker when winning extra credits
- const tiltEnabled =true
- const tiltSensitivity =4 'higher value = lower sensitivity
- const tiltSwingDecay =0.2 'higher value = faster pendulum decay
- const initBonusIs1000 =true 'true: each round starts with bonus 1000.
- const digitMatchEnabled =true 'true: score digit matching can win free credits
- const extraDigitsEnabled =true 'true: small numbers display missing higher score digits
- const chimeForExtraDigits =true 'true: a gong is played for extra (overflow) digits
- const lossyScoreCounter =false'true: score counter skips recognizing too fast points to
- 'simulate mechanical limitations. (This simulates a nasty
- 'technical flaw - PLEASE keep this turned off!)
- const ballRollNoisesEnabled=true'If you don't like the ball roll noises or the game runs
- 'too jerky on a slow PC,set this false to disable.
- const highScoreSoundEnabled=true'plays sound effect after highscore
- const highScoreFileName ="Boomerang_highscore.txt"
- 'name of highscore save file,or false to disable save/load
- '-----------------------------------------------------------------------------------------------
- 'Some general Visual Pinball tips:
- '
- 'Before you design a Visual Pinball playfield,check that the picture is not
- 'distorted;otherwise use a graphics program to remove the distortion.Due to
- 'parallax effects higher playfield parts on photos tend to appear closer to
- 'rims than low parts.Though e.g. the playfield bottom area(near the outhole)
- 'which should be a vertical wall can look like a slanted floor area.Cut this
- 'piece out and paste the rest of the photo together again to make the playfield
- 'shorter.(Though nudging the ball up again will work better.)
- 'In Visual Pinball the "Table" width and height parameters can be used to fix a
- 'wrong width to height ratio.Also the relative playfield size in ratio to the
- 'ball is determined this way.Adjust these values so that round bumper tops on
- 'the photo will match with the shape of a real bumper.Use a Kicker hole to see
- 'how large a ball is in ratio to the photo.
- '
- 'Decals can be used on lamps or to display a high resolution detail on a low-
- 'res background to save memory.To scale a decal for use as a playfield part or
- 'backbox part,use the following formula:
- '
- 'scaledLength = areaLength*(decalLength/pictureLength), where
- '
- ' length = a height or width
- ' areaLength = length of the entire playfield or backdrop in the editor
- ' decalLength = length of the decal (in pixels)
- ' pictureLength= length of the backdrop or playfield picture the decal was
- ' cut out from (in pixels)
- '
- '(When you use a decal with n times the picture resolution,you have to devide
- 'the result by n afterwards.)
- 'In a finished pinball game at least the picture of the flippers,spinners and
- 'bumpers should always be removed from the playfield floor photo,because they
- 'make the game look really stupid.To stay capable to adjust these objects
- 'correctly,set the Table picture to an unretouched "testFloor" version of the
- 'picture file while editing the playfield.Use JPEG pictures instead of BMP
- 'for all non-transparent textures to reduce the VPT file size.
- '
- 'When you start making a playfield,never use the "outerWall" object for model-
- 'ling playfield "buildings" unless you are entire sure that their height matches
- 'the rim and that they indeed consist of a single block that is not separated
- 'from the rim,because doing so makes later changes of these buildings extremely
- 'difficult and also side colours can not be set separately when everything was
- 'made from 1 piece.
- '
- 'Normal buildings on the playfield should not be made of one solid block but
- 'instead consist of at least 2 levels (middle and top) of those the middle
- 'level has a lightbulbish beige colour while the top level has the plastic
- 'texture on it.In its simplest form this is really easy to do;simply copy the
- 'building with cut and paste to the same place,and then change the height
- 'of the top part to about from bottom 48 to top 50 and the height of the middle
- 'part to about bottom 23 to top 27.Set the top colour of the resulting pane to
- 'about RGB 238,205,142.This simple trick makes playfields look much more
- 'realistic without much effort.
- '
- 'It looks even better to also add cylindrical posts between these panes,but
- 'don't use excessively many control points for them,because they can slow down
- 'Visual Pinball.The modelling of playfield objects those can neither be seen nor
- 'hit with the ball should be avoided or kept simple to keep the count of control
- 'points low for better performance.
- '
- 'For further important tips about playfield colour adjustment and lighting see
- 'resetLights() and lightCenterBumper().
- '
- 'For normal playfield "Wall" parts use elasticity 0.3,for fixed rubbers 0.4 and
- 'to the free,straight part of long rubbers add a slingshot property with sling-
- 'shot force 3 to get correct bouncing behaviour.Also targets should have an
- 'elasticity value about 0.35 to simulate the rebounce force of the leaf spring
- 'behind them.
- '
- 'To make a playfield appear realistic,a view angle about "Inclination 31" and
- '"Field Of View 66" should be used,because only this way the length of the
- 'playfield stays realizable.Many VP games try to make the playfield area on the
- 'screen bigger by choosing values those let the front and back edge of the table
- 'appear with the same width.But this makes the table look almost square and the
- 'spacial depth of the playfield gets completely lost.When in the opposit the
- 'front edge of the playfield occupies the entire screen to maximize the width
- 'by a very flat angle,the game becomes badly overviewable and looks like watched
- 'by a child.(This may remind your childhood pinball memories,but playing is dif-
- 'ficult this way.) Also top view should be avoided,because it is very unrealistic
- 'and uses only a tiny fraction of the screen for the playfield which makes it
- 'look very small and unspectacular.When a player wants to take an undistorted
- 'top view look onto the playfield,he should download a pinball flyer photo
- 'instead or watch the playfield in the editor. The playfield view angle during
- 'game is intended for playing and not for watching it in all details.
- 'To simulate a left and right playfield rim makes the machine look much more
- 'realistic;unfortunately a Visual Pinball bug displays any objects closely left
- 'or right outside the playfield bounds only in black.To define the table size
- 'wider would be possible,but this way all table texture pictures would need to
- 'be extended with a blank left and right area too.
- '
- 'The captive ball lanes in this game have a kink in their outer wall - this
- 'is a trick to make it more difficult to shoot their ball up to the top,because
- 'these lanes are intended as skill shots and though the the kink slows down the
- 'captive ball when it isn't hit at a particular angle.
- '
- 'The way a backglass shall be displayed depends very much on its motive.
- 'Important is that the score counters etc. must not become too small.In this
- 'game("Boomerang") I devides the backglass landscape into 2 halfs and addeded
- 'some missing graphics in the middle to create a rather impressionistic than
- 'realistic appearance of the game.But for other backglasses with more central
- 'motives this concept can also look really awful (look e.g. at the terribly
- 'mutilated graphics of VP "Magnotron" v1.1 by Hugh).For such backglasses the
- 'central picture should rather stay in 1 piece,but the score counters etc.
- 'in this case should not be displayed simply on the backglass,because this
- 'would make them way too small.Important is not that the backglass appearance
- 'stays completely authentic,but rather that the athmosphere of the game is
- 'kept intact.Way too small displays disturb this much more than a reasonably
- 're-arranged backglass layout.
- '
- 'When you use textboxes,always check how they look at different screen
- 'resolutions;due to the font size can slightly vary at different resolutions,
- 'sometimes characters or even entire words can disappear when they don't fit
- 'into their textbox at certain resolutions.Therefore make textboxes better a
- 'bit too large than too narrow.
- 'pinball machine constants: (for code portability)
- '--------------------------
- const maxCredits=25 'My Bally "Champ" could count credits up to 25(?).
- const maxPlayers =4 'player count of this machine
- const maxBalls =5 'range of the ball counter on this machine
- const matchedScoreDigit=1 'digit that is compared during matching
- const bonusStepWidth=1000 'amount by that the bonus ladder advances
- const coinMech1Time=500 'delay time for inserted coin (must be >0)
- const coinMech2Time=500
- const coinMech3Time=500
- const spinnerResetTime=7 'speed of the spinnner state wheel reset.
- const playerResetTime=14 'speed of the player state wheel reset.
- const playerUpResetTime=12 'speed of the player-up state wheel reset.
- const ballCounterResetTime=16 'speed of the ball counter state wheel reset.
- const creditAddTime=69 'speed of the credit counter wheel
- const ballTexture="shinyBall" 'picture for the balls
- const knockerKnocksAlways =false 'true: knocker knocks also by insert coin
- const smallCoinStartsGame =true 'true: inserting a 1 credit coin behaves like start button press
- const overPayBlocksChutes =true 'true: at maximum credit coins are rejected
- const startButtonQuitsGame =true 'true: a running game can be reset by start button presses
- const highScoreEnabled =true 'when the game contains a highscore display
- const shootAgainStaysLit =true 'true: after next ball "shoot again" lamps stay lit
- 'until scoring points,else they switch off immediately
- const restartWhen0Pts =true 'true: player shoots again when ball scored no points
- const tiltClearsPilotLamps =true 'true: tilt clears the playfield pilot lamps (typical for Bally)
- const tiltClearsIllumination=true 'true: illumination switches off when tilt
- '(this is not authentic Bally,but looks great)
- const tiltPreventsExtraBall =true 'true: during tilt no new extra ball can be won
- const tiltVoidsExtraBall =false 'true: like above,but a won extra ball is void by tilt
- const tiltVoids0PtsRestart =true 'Normally any player can shoot again when the round
- 'scored no points. When true a tilt prevents this.
- const tiltClearsDoubleBonus =true 'true: counts down bonus ladder faster when tilt
- const tiltPreventsAddBonus =true 'true: bonus ladder can't increment during tilt
- const noTiltDuringBonus =false 'true prevents tilt during bonus ladder countdown
- const credit0UnlightsCounter=true 'true: credit counter light switches off when credit=0
- const creditOverpayBounce =false 'true: credit counter reel rattles by overpaying
- 'score counter mechanism:
- const minCounterNr =1 'set this 0 when an additional score counter 0 exists;
- 'e.g. as a highscore display
- const reelsPerCounter=5 'digits per score counter
- const reelSpinTime =55 'normal score counter reel rotation speed (was 55)
- const reelCarryTime =5 'faster score counter reel speed for carry (was 5)
- const scoreMotorTime =24 'speed with that the score motor advances
- '(I don't know if this is mains frequency dependant,because many
- 'sound samples in VP games click with different speeds.)
- const motorStepWidth =10 'degree with that the simulated score motor wheels
- 'turn by each timer step (Never change this unless you
- 'adapt the camshaft simulations too!)
- const reelChimeForCarry=false 'true: play score chime also for digits flipped by carry
- const maxScoreReelNoises=9 'number of sound samples for clicks of multiple reels
- 'game rule sets: '(These are the values for ruleSet.)
- const RS_authentic=0 'the original Bally "Boomerang" rule set (too easy =>boring)
- const RS_enhanced =1 'enhanced game rules by =CO=Windler (more challenging)
- 'declarations of global variables:
- '---------------------------------
- ' Variables defined within procedures stay local. Local variables for internal
- ' use within procedures must never be declared here, because this would make
- ' them global and though could result in ugly side effect bugs due to a pinball
- ' game uses massive multi-threading.Variables become global when they are either
- ' declared or first used (assigned) outside all procedures.
- 'mechanical score counter variables:
- dim scoreDigit (4,5) 'holds the digit values;highest index stores all extra digits
- dim carry (4,5) 'carries are 1 when 1 will be added to next digit,else 0
- dim stuckScoreReel(4,5) 'counting semaphores for modelling stuck counter reels
- dim scoreReelTop (4,5)
- dim scoreReelMain (4,5)
- dim scoreReelLow (4,5)
- 'These arrays must have the numerical dimension values of (maxPlayers,
- 'reelsPerCounter);inserting the constant names directly doesn't work in this
- 'crappy Micro$oft BASIC.
- dim scoreReelBorder(4,2)
- 'Array must have dimensions scoreReelBorder(maxPlayers,2).
- dim scoreReelChime(5)
- 'Array must have dimensions of reelsPerCounter.
- dim scoreReelNoise(9) 'sample name strings for 1 to 10 simultaneous reels clicks
- 'Array must have dimensions of maxScoreReelNoises
- dim scoreAddTimer(4)
- dim scoreSpinTimer(4)
- dim reelTime(4) 'with this speed the counter reels rotate
- dim unaddedScore(4) 'this buffers the remaining score to add
- dim scoreIsCounting(4) 'flag signals that a player's counter has not finished counting yet
- 'These arrays must have dimensions of maxPlayers.
- dim scoreResetNumber 'will contain as many "1" as value of reelsPerCounter
- dim reelClicks 'actual count of counter click noises to play
- dim stuckReelsBuzz 'actual count of stuck reels to simulate solenoid buzz
- 'relays:
- dim gameOver 'true when game over
- dim gameIsResetting '"reset relay", true when the counters etc. are resetting
- dim tilt 'true when game in tilt state
- dim newRound 'true when bonusses are collected by score motor
- dim matching 'true during end game digit matching
- dim creditTaken '"credit relay", true when a credit from the counter is
- 'used to start the game)
- dim illumination 'true when the general illumination(plastics etc.) is lit
- dim centerBumperLit
- dim doubleBonus
- dim specialHit
- dim extraBallWon
- dim shootAgain 'when the actual round is a shoot again
- dim add500 'relays for points to be added by the score motor
- dim add3000
- dim A 'target A..D hit
- dim B
- dim C
- dim D
- dim A_lock 'these prevent adding Bonus twice when captive ball rolls back
- dim B_lock
- dim C_lock
- dim D_lock
- 'other flags:
- dim reelChimeEnabled 'signals that counter digits play a chime sound
- dim playfieldLightsEnabled 'true enables the various pilot lamps on playfield
- dim backboxLightsEnabled 'true enables various display lamps in the backbox
- dim creditsAddable 'true when credit counter is ready to add new credits
- dim ballInTopHole
- dim ballInDrain
- dim keepMotorRunning 'lets the score motor turn another round
- dim leftExtraBallLit
- dim rightExtraBallLit
- dim rollover1kLit
- dim flippersEnabled
- dim leftFlipButton 'true when button pressed
- dim rightFlipButton 'true when button pressed
- dim leftFlipperUp 'true when the flipper is up
- dim rightFlipperUp
- dim ballBefore10p1 'true when ball lies in front of the center rubbers
- dim ballBefore10p2
- dim ballBefore10p3
- dim ballBefore10p4
- 'stuck score reel semaphores: (1 per rollover switch with changeable score)
- dim triggerLeft1kStuck
- dim triggerRight1kStuck
- 'other state variables:
- dim scoreMotorAngle 'position of the score motor camshaft wheels
- '(0 when motor is at stand-still position)
- dim highScore(2) 'highscores for both rule sets
- dim rulesSwitchable 'true while rules can be switched (not during game)
- dim ruleSet 'the currently active rule set (0=authentic,1=advanced)
- dim bonus
- dim tiltSwing
- dim tiltHasEffect
- dim spins
- dim ballCount
- dim credits
- dim specialLit
- dim currentPlayer
- dim players
- dim unaddedCredits
- dim matchNumber 'actual pseudo random number of the matcher state wheel
- dim knockerActive 'false when inserting coin to disable the separate (loud) knocker
- dim ballCreated
- 'program initialization:
- '-----------------------
- highScore(RS_authentic)=0
- highScore(RS_enhanced )=0
- ruleSet=RS_authentic
- scoreMotorAngle=0
- keepMotorRunning=false
- triggerLeft1kStuck =0
- triggerRight1kStuck=0
- credits=0
- unaddedCredits=0
- creditsAddable=true
- creditTaken=false
- knockerActive=true
- matching=false
- illumination=true 'else setIllumination false will be ignored
- tiltSwing=0
- tiltHasEffect=false
- tilt=false
- If B2SOn Then
- Controller.B2SSetTilt 33,0
- Controller.b2ssetdata 1,1
- End If
- leftFlipButton =false
- rightFlipButton=false
- flippersEnabled=false
- gameOver=true
- players=1
- currentPlayer=1
- ballCount=1
- ballBefore10p1=false
- ballBefore10p2=false
- ballBefore10p3=false
- ballBefore10p4=false
- ballInDrain=true
- ballCreated=false 'no ball on playfield created yet.
- matchNumber=int(10*rnd())
- spins=int(11*rnd())
- 'In a real pinball machine these values stem from the previous game.
- sub theTable_init()
- LoadEM
- ' This is called by the pinball table at program start to generate the 2
- ' captive balls etc.
- setIllumination false
- scoreMotor.interval=scoreMotorTime
- clickTimer.interval=2
- spinnerReset.interval =spinnerResetTime
- playerCounterReset.interval=playerResetTime
- playerUpReset.interval =playerUpResetTime
- ballCounterReset.interval =ballCounterResetTime
- creditAdder.interval =creditAddTime
- coinMech1.interval=0
- coinMech2.interval=0
- coinMech3.interval=0
- initializeScoreCounters
- leftCBallKicker.createBall.image=ballTexture
- leftCBallKicker.kick 90,6
- rightCBallKicker.CreateBall.image=ballTexture
- rightCBallKicker.kick -100,6
- tiltTimer.interval=200
- tiltTimer.enabled=tiltEnabled 'true or false
- showKeySettings
- shootAgainText.text=""
- If B2SOn Then Controller.B2SSetShootAgain 36, 0
- setEndMatchLights(-1)
- If B2SOn Then Controller.B2SSetBallInPlay 32, 0
- if highScoreFileName<>false then LoadGameStats UserDirectory,highScoreFileName
- spinCreditReel
- doGameOver
- setGameRules ruleSet 'updates the rules display
- showIntro.enabled=true 'timer to pop up the intro text requester
- end sub
- 'Button press and release routines:
- '----------------------------------
- sub theTable_KeyDown(ByVal keycode)
- ' keysBox.text=keycode 'TEST!!!
- 'Coin inserted:
- if keycode = 2 and coinChute1Credits<>false then coinMech1.enabled=true 'key "5"
- if keycode = 3 and coinChute2Credits<>false then coinMech2.enabled=true 'key "6"
- if keycode = 4 and coinChute3Credits<>false then coinMech3.enabled=true 'key "7"
- 'By setting a coinChuteXCredits constant false,a coin slot can be "removed" to
- 'simulate a machine with less coin slots.
- 'Start button pressed:
- if (keycode = 2) and credits > 0 and not gameIsResetting then
- 'A start button press starts a new game after game over or adds a player
- 'to an already started game so far it is still playing the 1st ball.For
- 'both actions a credit is needed,that will be taken off the credit counter.
- if startButtonQuitsGame or gameOver or (ballCount=1 and players<maxPlayers) then
- 'When startButtonQuitsGame is set false,a running game can be canceled by
- 'pressing the start button again when the maximum number of players already
- 'has been added or the ball counter has advanced beyond the 1st ball.
- setRelay creditTaken,true
- creditButtonRoutine
- end if
- end if
- 'select game rules:
- if (keycode=50) and rulesSwitchable then 'key "M"
- if ruleSet=RS_authentic then
- setGameRules RS_enhanced
- else
- setGameRules RS_authentic
- end if
- playSoundMonophonic "diverter"
- saveGame
- end if
- 'rule sheet: (game rules)
- if keycode = 19 then showRuleSheet 'key "R"
- if keycode = plungerKey then
- plunger.PullBack
- end if
- if keycode = leftFlipperKey then
- leftFlipButton=true
- if flippersEnabled then moveLeftFlipper true
- if gameOver then setIllumination true
- end if
- if keycode = rightFlipperKey then
- rightFlipButton=true
- if flippersEnabled then moveRightFlipper true
- end if
- if keycode = LeftTiltKey then
- Nudge 90, 2
- nudged
- end if
- if keycode = RightTiltKey then
- Nudge 270, 2
- nudged
- end if
- if keycode = CenterTiltKey then
- Nudge 0, 2
- nudged
- end if
- end sub
- sub theTable_KeyUp(ByVal keycode)
- if keycode = plungerKey then
- playSoundMonophonic "plunger"
- plunger.fire
- 'Hint: When you have a combined sound sample of the plunger and ball rolling
- ' noise,please split it up into 2 samples and only play the plunger sound
- ' when no ball is in the firing chute - it sounds so silly when the plunger
- ' always starts a ball rolling noise when there is no ball in the chute.
- ' Instead place an overroll trigger in the chute that activates the rolling
- ' noise sample only when touched by the ball itself.
- end if
- if keycode = leftFlipperKey then
- leftFlipButton=false
- if flippersEnabled then moveLeftFlipper false
- end if
- if keycode = rightFlipperKey then
- rightFlipButton=false
- if flippersEnabled then moveRightFlipper false
- end if
- end sub
- 'target hit routines: (called when a ball hits playfield objects)
- '--------------------
- sub centerBumper_Hit()
- playSound SoundFXDOF("Bumper",107,DOFPulse,DOFContactors)
- DOF 108, DOFPulse
- if centerBumperLit then
- ' playSound "Bell100"
- addScore 100
- end if
- end sub
- sub topHoleKicker_Hit()
- ' triggered by top hole
- stopBallRollNoises
- rollover1kLit = true
- setLight lampLeft1k ,true
- setLight lampRight1k,true
- lightCenterBumper true
- 'This is just a guess from Bally "Champ",because on the bumper stands "when
- 'lit", therefore there must be anything in the game to light it.
- '
- 'I was told later that on the original pinball machine this bumper stays
- 'always lit,but I kept this in my enhanced rule set,because it is much more
- 'fun this way.
- if ruleSet=RS_enhanced and topHoleShootAgain<>false and bonus>=topHoleShootAgain then setExtraBall true 'Shoot Again!
- 'This rule was created by me from Bally "Champ",because otherwise there would
- 'be no "real" extra balls in the game. The outlane lamps basically do not more
- 'than a kind of ball saver;they just temporably disable the outlane danger
- 'but never give an extra ball without loosing the current ball in play.
- '
- 'The real pinball machine can instead make the captive ball lane specials
- 'earn an extra ball,therefore I use topHoleShootAgain only in my enhanced
- 'game rule set.
- ballInTopHole=true
- scoreMotor.enabled=true '3000 points are added by the score motor
- end sub
- sub leftSlingshot_slingshot()
- ' playSound "bell10"
- playSound SoundFXDOF("bigSlingshot",103,DOFPulse,DOFContactors)
- DOF 105, DOFPulse
- addScore (10)
- end sub
- sub rightSlingshot_slingshot()
- ' playSound "bell10"
- playSound SoundFXDOF("bigSlingshot",104,DOFPulse,DOFContactors)
- DOF 106, DOFPulse
- addScore (10)
- end sub
- sub spinner1_Spin()
- ' playSound "bell100"
- playSound "soloff2"
- DOF 109, DOFPulse
- if centerBumperLit then addScore 100 else addScore 10 'this rule is a guess
- incrementSpinner
- end sub
- ' The disc-shaped targets on the playfield are composite objects consisting of
- ' multiple vertically intersecting rectangular blocks.The blocks intersect each-
- ' other to keep the count of controll points reasonable low,because too many of
- ' them can slow down the game by too much CPU load. Unlike round decal props
- ' stuck on the backdrop over square targets in other games,these ones are ge-
- ' nuine 3 dimensional objects those can be viewed at any angle.The targets have
- ' a higher elasticity value than normal walls to simulate the rebounce of the
- ' metal leaf spring contact.
- '
- ' Due to the active part of these targets is as well their horizontal middle
- ' stripe as the black center spot(bolt),there are 2 hit subroutines for each
- ' target neccessary to recognize hits on both parts.In the below code I
- ' therefore each time call the subroutine of the main part of the same target
- ' when its center spot is hit,to make maintainance easier.The center spot
- ' has always the same name as the target but with the prefix "target2" instead
- ' of "target".
- '
- ' Tip: You can copy these targets into other Visual Pinball games using copy
- ' and paste.
- sub targetCenter1_hit()
- msgbox("test")
- playSound SoundFXDOF("soloff",112,DOFPulse,DOFTargets)
- call hitCenterTarget
- end sub
- sub target2Center1_hit()
- call targetCenter1_hit
- end sub
- sub targetCenter2_hit()
- playSound SoundFXDOF("soloff",112,DOFPulse,DOFTargets)
- call hitCenterTarget
- end sub
- sub target2Center2_hit()
- call targetCenter2_hit
- end sub
- sub targetCenter3_hit()
- playSound SoundFXDOF("soloff",113,DOFPulse,DOFTargets)
- call hitCenterTarget
- end sub
- sub target2Center3_hit()
- call targetCenter3_hit
- end sub
- sub targetCenter4_hit()
- playSound SoundFXDOF("soloff",113,DOFPulse,DOFTargets)
- call hitCenterTarget
- end sub
- sub target2Center4_hit()
- call targetCenter4_hit
- end sub
- sub hitCenterTarget()
- addScore (100)
- if ruleSet=RS_authentic or centerBumperLit then incrementSpinner
- end sub
- 'These memorize at which side the rubber bands in the tribal center are hit:
- sub before10p1_hit() : ballBefore10p1=true end sub
- sub before10p1_unhit(): ballBefore10p1=false end sub
- sub before10p2_hit() : ballBefore10p2=true end sub
- sub before10p2_unhit(): ballBefore10p2=false end sub
- sub before10p3_hit() : ballBefore10p3=true end sub
- sub before10p3_unhit(): ballBefore10p3=false end sub
- sub before10p4_hit() : ballBefore10p4=true end sub
- sub before10p4_unhit(): ballBefore10p4=false end sub
- sub rubber10p1_hit()
- if ballBefore10p1 and centerBumperLit then addScore 10
- end sub
- sub rubber10p1_slingshot() call rubber10p1_hit end sub
- sub rubber10p2_hit()
- if ballBefore10p2 and centerBumperLit then addScore 10
- end sub
- sub rubber10p2_slingshot() call rubber10p2_hit end sub
- sub rubber10p3_hit()
- if ballBefore10p3 and centerBumperLit then addScore 10
- end sub
- sub rubber10p3_slingshot() call rubber10p3_hit end sub
- sub rubber10p4_hit()
- if ballBefore10p4 and centerBumperLit then addScore 10
- end sub
- sub rubber10p4_slingshot() call rubber10p4_hit end sub
- sub targetLeftCLaneEnd_hit()
- playSound SoundFXDOF("soloff",112,DOFPulse,DOFTargets)
- call hitSpecial
- end sub
- sub target2LeftCLaneEnd_hit() call targetLeftCLaneEnd_hit end sub
- sub targetRightCLaneEnd_hit()
- playSound SoundFXDOF("soloff",113,DOFPulse,DOFTargets)
- call hitSpecial
- end sub
- sub target2RightCLaneEnd_hit() call targetRightCLaneEnd_hit end sub
- sub hitSpecial()
- ' This lets the player win an extra credit.Per round only one credit can be
- ' won. (This rule is a guess.)
- if specialLit then
- select case ruleSet
- case RS_authentic
- if specialWinsPoints0>0 then addScore specialWinsPoints0
- if specialWinsCredit0 then addCredit
- if specialShootAgain0 then setExtraBall true
- case RS_enhanced
- if specialWinsPoints1>0 then addScore specialWinsPoints1
- if specialWinsCredit1 then addCredit
- if specialShootAgain1 then setExtraBall true
- end select
- setLight lampLeftCLaneEnd ,false
- setLight lampRightCLaneEnd,false
- specialLit=false
- setRelay specialHit,true
- end if
- end sub
- sub targetRight500_hit()
- playSound "soloff"
- ' playSound "bell100-5"
- addScore500
- end sub
- sub target2Right500_hit()
- DOF 115, DOFPulse
- call targetRight500_hit
- end sub
- sub targetLeft500_hit()
- playSound "soloff"
- ' playSound "bell100-5"
- addScore500
- end sub
- sub target2Left500_hit()
- DOF 114, DOFPulse
- call targetLeft500_hit
- end sub
- sub triggerLeft1k_hit()
- DOF 122, DOFOn
- playSound "soloff2"
- addscore 1000
- if rollover1kLit then addScoreDigitStuckBySema 3,triggerLeft1kStuck
- 'This adds 1 to the 4th score counter digit in a way that the digit can get
- 'stuck and hum so long a ball lies on the switch (see reel-a-listic score
- 'counter description below).
- if ruleSet=RS_authentic then incrementBonus
- end sub
- sub triggerLeft1k_unhit()
- DOF 122, DOFOff
- playBallRollSound "ballRoll2"
- releaseScoreDigitBySema 3,triggerLeft1kStuck
- 'This releases the stuck counter digit again.
- end sub
- sub triggerRight1k_hit()
- DOF 123, DOFOn
- addscore 1000
- playSound "soloff2"
- if rollover1kLit then addScoreDigitStuckBySema 3,triggerRight1kStuck
- if ruleSet=RS_authentic then incrementBonus
- end sub
- sub triggerRight1k_unhit()
- DOF 123, DOFOff
- playBallRollSound "ballRoll2"
- releaseScoreDigitBySema 3,triggerRight1kStuck
- end sub
- sub triggerLeftCLaneMid_hit()
- DOF 117, DOFOn
- playSound "soloff2"
- select case ruleSet
- case RS_authentic
- addScoreDigitStuck 3
- incrementBonus
- case RS_enhanced
- addScore500
- end select
- end sub
- sub triggerLeftCLaneMid_unhit()
- DOF 117, DOFOff
- if ruleSet=RS_authentic then releaseScoreDigit 3
- end sub
- sub triggerRightCLaneMid_Hit()
- DOF 120, DOFOn
- playSound "soloff2"
- select case ruleSet
- case RS_authentic
- addScoreDigitStuck 3
- incrementBonus
- case RS_enhanced
- addScore500
- end select
- end sub
- sub triggerRightCLaneMid_unhit()
- DOF 120, DOFOff
- if ruleSet=RS_authentic then releaseScoreDigit 3
- end sub
- sub triggerLeftInLane_hit()
- DOF 125, DOFOn
- addscore 1000
- ' playSound "bell1000"
- playSound "soloff"
- incrementBonus
- addScoreDigitStuck(3)
- end sub
- sub triggerLeftInLane_unhit()
- DOF 125, DOFOff
- releaseScoreDigit(3)
- end sub
- sub triggerRightInLane_hit()
- DOF 126, DOFOn
- addscore 1000
- ' playSound "bell1000"
- playSound "soloff"
- incrementBonus
- addScoreDigitStuck(3)
- end sub
- sub triggerRightInLane_unhit()
- DOF 126, DOFOff
- releaseScoreDigit(3)
- end sub
- sub triggerLeftOutLane_Hit()
- DOF 124, DOFPulse
- playSound "soloff"
- ' playSound "bell100-5"
- checkExtraBall
- if leftExtraBallLit then
- ' lampLeft_XB.State = LightStateOff 'I doubt that the bonus wheel can do this
- setExtraBall true 'Shoot Again!
- end if
- addScore500
- end sub
- sub triggerRightOutLane_Hit()
- DOF 127, DOFPulse
- playSound "soloff"
- ' playSound "bell100-5"
- checkExtraBall
- if rightExtraBallLit then
- ' lampRight_XB.State = LightStateOff 'I doubt that the bonus wheel can do this
- setExtraBall true 'Shoot Again!
- end if
- addScore500
- end sub
- sub checkExtraBall()
- if bonus >= 15000 then
- leftExtraBallLit = true
- rightExtraBallLit = true
- end if
- if bonus = 8000 or bonus = 10000 or bonus = 12000 or bonus = 14000 then
- leftExtraBallLit = true
- end if
- if bonus = 9000 or bonus = 11000 or bonus = 13000 then
- rightExtraBallLit = true
- end if
- end sub
- 'A-B-C-D targets:
- sub target_A_hit()
- playSound SoundFXOF("soloff",114,DOFPulse,DOFTargets)
- addScore 1000
- ' playSound "bell1000"
- if not A then
- setLight lampTarget_A ,false
- setLight lampLeftCLane_A,true
- setRelay A,true
- checkDoubleBonus
- end if
- end sub
- sub target2_A_hit() call target_A_hit end sub
- sub target_B_hit()
- playSound SoundFXDOF("soloff",114,DOFPulse,DOFTargets)
- addScore 1000
- ' playSound "bell1000"
- if not B then
- setLight lampTarget_B ,false
- setLight lampLeftCLane_B,true
- setRelay B,true
- checkDoubleBonus
- end if
- end sub
- sub target2_B_hit() call target_B_hit end sub
- sub target_C_hit()
- playSound SoundFXDOF("soloff",115,DOFPulse,DOFTargets)
- addScore 1000
- ' playSound "bell1000"
- if not C then
- setLight lampTarget_C ,false
- setLight lampRightCLane_C,true
- setRelay C,true
- checkDoubleBonus
- end if
- end sub
- sub target2_C_hit() call target_C_hit end sub
- sub target_D_hit()
- playSound "soloff"
- addScore 1000
- ' playSound "bell1000"
- if not D then
- setLight lampTarget_D ,false
- setLight lampRightCLane_D,true
- setRelay D,true
- checkDoubleBonus
- end if
- end sub
- sub target2_D_hit() call target_D_hit end sub
- sub checkDoubleBonus()
- if A and B and C and D then
- if tiltClearsDoubleBonus and tilt then exit sub
- setRelay doubleBonus,true
- setLight lamp_DB ,true
- end if
- end sub
- sub triggerLeftCLane_A_hit()
- DOF 116, DOFOn
- playSound "soloff2"
- addscore 1000
- select case ruleSet
- case RS_authentic
- addScoreDigitStuck 3
- if A then incrementBonus
- case RS_enhanced
- if A and not A_lock then
- setRelay A_lock,true 'A_lock prevents twice bonus when ball rolls back
- incrementBonus
- end if
- end select
- end sub
- sub triggerLeftCLane_A_unhit()
- DOF 116, DOFOff
- if ruleSet=RS_authentic then releaseScoreDigit 3
- end sub
- sub triggerLeftCLane_B_hit()
- DOF 118, DOFOn
- playSound "soloff2"
- addscore 1000
- select case ruleSet
- case RS_authentic
- addScoreDigitStuck 3
- if B then incrementBonus
- case RS_enhanced
- if B and not B_lock then
- setRelay B_lock,true
- incrementBonus
- end if
- end select
- end sub
- sub triggerLeftCLane_B_unhit()
- DOF 118, DOFOff
- if ruleSet=RS_authentic then releaseScoreDigit 3
- end sub
- sub triggerRightCLane_C_hit()
- DOF 121, DOFOn
- playSound "soloff2"
- addscore 1000
- select case ruleSet
- case RS_authentic
- addScoreDigitStuck 3
- if C then incrementBonus
- case RS_enhanced
- if C and not C_lock then
- setRelay C_lock,true
- incrementBonus
- end if
- end select
- end sub
- sub triggerRightCLane_C_unhit()
- DOF 121, DOFOff
- if ruleSet=RS_authentic then releaseScoreDigit 3
- end sub
- sub triggerRightCLane_D_hit()
- DOF 119, DOFOn
- playSound "soloff2"
- addscore 1000
- select case ruleSet
- case RS_authentic
- addScoreDigitStuck 3
- if D then incrementBonus
- case RS_enhanced
- if D and not D_lock then
- setRelay D_lock,true
- incrementBonus
- end if
- end select
- end sub
- sub triggerRightCLane_D_unhit()
- DOF 119, DOFOff
- if ruleSet=RS_authentic then releaseScoreDigit 3
- end sub
- sub targetLeftCLaneStart_hit()
- ' triggered by left pin of left captive ball lane.
- setRelay A_lock,false
- setRelay B_lock,false
- 'The ball has rolled back and though the triggers are active again.
- end sub
- sub targetRightCLaneStart_hit()
- ' triggered by right pin of right captive ball lane.
- setRelay C_lock,false
- setRelay D_lock,false
- end sub
- 'pseudo-targets: (these only simulate playfield noises)
- '---------------
- 'Note: All ball rolling and -hit noises in this game are only an approximation
- ' simulated with invisible rollover targets.They therefore don't behave
- ' very realistic because volume and timbre neither react on the ball speed
- ' nor hit angle.
- ' For a proper simulation of these sounds it would be neccessary to give
- ' the floor and all other playfield materials a sound property depending on
- ' the actual ball speed and roll direction,which (by my knowledge) is yet
- ' impossible in Visual Pinball because there are no ways to measure the
- ' actual ball rolling speed and direction. At least the volume of hit noises
- ' should depend on the hit speed.
- '
- ' Also the volume of hit/bump noises is unrealistic because there is no way
- ' to determine with how much force an object was actually hit.Though they
- ' either sound at their fixed volume or not at all,instead of sounding louder
- ' when hit harder.The "Hit Treshold" parameter adjusts at which ball speed
- ' a hit event shall cause a (sound) reaction.
- '
- 'TIP: You can digitally re-sample sounds from other Visual Pinball games without
- ' external devices by using the Windoze audio recorder and the freeware
- ' wave loopback driver program "Virtual Audio Cable" by Eugene Muzychenko.
- sub ejectChute_hit() playSound SoundFXDOF("ballin",129,DOFPulse,DOFContactors) end sub
- sub firingChute_hit() playSound "tinRoll":DOF 130, DOFPulse:end sub
- 'should be different?
- sub beforePlunger_hit()
- ' playSound "soloff" 'should click differently?
- stopBallRollNoises
- end sub
- sub beforeLeftCLane_hit() playSound "ballKlick" end sub
- sub beforeRightCLane_hit() playSound "ballKlick" end sub
- sub metal1_hit() playSound "tinHit3" end sub
- sub metal2_hit() playSound "tinHit3" end sub
- sub metal3_hit() playSound "tinHit3" end sub
- sub metal4_hit() playSound "tinHit3" end sub
- sub metal5_hit() playSound "tinHit3" end sub
- sub metal6_hit() playSound "tinHit3" end sub
- sub spinnerMetal1_hit() playSound "tinHit3" end sub
- sub spinnerMetal2_hit() playSound "tinHit3" end sub
- sub protector_hit() playSound "tinHit3" end sub
- sub gateTrigger_hit() playSound "tinHit3" end sub
- sub tin1_hit() playSound "tinHit2":call undropGuideWires end sub
- sub tin2_hit() playSound "tinHit2":call undropGuideWires end sub
- sub damper_hit():stopBallRollNoises:playSound "bump5":end sub
- sub post1_hit() playSound "bump2" end sub
- sub post2_hit() playSound "bump2" end sub
- sub post3_hit() playSound "bump2" end sub
- sub post4_hit() playSound "bump2" end sub
- sub post5_hit() playSound "bump2" end sub
- sub post6_hit() playSound "bump2" end sub
- sub postL1_hit() playSound "bump5" end sub
- sub postL2_hit() playSound "bump5" end sub
- sub postL3_hit() playSound "bump5" end sub
- sub postL4_hit() playSound "bump5" end sub
- sub postR1_hit() playSound "bump5" end sub
- sub postR2_hit() playSound "bump5" end sub
- sub postR3_hit() playSound "bump5" end sub
- sub postR4_hit() playSound "bump5" end sub
- sub leftSlingshot_hit() playSound "bump5" end sub
- sub rightSlingshot_hit() playSound "bump5" end sub
- sub tinRoll1_hit() playBallRollSound "tinRoll" end sub
- sub tinRoll2_hit() playBallRollSound "tinRoll" end sub
- sub roll1_hit() playBallRollSound "ballRoll1" end sub
- sub roll2_hit() playBallRollSound "ballRoll1" end sub
- sub roll3_hit() playBallRollSound "ballRoll1" end sub
- sub roll4_hit() playBallRollSound "ballRoll1" end sub
- sub roll5_hit() playBallRollSound "ballRoll1" end sub
- sub roll6_hit() playBallRollSound "ballRoll1" end sub
- sub roll7_hit() playBallRollSound "ballRoll1" end sub
- sub roll8_hit() playBallRollSound "ballRoll1":call undropGuideWires end sub
- sub rollDown1_hit() playBallRollSound "ballRoll2" end sub
- sub rollDown2_hit() playBallRollSound "ballRoll2" end sub
- sub rollDown3_hit() playBallRollSound "ballRoll2" end sub
- sub rollDown4_hit() playBallRollSound "ballRoll2" end sub
- sub rollDown5_hit() playBallRollSound "ballRoll2" end sub
- sub playBallRollSound(aSoundString)
- ' This plays a sound sample so far ballRollNoisesEnabled=true.
- if ballRollNoisesEnabled then playSound aSoundString
- end sub
- sub stopBallRollNoises()
- ' This routine stops all still sounding ball roll samples.It should be called
- ' at all places where the ball is known to stand still (e.g. in the outhole
- ' or tophole etc.) to prevent confusion of the player.
- '
- ' Note: In a multi-ball game this would need to be modelled more complex (using
- ' counting semaphores?). The easiest modelling there would be to stop these
- ' noises only when all balls stand still.
- '
- ' TIP: A routine like this could also be used in a simulation of an electronic
- ' pinball machine to prevent a certain type of sounds(e.g. speech) to be
- ' played polyphonic when the simulated sound hardware could only play them
- ' monophonic.
- stopSound "tinRoll"
- stopSound "ballRoll1"
- stopSound "ballRoll2"
- end sub
- 'The following prevents the ball getting stuck under a flipper by temporary
- 'dropping the guiding wire below it.Without this trick an ugly buzz was audible
- 'and it ate that much CPU load that it might crash VP on very slow PCs.
- sub leftGuide_hit()
- playSound "tinHit3"
- if not leftFlipperUp then leftGuide.isDropped=true
- end sub
- sub rightGuide_hit()
- playSound "tinHit3"
- if not rightFlipperUp then rightGuide.isDropped=true
- end sub
- sub undropGuideWires()
- ' This restores the guide wires below the flippers so far they were dropped
- ' by a stuck ball.
- leftGuide.isDropped =false
- rightGuide.isDropped=false
- end sub
- 'misc game related routines:
- '---------------------------
- sub drain_hit()
- DOF 128, DOFPulse
- ' This function is called when a ball falls into the outhole.
- '
- ' TIP: The outhole should always be placed outside the viewfield of the player
- ' (except when the real pinball had a visible one) because it looks really
- ' ugly when the ball just disappears below the flippers.It is very easy
- ' to form a mould and place a flat cover tin above it to place the outhole
- ' (and possibly eject hole) away from the player's sight.(Setting the
- ' kicker type "invisible" may safe a little CPU time because it avoids
- ' drawing it.)
- '
- ' When you want to make a visible fall-through hole,then don't call
- ' .destroyBall immediately but start a timer for this with a small delay.
- ' Though the ball stays visible in the hole for a fraction of a second,
- ' which looks much more realistic.
- ' (This game doesn't destroy the ball at all,but re-ejects it like a
- ' real pinball machine.)
- stopBallRollNoises
- playSound "Ballout"
- ballInDrain=true
- 'When points were scorend,then this will add the actual bonus from the bonus
- 'ladder to the score and start the next round. Else the ball will be just
- 're-ejected.
- undropGuideWires
- 'When a ball got stuck under a flipper,this restores the dropped guide wire.
- if noTiltDuringBonus then tiltHasEffect=false
- 'prevents tilt happening while collecting bonus
- scoreMotor.enabled=true
- end sub
- sub ejectBall()
- if ballInDrain then
- if not ballCreated then
- drain.createBall.image=ballTexture
- ballCreated=true
- end if
- 'Like a real pinball machine,this game does not delete and re-create
- 'the ball again and again,but re-uses the same ball (after it is
- 'created a single time).
- 'By a Visual Pinball bug drain.createBall works only when the ball is
- 'kicked out afterward, therefore it can not be done in theTable_init()
- 'without also kicking it out there,which would result in the drain_hit()
- 'noise when the program starts.
- '
- 'Important: Most Visual Pinball games hang/become unplayable when the player
- ' nudges the ball back into the eject hole near the plunger.This
- ' is a very annoying flaw.To prevent this,a hit event on the eject
- ' kicker should always be sensed and cause a re-eject of the ball,
- ' no matter if the game uses a separate eject kicker or the drain
- ' kicker to eject the ball.
- playSound "ballrel" 'eject coil sound
- drain.kick 80,15
- ballInDrain=false
- end if
- end sub
- 'tilt routines:
- sub nudged()
- ' This is a crude approximation of a nudge against a tilt pendulum. Only an
- ' amplitude is simulated - neither sine swinging nor different directions. The
- ' decay of the pendulum is simulated by tiltTimer.
- if tiltEnabled then
- tiltSwing=tiltSwing+1
- if tiltSwing>=tiltSensitivity then
- tiltSwing=tiltSensitivity
- if not gameOver and tiltHasEffect then doTilt
- end if
- end if
- end sub
- sub tiltTimer_timer()
- ' This timer periodically decreases tiltSwing to approximate pendulum decay.
- if tiltSwing>0 then subtractUntil0 tiltSwing,tiltSwingDecay
- end sub
- sub doTilt()
- ' This sets the machine into tilt state.
- '
- ' Hint: For simulating a pinball machine with "tilt disqualifies player"
- ' behaviour it would be necessary to make one tilt relay per player
- ' (i.e. replace tilt variable by an array) and in startNextRound()
- ' instead of calling unTilt() players with a set tilt relay would need
- ' to be skipped by executing the section that calls nextPlayer() again
- ' (using the score motor) until tilt(currentPlayer)=false.This way a
- ' tilted players would be skipped until game over.
- setRelay tilt,true
- tiltText.Text = "TILT"
- setBumpers false
- setFlippers false
- If B2SOn Then
- Controller.B2SSetTilt 33,1
- Controller.b2ssetdata 1,0
- End If
- if tiltClearsPilotLamps then
- setPlayfieldLights false 'typical for Bally
- lightCenterBumper false
- end if
- if tiltClearsIllumination then setIllumination false
- 'this one may be not authentic for Bally,but looks great
- if tiltClearsDoubleBonus then setRelay doubleBonus,false 'counts down faster
- if tiltVoids0PtsRestart then newRound=false
- if tiltVoidsExtraBall then
- setExtraBall false
- setShootAgain false
- '(In setExtraBall() also the win of a new extra ball is disabled during tilt
- ' when const tiltVoidsExtraBall=true.)
- end if
- end sub
- sub unTilt()
- ' When the game was tilted, then this ends tilt state.
- setRelay tilt,false
- tiltText.Text = " "
- setBumpers true
- setFlippers true
- If B2SOn Then
- Controller.B2SSetTilt 33,0
- Controller.b2ssetdata 1,1
- End If
- end sub
- 'extra balls:
- sub setExtraBall(aBoolean)
- ' When aBoolean=true this lets the player win an extra ball (by setting the
- ' extraBallWon relay) and lights the "shoot again" lamps. When aBoolean=false,
- ' the extraBallWon relay is cleared.(The "shoot again" lamps must be cleared
- ' elsewhere by calling setShootAgain false.)
- '
- ' During tilt no extra ball can be won when const tiltPreventsExtraBall or
- ' tiltVoidsExtraBall is set true.
- if aBoolean then
- if not (tilt and (tiltPreventsExtraBall or tiltVoidsExtraBall)) then
- setRelay extraBallWon,true
- setShootAgain true
- end if
- else
- setRelay extraBallWon,false
- end if
- end sub
- sub setShootAgain(aBoolean)
- ' This sets the shootAgain relay and the shootAgain lamps on playfield and
- ' backbox to the value of aBoolean.
- '
- ' In Bally EM pinball machines the "shoot again" lamp on the playfield and the
- ' backbox are wired in parallel and therefore always switch simulataneously.
- '
- ' Unlike other playfield lamps, these lamps stays also lit when
- ' playfieldLightsEnabled=false,because this way also after a tilt the player
- ' can see if he still has a shoot again. (I believe to remember this from Bally
- ' "Champ";is this correct?)
- '
- ' Important: The extraBallWon relay must be set to tell the program that the next
- ' ball shall be a shoot again.The shootAgain relay itself does nothing
- ' except holding its own lamps lit. (I don't know if real Bally machines
- ' handle this differently.)
- setRelay shootAgain,aBoolean
- if aBoolean then
- lampShootAgain.state=lightStateOn
- shootAgainText.Text = "SHOOT AGAIN"
- If B2SOn Then Controller.B2SSetShootAgain 36, 1
- else
- lampShootAgain.state=lightStateOff
- shootAgainText.Text =""
- If B2SOn Then Controller.B2SSetShootAgain 36, 0
- end if
- end sub
- 'move flippers:
- sub moveLeftFlipper(aBoolean)
- ' This moves the left flipper up when aBoolean=true or down when false with the
- ' corresponding sound so far the flipper was not already in that position.
- if aBoolean and not leftFlipperUp then
- leftFlipper.rotateToEnd
- stopSound "leftFlipperDown"
- 'The stopSound reduces CPU load when someone chatters with the flippers and
- 'it also makes it sound more realistic.
- playSound SoundFXDOF("leftFlipperUp",101,DOFOn,DOFFlippers)
- playSound "leftFlipperHum",-1 'endless loop sample of solenoid hum
- leftFlipperUp=true
- elseIf not aBoolean and leftFlipperUp then
- leftFlipper.RotateToStart
- stopSound "leftFlipperUp"
- playSound SoundFXDOF("leftFlipperDown",101,DOFOff,DOFFlippers)
- stopSound "leftFlipperHum"
- leftFlipperUp=false
- end if
- end sub
- sub moveRightFlipper(aBoolean)
- ' This moves the right flipper up when aBoolean=true or down when false with the
- ' corresponding sound so far the flipper was not already in that position.
- if aBoolean and not rightFlipperUp then
- rightFlipper.rotateToEnd
- stopSound "rightFlipperDown"
- playSound SoundFXDOF("rightFlipperUp",102,DOFOn,DOFFlippers)
- playSound "rightFlipperHum",-1 'endless loop sample of solenoid hum
- rightFlipperUp=true
- elseIf not aBoolean and rightFlipperUp then
- rightFlipper.RotateToStart
- stopSound "rightFlipperUp"
- playSound SoundFXDOF("rightFlipperDown",102,DOFOff,DOFFlippers)
- stopSound "rightFlipperHum"
- rightFlipperUp=false
- end if
- end sub
- 'enable/disable playfield stuff:
- sub setFlippers(aBoolean)
- ' This switches the flippers on or off and regards (with correct sound) when
- ' a flipper button is held down.
- ' (In a real pinball machine the flippers react when the button is held and
- ' then the current to the flippers is switched on.)
- flippersEnabled=aBoolean
- if flippersEnabled then
- moveLeftFlipper leftFlipButton
- moveRightFlipper rightFlipButton
- else
- moveLeftFlipper false
- moveRightFlipper false
- end if
- end sub
- sub setBumpers(aBoolean)
- ' This shall turn the bumping of all bumpers on or off.
- ' BUG: this doesn't work yet (a Visual Pinball bug?). Bumpers remain always on.
- 'TIP: Beside slingshots (triangular "bumpers") also the straight part of long
- ' rubber bands(where they don't touch the post) should be set as slingshots
- ' with a low slingshot force(about 2) to get a more realistic ball bounce
- ' behaviour in Visual Pinball.
- ' (Note: In Visual Pinball 3 slingshot areas of "Wall" objects must not be
- ' convex,because otherwise their slingshot part(black line in editor) can
- ' not be hit with the ball.Instead make such areas as multiple small straight
- ' lines. Concave slingshot areas make no problems.)
- '
- ' To simulate bumpers with a non-round or flat top cover (like e.g. in
- ' Interflip "Alaska"),I recommend to use the Visual Pinball bumper only for
- ' the round center part,and add a flat disc of "wall" material to draw the
- ' top. If the top shall be lightable,use the same trick I did with the
- ' playfield plastics.
- if aBoolean=true then
- centerBumper.threshold =1
- leftSlingshot.threshold =1
- rightSlingshot.threshold=1
- else
- centerBumper.threshold =1000
- leftSlingshot.threshold =1000
- rightSlingshot.threshold=1000
- end If
- end sub
- 'coin chutes:
- sub creditButtonRoutine
- ' This routine adds a player to a running game when it is still at the 1st ball,
- ' or otherwise resets the current game and starts a new one with 1 player.
- '
- ' This is called when the start button is pressed and credits are present or
- ' when a valid washer was inserted into a 1-credit coin chute.
- rulesSwitchable=false 'During a game the rules must stay the same.
- if gameOver or ballCount>1 or players=maxPlayers then
- 'starting a new game with 1 player
- resetGame
- else
- if creditTaken then decrementCredit
- 'When this was called by a start button press,a credit is consumed from the
- 'credit counter (decrementCredit also clears the creditTaken relay).
- addPlayer
- 'Players can be added to a running game as long the 1st ball is in play.
- 'After a player lost a ball, it would become unfair to permit this, because
- 'the new player would get less balls for his credit due to these old Bally
- 'machines have only a single ball counter wheel shared among all players.
- end if
- end sub
- 'The following timers delay inserted coins to simulate the coin mech sound
- 'correctly.
- '
- 'Important: The constants coinMechXTime must be >0,because Timer.interval is abused
- ' here as a state variable. The coinMechX timers must be initialized with
- ' .interval=0 to work correctly.
- sub coinMech1_timer()
- if coinMech1.interval=0 then
- coinMech1.interval=coinMech1Time
- playSound "coin1"
- playSound "coinMech1"
- else
- coinMech1.enabled=false
- coinMech1.interval=0
- insertCoin coinChute1Credits
- end if
- end sub
- sub coinMech2_timer()
- if coinMech2.interval=0 then
- coinMech2.interval=coinMech2Time
- playSound "coin1"
- playSound "coinMech1"
- else
- coinMech2.enabled=false
- coinMech2.interval=0
- insertCoin coinChute2Credits
- end if
- end sub
- sub coinMech3_timer()
- if coinMech3.interval=0 then
- coinMech3.interval=coinMech3Time
- playSound "coin1"
- playSound "coinMech1"
- else
- coinMech3.enabled=false
- coinMech3.interval=0
- insertCoin coinChute3Credits
- end if
- end sub
- sub insertCoin(someCredits)
- ' This simulates inserting a coin. The coins are added using the score motor.
- ' Therefore when the score motor is still running (e.g. by a previous coin), the
- ' then the coin is rejected. Coins with a single credit can be inserted at any
- ' time, because these don't need the score motor to be counted.
- '
- ' (A real mechanical pinball machine can not memorize the credits of multiple
- ' inserted coins,therefore a mechanism in the coindoor re-directs inserted coins
- ' to the coin return chute when the counter is still counting. But so far I know,
- ' the machine does not recognize overpaying the credit counter.)
- if creditsAddable then
- select case int(3*rnd())
- case 0: playSound "cashbox2"
- case 1: playSound "cashbox9"
- case 2: playSound "cashbox15"
- 'This plays randomly 1 of multiple different cashbox noises,but they are
- 'anyway badly audible due to the loud rest of the mechanism.
- end select
- setIllumination true 'a coin activates illumination
- if someCredits=1 then
- 'For adding a single credit the score motor is not involved.
- if smallCoinStartsGame then
- 'When true,it behaves much like a start button press,except that no credit
- 'is subtracted from the counter and that a running game can always be reset
- 'by this.
- creditButtonRoutine
- else
- if not knockerKnocksAlways then knockerActive=false
- 'disables the separate(loud) knocker coil
- addCredit
- knockerActive=true
- end if
- else
- if not knockerKnocksAlways then knockerActive=false
- addCredits(someCredits)
- '(The score motor re-enables the separate knocker after credits are added.)
- end if
- else
- 'here the coin was refused by the rejector because creditsAddable=false.
- playSound "coinEject4" 'coin reject sound.
- end if
- end sub
- sub setCoinLockOut(aBoolean)
- ' A real mechanical pinball machine can not memorize the credits of multiple
- ' inserted coins,therefore a latch in the coindoor re-directs inserted coins to
- ' the coin return chute when the counter is still counting or the score motor
- ' is occupied with other stuff.
- '
- ' This routine is called by processAddCredits() to emulate this feature,because it
- ' makes a characteristic noise when the score motor starts and stops.True means
- ' no credits can be added,false means that credits can be added.(Don't call this
- ' routine elsewhere. The value of creditsAddable is also important for the
- ' proper function of the end game matching routine.)
- '
- ' (I am not sure if this is correctly emulated,but I remember that a mechanism
- ' in the coin door of my Bally "Champ" clicked very often during many kinds of
- ' actions the machine did.)
- if not creditsAddable and not aBoolean then
- creditsAddable=true
- playSound "lockOutOn"
- elseIf creditsAddable and aBoolean then
- creditsAddable=false
- playSound "lockOutOff"
- end if
- end sub
- 'switch game rule set:
- sub setGameRules(aValue)
- ' This swithes the game rule set between authentic and enhanced rules and
- ' updates the displays accordingly.
- '
- ' Attention: This should not be called while a game is running,because it
- ' would mess up the saved highscore.
- ruleSet=aValue
- if aValue=RS_authentic then
- rulesBox.text="authentic game rules"
- elseIf aValue=RS_enhanced then
- rulesBox.text="enhanced mode"
- end if
- releaseStuckScoreReels 'so far captive balls can make mess here
- displayHighScore
- updatePlayfieldLights
- end sub
- 'reset:
- sub resetGame
- ' This routine starts the reset sequence so far the gameOver relay is set.
- ' Below are some lines of test code with that you can experiment to see how the
- ' emulated reset routine reacts on unusual conditions.Especially try to
- ' uncomment "ejectBall" and/or "bonus=..." to see what the simulated mechanism
- ' does when the pinball machine was switched off during a game in such a situ-
- ' ation.
- '
- ' setScore 1,12121 'TEST!!!
- ' setScore 2,91010 'TEST!!!
- ' setScore 3,70000 'TEST!!!
- ' setScore 4,12345 'TEST!!!
- ' debugScoreDisplayAlignment 'TEST!!!
- ' bonus=12000 'TEST!!!
- ' ejectBall 'TEST!!!
- if not gameOver then doGameOver
- 'The gameOver relay must be set because it also controlls the reset sequence.
- setRelay newRound,false
- 'This is important to reset the per-round stuff (spinner etc.) when
- 'we start a new game.Also processCollectBonus() needs this to reset
- 'the bonus ladder.
- setIllumination true
- gameIsResetting =true
- reelChimeEnabled=false 'prevents a racket when score reels reset
- scoreMotor.enabled=true
- end sub
- 'general light related routines:
- '-------------------------------
- sub setLight(aLamp,aBoolean)
- ' This sets a lamp on or off depending on the value of aBoolean (useful for
- ' setting a lamp according to a given boolean state variable).
- if aBoolean and playfieldLightsEnabled then
- aLamp.state=lightStateOn
- else
- aLamp.state=lightStateOff
- end if
- end sub
- sub resetLights()
- ' This resets playfield lights and their variables to their default state
- ' when a round begins.
- '
- ' Hint: To make a pinball game look realistic, the 'colourless' lights must not
- ' be white but instead have a quite orange, lightbulbish hue. RGB 254,235,
- ' 175 is a good value to use. Due to the brightness of this colour is lower
- ' than cranking R+G+B fully up to 255 (as white light could do), the entire
- ' playfield and backglass picture needs to be darkened by a few percent with
- ' a graphics program to make the lamps appear bright enough. Usually bright-
- ' ness and contrast need to be reduced by the same amount. The brightness
- ' also should be set in a way that the black parts of a playfield look
- ' really almost black and not just grey.
- '
- ' Generally the key for a realistic looking playfield is the use of lightbulb
- ' light effects. For these transparent round spots of yellowish orange with
- ' fading rims should be painted onto the playfield picture everywhere where
- ' under plastic parts light bulbs can be expected. (RGB 255,204,17 looks
- ' good.)
- '
- ' With my German "Micrografx Picture Publisher 8" version the best looking
- ' painting mode for drawing light spots is "Schicht"(i.e. layer) with a
- ' transparency about 42% and 100% "Verwischen" (fading brush rims).
- setRelay A,false
- setRelay B,false
- setRelay C,false
- setRelay D,false
- setRelay A_lock,false
- setRelay B_lock,false
- setRelay C_lock,false
- setRelay D_lock,false
- rollover1kLit=(ruleSet=RS_authentic) 'test on true or false
- specialLit =false
- setRelay doubleBonus,false
- updatePlayfieldLights
- lightCenterBumper (ruleSet=RS_authentic) 'test on true or false
- 'The center bumper is always lit on the original pinball machine,though
- 'this is standard for the authentic rule set.
- end sub
- sub updatePlayfieldLights()
- ' This routine shall update all the pilot lamps on the playfield according to
- ' their variables. It is ment as an initialization routine and it is also very
- ' good for debugging states of the playfield variables.But it is not a good
- ' idea to update a single changed light by calling this after setting its
- ' variable. When single variables change,by performance reasons instead only its
- ' corresponding lamp should be updated by hand using setLight().
- '
- ' Note: Although possible, this routine should NOT be called by a fast timer to
- ' periodically update lights when the variables change,because this would
- ' waste much CPU time.
- '
- ' When playfieldLightsEnabled=false,this routine switches all lamps off
- ' (implemented in setLight() ). The "shoot again" lamp is independent from this
- ' (see setShootAgain() ).
- '
- ' When playfield lights are initialized,then this routine must be the central
- ' place to do this,instead of setting some lamps here and others there in the
- ' code.
- setLight lampLeftCLaneMid ,true
- setLight lampRightCLaneMid,true
- setLight lampLeft1k ,rollover1kLit
- setLight lampRight1k ,rollover1kLit
- setLight lamp_DB ,doubleBonus
- setLight lampLeftCLaneEnd ,specialLit
- setLight lampRightCLaneEnd,specialLit
- setLight lampTarget_A ,not A
- setLight lampTarget_B ,not B
- setLight lampTarget_C ,not C
- setLight lampTarget_D ,not D
- setLight lampLeftCLane_A ,A
- setLight lampLeftCLane_B ,B
- setLight lampRightCLane_C,C
- setLight lampRightCLane_D,D
- setSpinnerLights spins
- setBonusLights bonus 'this also updates extra ball lights
- end sub
- sub setPlayfieldLights(aBoolean)
- ' This switches the pilot lamps on the playfield on or off.Important is that switching it
- ' on again during a game relies on a complete implementation of updatePlayfieldLights().
- playfieldLightsEnabled=aBoolean
- updatePlayfieldLights
- end sub
- sub setBackboxLights(aBoolean)
- ' This switches various display lamps in the backbox on or off. To make this
- ' work correctly,they must stay dark when they become set and
- ' backboxLightsEnabled=false.
- '
- ' The "game over" lamp is wired independently and therefore not affected by
- ' this.
- backboxLightsEnabled=aBoolean
- updateBackboxLights
- end sub
- sub updateBackboxLights()
- ' This routine shall update various display lamps in the backbox according to
- ' their variables.
- setBallCounterLights ballCount
- setPlayerLights players
- setPlayerUpLights currentPlayer
- end sub
- sub setIllumination(aBoolean)
- ' This switches the general illumination (playfield plastics illumination and
- ' some backbox illumination lights) on or off.
- if illumination=aBoolean then exit sub
- If aBoolean then
- captLaneLLit.isDropped =false
- captLaneRLit.isDropped =false
- bankLLit.isDropped =false
- bankRLit.isDropped =false
- slingshotLLit.isDropped=false
- slingshotRLit.isDropped=false
- lampHead.state =lightStateOn
- lampKang1.state =lightStateOn
- lampKang2.state =lightStateOn
- lampTitle1.state=lightStateOn
- lampTitle2.state=lightStateOn
- 'These are the translucent objects on the backglass.Sorry that they
- 'have an ugly black rim;a Visual Pinball bug draws a black rectangle
- 'around every lit lamp behind a decal.
- else
- captLaneLLit.isDropped =true
- captLaneRLit.isDropped =true
- bankLLit.isDropped =true
- bankRLit.isDropped =true
- slingshotLLit.isDropped=true
- slingshotRLit.isDropped=true
- lampHead.state =lightStateOff
- lampKang1.state =lightStateOff
- lampKang2.state =lightStateOff
- lampTitle1.state=lightStateOff
- lampTitle2.state=lightStateOff
- end if
- setRelay illumination,aBoolean
- end sub
- sub lightCenterBumper(aBoolean)
- ' This switches the light of the center bumper on or off.
- setRelay centerBumperLit,aBoolean
- If aBoolean=true then
- centerBumper.state =lightStateOn
- litCenterFloor.isDropped=false
- else
- centerBumper.state =lightStateOff
- litCenterFloor.isDropped=true
- end if
- 'Note:
- 'To simulate an illuminated floor part, a flat wall object with the illuminated
- 'floor texture is risen or dropped here. Due to even a dropped object has still
- 'a height above the playfield, its bottom height must be set below the playfield
- 'to become capable to sink completely.
- 'Due to unlit bumpers look awfully dark on Visual Pinball, for the center
- 'bumper a white texture with a checkered pixel pattern is used of that
- 'every 2nd pixel its pixels is set to the transparent colour; though the light
- 'of the lit bumper can shine through the transparent pixels while the rest
- 'looks white - very much like light shining through a white gaze curtain at
- 'your window. When the daylight turns dark, you can still see the white of the
- 'curtain by room light from the other side, and not just the black of the night
- 'behind it.
- 'Also here, the lamp of the bumper was set to a lightbulbish orange tint, and
- 'not to the awful xenon-tube white that is default in Visual Pinball.
- '
- 'TIP: Before you "perforate" a picture this way for transparency,I recommend to
- ' resize it to the twice resolution to make it look less gritty.
- 'BUG: Since I added playfield illumination,due to a Visual Pinball bug the spinner
- ' is now partly invisible when the lit floor is not dropped. This seems to be
- ' a Z-buffer rendering problem.I therefore placed a triangle with Can Drop
- ' property directly under the playfield at the spinner in a way that both
- ' spinner posts stand on it. Despite it is invisible,its bare existence seems
- ' to convince the Z-Buffer to display the spinner correctly. (The less control
- ' points an object has,the less CPU time is needed, therefore a triangular
- ' object burdens the CPU the least.)
- '
- ' The surface order recognition algorithm of Visual Pinball seems to be as
- ' lousy as the one in the terrible "Hard Drivin'" (3D car racing) home computer
- ' versions.
- end sub
- 'small counters and state wheel emulations:
- '------------------------------------------
- 'credit counter:
- '
- 'This credit display employs the "reel-a-listic" technology to simulate counter
- 'reel rotation.(See score counter section for more explanations of the used
- 'technical tricks.)
- sub addCredits(anInteger)
- ' This adds multiple credits to the credit counter using the score motor,which
- ' calls addCredit() multiple times.
- '
- ' Warning: This routine must only be called when scoreMotorAngle=0,otherwise
- ' it would count wrong. (Test on "if creditsAddable=true" for this.)
- unaddedCredits=unaddedCredits+anInteger
- scoreMotor.enabled=true
- If unaddedCredits > 0 Then
- DOF 132, DOFOn
- Else
- DOF 132, DOFOff
- End If
- end sub
- sub addCredit()
- ' This adds 1 credit to the credit counter and updates the counter display.
- ' (This routine does not depend on the score motor but is called from there.)
- if credits=maxCredits and not creditOverpayBounce then exit sub
- playSoundMonophonic "creditSolenoid"
- if knockerEnabled and knockerActive then playSound SoundfXDOF("knocker",131,DOFPulse,DOFKnocker):DOF 111, DOFPulse
- 'This triggers a loud separate knocker when the credit was added by anything
- 'else than inserting a coin.
- if credits<maxCredits then credits = credits+1
- 'I increment the credit counter variable already here because between the
- 'creditAdder timer and the scoreMotor timer exists a race condition that in
- 'rare cases (only on slow PCs?) sometimes made the counter count wrong.
- 'For a physically correct behaviour this instead should be done in
- 'creditAdder_timer().
- setCreditDisplay credits
- creditAdder.enabled=true
- If B2SOn Then Controller.B2ssetcredits Credits
- end sub
- sub creditAdder_timer()
- 'This timer updates the credit display after a small delay to simulate a
- 'rotation at the new stable position.It also plays mechanical sounds after
- 'the delay to achieve a more realistic sound timing for the credit counter.
- creditAdder.enabled=false
- playSound "creditCounter" 'tinny knock/clang of the credit wheel
- 'I remember that the credit counter knocks allways,and not only by
- 'replay credits.
- spinCreditReel
- end sub
- sub decrementCredit()
- ' This subtracts 1 credit from the credit counter.
- if credits>0 then
- credits=credits-1
- setCreditDisplay credits
- If B2SOn Then Controller.B2ssetcredits Credits
- creditAdder.enabled=true
- if credits=maxCredits-1 and unaddedCredits=0 and scoreMotorAngle=0 then setCoinLockOut false
- 'When overpaying blocked the coin chutes,this will free them again because
- 'now there will be at least 1 credit less.
- end if
- setRelay creditTaken,false 'credit relay cleared
- saveGame 'save new credit counter value
- end sub
- sub setCreditDisplay(aValue)
- ' This displays on the credit counter reel the number aValue halfway rotated
- ' to the next value.
- dim top
- if aValue<maxCredits then top=aValue+1 else top=""
- creditLow.text =credits 'print bottom number
- creditClr1.text="" 'clear its lower half
- creditTop.text =top 'print top number
- creditClr0.text="" 'clear its upper half
- end sub
- sub spinCreditReel()
- ' This updates the credit display and its lamp.This routine must also be called
- ' during init of this program.
- if credits=0 and credit0UnlightsCounter then
- lampCreditBox.state=lightStateOff
- else
- lampCreditBox.state=lightStateOn
- end if
- 'This darkens credit counter illumination at 0 credit. (I am not sure if
- 'this is correct.)
- creditClr1.text=""
- creditClr0.text=""
- creditTop.text =""
- creditLow.text =""
- creditMid.text =credits
- If B2SOn Then Controller.B2SSetcredits Credits
- end sub
- 'ball count assembly:
- sub ballCounterReset_timer
- ' Enabling this timer resets the ball counter and buzzes with the correct
- ' length to simulate the state wheel mechanism behaviour.
- '
- ' Warning: to function properly,ballCount must be initialized with 1 before use.
- if ballCount>1 then
- ballCount=ballCount-1
- playSound "diverter"
- else
- ballCounterReset.enabled=false
- end if
- setBallCounterLights ballCount
- end sub
- function isBallCounterResetted()
- ' just a useless feature to emulate the score motor driven reset sequence
- if ballCount=1 then isBallCounterResetted=true else isBallCounterResetted=false
- end function
- sub incrementBallCounter()
- ' This lets the ball counter count 1 ball forward and displays the new value.
- '
- ' Note: In these old Bally machines there is only one ball counter wheel for all
- ' players, though the players can never habe different numbers of balls
- ' per game. Due to all players are strictly served in the sequence 1,2,3,4,
- ' 1,2..., it can never happen that a 'later' player has more balls left than
- ' an earlier player.(A 'shoot again' only prevents loosing a ball, but never
- ' adds a new ball to the counter, because otherwise the later players would
- ' get the additional ball too, because the ball counter state wheel can not
- ' distinguish to whom the added ball would belong.)
- '
- ' In the "Boomerang" flyer stands that these machines could be modified to
- ' support "add-a-ball", i.e. that a player can launch multiple of his balls
- ' into play at any time during a game. Due to there is only one ball counter,
- ' I can only imagine that for this the machine was re-wired in a way that
- ' every player finishes all his balls and only after he goes game over, the
- ' next player could play (i.e. after the modification players can't play
- ' their rounds alternatingly anymore, what I find quite stupid.
- if ballCount<=maxBalls+1 then ballCount = ballCount+1
- 'The counter in a real machine can step 1 position further than maxBalls.
- playSound "diverter"
- setBallCounterLights ballCount
- end sub
- sub setBallCounterLights(aValue)
- ' This routine simulates a row of 5 number-shaped lamps on the backbox to
- ' display the ball counter.Use -1 (or 0) to clear all lamps.
- ball1Text.text=" "
- ball2Text.text=" "
- ball3Text.text=" "
- ball4Text.text=" "
- ball5Text.text=" "
- If B2SOn Then Controller.B2SSetBallInPlay 32, 0
- if not backboxLightsEnabled then exit sub
- if aValue=1 then
- ball1Text.text="1"
- elseIf aValue=2 then ball2Text.text="2"
- elseIf aValue=3 then ball3Text.text="3"
- elseIf aValue=4 then ball4Text.text="4"
- elseIf aValue=5 then ball5Text.text="5"
- end if
- If B2SOn Then Controller.B2SSetBallInPlay 32, ballcount
- 'At game over the ball counter advances 1 beyond ballsPerGame (also in a real
- 'Bally machine),but the lamps stay off in this case.
- end sub
- 'player counter assembly: (Bally "coin unit")
- sub playerCounterReset_timer
- ' Enabling this timer resets the player counter and buzzes with the correct
- ' length to simulate the state wheel mechanism behaviour.
- '
- ' Warning: to function properly,players must be initialized with 1 before use.
- if players>1 then
- players=players-1
- playSound "diverter"
- else
- playerCounterReset.enabled=false
- end if
- setPlayerLights players
- end sub
- function isPlayerCounterResetted()
- ' just a useless feature to emulate the score motor driven reset sequence
- if players=1 then isPlayerCounterResetted=true else isPlayerCounterResetted=false
- end function
- sub addPlayer()
- 'This adds a player to the game and updates the player counter display.
- playSound "diverter" 'loud click when counter wheel steps
- if players<maxPlayers then players=players+1
- setPlayerLights players
- end sub
- sub setPlayerLights(aValue)
- ' This sets the player counter display and lights the score counter according to
- ' the number of players.Use -1 (or 0) to clear all lamps.
- dim t
- if backboxLightsEnabled then t=aValue else t=-1
- if t>=1 then
- lamp1CanPlay.state=lightStateOn
- lampScoreP1.state =lightStateOn
- If B2SOn Then Controller.B2SSetCanPlay 31, t
- else
- lamp1CanPlay.state=lightStateOff
- lampScoreP1.state =lightStateOff
- end if
- if t>=2 then
- lamp2CanPlay.state=lightStateOn
- lampScoreP2.state =lightStateOn
- If B2SOn Then Controller.B2SSetCanPlay 31, t
- else
- lamp2CanPlay.state=lightStateOff
- lampScoreP2.state =lightStateOff
- end if
- if t>=3 then
- lamp3CanPlay.state=lightStateOn
- lampScoreP3.state =lightStateOn
- If B2SOn Then Controller.B2SSetCanPlay 31, t
- else
- lamp3CanPlay.state=lightStateOff
- lampScoreP3.state =lightStateOff
- end if
- if t>=4 then
- lamp4CanPlay.state=lightStateOn
- lampScoreP4.state =lightStateOn
- If B2SOn Then Controller.B2SSetCanPlay 31, t
- else
- lamp4CanPlay.state=lightStateOff
- lampScoreP4.state =lightStateOff
- end if
- updateScoreDisplays
- end sub
- 'player-up unit:
- sub playerUpReset_timer
- ' Enabling this timer resets the playerUp counter and buzzes with the correct
- ' length to simulate the state wheel mechanism behaviour.
- '
- ' Warning: to function properly,currentPlayer must be initialized with 1 before
- ' use.
- if currentPlayer>1 then
- currentPlayer=currentPlayer-1
- playSound "diverter"
- else
- playerUpReset.enabled=false
- end if
- setPlayerUpLights currentPlayer
- end sub
- function isPlayerUpResetted()
- ' just a useless feature to emulate the score motor driven reset sequence
- if currentPlayer=1 then isPlayerUpResetted=true else isPlayerUpResetted=false
- end function
- sub nextPlayerUp()
- ' This sets the next player as the current player,depending on the number of
- ' players per game stored in the player counter state wheel.
- playSound "diverter" 'loud click when player wheel steps
- if currentPlayer<players then
- setCurrentPlayer currentPlayer+1
- else
- setCurrentPlayer 1
- 'I don't know if the state wheel should be resetted here or if it just steps
- 'around and uses different contacts for different numbers of players
- 'or anything like that.
- end if
- end sub
- sub setCurrentPlayer(aPlayerNumber)
- ' This sets the player (1 to 4) that is actually playing and lights its lamp
- ' on the backbox.
- if aPlayerNumber<>currentPlayer then
- releaseStuckScoreReels 'so far any are stuck
- currentPlayer=aPlayerNumber
- end if
- setPlayerUpLights currentPlayer
- end sub
- sub setPlayerUpLights(aValue)
- ' This lights the player-up lamp on backbox.Use -1 (or 0) to clear all lamps.
- dim t
- if backboxLightsEnabled then t=aValue else t=-1
- If B2SOn Then Controller.B2SSetPlayerUp 30,0
- if t=1 then lamp1Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp1Up.state=lightStateOff
- if t=2 then lamp2Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp2Up.state=lightStateOff
- if t=3 then lamp3Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp3Up.state=lightStateOff
- if t=4 then lamp4Up.state=lightStateOn:If B2SOn Then Controller.B2SSetPlayerUp 30,t else lamp4Up.state=lightStateOff
- end sub
- 'spinner lights state wheel assembly:
- sub spinnerReset_timer
- ' Enabling this timer resets the spinner state wheel.
- '
- ' Note:I was told that the spinner state wheel of the original pinball machine
- ' can not reset,though I use this only in my enhanced rule set but not
- ' in the authentic rule set one.
- if spins>0 then
- spins=spins-1
- playSound "diverter"
- else
- spinnerReset.enabled=false
- end if
- setSpinnerLights spins
- end sub
- function isSpinnerResetted()
- ' another dummy for emulation
- if spins=0 then isSpinnerResetted=true else isSpinnerResetted=false
- end function
- sub incrementSpinner()
- spins=spins+1
- if spins>10 then
- spins=0
- incrementBonus 'Bonus added and spinner lights go off again
- end if
- if spins=0 and ruleSet=RS_authentic then spins=1
- 'Is this rule correct? (Returning only to lamp 1 would waste a lamp,
- 'and also psychologically it makes more sense when the bonus increases
- 'while all lights zap away.But otherwise the walking dot in authentic
- 'mode looks more correct to me when there stays always a light visible,
- 'similar like a hand on a clock face.)
- if spins=0 or not playfieldLightsEnabled then
- lampSpin1.State = LightStateOff
- lampSpin2.State = LightStateOff
- lampSpin3.State = LightStateOff
- lampSpin4.State = LightStateOff
- lampSpin5.State = LightStateOff
- lampSpin6.State = LightStateOff
- lampSpin7.State = LightStateOff
- lampSpin8.State = LightStateOff
- lampSpin9.State = LightStateOff
- lampSpin10.State = LightStateOff
- else
- setSpinnerLights spins
- end if
- end sub
- sub setSpinnerLights(aValue)
- ' This lights on playfield the spinner lamps for aValue.Use -1 (or 0) to clear
- ' all lamps.
- '
- ' I was told that the original pinball machine only lights 1 light at a time,
- ' but I kept my previous light row implementation in my enhanced rule set be-
- ' cause it looks much better.
- dim t
- if playfieldLightsEnabled then t=aValue else t=-1
- if ruleSet=RS_authentic then
- 'This lights only the lamp with the number t (authentic Bally).
- if t=1 then lampSpin1.State = LightStateOn else lampSpin1.State = LightStateOff
- if t=2 then lampSpin2.State = LightStateOn else lampSpin2.State = LightStateOff
- if t=3 then lampSpin3.State = LightStateOn else lampSpin3.State = LightStateOff
- if t=4 then lampSpin4.State = LightStateOn else lampSpin4.State = LightStateOff
- if t=5 then lampSpin5.State = LightStateOn else lampSpin5.State = LightStateOff
- if t=6 then lampSpin6.State = LightStateOn else lampSpin6.State = LightStateOff
- if t=7 then lampSpin7.State = LightStateOn else lampSpin7.State = LightStateOff
- if t=8 then lampSpin8.State = LightStateOn else lampSpin8.State = LightStateOff
- if t=9 then lampSpin9.State = LightStateOn else lampSpin9.State = LightStateOff
- if t=10 then lampSpin10.State= LightStateOn else lampSpin10.State= LightStateOff
- else
- 'With enhanced game rules this displays a row of t lights (looks better).
- if t>=1 then lampSpin1.State = LightStateOn else lampSpin1.State = LightStateOff
- if t>=2 then lampSpin2.State = LightStateOn else lampSpin2.State = LightStateOff
- if t>=3 then lampSpin3.State = LightStateOn else lampSpin3.State = LightStateOff
- if t>=4 then lampSpin4.State = LightStateOn else lampSpin4.State = LightStateOff
- if t>=5 then lampSpin5.State = LightStateOn else lampSpin5.State = LightStateOff
- if t>=6 then lampSpin6.State = LightStateOn else lampSpin6.State = LightStateOff
- if t>=7 then lampSpin7.State = LightStateOn else lampSpin7.State = LightStateOff
- if t>=8 then lampSpin8.State = LightStateOn else lampSpin8.State = LightStateOff
- if t>=9 then lampSpin9.State = LightStateOn else lampSpin9.State = LightStateOff
- if t =10 then lampSpin10.State= LightStateOn else lampSpin10.State= LightStateOff
- end if
- end sub
- 'bonus ladder assembly: ("bonus unit")
- sub incrementBonus()
- ' This routine increments the bonus ladder by const bonusStepWidth.
- if bonus=maxBonus or tilt and tiltPreventsAddBonus then exit sub
- bonus=bonus+bonusStepWidth
- playSound "diverter" 'loud click sound when bonus wheel steps
- if ruleSet=RS_enhanced and bonusClearsBumper and bonus=topHoleShootAgain then lightCenterBumper false
- 'This unlights the center bumper in enhanced game rule mode when the bonus
- 'reached the topHoleShootAgain value as a hint to shoot the tophole.
- '(I don't do this in setBonusLights() because it shall be triggered only
- 'once and not when the lights get updated by any other reasons.)
- setBonusLights bonus
- end sub
- sub decrementBonus()
- ' This routine steps down the bonus ladder by const bonusStepWidth.
- if bonus>0 then playSound "diverter" 'loud click sound when bonus wheel steps
- subtractUntil0 bonus,bonusStepWidth
- setBonusLights bonus
- end sub
- sub setBonusLights(aValue)
- ' This routine displays the lights of the bonus ladder according to the actual
- ' value of the argument variable. This is an approximation of what the bonus
- ' state wheel in a real mechanical pinball machine does.
- '
- ' Due to both extra ball lights and specials are also controlled by contacts
- ' on the wheel (a guess from Bally "Champ"),these are also set here depending
- ' on the actual bonus value.
- '
- ' (This routine is game rule dependant and though must be alterated for
- ' emulating other pinball machines.)
- dim x,value
- 'set extra ball lamps: (At bonus >=15000 both lamps are lit.)
- if playfieldLightsEnabled then value=aValue else value=0
- 'This disables all bonus lights when playfieldLightsEnabled=false.
- '(Note: As a side effect then also the extraBallLit variables become false.)
- leftExtraBallLit = false
- rightExtraBallLit = false
- lampLeft_XB.State = LightStateOff
- lampRight_XB.State = LightStateOff
- if value=8000 or value=10000 or value=12000 or value=14000 or value>=15000 then
- lampLeft_XB.State = LightStateOn
- leftExtraBallLit = true
- end if
- if value=9000 or value=11000 or value=13000 or value>=15000 then
- lampRight_XB.State = LightStateOn
- rightExtraBallLit = true
- end if
- 'set bonus ladder lamps:
- x=value mod 10000
- lampBonus1k.State = LightStateOff 'all bonus lamps off
- lampBonus2k.State = LightStateOff
- lampBonus3k.State = LightStateOff
- lampBonus4k.State = LightStateOff
- lampBonus5k.State = LightStateOff
- lampBonus6k.State = LightStateOff
- lampBonus7k.State = LightStateOff
- lampBonus8k.State = LightStateOff
- lampBonus9k.State = LightStateOff
- lampBonus10k.State= LightStateOff
- if ruleSet=RS_authentic then
- 'This lights only the lamp with the number x (authentic Bally).
- select case x
- case 1000: lampBonus1k.State=lightStateOn
- case 2000: lampBonus2k.State=lightStateOn
- case 3000: lampBonus3k.State=lightStateOn
- case 4000: lampBonus4k.State=lightStateOn
- case 5000: lampBonus5k.State=lightStateOn
- case 6000: lampBonus6k.State=lightStateOn
- case 7000: lampBonus7k.State=lightStateOn
- case 8000: lampBonus8k.State=lightStateOn
- case 9000: lampBonus9k.State=lightStateOn
- end select
- else
- 'With enhanced game rules this displays a vertical bar of x lights (looks better).
- if x>=1000 then lampBonus1k.State = LightStateOn
- if x>=2000 then lampBonus2k.State = LightStateOn
- if x>=3000 then lampBonus3k.State = LightStateOn
- if x>=4000 then lampBonus4k.State = LightStateOn
- if x>=5000 then lampBonus5k.State = LightStateOn
- if x>=6000 then lampBonus6k.State = LightStateOn
- if x>=7000 then lampBonus7k.State = LightStateOn
- if x>=8000 then lampBonus8k.State = LightStateOn
- if x =9000 then lampBonus9k.State = LightStateOn
- end if
- if value>=10000 then lampBonus10k.State = LightStateOn
- 'set specials:
- if specialLitValue<>false and bonus>=specialLitValue and not specialHit then
- 'light both specials when enough bonus
- lampLeftCLaneEnd.State = LightStateOn
- lampRightCLaneEnd.State = LightStateOn
- specialLit=true
- end if
- end sub
- 'end game digit matching assembly: (Bally "00-90 unit")
- sub advanceMatcher()
- ' This advances the simulated state wheel of the pseudo random number generator
- ' for the end game digit matching feature of the mechanical pinball machine.
- playSound "rflipper" 'quiet click
- if matchNumber>=9 then
- matchNumber=0
- else
- matchNumber=matchNumber+1
- end if
- end sub
- sub setEndMatchLights(aValue)
- ' This lights a number on the 00-90 score end digit matching lamp row on the
- ' backglass. aValue must be between 0 and 9 to light one of the lamps,or -1
- ' to light none.
- dim i,t
- t="" & aValue 'string of aValue
- for i=0 to matchedScoreDigit
- t=t&"0"
- 'concatenates the correct number of zeroes behind the digit
- next
- if aValue=0 then match0Text.text=t else match0Text.text=""
- if aValue=1 then match1Text.text=t else match1Text.text=""
- if aValue=2 then match2Text.text=t else match2Text.text=""
- if aValue=3 then match3Text.text=t else match3Text.text=""
- if aValue=4 then match4Text.text=t else match4Text.text=""
- if aValue=5 then match5Text.text=t else match5Text.text=""
- if aValue=6 then match6Text.text=t else match6Text.text=""
- if aValue=7 then match7Text.text=t else match7Text.text=""
- if aValue=8 then match8Text.text=t else match8Text.text=""
- if aValue=9 then match9Text.text=t else match9Text.text=""
- end sub
- 'relay
- '-----
- sub setRelay(aRelayName,aBoolean)
- ' This sets the boolean variable aRelayName to the value aBoolean and clicks
- ' when the variable had not already the same value to simulate a relay click.
- if not aRelayName and aBoolean then
- aRelayName=true
- playSound "relayOn"
- elseIf aRelayName and not aBoolean then
- aRelayName=false
- playSound "relayOff"
- end if
- end sub
- 'the score motor (main game logics sequencing unit)
- '~~~~~~~~~~~~~~~
- sub scoreMotor_timer()
- ' This simulates the score motor,which is the central main command sequencing
- ' unit in an electro-mechanical Bally pinball machine.
- '
- ' The score motor consists of a motor that rotates a camshaft with multiple
- ' toothed wheels on it.Above each wheel is a stack of leaf switch contacts
- ' those get opened and or closed by the teeth(cams) when the wheels turn.
- ' The camshaft can only rotate a full turn, because only at its 0 position
- ' is a contact that can stop the motor when it has completed a job.
- '
- ' You can roughly compare the assembly with a mechanical wind-up music box
- ' clockwork,which is designed always to plays the melody to the end before
- ' the cylinder stops at fixed position. Like the tines on the cylinder play
- ' their tones in a fixed sequence, also the leaf switch stacks above the
- ' camshaft move in the same sequence no matter what the relays do.
- '
- ' A Bally-specific special feature of the score motor is that all cam wheels
- ' on the camshaft have 2 equal hemispheres with each the full set of cams,
- ' and therefore the capstan turns instead of full 360 degree rotations
- ' physically only 180 degree until it stops. Though what I modelled as a full
- ' logical 360 degree rotation would be technically only a half rotation. To
- ' make adaptions for other score motor brands easier,I despite devided each
- ' rotation into 360 degree.
- '
- '
- ' This subroutine is called by a timer again and again, and though can be
- ' imagined as the inner part of a for-loop with the running variable
- ' scoreMotorAngle. Like the score motor in a real pinball machine,it serves
- ' many different purposes and therefore contradicts the paradigms of modern
- ' software engineering. What it does depending on which boolean variables
- ' (equivalent to relays in a real pinball machine) are actually set.
- if scoreMotorAngle=0 then
- stopSound "motorHum1"
- playSound "motorHum1",-1
- '(re-)starts endless loop sample of Bally score motor hum
- end if
- processBallInTopHole
- processResetSequence
- processCollectBonus
- processEndGameMatching
- processAddCredits
- processAddScore
- 'Note: The order of the above commands is crucial for the correct behaviour
- ' of this emulation,because unlike with real in series wired relay
- ' switches, the if-statements of this BASIC programs can't recognize when
- ' the sequence of testing and setting a boolean variable is reversed.
- ' (The real circuit would behave rather like when all if-statements in the
- ' above routines would be continuously checked simultaneously. A circuit
- ' simulation program based on constraint logics could simulate this,but
- ' I doubt that this can be done in this poor BASIC,and it also would likely
- ' consume even more CPU capacity than this already quite CPU intensive
- ' piece of code takes.)
- scoreMotorAngle=scoreMotorAngle+motorStepWidth
- if scoreMotorAngle>359 then '360 degree shall not happen
- 'now check how to start the next camshaft round:
- if not keepMotorRunning then
- stopSound "motorHum1" 'stops motor rotation sound
- scoreMotor.enabled=false
- else
- keepMotorRunning=false
- end if
- 'The motor stops after one complete round of the camshaft so far the flag
- 'keepMotorRunning isn't set true. Otherwise the motor continues spinning the
- 'next cycle,but keepMotorRunning turns false by this,though the flag each time
- 'needs to be set true again to extend the motor spin time by another round.
- scoreMotorAngle=0
- end if
- end sub
- ' The following boolean functions simulate the cams(teeth) on the wheels rotated
- ' by the score motor. Depending on how many teeth a wheel has and where they
- ' are,its contacts are triggered at different times depending on the actual
- ' camshaft rotation angle. In this simulation the rotation angle is modelled by
- ' the scoreMotorAngle variable.
- '
- ' Note: Important is that the modulo values for the cams can indeed be reached
- ' with the current motorStepWidth setting. Otherwise the function will be
- ' triggered too seldomly or not at all. The reason why I don't use a single
- ' function here is because with multiple functions the exact tooth position
- ' of each wheel can be easier changed by hand. The actual ones are only
- ' guesses. Each function shall correspond to a wheel, but as well the names
- ' as the count of teeth per wheel were choosen by reason and have not
- ' neccessarily equivalents in Bally schematics. I test on
- ' (scoreMotorAngle mod x)=y instead on "...mod x)=0" because this way the
- ' 1st cam of each wheel is simulated slightly(by an angle of y) after the 0
- ' position,which sounds more realistic than when the 1st pulse always starts
- ' already at 0.
- '
- 'When a routine shall wait on a certain cam that may ocure only in the next camshaft
- 'round,then keepMotorRunning must be set true to prevent the motor from stopping
- 'before reaching it.(The real pinball circuit would use parallel wired switches for
- 'this.)
- 'multi-cam wheels: (good for counting)
- function is2StepsCamOn()
- is2StepsCamOn=(scoreMotorAngle=110 or scoreMotorAngle=290)
- 'This one must not come before the 2nd step of is4StepsCamOn() - otherwise
- 'the bonus ladder adds 1000 points to little during double bonus.
- end function
- function is3StepsCamOn()
- is3StepsCamOn=((scoreMotorAngle mod 40)=20 and scoreMotorAngle<140)
- end function
- function is4StepsCamOn()
- is4StepsCamOn=((scoreMotorAngle mod 90)=20 and scoreMotorAngle<=290)
- end function
- function is5StepsCamOn()
- is5StepsCamOn=((scoreMotorAngle mod 50)=20 and scoreMotorAngle<270)
- end function
- function is6StepsCamOn()
- is6StepsCamOn=((scoreMotorAngle mod 50)=20 and scoreMotorAngle>30)
- 'The position of these cams may be inaccurate.There should be 0.5s pause before
- 'the 1st and after the 6th cam.
- end function
- 'single-cam wheels: (good for sequencing)
- function isZeroCamOn()
- ' This is true when the motor stands still or is passing its default position.
- ' Note: To directly test on "scoreMotorAngle=0" in the code may be clearer.
- isZeroCamOn=(scoreMotorAngle=0)
- end function
- function isEarlyCamOn()
- isEarlyCamOn=(scoreMotorAngle=30)
- end function
- function isMiddleCamOn()
- isMiddleCamOn=(scoreMotorAngle=170)
- end function
- function isLateCamOn()
- ' This cam must come later than all others to work correctly.
- isLateCamOn=(scoreMotorAngle=350)
- end function
- function isCamBeforeEnd()
- ' This means that the camshaft is about to reach the 0 position again (where the
- ' score motor will stop when keepMotorRunning=false).Due to after the motor timer
- ' stands still any procedures of the score motor can not be executed anymore until
- ' the motor is started again,this function must be used instead of isZeroCamOn()
- ' to do a job that is intended to be completed when the motor reaches 0 again.
- '
- ' Note: The value of this function may overleap with isLateCamOn().
- isCamBeforeEnd=((scoreMotorAngle+motorStepWidth)>359)
- end function
- ' The following procedures are repeatedly called by the scoreMotor_timer()
- ' rountine and can be imagined as the inner part of a for-loop that has
- ' the running variable scoreMotorAngle.What they do depends on which camshaft
- ' cam is active at the moment, and this depends on the value of scoreMotorAngle.
- sub processAddScore()
- ' This adds points to the score using the score motor. In a real Bally pinball
- ' machine this is neccessary when digits need to be flipped multiple times to
- ' score other point amounts than 10,100,1000 and so on.
- if add500 and is5StepsCamOn then addScore(100)
- if add3000 and is3StepsCamOn then addScore(1000)
- ' These relays are set by the addScore...() procedures,because on a real machine
- ' they can only be set when the score motor is in 0 position. Though a target
- ' only scores points when the motor is not running, because when the relay would
- ' be set in between, less points than the value of the target would be added
- ' depending on the actual score motor position.
- '
- ' For emulating other pinball machines,relays with other values could be added
- ' here.
- if isLateCamOn then
- 'At the end of a camshaft round the score relays have to be reset to prevent
- 'endless counting:
- setRelay add500 ,false
- setRelay add3000,false
- end if
- end sub
- sub processAddCredits()
- ' This adds credits using the score motor. Important is that new credits must only
- ' be added to unaddedCredits when scoreMotorAngle=0.Otherwise it would count wrong.
- '
- ' This routine must be called after processResetSequence() and processCollectBonus()
- ' to set the coin rejector correctly.
- if isZeroCamOn then setCoinLockOut true 'sets creditsAddable=false
- 'New credits can only be added when the score motor camshaft has rotated
- 'entirely to its end(i.e. the motor will stop now so far keepMotorRunning
- '= false) and the credit counter is not occupied with counting old credits
- 'anymore.
- if unaddedCredits>0 then
- 'First I had used different cam wheels for adding different number of credits,
- 'but Frank Menzler told me that credits are always added by a 6 cam wheel of
- 'the score motor.But I don't know how it works in detail that the number of
- 'credits per coin can be adjusted for different coin chutes.
- '
- 'Possibly it uses boolean comparisons among multiple cam wheel switch outputs
- 'to generate an adjustable count of pulses.I just remember that a Bally "Champ"
- 'had tons of jumper plugs to set the credits per coin,and that in some odd
- 'positions (or by bent relay contacts?) it sometimes gave something like 13
- 'credits per coin.
- '
- 'Frank Menzler claimed that the maximum number of credits per coin would be
- '12 because this would be a full physical rotation of the camshaft (i.e. 2
- 'logical rotations in my model).I don't know if for adding more than 6 credits
- 'an additional relay was set or if there was a special cam wheel that marks
- 'by a long cam the current hemisphere of the otherwise perfectly symmetrical
- 'Bally camshaft.
- '
- 'I just simulated the credit counter behaviour phenomenologically by adding
- '1 credit during each cam of the 6 cam wheel and counting down unaddedCredits
- 'to memorize the number of credits to count,but this has likely nothing to
- 'do with the inner working of the real machine.
- if is6StepsCamOn then
- addCredit
- unaddedCredits=unaddedCredits-1
- if unaddedCredits=0 then saveGame 'save new credit counter value
- end if
- if isLateCamOn and unaddedCredits>0 then keepMotorRunning=true
- 'for adding the remaining credits
- end if
- if isCamBeforeEnd and unaddedCredits=0 and not gameIsResetting then
- knockerActive=true 'when it was disabled by inserting a coin
- if not(overPayBlocksChutes and credits=maxCredits) then setCoinLockOut false
- end if
- end sub
- sub processBallInTopHole()
- ' This routine adds 3000 points and ejects the ball from the tophole.
- ' This must be called before processAddScore() to work correctly.
- if not ballInTopHole then exit sub
- if scoreMotorAngle=0 then
- setRelay add3000,true
- 'The relay must only be set at angle 0 to count the correct amount of points.
- else
- keepMotorRunning=true
- end if
- if add3000 and isLateCamOn then
- 'The test for add3000 prevents that the ball will be kicked out without
- 'points when the motor is still running from previous activation.
- playSound SoundfXDOF("Saucer",110,DOFPulse,DOFContactors)
- DOF 111, DOFPulse
- topHoleKicker.Kick (85+(120*rnd())),8
- setRelay ballInTopHole,false
- end if
- end sub
- sub processEndGameMatching()
- ' This simulates the end digit matching feature with that players can win
- ' free credits depending on the last score digits.
- '
- ' I know very little how this works internally. Though this is rather a dummy
- ' than anything emulated. The only thing I know is that a state wheel is
- ' stepped by various events to simulate a random number generator.
- '
- ' This procedure must be called before processAddCredits to work correctly.
- if not digitMatchEnabled or gameIsResetting then exit sub
- dim i,n
- if matching then
- if creditsAddable then
- setEndMatchLights(matchNumber)
- n=0
- for i=1 to players
- if scoreDigit(i,matchedScoreDigit)=matchNumber then n=n+1
- next
- addCredits(n)
- setRelay matching,false
- else
- keepMotorRunning=true
- end if
- else
- if isEarlyCamOn and not gameOver then advanceMatcher
- 'just a guess - I don't know in which situations the random number state wheel
- 'should advance.
- end if
- end sub
- sub processResetSequence()
- ' The following is an attempt to emulate the start-up sequence of a mechanical
- ' Bally pinball machine. I don't know how exact my emulation is.My only source of
- ' information about the reset sequence is based on the great pinball repair site
- ' at http://marvin3m.com and some newsgroup articles.
- '
- ' This procedure must be called before processCollectBonus() to work correctly.
- '
- ' Important:
- '
- ' This is a BALLY reset sequence.EM pinball machines of other manufacturers
- ' process their reset commands in different orders,though if you want to
- ' faithfully emulate machines of other companies(Gottlieb,Chicago Coins etc.),
- ' then the below code must be modified to correctly model their behaviour.
- if not gameIsResetting then exit sub
- dim i
- if gameOver then
- keepMotorRunning=true
- if isZeroCamOn and not isScoreResetted then
- for i=1 to maxPlayers
- scoreDigit(i,reelsPerCounter)=0
- if extraDigitsEnabled then scoreReelMain(i,reelsPerCounter).text=""
- next
- 'this clears higher surplus digits
- end if
- if is5StepsCamOn then
- if not isScoreResetted then
- for i=1 to maxPlayers
- unaddedScore(i)=scoreResetNumber
- scoreAddTimer(i).enabled=true
- next
- '(the reset relay noise is part of the counter clicks)
- else
- playSoundMonophonic "resetClick10Relay"
- 'let the score reset relay continue clicking
- end if
- end if
- 'The score reset mechanism advances all reels of all counter until they
- 'have reached 0. When gameIsResetting is true, the counters don't step
- 'further than 0, and also carries are ignored to speed up resetting.
- '
- 'A real pinball machine does this the same way,and each reel has a switch
- 'in its 0 position,though after all reels are at 0, a signal current can
- 'flow through all the switches to continue the reset sequence.
- '
- 'I don't know if it is correct that I advance all reels of all counters
- 'simultaneously.Possibly a real pinball machines devided them into multiple
- 'groups those are advanced alternatingly (to limit the electric peak current
- 'that would be likely quite high when about 20 solenoid coils switch simul-
- 'taneously?) Also my "Royal Flush" PC game advances the digits in groups
- 'instead,but possibly this is just Gottlieb specific.
- if isScoreResetted then 'after all digits reached 0
- if isBallCounterResetted and isPlayerCounterResetted and isPlayerUpResetted then
- 'Here the machine waits until all state wheels are resetted and that the
- 'ball has returned to the outhole.When it is still in the firing chute,the
- 'machine starts the new game without the need of ejecting a ball.
- '(The nested if-statement keeps the CPU load low in this case,because star-
- 'ting the state wheel reset timers unneccessary wasted much CPU time.)
- if isMiddleCamOn and bonus=0 then
- 'after bonus ladder "resetted" itself by counting down...
- if creditTaken then decrementCredit 'also clears creditTaken relay
- setRelay gameOver,false
- gameOverText.Text = "" 'counters are resetted now.
- If B2SOn Then
- Controller.B2SSetGameOver 35,0
- Controller.B2SSetMatch 34,0
- End If
- displayHighScore 'removes "new" after a previous highscore
- setEndMatchLights -1 'clears end match number display
- setBackboxLights true
- playfieldLightsEnabled=true
- resetLights '(Here the playfield lights get updated.)
- Dim Fuckingcode:fuckingcode=0
- If B2SOn Then
- Controller.B2ssetscore 1, fuckingcode
- Controller.B2ssetscore 2, fuckingcode
- Controller.B2ssetscore 3, fuckingcode
- Controller.B2ssetscore 4, fuckingcode
- End If
- 'The game will now be started by processCollectBonus() because the
- 'gameOver relay was cleared.
- end if
- elseIf isEarlyCamOn then
- 'This resets the state wheels on those we waited above.
- ' setBackboxLights true 'TEST!!! this shows the state wheel resets
- playerCounterReset.enabled=true
- playerUpReset.enabled =true
- ballCounterReset.enabled =true
- end if
- end if
- else
- 'When we are here,after a successful reset the gameOver relay was cleared...
- keepMotorRunning=true
- 'This waits until processCollectBonus() can eject the ball.
- end if
- end sub
- sub processCollectBonus()
- ' This routine is started by the score motor.It steps down the bonus ladder and
- ' adds the bonus to the score so far there is no tilt.
- if not (ballInDrain or gameIsResetting) then exit sub
- 'Reset must count down the bonus even without ball in drain.
- if bonus>0 and not newRound then
- keepMotorRunning=true
- select case ruleSet
- case RS_authentic
- if (not doubleBonus and is2StepsCamOn) or (doubleBonus and is4StepsCamOn) then addScore bonusStepWidth
- if is2StepsCamOn then decrementBonus
- 'This makes the bonus step down with a fixed speed and during double bonus
- 'adds twice as fast const bonusStepWidth to the score.(In "Boomerang"
- 'bonusStepWidth=1000 points.)
- case RS_enhanced
- if is4StepsCamOn then addScore bonusStepWidth
- if (doubleBonus and is2StepsCamOn) or (not doubleBonus and is4StepsCamOn) then decrementBonus
- 'In the enhanced mode I use the old version (counts faster,sounds better).
- end select
- 'is4StepsCamOn comes not before the 2nd step of is4StepsCamOn() - otherwise
- 'we would empty the bonus counter to soon and though add 1x bonusStepWidth to
- 'little(?) during double bonus.
- '
- 'In an old version of this program I added 4x bonusStepWidth per camshaft
- 'round,because otherwise the bonus ladder descended very slowly,but I was
- 'told that the bonus must count faster and not longer during double bonus,
- 'therefore I modified this for the authentic mode.
- '
- 'Is my new speed and working principle assumption more correct?
- else
- if not gameOver then startNextRound
- end if
- end sub
- sub startNextRound()
- ' This routine is called after bonus collecting was finished.
- ' It checks if we have game-over or how the game continues. This routine is
- ' also called at game start by processCollectBonus().
- if isMiddleCamOn and not newRound then
- if not extraBallWon and not gameIsResetting then
- 'Not gameIsResetting prevents mess when the pinball machine comes up and
- 'the ball is still in the firing chute during reset. (E.g. after overpaying
- 'the 1st coin chute.)
- setShootAgain false
- nextPlayer 'this can set game over
- end if
- setExtraBall false
- setRelay specialHit,false
- if gameOver then exit sub
- 'The game over relay would prevent the rest of this procedure.
- if not shootAgainStaysLit then setShootAgain false '("shoot again" lamps off)
- if not ruleSet=RS_authentic then
- spinnerReset.enabled=true
- else
- if spins=0 then spins=1
- 'In authentic mode the spinner never resets,but it can not have the
- 'value 0.
- end if
- unTilt 'also activates flippers & bumpers
- tiltHasEffect=true
- setPlayfieldLights true
- setIllumination true
- resetLights
- if initBonusIs1000 then incrementBonus
- ' setScore 1,96000 'TEST!!!
- ' bonus=13000 'TEST!!!
- ' doubleBonus=true 'TEST!!!
- ' updatePlayfieldLights 'TEST!!!
- setRelay newRound,true
- 'This relay stays true until points are scored. Though as long no points
- 'are scored in a round,the ball will be only re-ejected without collecting
- 'bonus or changing any lights on the playfield. This is known as the
- 'famous "fairplay interlock" feature.It can be disabled by setting const
- 'restartWhen0Pts=false.
- '
- 'Note: The behaviour that even a tilt voids no ball in the above state is
- ' realistic, because it was invented to prevent the loose of 2 balls
- ' when by too much nudging the freshly ejected ball plummeted back into
- ' the eject hole without scoring a single point.
- '
- ' By setting const tiltVoids0PtsRestart=true the behaviour at tilt can
- ' be changed.
- end if
- if isLateCamOn and newRound and (isSpinnerResetted or ruleSet=RS_authentic) then
- '(newRound was set above...)
- setRelay gameIsResetting,false 'this is a guess to exit reset loop
- reelChimeEnabled=true
- releaseStuckScoreReels 'so far any are stuck
- ejectBall 'so far in outhole
- if not restartWhen0Pts then setRelay newRound,false 'fairplay interlock
- end if
- end sub
- sub nextPlayer()
- ' This switches to the next player or does game over depending on how many
- ' balls are left and players in the game.
- if currentPlayer>=players then
- incrementBallCounter
- if (ruleSet=RS_authentic and ballCount>ballsPerGame0) or (ruleSet=RS_enhanced and ballCount>ballsPerGame1) then
- doGameOver
- if digitMatchEnabled then
- setRelay matching,true 'starts end digit matching
- keepMotorRunning=true
- end if
- exit sub
- end if
- end if
- nextPlayerUp 'This increments currentPlayer or sets it 1 when >players.
- end sub
- sub doGameOver()
- setRelay gameOver,true
- gameOverText.text = "GAME OVER"
- If B2SOn Then
- Controller.B2SSetGameOver 35,1
- Controller.B2SSetBallInPlay 32, 0
- End If
- setPlayfieldLights false
- tiltHasEffect=false
- if tilt then untilt 'clears tilt sign
- setIllumination true
- setPlayfieldLights false
- lightCenterBumper false
- setShootAgain false 'so far a running game was canceled by creditButtonRoutine()
- setBackboxLights false
- setBumpers false
- setFlippers false
- releaseStuckScoreReels 'so far any are stuck
- if digitMatchEnabled then setEndMatchLights(matchNumber)
- If B2SOn Then Controller.B2SSetMatch 34,matchnumber
- updateHighscore
- saveGame 'saving highscore etc.
- rulesSwitchable=true
- end sub
- ' The following routines simulate the way a mechanical Bally pinball machine
- ' adds score amounts those multiple times advance digits using the score
- ' motor. Note that these routines only add score when the camshaft is in its 0
- ' position (i.e. e.g. when it is not turning).
- sub addScore500()
- if scoreMotorAngle=0 then setRelay add500,true
- scoreMotor.enabled=true 'rest does the score motor
- end sub
- sub addScore3000()
- if scoreMotorAngle=0 then setRelay add3000,true
- scoreMotor.enabled=true 'rest does the score motor
- end sub
- 'the reel-a-listic(tm) ;-) score counter assembly:
- '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 'This code simulates a great looking,backlighted mechanical reel score counter
- 'assembly;the reels can even get stuck and hum when a ball lies on a playfield
- 'switch. When the counter flows over,the missing score digits don't get lost but
- 'are displayed outside the reel counter using lamp-like looking extra digits(so
- 'far extraDigitsEnabled is set true).The extra digits can display even multi-
- 'digit overflow values - the digit count of them is only limited by the used
- 'textBox width.
- sub initializeScoreCounters()
- ' This initializer must be called at program start before the score counter can
- ' be used.
- set scoreReelTop(1,0)=scoreTopP1D0
- set scoreReelTop(1,1)=scoreTopP1D1
- set scoreReelTop(1,2)=scoreTopP1D2
- set scoreReelTop(1,3)=scoreTopP1D3
- set scoreReelTop(1,4)=scoreTopP1D4
- set scoreReelMain(1,0)=scoreMidP1D0
- set scoreReelMain(1,1)=scoreMidP1D1
- set scoreReelMain(1,2)=scoreMidP1D2
- set scoreReelMain(1,3)=scoreMidP1D3
- set scoreReelMain(1,4)=scoreMidP1D4
- set scoreReelMain(1,5)=scoreP1Extra
- set scoreReelLow(1,0)=scoreLowP1D0
- set scoreReelLow(1,1)=scoreLowP1D1
- set scoreReelLow(1,2)=scoreLowP1D2
- set scoreReelLow(1,3)=scoreLowP1D3
- set scoreReelLow(1,4)=scoreLowP1D4
- set scoreReelBorder(1,0)=scoreClr0P1
- set scoreReelBorder(1,1)=scoreClr1P1
- set scoreReelTop(2,0)=scoreTopP2D0
- set scoreReelTop(2,1)=scoreTopP2D1
- set scoreReelTop(2,2)=scoreTopP2D2
- set scoreReelTop(2,3)=scoreTopP2D3
- set scoreReelTop(2,4)=scoreTopP2D4
- set scoreReelMain(2,0)=scoreMidP2D0
- set scoreReelMain(2,1)=scoreMidP2D1
- set scoreReelMain(2,2)=scoreMidP2D2
- set scoreReelMain(2,3)=scoreMidP2D3
- set scoreReelMain(2,4)=scoreMidP2D4
- set scoreReelMain(2,5)=scoreP2Extra
- set scoreReelLow(2,0)=scoreLowP2D0
- set scoreReelLow(2,1)=scoreLowP2D1
- set scoreReelLow(2,2)=scoreLowP2D2
- set scoreReelLow(2,3)=scoreLowP2D3
- set scoreReelLow(2,4)=scoreLowP2D4
- set scoreReelBorder(2,0)=scoreClr0P2
- set scoreReelBorder(2,1)=scoreClr1P2
- set scoreReelTop(3,0)=scoreTopP3D0
- set scoreReelTop(3,1)=scoreTopP3D1
- set scoreReelTop(3,2)=scoreTopP3D2
- set scoreReelTop(3,3)=scoreTopP3D3
- set scoreReelTop(3,4)=scoreTopP3D4
- set scoreReelMain(3,0)=scoreMidP3D0
- set scoreReelMain(3,1)=scoreMidP3D1
- set scoreReelMain(3,2)=scoreMidP3D2
- set scoreReelMain(3,3)=scoreMidP3D3
- set scoreReelMain(3,4)=scoreMidP3D4
- set scoreReelMain(3,5)=scoreP3Extra
- set scoreReelLow(3,0)=scoreLowP3D0
- set scoreReelLow(3,1)=scoreLowP3D1
- set scoreReelLow(3,2)=scoreLowP3D2
- set scoreReelLow(3,3)=scoreLowP3D3
- set scoreReelLow(3,4)=scoreLowP3D4
- set scoreReelBorder(3,0)=scoreClr0P3
- set scoreReelBorder(3,1)=scoreClr1P3
- set scoreReelTop(4,0)=scoreTopP4D0
- set scoreReelTop(4,1)=scoreTopP4D1
- set scoreReelTop(4,2)=scoreTopP4D2
- set scoreReelTop(4,3)=scoreTopP4D3
- set scoreReelTop(4,4)=scoreTopP4D4
- set scoreReelMain(4,0)=scoreMidP4D0
- set scoreReelMain(4,1)=scoreMidP4D1
- set scoreReelMain(4,2)=scoreMidP4D2
- set scoreReelMain(4,3)=scoreMidP4D3
- set scoreReelMain(4,4)=scoreMidP4D4
- set scoreReelMain(4,5)=scoreP4Extra
- set scoreReelLow(4,0)=scoreLowP4D0
- set scoreReelLow(4,1)=scoreLowP4D1
- set scoreReelLow(4,2)=scoreLowP4D2
- set scoreReelLow(4,3)=scoreLowP4D3
- set scoreReelLow(4,4)=scoreLowP4D4
- set scoreReelBorder(4,0)=scoreClr0P4
- set scoreReelBorder(4,1)=scoreClr1P4
- set scoreAddTimer(1)=scoreAddTimer1
- set scoreAddTimer(2)=scoreAddTimer2
- set scoreAddTimer(3)=scoreAddTimer3
- set scoreAddTimer(4)=scoreAddTimer4
- set scoreSpinTimer(1)=scoreSpinTimer1
- set scoreSpinTimer(2)=scoreSpinTimer2
- set scoreSpinTimer(3)=scoreSpinTimer3
- set scoreSpinTimer(4)=scoreSpinTimer4
- dim i,j
- for i=0 to maxScoreReelNoises
- scoreReelNoise(i)=false
- next
- scoreReelNoise(0)="resetClick10Relay"
- scoreReelNoise(1)="resetClick9"
- scoreReelNoise(2)="resetClick8"
- scoreReelNoise(3)="resetClick7"
- scoreReelNoise(4)="resetClick6"
- scoreReelNoise(5)="resetClick5"
- scoreReelNoise(6)="resetClick4"
- scoreReelNoise(7)="resetClick3"
- scoreReelNoise(8)="resetClick2"
- scoreReelNoise(9)="resetClick1"
- 'These samples shall simulate the click of n simultaneous score counter reels.
- '
- 'Note: These are just samples taken from the n-th counter click of a Bally
- ' "Fireball" reset sequence.I don't know how many counters really turned
- ' at each click.
- for i=0 to reelsPerCounter
- scoreReelChime(i)=false
- next
- scoreReelChime(1)="bell10"
- scoreReelChime(2)="bell100"
- scoreReelChime(3)="bell1000"
- 'Each counter digit can have a chime sound that is played whenever the counter
- 'reel of the digit advances. The chime sound can be disabled by setting
- 'reelChimeEnabled=false. When only for certain digits chime sounds exist,the
- 'remaining array indices of scoreReelChime() must be set false.
- '
- 'Note: This chime emulation needs a little extra CPU time and it may sound
- ' less accurate than a simulation by a long sample of multiple clangs
- ' that is directly started each time a ball hits a trigger. But as a
- ' benefit the number of clangs with the emulation allways exactly matches
- ' the earned score.
- if chimeForExtraDigits then scoreReelChime(reelsPerCounter)="gong"
- 'This sound is played when an extra digit advances by a reel counter overflow.
- for i=minCounterNr to maxPlayers
- scoreAddTimer(i).interval=reelCarryTime
- reelTime(i)=reelSpinTime
- for j=0 to reelsPerCounter 'the last element contains extra digits
- scoreDigit (i,j)=0
- stuckScoreReel(i,j)=0
- next
- next
- scoreResetNumber=0
- for i=0 to reelsPerCounter-1
- scoreResetNumber=scoreResetNumber+(10^i)
- 'Generates a number of reelsPerCounter times "1" (like 11111) which is used
- 'in the counter reset emulation.
- next
- reelChimeEnabled=true
- end sub
- sub addScore(points)
- ' This routine adds points to the score counter of the current player.
- '
- ' (This behaviour reflects quite well the hardware architecture of the
- ' mechanical Bally pinball machine, which can add points also only to the
- ' currently selected player.)
- '
- ' Important:
- ' This routine can basically add any amount of points at any time. A real
- ' mechanical pinball machine counter can only add 1 to any digit and though
- ' can only add point values consisting of 1 and 0 (e.g. 100,1000,110 etc.),
- ' though it needs the score motor to add other values. To emulate the behaviour
- ' correct, therefore routines in the style of addScore500() and addScore3000()
- ' (see there) should be used to get realistic timing. (E.g. while these are
- ' counting,another hit of a target with the same value has no effect.)
- setRelay newRound,false
- 'This relay stays true until points are scored. Though as long no points
- 'are scored in a round,the ball will be only re-ejected without collecting
- 'bonus or changing any lights on the playfield. This is known as the
- 'famous "fairplay interlock" feature.
- if shootAgain and not extraBallWon then setShootAgain false
- 'When we had an extra ball and the next round has begun,the extraBallWon relay
- 'has been cleared,but the shootAgain relay (and though lamps) stay on until
- 'points are scored. (When shootAgainStaysLit=false,it will have been cleared
- 'already when the new round started.)
- if not gameOver and not tilt and currentPlayer>0 then addScoreToPlayer currentPlayer,points
- end sub
- dim ascore
- sub addScoreToPlayer(aPlayer,points)
- ' This adds points to the score counter of a Player.
- if lossyScoreCounter then unaddedScore(aPlayer)=0
- 'A real mechanical score counter can't memorize multiple scores to
- 'add,though when there is too little time left between score events,
- 'the counter skips counting the rest of the previous score.
- '
- 'This is an approximation of this behaviour. (In reality it would
- 'possibly affect only certain digits and digit values.)
- unaddedScore(aPlayer)=unaddedScore(aPlayer)+points
- If B2SOn Then Controller.B2ssetscore (aplayer), getscore(aplayer)+unaddedscore(aplayer)
- scoreAddTimer(aPlayer).enabled=true
- 'The digits of unaddedScore are counted down while the counter adds them
- 'to the score.
- end sub
- sub addScoreDigitStuck(aDigit)
- ' This advances a score digit reel by 1 and locks it until releaseScoreDigit()
- ' for that digit will be called.
- '
- ' Call this procedure when a ball hits a rollover trigger that has a fixed score
- ' value that advances a digit by 1 directly (without the score motor).Call
- ' releaseScoreDigit() for the digit in the _unhit() procedure of the same target
- ' to enable the digit again when the ball leaves the switch.
- ' Important is that the currentPlayer must not be changed so long a digit is
- ' stuck. After a different player is up, releaseStuckScoreReels() should be
- ' called to free any stuck digits (setCurrentPlayer() does this).
- '
- ' (Internally a counting semaphore memorizes how often this procedure is called,
- ' therefore to release the digit,releaseScoreDigit() must be called for it
- ' the same number of times.)
- setRelay newRound,false
- if shootAgain and not extraBallWon then setShootAgain false
- if not gameOver and not tilt and currentPlayer>0 then
- incrementScoreDigit currentPlayer,aDigit,true
- end if
- end sub
- sub releaseScoreDigit(aDigit)
- ' This frees a score digit that was stuck with addScoreDigitStuck().
- releaseScoreReel currentPlayer,aDigit
- end sub
- sub addScoreDigitStuckBySema(aDigit,aSema)
- ' This does the above but additionally uses an external semaphore.
- '
- ' This routine should be used for rollover triggers those score value can
- ' change(e.g. become 0) during the game,because releaseScoreDigit() must
- ' only be called for a digit that was previously stuck by the same rollover
- ' trigger to get realistic behaviour in multi-ball games. Each such rollover
- ' trigger must use an own semaphore (global variable initialized with 0) per
- ' involved digit to work correctly (i.e. use an array when the rollover can
- ' stick different digits depending on its actual score value.)
- if aSema<0 then aSema=0 'would be out of range
- if aSema=0 then addScoreDigitStuck(aDigit)
- aSema=aSema+1
- 'We use here a counting semaphore,because Visual Pinball can(according to the
- 'docs) call the _hit() procedure of a switch multiple times before calling
- '_unhit() when multiple balls touch the same switch simultaneously.
- end sub
- sub releaseScoreDigitBySema(aDigit,aSema)
- ' This does the above but additionally uses an external semaphore
- ' (see addScoreDigitStuckBySema() ).
- if aSema=1 then releaseScoreDigit(aDigit) 'only when sema switches from 1 to 0
- if aSema>=1 then aSema=aSema-1
- end sub
- sub scoreAddTimer1_timer()
- scoreAddTimerRoutine 1
- end sub
- sub scoreAddTimer2_timer()
- scoreAddTimerRoutine 2
- end sub
- sub scoreAddTimer3_timer()
- scoreAddTimerRoutine 3
- end sub
- sub scoreAddTimer4_timer()
- scoreAddTimerRoutine 4
- end sub
- sub scoreAddTimerRoutine(aPlayer)
- ' To add some points to the score counter of a player, the points first need
- ' to be added to the variable unaddedScore(aPlayer) and then the timer
- ' scoreAddTimer(aPlayer) for the player must be enabled to start counting, which
- ' is done in addScore().
- '
- ' This reel counter simulation code is capable to run multiple score displays at
- ' the same time, and they even have independent timers, which enables them to
- ' rotate (and though click) out of phase to each other when started this way.
- '
- ' Notes:
- ' To implement a mechanical highscore display (like in "Kewpie Doll"),the unused
- ' player 0 postion in the arrays could be used.
- ' This routine can not count backwards,because this would need a completely
- ' different carry processing logics. But so far I know,no mechanical pinball
- ' machine uses countdown counters, and also the normal Bally counters could
- ' not do this. If anybody needs such counters, it would certainly be possible to
- ' re-write this code with a backward stepping routine, but it would take likely
- ' twice as much effort.
- if unaddedScore(aPlayer)=0 then
- scoreAddTimer(aPlayer).enabled=false
- scoreAddTimer(aPlayer).interval=reelCarryTime 'makes counter start faster
- scoreIsCounting(aPlayer)=false
- else
- scoreAddTimer(aPlayer).interval=reelSpinTime
- scoreIsCounting(aPlayer)=true
- end if
- 'The scoreAddTimer is not delayed by the carry routine,but it always expects
- 'that the carry routine will be faster. This theoretically might make trouble
- 'on stupidly slow PCs or when the reelCarryTime would be set ridicoulously slow,
- 'but it has the benefit that the counter clicks much more rhythmical this way
- 'and especially the reel chimes sound way better because they don't get out of
- 'sync by the varying reelSpinTime speed.
- dim i,t
- if extraDigitsEnabled then t=reelsPerCounter else t=reelsPerCounter-1
- for i=0 to t
- if getDigit(unaddedScore(aPlayer),i)>0 then
- unaddedScore(aPlayer)=unaddedScore(aPlayer)-(10^i)
- incrementScoreDigit aPlayer,i,false
- end if
- next
- 'This decrements each non-zero digit of unaddedScore(aPlayer) by 1 while it adds the
- '1 to the digit of the score counter.
- end sub
- sub incrementScoreDigit(aPlayer,digit,isStuck)
- ' This increments a score counter reel(a digit) of the current player by 1 and
- ' displays it halfway rotated to its next value. The digits are numbered from
- ' right to left starting with 0.
- '
- ' The trick how this works was discovered by PeBo.It is based on the use of
- ' 3 vertically overleaping text boxes per score display digit. To draw a
- ' counter reel in a halfway rotated position,the old cipher is drawn in the
- ' top text box while the coming cipher is drawn in the lower text box.(The
- ' cipher in the middle text box is erased by overdrawing from the other
- ' text boxes.) To get rid of the top half of the top cipher and the bottom
- ' half of the bottom cipher,2 bar-shaped empty textboxes are drawn over them.
- ' (Printing "" into a textbox displays nothing but overwrites the contents
- ' of other text boxes with blanks at the overleaped area.) Then the
- ' scoreSpinTimer is started,which after a fraction of a second clears the top
- ' and the bottom text box and instead writes the new cipher into the middle
- ' ("main") text box.
- '
- ' To make this counter work correctly,it is crucial that the drawing order of
- ' the overleaping textboxes (changable with "draw in front","draw in back") in
- ' the editor must be the same as intended in the game;otherwise the overdraw-
- ' ing trick fails. The layers should be (from back to front):
- '
- ' 1.counter reel decale,frame decale and backlight (order is not that important)
- ' 2.top and bottom cipher text boxes
- ' 3.main cipher text boxes
- ' 4.upper and lower clearing bar text boxes
- '
- ' Note: The way how the counter reels jerk into an intermediate position and
- ' then further to the next cipher look not that unrealistic. In a real
- ' mechanical pinball counter the reels also don't rotate smoothly,
- ' because they are not turned by a motor,but instead a coil quickly
- ' pulls each reel quickly into the intermediate position by a short
- ' current pulse, and when the pulse ends, a spring makes the reel flip
- ' further to its next stable position.
- '
- ' When the boolean isStuck is set true,the reel will stay in this position and
- ' refuse any further counting until releaseScoreReel() for this digit will be
- ' called to simulate a blocked solenoid like e.g. when a ball laying on a
- ' playfield overroll switch. This is implemented with counting semaphores,
- ' therefore when incrementScoreDigit(...,...,true) is called multiple times,
- ' releaseScoreReel() must be called the same times to enable it again.The
- ' purpose of this is to simulate multiple switches wired in series those e.g.
- ' in a multi-ball game could be blocked by multiple balls simultaneously.
- '
- ' To simulate the counter backlight,all the mentioned textboxes are transparent
- ' and behind them is rectangular lamp. Due to lights in visual pinball look
- ' extremely dark when they are off, between the lamp and the textboxes there
- ' is a white decal,of that every 2nd pixel is black;due to black was set to
- ' its transparent colour, though the light of the lamp can shine trough it like
- ' daylight through a white gaze curtain. (Also see lightCenterBumper().)
- '
- ' Due to a Visual Pinball bug the background of text boxes gets not updated when
- ' a lamp behind it is switched on or off,therefore the routine
- ' updateScoreDisplays() must be called every time these lamps are switched.
- if isStuck then
- stuckScoreReel(aPlayer,digit)=stuckScoreReel(aPlayer,digit)+1
- if stuckScoreReel(aPlayer,digit)=1 then
- if stuckReelsBuzz=0 then playSound "reelStuckBuzz",-1
- 'starts endless loop of solenoid buzz
- stuckReelsBuzz=stuckReelsBuzz+1
- end if
- end if
- if not(stuckScoreReel(aPlayer,digit)=0 or isStuck and stuckScoreReel(aPlayer,digit)=1) then exit sub
- 'This executes the rest of this procedure only when the stuckScoreReel()
- 'semaphore of a digit is 0 or when it is set to 1 the first time.
- if gameIsResetting and scoreDigit(aPlayer,digit)=0 then exit sub
- 'During reset any reels rotate only until 0.
- scoreIsCounting(aPlayer)=true
- 'This flag signals that the counter is still running. As long it is true,
- 'the game should not be switched to a different player.
- '(A real mechanical pinball machine would count wrong when the player is
- 'switched at the wrong moment. My algorithm may prevent this,but I am not
- 'sure if it works reliable,nor would this be realistic.)
- dim top
- top=scoreDigit(aPlayer,digit)
- scoreDigit(aPlayer,digit)=scoreDigit(aPlayer,digit)+1
- if digit<reelsPerCounter then 'when not an extra digit,display it as rotated reel
- if scoreDigit(aPlayer,digit)=10 then
- scoreDigit(aPlayer,digit)=0
- top=9
- if not gameIsResetting then carry(aPlayer,digit)=1
- 'sets a carry for the next digit
- end if
- scoreReelLow(aPlayer,digit).text=scoreDigit(aPlayer,digit) 'print bottom cipher
- scoreReelBorder(aPlayer,1).text ="" 'clear its lower half
- scoreReelTop(aPlayer,digit).text=top 'print top cipher
- scoreReelBorder(aPlayer,0).text ="" 'clear its upper half
- if isStuck then playSound "reelStuck2" else playReelClick
- 'This is the mechanical counter click for a digit,that is played always
- 'when the digit switches.
- end if
- if reelChimeEnabled and scoreReelChime(digit)<>false and aPlayer>0 then
- 'aPlayer=0 can only be a highscore counter that shall remain mute.
- if not(reelChimeForCarry=false and digit>0 and carry(aPlayer,max(digit-1,0))=1) or digit=reelsPerCounter then
- 'so far this digit was not flipped by a carry and reelChimeForCarry=false
- 'or when this is an extra digit.
- if digit = 5 Then
- playSoundMonophonic SoundFXDOF(scoreReelChime(digit),145,DOFPulse,DOFBell)
- else
- playSoundMonophonic SoundFXDOF(scoreReelChime(digit),140 + digit,DOFPulse,DOFChimes)
- end if
- 'This is an optional chime sound for each digit,that can be disabled e.g. when
- 'the counter is in reset mode.
- '
- 'In a real mechanical pinball machine it depends on how the chimes are wired,
- 'whether they sound also by carry. When they are directly connected with the
- 'counter coils, they unavoidably will. When they are instead connected with a
- 'score motor switch or relay that steps the counter forward, then they won't
- 'notice carries and though sound only when the score reel advances by adding
- 'points. The constant reelChimeForCarry makes both behaviours selectable.
- '
- 'The chime for extra digits(digit=reelsPerCounter) always reacts on carries,
- 'because in a proper designed simulation they could anyway only be trigger by
- 'the "carry" from an overflowing score counter,though otherwise it would
- 'sound never.
- end if
- end if
- scoreSpinTimer(aPlayer).interval=reelTime(aPlayer)
- scoreSpinTimer(aPlayer).enabled=true 'the rest does spinScoreReels()
- end sub
- sub scoreSpinTimer1_timer()
- spinScoreReels 1
- end sub
- sub scoreSpinTimer2_timer()
- spinScoreReels 2
- end sub
- sub scoreSpinTimer3_timer()
- spinScoreReels 3
- end sub
- sub scoreSpinTimer4_timer()
- spinScoreReels 4
- end sub
- sub spinScoreReels(aPlayer)
- ' This is called by the scoreSpinTimers completes rotation of the reels to their
- ' stable position.This routine is also called by setScore() to update the
- ' display.
- '
- ' Note: Actually all digits are flipped simultaneously when this counter gets
- ' active. This in some cases can look unrealistic.Better would be the use
- ' of an agenda (time job list) in that all reels write their intended
- ' flip time to flip independently by using a timer that repeatedly
- ' executes the next pending job. An agenda would help a lot also with other
- ' problems of this emulation, but I don't know any easy way to do something
- ' like this in BASIC.
- ' gameOverText.text=getScore(aPlayer) 'TEST!!!
- dim i
- scoreSpinTimer(aPlayer).enabled=false
- for i=0 to reelsPerCounter-1
- if stuckScoreReel(aPlayer,i)=0 then scoreReelMain(aPlayer,i).text=scoreDigit(aPlayer,i)
- 'print cipher in digit middle
- next
- if extraDigitsEnabled then
- 'This displays the missing digits when the score counter overflows in a
- '(sort of) "light ciphers" style:
- if scoreDigit(aPlayer,reelsPerCounter)>0 then
- scoreReelMain(aPlayer,reelsPerCounter).text=scoreDigit(aPlayer,reelsPerCounter)
- else
- scoreReelMain(aPlayer,reelsPerCounter).text=""
- 'prevents a "0" when counter is long enough
- end if
- end if
- carryForward aPlayer
- end sub
- sub carryForward(aPlayer)
- ' This checks if a carry value must ripple upward to a higher digit.
- ' I use here one carry array per counter. This may appear unneccessary,but when
- ' aPlayer is switched at the wrong moment,this may be safer than sharing
- ' a single array for all score counters.
- dim i
- for i=0 to reelsPerCounter-1
- if carry(aPlayer,i)=1 then
- reelTime(aPlayer)=reelCarryTime 'a digit moved by carry shall flip faster
- incrementScoreDigit aPlayer,i+1,false
- 'The highest array cell accumulates all missing higher digits.
- carry(aPlayer,i)=0
- end if
- next
- reelTime(aPlayer)=reelSpinTime
- ' if unaddedScore(aPlayer)>0 then 'a stricter synchronization method (looks jerky)
- ' scoreAddTimer(aPlayer).enabled=true
- ' else
- ' scoreIsCounting(aPlayer)=false
- ' end if
- end sub
- sub releaseScoreReel(aPlayer,digit)
- ' This releases a stuck scoring reel digit of a player
- ' (see IncrementScoreDigit() ).
- if stuckScoreReel(aPlayer,digit)=0 then exit sub
- subtractUntil0 stuckScoreReel(aPlayer,digit),1
- if stuckScoreReel(aPlayer,digit)=0 then
- subtractUntil0 stuckReelsBuzz,1
- if stuckReelsBuzz=0 then stopSound "reelStuckBuzz" 'stops solenoid buzzing
- scoreReelMain(aPlayer,digit).text=scoreDigit(aPlayer,digit)
- playSound "reelRelease"
- end if
- end sub
- sub releaseStuckScoreReels()
- ' This is a safety command that releases all stuck score reels for the case
- ' that anything goes wrong with the counting semaphores.
- dim i,j
- for i=minCounterNr to maxPlayers
- for j=0 to reelsPerCounter
- if stuckScoreReel(i,j)<>0 then
- stuckScoreReel(i,j)=1
- releaseScoreReel i,j
- end if
- next
- next
- stuckReelsBuzz=0
- end sub
- sub updateScoreDisplays()
- ' This must be called after switching the score backlight on or off to fix a
- ' graphical bug in Visual Pinball (lighted areas behind textboxes otherwise
- ' don't get updated).
- dim i,j
- for i=minCounterNr to maxPlayers
- for j=0 to reelsPerCounter-1
- scoreReelLow(i,j).text=scoreReelLow(i,j).text
- scoreReelBorder(i,1).text=""
- scoreReelTop(i,j).text=scoreReelTop(i,j).text
- scoreReelBorder(i,0).text=""
- scoreReelMain(i,j).text=scoreReelMain(i,j).text
- next
- if extraDigitsEnabled then scoreReelMain(i,reelsPerCounter).text=scoreReelMain(i,reelsPerCounter).text
- next
- end sub
- sub setScore(aPlayer,aNumber)
- ' This sets a score counter directly to a value (possibly useful for loading
- ' a highscore into a counter).
- dim i
- for i=0 to reelsPerCounter-1
- scoreDigit(aPlayer,i)=getDigit(aNumber,i)
- carry(aPlayer,i)=0
- next
- scoreDigit(aPlayer,reelsPerCounter)=int(aNumber/(10^reelsPerCounter))
- spinScoreReels aPlayer 'this updates the score display
- end sub
- function getScore(aPlayer)
- ' This returns as an integer the actually displayed score value of a player,
- ' including all missing higher digits.
- '
- ' Note: This routine does NOT care about the still unadded (buffered) score
- ' the counter is about to add.Therefore it must be waited until
- ' scoreIsCounting(aPlayer)=false to get the completed score value of a
- ' player (e.g. for saving highscores etc.).
- dim i,aScore
- aScore=0
- for i=0 to reelsPerCounter
- aScore=aScore+(scoreDigit(aPlayer,i)*10^i)
- next
- getScore=aScore
- end function
- function getDigit(aNumber,n)
- ' This returns the n-th digit from aNumber; aNumber can be a number string or
- ' an integer number.
- if len(aNumber)>n then
- getDigit=mid(aNumber,len(aNumber)-n,1)
- else
- getDigit="0"
- end if
- 'return values look like a function name assignment in this silly BASIC.
- end function
- function isScoreResetted()
- ' This returns true when all score reels of all score counters are 0.
- dim i
- for i=minCounterNr to maxPlayers
- if getScore(i)<>0 then
- isScoreResetted=false
- exit function
- end if
- next
- isScoreResetted=true
- end function
- sub debugScoreDisplayAlignment
- ' This is a test routine that can be used to align the text boxes of the score
- ' counters on the backdrop picture correctly.
- ' (Don't fear, although the result looks nasty, these counter solenoids are
- ' purely virtual and won't be toasted by this procedure. ;-) )
- '
- ' This routine is not intended to be used within a finished game program.
- dim i,j
- for i=1 to maxPlayers
- unaddedScore(i)=0
- for j=0 to reelsPerCounter-1
- scoreDigit(i,j)=8
- carry(i,j)=0
- incrementScoreDigit i,j,true
- 'this sticks all score reels between 8 and 9
- next
- next
- end sub
- 'sound effect routines:
- '----------------------
- sub playReelClick()
- 'This is the mechanical counter click for a score reel,that is played always
- 'when the digit switches.
- reelClicks=reelClicks+1
- clickTimer.enabled=true
- end sub
- sub clickTimer_timer
- dim n
- if reelClicks>0 then
- n=reelClicks
- if n>maxScoreReelNoises then n=maxScoreReelNoises
- if n>3 then n=n-int(3*rnd()) 'a little random makes sound more vivid
- subtractUntil0 reelClicks,n
- playSound scoreReelNoise(n)
- 'This plays now a sample of n simultaneous reel clicks.The remaining clicks
- 'will be played the next times this timer re-starts.
- clickTimer.interval=1+2*rnd()
- 'This adds a little random to the timer delay to prevent phasing and avoid
- 'overdrive distortion of the output during counter reset.
- else
- clickTimer.enabled=false
- end if
- end sub
- sub playSoundMonophonic(aSoundString)
- ' This plays a sound only on a single voice at a time by shutting down all old
- ' still playing samples of the same name.This can dramatically reduce the
- ' polyphony demand when a sound plays in a loop,and though saves a lot of CPU
- ' time and also prevents overdriving the maximum volume.
- '
- ' Attention: This works only well with sounds that start immediately.When the
- ' loop is too fast and there is a delay in the sample then the
- ' main sound won't be audible at all.Don't use this routine when
- ' the same sample is used for multiple playfield objects those shall
- ' be capable to sound independantly,because their sound would cancel
- ' out each other with this routine.A memory wasting workaround would
- ' be to load multiple copies of the same samples under a different
- ' name.
- '
- stopSound aSoundString
- playSound aSoundString
- end sub
- 'highscore routines:
- '-------------------
- sub updateHighScore()
- ' This calculates the new highscore from the scores of all players and displays it.
- if not highScoreEnabled then exit sub
- dim i,t,updated
- updated=false
- for i=1 to maxPlayers
- if getScore(i)>highScore(ruleSet) then
- highScore(ruleSet)=getScore(i)
- updated=true
- end if
- next
- if updated then t="new " else t=""
- t=t+"high score:" & chr(13) & highScore(ruleSet)
- highScoreText.text=t
- if updated then
- if highScoreSoundEnabled then playSound "didge-a-redo"
- 'This sample I found on a very old "Romware" freeware CD-ROM under the
- 'name SHIPHORN.WAV and immediately I thought whow - what a racket!
- 'It's so incredible lo-fi and crackling that it sounds much rather like a
- '(kind of) mechanically played didgerido,though I immediately knew where
- 'to use it... All attempts to de-click it made it sound just boring,thus
- 'I kept it in all its grainy beauty like it is.
- end if
- end sub
- sub displayHighScore()
- ' This displays the actual highscore. (used when a new game starts to remove
- ' the "new" before the word "high score:",or when the game rules get
- ' switched.)
- highScoreText.text="high score:" & chr(13) & highScore(ruleSet)
- end sub
- ' This was copied from Mr.Do!,where it was copied from the brilliant Eight Ball
- ' Deluxe but I(we?) believe credit and thanks should go to boris for the script.
- Sub loadGameStats(dirFile, nameFile)
- Dim fso, ts
- Dim TestFolder, files, file, f
- Dim temp, pos
- Dim prm, value
- Set fso = CreateObject("Scripting.FileSystemObject")
- If Not fso.FolderExists(dirFile) Then Exit Sub
- Set TestFolder = fso.GetFolder(dirFile)
- Set files = TestFolder.Files
- If (files.Count <> 0) Then
- For Each file in files
- If nameFile=file.Name Then
- Set f = fso.GetFile(dirFile & nameFile)
- Set ts = f.OpenAsTextStream(1,0)
- Do While (ts.AtEndOfStream <> True)
- temp = ts.ReadLine
- pos = instr(1,temp,"=")
- If (pos > 1) Then
- prm = Left(temp,pos-1)
- value = mid(temp,pos+1)
- Select Case prm
- case "highscore_authentic"
- highScore(RS_authentic)=CDbl(value)
- case "highscore_enhanced"
- highScore(RS_enhanced) =CDbl(value)
- case "currentRuleSet"
- ruleSet=CDbl(value)
- case "credits"
- credits=CDbl(value)
- case "matchNumber"
- matchNumber=CDbl(value)
- case "spinnerState"
- spins=CDbl(value)
- case "score1"
- setScore 1,CDbl(value)
- case "score2"
- setScore 2,CDbl(value)
- case "score3"
- setScore 3,CDbl(value)
- case "score4"
- setScore 4,CDbl(value)
- End Select
- If B2SOn Then Controller.B2SSetcredits Credits
- End If
- Loop
- ts.close
- Set f = Nothing
- End If
- Next
- End If
- Set fso = Nothing
- End Sub
- Sub saveGameStats(fileName)
- Dim fso, f
- Set fso = CreateObject("Scripting.FileSystemObject")
- If Not fso.FolderExists(UserDirectory) Then Exit Sub
- Set f = fso.CreateTextFile(fileName, True)
- f.writeLine "highscore_authentic=" & highScore(RS_authentic)
- f.writeLine "highscore_enhanced=" & highScore(RS_enhanced)
- f.writeLine "currentRuleSet=" & ruleSet
- f.writeLine "credits=" & credits
- f.writeLine "matchNumber=" & matchNumber
- f.writeLine "spinnerState=" & spins
- f.writeLine "score1=" & getScore(1)
- f.writeLine "score2=" & getScore(2)
- f.writeLine "score3=" & getScore(3)
- f.writeLine "score4=" & getScore(4)
- f.close
- Set f = Nothing
- Set fso = Nothing
- End Sub
- sub saveGame()
- ' This saves the game status so far the highScoreFileName variable is set.
- if highScoreFileName<>false then saveGameStats UserDirectory & "\" & highScoreFileName
- end sub
- 'general arithmetics:
- '--------------------
- function max(value1,value2)
- ' returns the bigger of 2 values
- if value2>value1 then max=value2 else max=value1
- end function
- function min(value1,value2)
- ' returns the smaller of 2 values
- if value2<value1 then min=value2 else min=value1
- end function
- sub subtractUntil0(aVariable,aNumber)
- ' This subtracts aNumber from aVariable so far the result is >0;else aVariable
- ' is set 0 without getting negative in between.
- ' (This is intended to prevents race conditions with counting semaphores and
- ' other state variables those can be simultaneously set and decremented by
- ' different tasks.)
- if aVariable>aNumber then aVariable=aVariable-aNumber else aVariable=0
- end sub
- 'instruction messages:
- sub showIntro_timer()
- ' This displays the intro text requester.
- showIntro.enabled=false
- dim t
- end sub
- sub showRuleSheet()
- ' This displays a brief summary of the game rules.
- dim t
- t= "RULE SHEET: "
- select case ruleSet
- case RS_authentic: t=t+"(authentic Bally)"
- case RS_enhanced : t=t+"(enhanced mode - more challenging)"
- end select
- t=t+chr(13)&chr(13)
- if ruleSet=RS_enhanced then t=t&"Tophole lights center bumper and upper 1k rollovers."& chr(13)
- t=t&"A,B,C,D targets light A,B,C,D rollovers in captive ball lanes."& chr(13)
- t=t&"Hit A+B+C+D targets to win double bonus."& chr(13)
- t=t&"Left & right inlanes, spinner hits and lit "
- if ruleSet=RS_enhanced then t=t&"A,B,C,D "
- t=t&"rollovers advance bonus."& chr(13)
- t=t&"Maximum bonus="& maxBonus &".High bonusses light outlane extra ball & special."& chr(13)
- select case ruleSet
- case RS_authentic
- if specialLitValue<>false then
- if specialShootAgain0 then t=t&"Special wins extra ball."& chr(13)
- if specialWinsCredit0 then t=t&"Special wins 1 free credit."& chr(13)
- if specialWinsPoints0>0 then t=t&"Special scores "&specialWinsPoints0&" points."&chr(13)
- end if
- case RS_enhanced
- if topHoleShootAgain<>false then
- t=t&"At bonus "& topHoleShootAgain &" tophole wins extra ball."& chr(13)
- end if
- if specialLitValue<>false then
- if specialShootAgain1 then t=t&"Special wins extra ball."& chr(13)
- if specialWinsCredit1 then t=t&"Special wins 1 free credit."& chr(13)
- if specialWinsPoints1>0 then t=t&"Special scores "&specialWinsPoints1&" points."&chr(13)
- end if
- end select
- t=t&"A tilt does not disqualify a player."& chr(13)
- t=t&chr(13)
- t=t&"Push credit button to start game or add 2nd,3rd or 4th player."& chr(13)
- t=t&chr(13)
- t=t&"Insert "
- if coinChute1Credits<>false then
- t=t&"small washer for "& coinChute1Credits &" credit"
- if coinChute1Credits>1 then t=t&"s" else t=t&" (starts game)"
- t=t&chr(13)
- end if
- if coinChute2Credits<>false then
- t=t&"mid size washer for "& coinChute2Credits &" credits"& chr(13)
- end if
- if coinChute3Credits<>false then
- t=t&"large washer for "& coinChute3Credits &" credits"& chr(13)
- end if
- t=t&chr(13)
- t=t&"Washers® - the arcade operator's least beloved foreign currency..."
- msgBox t
- end sub
- sub showKeySettings()
- ' This simply fills a static text box with instruction text.
- dim t
- t= "M = Select Game Mode"& chr(13)
- t=t&"5,6,7 = Insert Washer"& chr(13)
- t=t&"S = Credit Button"& chr(13)
- t=t&"R for Rule Sheet"& chr(13)
- keysBox.text=t
- end sub
- Sub LeftFlipper_Collide(parm)
- PlaySound "rubber"
- End Sub
- Sub RightFlipper_Collide(parm)
- PlaySound "rubber"
- End Sub
- Sub Rubbers_Hit(IDX)
- PlaySound "rubber"
- End Sub
- Sub theTable_Exit
- If B2SOn Then Controller.Stop
- End Sub
Add Comment
Please, Sign In to add comment