'...:....1....:....2....:....3....:....4....:....5....:....6....:....7....:....8
' ballycams - All routines specific to Bally Tables
'
' version 2.  Attempting to clean up and reroute much of the damage
' caused by version 1.  Okay, it's not so much "damage" as much as it
' was starting to get unwieldy and a bit overblown, and was becoming a bit
' disorganized as a result.
'
' So let's try it again.
'
' We need to load the core em driver, which contains all the necessarya
' object classes and logic functions to build our cam driver.
'
' This file contains all constants, routines and functions necessary to
' Bally machines.
'
' NOTE:  from version 1:  scoring reels, score handling and backbox
'	initialisation have all been moved to emCORE.vbs, as they are
'	handled identically across all platforms.
'
Option Explicit
const emBallyCamsVersion=2.6

' changes to 2.6
'	Added C_USER1 for a user-defined cam action.

' And away we go.
' **** Constants, with variables close by.
' Limits 
const	GLOB_MAXBALLS=10	' no more than this
const	GLOB_MAXPLAYERS=4	' no more than this
const	GLOB_MAXBONUS=19	' Bally topped out at 19
const	NPULSES=7		' Bally's repeaters had 8 pulses
const GLOB_MAXREPLAYS=25

' Scoring

public	score_min		' minimum score
		score_min = 10		' currently unused

' Bonus
dim	bonus_style		' default: 0 (fast collect)
const	BONUS_FAST=0		' fast (5/cycle) collect
const	BONUS_SLOW=1		' slow (2/cycle) collect
const	BONUS_QUICK=2		' quick (3 or 6/cycle) collect [quarterback]
							' NOT YET IMPLEMENTED [XXX]
bonus_style = BONUS_FAST

' APPLIES TO SEQUENTIAL BONUS ONLY!!!
dim	collecting_bonus
const	COLL_NONE=0		' not currently collecting bonus
const	COLL_RESET=1	' decrement but do not score (always superfast)
const	COLL_DECR=2		' collecting and decrementing
const	COLL_KEEP=3		' collecting and keeping


' What the backbox says...
const	SHOOT_AGAIN="SAME PLAYER SHOOTS AGAIN"
' Gottlieb says "SHOOT AGAIN".
' Williams says either/or.
'****
' Cam State
' The following ONLY apply to values resulting from a cam start, such as
' the lit specials on the rollovers on flip-flop, or the standing 5000
' target on Wizard.

const	C_SCORE=16383		' maximum raw score, i.e. score / minscore
					' is 1000, so we mask everything beneath this.
'	observations about scoring and bits (go figure!):
'		* the 8 bit is shared by 10 and 1000.
'		* the 32 bit is shared by 100 and 1000.
'		* the 64 bit is shared by 100 and 1000.
const	SCORE_1=	&H0001	' only 1 has the 1 bit set
const	SCORE_10=	&H0002	' only 10 has the 2 bit set
const	SCORE_100=	&H0004	' only 100 has the 4 bit set
const	SCORE_1k=	&H0080	' only 1000 has the 128 bit set
	' 	*** features ***
	' NOTE: VB likes to think that &H8000 is -32768 :/
const	C_ADDP=	&H00004000	' add a player
const	C_REPLAY=	32768		' add a credit
const	C_SPEC=	&H00010000	' score a special
const	C_EJECT=	&H00020000	' cause a saucer to eject
const	C_ADDB=	&H00040000	' add bonus (calls add_bonus)
const	C_DECB=	&H00080000	' decrease bonus (calls dec_bonus)
const	C_RDROPS=	&H00100000	' reset drop targets
const	C_DPOST=	&H00200000	' retract the flipper post
const	C_CGATE=	&H00400000	' close an open gate (calls close_gate)
const	C_LOADB=	&H00800000	' load the ball into the shooter
const	C_RSPSA=	&H01000000	' reset Same Player Shoots Again
const	C_NEXT=	&H02000000	' go to next player/ball
const	C_FLIP=	&H04000000	' flip a flag (depends on cp)
const	C_GAMEON=	&H08000000	' engage game-on relay
const	C_GAMEOFF=	&H10000000	' true end-of-game
' ADDBALL and DECBALL have been removed; eventually emNextUp will be
' hacked to handle single-player games, maybe call emNext1p.
' Someday.
const C_RXREEL=	&H20000000	' reset reel pulse
const C_USER1=	&H40000000	' User-defined cam pulse.

const	C_FMASK=	&H7FFFC000	' feature mask

'*** CAM ENGINE
dim incam		' we in the cam engine already?
dim cp			' cam position
dim camstack(): redim camstack(NPULSES)

public u1sub

'*** SPECIFICS
dim	CurrentBank
dim	CurrentEject
dim	CurrentPost
dim	CurrentGate
dim	CurrentAltq
dim	CurrentBQueue
dim	CurrentCQueue
dim	CurrentCycler
dim	CurrentBuilder

camtimer.interval = TIMER_BALLY
dim clight(7)

'dim x, l
'x = 0
'for each l in array(l_cam0,l_cam1,l_cam2,l_cam3,l_cam4,l_cam5,l_cam6,l_cam7)
'	set clight(x)= l
'	x = x + 1
'next

sub runcam() 
	dim ao
'showcams
	if incam then exit sub
	incam = true
	cp = 0
	camtimer.enabled = true
end sub

' user-settable routine 
sub cuser1(p)
end sub

' to override the blank cuser1 routine...
sub bcUserSet(s)
	executeglobal "sub cuser1(p): " & s & " p: end sub"
end sub

sub clearcams()
	dim i
	for i = 0 to ubound(camstack)
		camstack(i) = 0
	next
end sub	

sub camtimer_timer()
	dim o, r
	dim dt

	addval camstack(cp)
	playsound S_camclick	' something suitably soft for a cam-hitting-relay
	me.enabled = false

' 	clight(max(0,cp - 1)).state = lightstateoff
 	if (cp < ubound(camstack)) then 
		dbx "Pending: " & C_pending & "; game state: " & TableState.game
		me.enabled = true
'		clight(cp).state = lightstateon
		cp = cp + 1
		incam = true
	else
		' clear possible state
'		clight(7) = lightstateoff
		clearcams
		CurrentBank = NULL
		CurrentGate = NULL
		playsound S_coinlock
		incam = false
		emAltQChange ALTQ_CAM
		' check for state here, in this order:
		' 1.  Drop targets
		' 2.  Flip flags
		' 3.  Eject holes
		' 4.  Bonus
		' 5.  Drain
		' NOTE:  This should probably be more of a
		' evaldrops routine to see if we should even
		' reset them.
		' ** 1.  Drop targets
		dt = emCheckDrops
		if (not isEmpty(dt)) then
			CurrentBank = dt
			camstack(0) = C_RDROPS
			runcam
		end if
		' ** 2. Eject Holes
		dbx "Pending: " & C_pending & "; game state: " & TableState.game
		if (c_pending AND PEND_EJECT) then
			emScoreSaucers CurrentEject
			emPend PEND_EJECT, FALSE
			' insert eject handler here
		' ** 3. Flip Flags
'		elseif (c_pending AND PEND_FLIPS) then
'			emResetAllFlips
'			runcam
		' ** 4. Bonus
		elseif (c_pending AND PEND_BONUS) then
			emCollect bonus_style
		' ** 5. Drain
		elseif (c_pending AND PEND_DRAIN) then
			Drain_Hit
		elseif (c_pending AND PEND_RESET) then
			r = 0
			for o = 0 to Scores.maxplayers
				if Scores.score(o) <> 0 then r = r + 1
			next
			if (nbqueue >= 0) then
				for each o in BBonusQ
					if o.bonus then r = r + 1
				next
			end if
			if (r > 0) then
				camstack(1) = C_RXREEL
				camstack(2) = C_RXREEL
				camstack(3) = C_RXREEL
				camstack(4) = C_RXREEL
				camstack(5) = C_RXREEL
			else
				init_ball
				emPend PEND_RESET, FALSE
			end if
			runcam
		end if
	end if
		dbx "Pending: " & C_pending & "; game state: " & TableState.game
end sub

' addval - the world is inside this call.

const	ADD_ON	= 1
const	ADD_OFF	= 2
const ADD_PULSE	= 3

sub addval(v)
	addval_on v, ADD_PULSE
end sub

sub addval_on(v, down)
	' down: TRUE, engages the relay (play the sound)
	' 	  FALSE, disengage the relay (flip the reel)

	dim f	 ' feature
	dim s	 ' score

	s = (v and C_SCORE) \ minscore

	f = (v and C_FMASK)

	if (v = TRUE) then dd "addval", "v = TRUE!  WTF?!?"

	'  **** FEATURES ****
	if (down and ADD_ON) then
		if (f and C_ADDP) then emAddPlayer

		if (f and C_REPLAY) then emAddReplay

		if (f and C_SPEC) then scores.score_Special

		if (f and C_EJECT) then emClearSaucers	CurrentEject

		if (f and C_ADDB) then emAddBonus CurrentBQueue, _
					(Scores.didscore <> 0)

		if (f and C_DECB) then emTakeBonus(CurrentBQueue)

		if (f and C_RDROPS) then emResetDrops CurrentBank

		if (f and C_DPOST) then emSetPost false

		if (f and C_CGATE) then emCloseGate CurrentGate

		if (f and C_LOADB) then emLoadBall

		if (f and C_RSPSA) then TableState.shootagain false

		if (f and C_NEXT) then emNextUp

		if (f and C_FLIP) then emFlipReset (cp - 2)	'uses cp

		if (f and C_RXREEL) then Scores.reset
 
	 	if (f and C_GAMEOFF) then TableState.game = GAME_OVER

		if (f and C_GAMEON) then TableState.game = GAME_ON

		if (f and C_USER1) then cuser1 cp

		if (TableState.tilted) or (TableState.game < GAME_ON) then exit sub

	end if

' Potentially this part could be moved to emCORE.

	if (s and SCORE_1) then
		if (down and ADD_ON) then
			playsound S_highchime
		end if
		if (down and ADD_OFF) then
			Scores.Creel.Addvalue(minscore)
			addscore minscore
		end if
	end if
	if (s and SCORE_10) then
		if (down and ADD_ON) then
			playsound S_midchime
		end if
		if (down and ADD_OFF) then
			Scores.Creel.AddValue(minscore * 10)
			addscore minscore * 10
		end if
	end if
	if (s and SCORE_100) then
		if (down and ADD_ON) then
			playsound S_lowchime
		end if
		if (down and ADD_OFF) then
			Scores.Creel.AddValue(minscore * 100)
			addscore minscore * 100
		end if
	end if
	if (s and SCORE_1k) then
		if (down and ADD_ON) then
			playsound S_lowchime
		end if
		if (down and ADD_OFF) then
			Scores.Creel.AddValue(minscore * 1000)
			addscore minscore * 1000
		end if
	end if
end sub

sub add_nx (n, x, force)
	dim pp		  ' position pointer

	if (n AND C_SCORE) AND _
		(TableState.tilted OR (TableState.game <> GAME_ON)) then
			exit sub
	end if

	if (incam) and not force then exit sub

	for pp = 1 to 5
		if (x > 0) then 
			camstack(pp) = camstack(pp) or n
		end if
		x = x - 1
	next

	runcam
end sub

' add_nxe:  Add the requested value (n) x number of times at the END
' of the cycle.	 Bally liked to do this a lot.

sub add_nxe (n, x, force)
	dim pp

	if (n AND C_SCORE) AND _
		(TableState.tilted OR (TableState.game <> GAME_ON)) then
			exit sub
	end if

	if (incam) and not force then exit sub

	for pp = 5 to 1 step -1
		if (x > 0) then
			camstack(pp) = camstack(pp) or n
		end if
		x = x - 1
	next

	runcam
end sub

' some simple routines:
'

sub adv3 (n)
	add_nxe n or C_ADDB, 3, true
end sub

sub add3 (n)
	add_nxe n, 3, true
end sub

sub add3b (n)
	camstack(4) = C_ADDB
	add_nxe n, 3, true
end sub

sub add5 (n)
	add_nx n, 5, true
end sub

sub add5b (n)
	camstack(4) = C_ADDB
	add_nx n, 5, true
end sub

' bonus collection
' Collecting in the drain or other retractor...

Public sub emCollect (how)
	dim x, flags

	if nbqueue < 0 then exit sub

	if (isNull(CurrentBQueue)) then exit sub
	if (tablestate.tilted) or (tablestate.game < GAME_ON) then
		how = BONUS_FAST
		x = 0
	else 
		x = CurrentBQueue.worth
	end if
' if we're keeping it, pass it off to the emBScore routine
	if (collecting_bonus = COLL_KEEP) then
		emBScore CurrentBQueue, _
		((tablestate.game > GAME_OVER) and (tablestate.tilted = false))
		exit sub
	end if

	if (CurrentBQueue.bonus > 0 AND scores.didscore) OR (tablestate.game > GAME_OVER) then
		emPend PEND_BONUS, TRUE
		if (how = BONUS_SLOW OR (CurrentBQueue.multiplier > 1)) and _
			(not(tablestate.tilted) and (tablestate.game = GAME_ON)) then
			camstack(1) = camstack(1) or x
			if (CurrentBqueue.multiplier >= 2) then camstack(2) = camstack(2) or x
			camstack(3) = camstack(3) or C_DECB
			if (CurrentBQueue.Multiplier = 3) then camstack(3) = camstack(3) or x
			if (CurrentBQueue.bonus > 1) then
				camstack(4) = camstack(4) or x
				if (CurrentBQueue.Multiplier >= 2) then camstack(5) = camstack(5) or x
				camstack(6) = camstack(6) or C_DECB
				if (CurrentBQueue.Multiplier = 3) then camstack(6) = camstack(6) or x
			end if
		else
			if (CurrentBQueue.bonus >= 1) then 
				camstack(1) = camstack(1) or x or C_DECB
			end if
			if (CurrentBQueue.bonus >= 2) then 
				camstack(2) = camstack(2) or x or C_DECB
			end if
			if (CurrentBQueue.bonus >= 3) then 
				camstack(3) = camstack(3) or x or C_DECB
			end if
			if (CurrentBQueue.bonus >= 4) then 
				camstack(4) = camstack(4) or x or C_DECB
			end if
			if (CurrentBQueue.bonus >= 5) then 
				camstack(5) = camstack(5) or x or C_DECB
			end if
			if (tablestate.tilted) or (TableState.game < GAME_ON) then 
				camstack(6) = camstack(6) or C_DECB
			end if
		end if
	else
		if TableState.game = GAME_TRANS then
			camstack(7) = C_GAMEON
		end if
		collecting_bonus = COLL_NONE
	end if
	emPend PEND_BONUS, FALSE
	runcam
end sub

'  and simply scoring on the playfield (no retracting)

dim btmp
sub emBScore(bonusq, doscore)
	dim x

	if nbqueue < 0 then exit sub
	if (isNull(bonusq)) then exit sub	' we can't collect both on field.

	collecting_bonus = COLL_KEEP
	if incam then exit sub: end if

	x = bonusq.worth

	if (btmp > 0) then
		emPend PEND_BONUS, TRUE
		if (btmp >= 1) then camstack(1) = camstack(1) or x: btmp = btmp - 1
		if (btmp >= 1) then camstack(2) = camstack(2) or x: btmp = btmp - 1
		if (btmp >= 1) then camstack(3) = camstack(3) or x: btmp = btmp - 1
		if (btmp >= 1) then camstack(4) = camstack(4) or x: btmp = btmp - 1
		if (btmp >= 1) then camstack(5) = camstack(5) or x: btmp = btmp - 1
	end if

	if (btmp = 0) then
		emPend PEND_EJECT, FALSE
		camstack(5) = camstack(5) OR C_EJECT
		emPend PEND_BONUS, FALSE
		collecting_bonus = COLL_NONE
	end if
	runcam
end sub


'** game init **

' init the game - Bally-style
sub emGameReset()
	dim i

	emPend PEND_RESET, TRUE

	CurrentBank = NULL
	CurrentGate = NULL

	camstack(1) = C_RXREEL
	camstack(2) = C_RXREEL
	camstack(3) = C_RXREEL
	camstack(4) = C_RXREEL
	camstack(5) = C_RXREEL

	TableState.setcanplay 0
 	TableState.nplayers = 0
	TableState.showmatch false
	TableState.gameover false	' only sets gamestate to TRANS
	TableState.setball 1
	emResetDrops NULL
	emCloseGate NULL
	emSetPost FALSE
	emBonusMX NULL, 1
	emResetAllFlips
	emBuilderReset CurrentBuilder, TRUE
	emCyclerSet NULL, TRUE
	TableState.setplayer 0
	emTiltReset
	runcam
end sub

sub emEndGame
	dim s, m
' dd "emEndGame", "players: " & tablestate.nplayers
	m = "matches: "
	if TableState.nplayers > 0 then
		for s = 1 to TableState.nplayers
		m = m & tablestate.checkmatch(scores.score(s)) & " "
			if TableState.checkmatch(Scores.score(s)) then
				camstack(s+1) = (camstack(s+1) OR C_REPLAY)
			end if
		next
	end if
	TableState.showmatch true
	TableState.game = GAME_TRANS
	camstack(5) = camstack(5) or C_GAMEOFF
	TableState.setball 0
	emBuilderReset CurrentBuilder, FALSE
	emCyclerSet NULL, FALSE
	emFLights FALSE
	runcam
	dbx m
	game_end	' must be provided by table
end sub

' addplayer.  KINDA bally-specific.

sub emAddPlayer()
 	if TableState.ballinplay <> 1 then
		emGameReset
		exit sub
	end if
	if TableState.addplayer then
		playsound S_addplayer
	end if
end sub

sub StartButton()
 	if emTakeReplay(Scores) = FALSE then exit sub
 	if TableState.nplayers >= Scores.maxplayers AND _
		TableState.ballinplay = 1 then exit sub
	camstack(0) = camstack(0) or C_ADDP
	runcam
end sub	

sub emNextUp()	' FOR MULTI-PLAYER TABLES
				' NEED TO WORK ON MAKING ONE FOR SINGLE-PLAYER TABLES
' *** we are always called on cam position 1
	dim a

	TableState.tilt FALSE
	if NOT Scores.didscore then 
		exit sub
	end if
	Scores.didscore = false
'	collecting = COLL_NONE	' handle in table
	emResetAllFlips		' will be ignored if no flips exist
	CurrentGate = NULL
	CurrentBank = NULL
'	addval C_FLIP or camstack(1)
	camstack(2) = camstack(2) or C_DPOST
	camstack(5) = camstack(5) or C_CGATE
	camstack(7) = camstack(7) or C_RSPSA
	Table_NextUp
	if (TableState.same_player) then exit sub

	a = Tablestate.nextplayer
	if not (a) then
		clearcams
		addval C_DPOST or C_CGATE
		TableState.setplayer -1	'clear all playerup lights
		emResetAllFlips
' I despise this hack, but for authenticity it is necessary.
		a = TableState.checkmatch(Scores.score(0))
dbx "a = " & a & "; Scores.score(0) = " & Scores.score(0)
		if a then
			addval C_REPLAY
		end if
		TableState.gameover true
		emEndGame			' handles the match routine
 	end if
	Table_NextUp
' player-made routine
' handles retracting bonus, resetting bonus multipliers,
' and re-initialising the table lights
end sub

