示例#1
0
void rudy_eye_update (void)
{
	switch (EYE_DIRECTION (rudy_eyes))
	{
		case EYES_STRAIGHT:
			eye_direction_stop ();
			break;
		case EYES_LEFT:
			eye_direction_start_reverse ();
			task_recreate_gid_while (GID_RUDY_EYE_TIMEOUT, rudy_eyedir_timeout,
				TASK_DURATION_INF);
			break;
		case EYES_RIGHT:
			eye_direction_start_forward ();
			task_recreate_gid_while (GID_RUDY_EYE_TIMEOUT, rudy_eyedir_timeout,
				TASK_DURATION_INF);
			break;
	}

	switch (EYE_LID(rudy_eyes))
	{
		case EYELID_NORMAL:
		case EYELID_OPEN:
			sol_request (SOL_EYELIDS_OPEN);
			break;

		case EYELID_CLOSED:
			sol_request (SOL_EYELIDS_CLOSED);
			break;
	}
	task_sleep_sec (2);
	task_exit ();
}
示例#2
0
CALLSET_ENTRY (trapdoor, ball_search)
{
	trap_door_state = DISABLED;
	sol_request (SOL_TRAP_DOOR_CLOSE);
	task_sleep_sec (2);
	sol_request (SOL_TRAP_DOOR_OPEN);
	task_sleep_sec (2);
	trap_door_state = OPEN;
	trapdoor_device_update ();
}
示例#3
0
static void trap_door_pulse (void)
{
	if (trap_door_state == CLOSED)
	{
		sol_request (SOL_TRAP_DOOR_CLOSE);
		if (in_live_game && valid_playfield)
			sample_start (SND_BOOM1, SL_1S);
	}
	else
		sol_request (SOL_TRAP_DOOR_OPEN);

	task_sleep (TIME_500MS);
	/* ODO - read 'closed' sw. to see if it worked OK */
	task_exit ();
}
示例#4
0
static void lock_and_outhole_monitor (void)
{
	/* Wait for balls to settle/amode to start before emptying
	 * locks/outhole */
	task_sleep_sec (3);
	while (!in_live_game)
	{
		if (switch_poll (SW_LOCK_LOWER))
		{
			device_request_kick (device_entry (DEVNO_LOCK));
		}

		if (switch_poll (SW_OUTHOLE))
		{
			sol_request (SOL_OUTHOLE);
		}
		if (!switch_poll (SW_AUTOFIRE2))
		{
			callset_invoke (clear_autofire);	
		}
		/* Wait for the balls to be cleared before starting again */
		task_sleep_sec (3);
	}
	task_exit ();
}
示例#5
0
void eject_video_mode_finished(void) {
//	task_create_gid1(GID_EJECT_LEFF, eject_leff);
	sol_request (SOL_EJECT); //request to fire the eject sol
	ball_search_monitor_start ();
//	task_sleep (TIME_300MS);
//	flag_off(FLAG_KILL_NORMAL_EJECT);
} //end of function
示例#6
0
/** A task that manages the autolaunching of balls.
Upon entry, the autofire divertor solenoid is already pulsing
and a ball is being kicked from the trough. */
void autofire_monitor (void)
{
    /* Open the divertor to catch the ball.  Because it may be
    coming from either the trough or a ramp divert, the
    timings are variable. */
    if (shooter_div_delay_time)
        task_sleep_sec (shooter_div_delay_time);

    autofire_busy = TRUE;
    //if (autofire_full ()
    //	don't open to catch
    shooter_div_start ();
    /* TODO - If the autofire switch trips during the 'open
    time', we can abort this delay early and go ahead and
    close the divertor.  This is safe because only one
    ball can appear here at a time. */
    //task_sleep_sec (shooter_div_open_time);
    autofire_ball_catch_wait ();
    shooter_div_stop ();

    /* Wait a little longer for the ball to settle */
    task_sleep (TIME_200MS);

    /* If Right inlane -> Left ramp combo, start tnf mode */
    if (event_did_follow (left_ramp_exit, tnf) && single_ball_play ())
    {
        callset_invoke (tnf_start);
    }

    /* Wait until allowed to kickout */
    while (kickout_locks > 0)
        task_sleep (TIME_100MS);

    /* Open diverter again */
    shooter_div_start ();
    /* Wait for the diverter to fully open before firing */
    U8 timeout = 20;
    while (--timeout != 0)
        task_sleep (TIME_100MS);

    if (in_live_game && single_ball_play ())
    {
        sound_send (SND_EXPLOSION_1);
        leff_start (LEFF_STROBE_UP);
    }
    /* Say that the ball is heading into the right loop */
    timer_restart_free (GID_BALL_LAUNCH, TIME_3S);
    event_can_follow (autolaunch, right_loop, TIME_4S);
    /* Clear the magnet so we can fire a ball */
    magnet_disable_catch (MAG_RIGHT);
    /* Launch the ball */
    sol_request (SOL_AUTOFIRE);
    /* Wait for the ball to clear the divertor
     * before closing*/
    task_sleep (TIME_700MS);
    shooter_div_stop ();
    autofire_busy = FALSE;
    task_exit ();
}
示例#7
0
CALLSET_ENTRY (outhole, ball_search)
{
	while (switch_poll_logical (SW_OUTHOLE))
	{
		sol_request (SOL_OUTHOLE);
		task_sleep_sec (1);
	}
}
示例#8
0
CALLSET_ENTRY (autofire, clear_autofire)
{
    /* Used to empty autofire if found full
     * during attract mode */
    shooter_div_start ();
    task_sleep_sec (2);
    sol_request (SOL_AUTOFIRE);
    task_sleep_sec (1);
    shooter_div_stop ();
}
示例#9
0
文件: rules.c 项目: SonnyJim/freewpc
void rules_hitchhiker_leff (void)
{
	for (;;)
	{
		lamp_tristate_flash (LM_RIGHT_JET);
		lamp_tristate_flash (LM_CAMERA);
		task_sleep_sec (2);
		lamp_tristate_off (LM_RIGHT_JET);
		lamp_tristate_off (LM_CAMERA);
		sol_request (SOL_UR_FLIP_POWER);
		task_sleep (TIME_500MS);
	}
}
示例#10
0
文件: rules.c 项目: SonnyJim/freewpc
void rules_spiralaward2_leff (void)
{
	for (;;)
	{
		lamplist_apply (LAMPLIST_SPIRAL_AWARDS, lamp_flash_on);
		lamp_tristate_flash (LM_RIGHT_SPIRAL);
		lamp_tristate_flash (LM_RIGHT_POWERBALL);
		task_sleep_sec (1);
		sol_request (SOL_LL_FLIP_POWER);
		lamp_tristate_off (LM_RIGHT_SPIRAL);
		lamp_tristate_off (LM_RIGHT_POWERBALL);
		task_sleep (TIME_500MS);
	}
}
示例#11
0
文件: rules.c 项目: SonnyJim/freewpc
void rules_doinks_leff (void)
{
	for (;;)
	{
		lamp_tristate_flash (LM_RIGHT_INLANE);
		task_sleep_sec (1);
		sol_request (SOL_LR_FLIP_POWER);
		lamp_tristate_off (LM_RIGHT_INLANE);
		lamp_tristate_flash (LM_SUPER_SKILL);
		task_sleep (TIME_500MS);
		lamp_tristate_off (LM_SUPER_SKILL);
		lamp_tristate_flash (LM_MULTIBALL);
		task_sleep (TIME_500MS);
		lamp_tristate_off (LM_MULTIBALL);
		lamp_tristate_flash (LM_BONUS_X);
		task_sleep (TIME_500MS);
		lamp_tristate_off (LM_BONUS_X);
	}
}
示例#12
0
void gumball_release_task (void)
{
	event_should_follow (gumball_release, gumball_exit, TIME_4S);
	while (gumball_pending_releases > 0)
	{
		gumball_geneva_tripped = FALSE;
		gumball_exit_tripped = FALSE;

		/* Original timeout was 90x33ms = 3sec */
		/* Shorter timeout will work when geneva is broken */
		timeout = 25;
		while ((gumball_geneva_tripped == FALSE) && (gumball_exit_tripped == FALSE) && (--timeout > 0))
		{
			sol_request (SOL_GUMBALL_RELEASE);
			task_sleep (TIME_33MS);
		}
		sol_stop (SOL_GUMBALL_RELEASE);
		gumball_running = FALSE;
		bounded_decrement (gumball_pending_releases, 0);
	}
	task_exit ();
}
示例#13
0
/** Request that a new ball be autolaunched into play. */
void autofire_add_ball (void)
{
    autofire_request_count++;

    if (!in_game || switch_poll_logical (SW_FAR_LEFT_TROUGH))
    {
        /* For special situations.  If not in game, the
        kick_attempt hook won't be called to open for trough.
        If far left trough is set, the kick will appear failed
        because the trough count won't change (even though a
        ball was successfully kicked).  In these cases, do it
        manually.  However, you get no retry capability here. */
        autofire_open_for_trough ();
        /* Wait for divertor to open */
        task_sleep_sec (2);
        sol_request (SOL_BALL_SERVE);
    }
    else
    {
        /* The normal way to kick a ball from the trough.
         * dev_trough_kick_attempt will be called next */
        device_request_kick (device_entry (DEVNO_TROUGH));
    }
}
示例#14
0
CALLSET_ENTRY (eject, dev_eyeball_eject_enter) {
	//HUXLEY MADE
	if ( !flag_test(FLAG_BACK_IN_THE_FRIDGE_RUNNING)
		&&	task_kill_gid(GID_LEFT_INLANE_MADE) ) {
//		flag_on(FLAG_KILL_NORMAL_EJECT);
		huxley_made();
		return;
//		task_sleep (TIME_300MS);//give time for huxley to catch up
	}

	//START VIDEO MODE
	else if (  !flag_test(FLAG_BACK_IN_THE_FRIDGE_RUNNING)
			&& !flag_test(FLAG_IS_FORTRESS_MB_RUNNING)
			&& !flag_test(FLAG_IS_MUSEUM_MB_RUNNING)
			&& !flag_test(FLAG_IS_CRYOPRISON_MB_RUNNING)
			&& !flag_test(FLAG_IS_WASTELAND_MB_RUNNING)
			&& !flag_test(FLAG_IS_DEMOTIME_RUNNING)
			&&	flag_test (FLAG_VIDEO_MODE_ENABLED) ) {
//						flag_on(FLAG_KILL_NORMAL_EJECT);
						start_video_mode(3);

						sound_start (ST_ANY, SPCH_PLAYER_TWO, SL_1S, PRI_GAME_QUICK5);
						task_sleep (TIME_500MS);
						task_sleep (TIME_500MS);
						task_sleep (TIME_500MS);
						task_sleep (TIME_500MS);


						combo_rehit_check (); //check to see if enough combos to relight video mode
						sol_request (SOL_EJECT); //request to fire the eject sol
						ball_search_monitor_start ();
//						return;
						task_sleep (TIME_300MS);
	}//end of start video mode

	//NORMAL RETINA SCAN
	else //if (!flag_test(FLAG_KILL_NORMAL_EJECT) )
	{
						leff_start (LEFF_EJECT);

						sound_start (ST_SAMPLE, RETINA_SCAN_LONG, SL_4S, PRI_GAME_QUICK1);
						score (EJECT_SCORE);
						//100k per jet hit here
						if (jet_shots_made > 0) {
							score_zero (temp_score);//zero out temp score
							score_add (temp_score, score_table[SC_100K]);//multiply 100K by jet count
							score_mul (temp_score, jet_shots_made);//multiply 100K by jet count
							score_long (temp_score); //add temp score to player's score
						}//end of if

						if (retina_scan_multiplier == 2)	{
							sound_start(ST_SPEECH, SPCH_DOUBLE_RETINA_SCAN, SL_4S, PRI_GAME_QUICK5);
							score (EJECT_SCORE);
							score_long (temp_score); //add temp score to player's score
						}

						deff_start (DEFF_EJECT_EFFECT);
						task_sleep_sec (1);

						sol_request (SOL_EJECT); //request to fire the eject sol
	}//end of else !flag_test(FLAG_KILL_NORMAL_EJECT)
}//end of function
示例#15
0
文件: rules.c 项目: Dave2084/freewpc
void rules_deff (void)
{
	music_disable ();
	music_set (MUS_RESTART_PLUNGER);
	sol_request (SOL_EYELIDS_OPEN);

	rule_begin ();
	rule_msg ("HOW TO PLAY", "FUNHOUSE");
	task_create_gid1 (GID_RULES_LEFF, rules_flasher_leff);
	rule_complete ();

	rule_begin ();
	rule_msg ("ALL TARGETS", "ADVANCE CLOCK");
	task_create_gid1 (GID_RULES_LEFF, rules_clock_leff);
	rule_complete ();

	rule_begin ();
	rule_msg ("REACH 11:30", "TO LIGHT LOCK");
	fh_clock_set (11, TIME_30_MIN);
	lamp_tristate_flash (LM_LOCK);
	rule_complete ();

	rule_begin ();
	rule_msg ("LOCK BALL 1", "FOR 11:45");
	fh_clock_set (11, TIME_45_MIN);
	lamp_tristate_flash (LM_LOCK);
	rule_complete ();

	rule_begin ();
	rule_msg ("LOCK BALL 2", " FOR 12:00");
	fh_clock_set (12, TIME_0_MIN);
	lamp_tristate_flash (LM_MILLION);
	sol_request (SOL_EYELIDS_CLOSED);
	rule_complete ();

	rule_begin ();
	rule_msg ("WAKE RUDY", "FOR MULTIBALL");
	sol_request (SOL_EYELIDS_OPEN);
	rule_complete ();

	rule_begin ();
	rule_msg ("SHOOT TRAP DOOR", "FOR JACKPOTS");
	lamp_tristate_flash (LM_MILLION_PLUS);
	rule_complete ();

	rule_begin ();
	rule_msg ("SHOOT MIRROR TO", "COLLECT AWARDS");
	lamplist_apply (LAMPLIST_MIRROR_AWARDS, lamp_flash_on);
	lamp_tristate_flash (LM_MIRROR_VALUE);
	rule_complete ();

	rule_begin ();
	rule_msg ("HIT RUDY TO", "RELIGHT MIRROR");
	lamplist_apply (LAMPLIST_MIRROR_AWARDS, lamp_on);
	rule_complete ();

	rule_begin ();
	rule_msg ("BLUE TARGETS", "LIGHT THE STEPS");
	lamplist_apply (LAMPLIST_STEP_TARGETS, lamp_flash_on);
	lamplist_apply (LAMPLIST_STEPS_AWARDS, lamp_flash_on);
	rule_complete ();

	rule_begin ();
	rule_msg ("SHOOT STEPS FROM", "LEFT PLUNGER");
	lamplist_apply (LAMPLIST_STEPS_AWARDS, lamp_flash_on);
	lamp_tristate_flash (LM_STEPS_GATE_OPEN);
	lamp_tristate_flash (LM_RAMP_STEPS);
	rule_complete ();

	rule_begin ();
	rule_msg ("SHOOT LOOPS TO", "COLLECT GANGWAYS");
	lamp_flash_on (LM_LEFT_GANGWAY);
	lamp_flash_on (LM_RIGHT_GANGWAY);
	lamplist_apply (LAMPLIST_GANGWAYS, lamp_on);
	rule_complete ();

	rule_begin ();
	rule_msg ("EXTRA BALLS LIT", "AT TOP LOOP");
	lamp_on (LM_EXTRA_BALL);
	lamp_flash_on (LM_MIRROR_EX_BALL);
	lamp_flash_on (LM_GANGWAY_EX_BALL);
	lamp_flash_on (LM_STEPS_EB);
	rule_complete ();

	rule_begin ();
	rule_msg ("PLAY", "FUNHOUSE");
	task_create_gid1 (GID_RULES_LEFF, rules_flasher_leff);
	task_create_gid1 (GID_RULES_LEFF, rules_clock_leff);
	rule_complete ();

	sol_request (SOL_EYELIDS_CLOSED);
	music_enable ();
	deff_exit ();
}
示例#16
0
CALLSET_ENTRY (slot, ball_search)
{
	sol_request (SOL_SLOT);
}
示例#17
0
CALLSET_ENTRY (outhole, init)
{
	if (switch_poll_logical (SW_OUTHOLE))
		sol_request (SOL_OUTHOLE);
}
示例#18
0
//this should reach her if there are two balls on the saucer in rapid succession or if first ball doesn't leave
CALLSET_ENTRY (eject, dev_eyeball_eject_kick_failure) {
	eject_kick_attempts++;
	task_sleep (TIME_1S);
	sol_request (SOL_EJECT); //request to fire the eject sol
}//end of function
示例#19
0
CALLSET_ENTRY (eject, amode_start) 		{
	if (switch_poll_logical (SW_EJECT) ) 	sol_request (SOL_EJECT); //request to fire the eject sol
}//end of function
示例#20
0
CALLSET_ENTRY (eject, dev_eyeball_eject_enter) {
	if (!in_game) {
		sol_request (SOL_EJECT);
		task_sleep (TIME_1S);
	}
	else {
			//HUXLEY MADE
			if ( !get_back_in_the_fridge_running()
				&&	!get_back_in_the_fridge_ending()
				&&	task_kill_gid(GID_LEFT_INLANE_MADE)  && !in_tilt) {
				is_normal_eject_killed = TRUE;
				huxley_made();
				return;
			}//end of huxley made

			//START VIDEO MODE
			else if (  (system_config.disable_kaboom  == NO || system_config.disable_simon_says == NO || system_config.disable_shooter == NO)
					&&	flag_test (FLAG_VIDEO_MODE_ENABLED)
					&&  !in_tilt
					&&	!get_back_in_the_fridge_running()
					&&	!get_back_in_the_fridge_ending()
					&& !get_fortress_running ()
					&& !get_museum_running ()
					&& !get_cryoprison_running ()
					&& !get_wasteland_running ()
					&& !get_demotime_running ()
					&& !is_frenzy_running () ) {       //this is here since starting frenzy dumps ball into eject hole
					switch (get_next_video_mode() ) {
							case 0: start_kaboom(); 		break;
							case 1: start_simon_says(); 	break;
							case 2: start_shooter(); 		break;
							}//end of switch
							combo_rehit_check (); //check to see if enough combos to relight video mode
							//we just returned from video mode so flash the lights before you fire the coil
							leff_start (LEFF_EJECT);
							task_sleep (TIME_500MS);

							sol_request (SOL_EJECT); //request to fire the eject sol
							task_create_gid1 (GID_EJECT_1, eject_switch_task);
							ball_search_monitor_start ();
							task_sleep (TIME_300MS);
			}//end of start video mode

			//NORMAL RETINA SCAN
			else if (!is_normal_eject_killed ) {
					if (!in_tilt) {
						score (EJECT_SCORE);
						//100k per jet hit here
						if (jet_shots_made > 0) {
							score_zero (temp_score);//zero out temp score
							score_add (temp_score, score_table[SC_100K]);//multiply 100K by jet count
							score_mul (temp_score, jet_shots_made);//multiply 100K by jet count
							score_long (temp_score); //add temp score to player's score
						}//end of if

						if (retina_scan_multiplier == 2)	{
							sound_start(ST_SPEECH, SPCH_DOUBLE_RETINA_SCAN, SL_4S, PRI_GAME_QUICK4);
							score (EJECT_SCORE);
							score_long (temp_score); //add temp score to player's score
						}

						leff_start (LEFF_EJECT);
						sound_start (ST_SAMPLE, RETINA_SCAN_LONG, SL_4S, PRI_GAME_QUICK1);
						deff_start (DEFF_EJECT_EFFECT);
						task_sleep (TIME_300MS);
					}//end of not in tilt
						sol_request (SOL_EJECT); //request to fire the eject sol
						task_create_gid1 (GID_EJECT_1, eject_switch_task);//this is a 2 second ball save timer for eject coil
			}//end of else !is_normal_eject_killed = FALSE
	}//end of else in game
}//end of function
示例#21
0
CALLSET_ENTRY (outhole, sw_outhole)
{
	/* Wait for ball to settle */
	task_sleep (TIME_100MS);
	sol_request (SOL_OUTHOLE);
}
示例#22
0
文件: device.c 项目: Dmilo/freewpc
/** The core function for handling a device.
 * This function is invoked (within its own task context) whenever
 * a switch closure occurs on a device, or when a request is made to
 * kick a ball from a device.
 * The update task will get killed and restarted anytime a switch
 * closure occurs on the device, so it may be interrupted, but only
 * while it is sleeping.
 */
void device_update (void)
{
	device_t *dev = &device_table[device_getgid () - DEVICE_GID_BASE];

wait_and_recount:
	/* We are really interested in the total count of the
	 * device, not which switches contributed to it.
	 * Since multiple switch transitions occur as a ball
	 * "slides through", don't act on a transition right
	 * away.  Instead, wait awhile until no further transitions
	 * occur, so that the count is stable.  If another closure on
	 * this device happens while we sleep here, this task will
	 * be killed and restarted.
	 */
	task_sleep (dev->props->settle_delay);

start_update:
	task_sleep (TIME_50MS);

	/* The device is probably stable now.  Poll all of the
	 * switches and recount */
	device_recount (dev);

	device_update_globals ();
	device_debug (dev);

	dbprintf ("Updating device %s\n", dev->props->name);

	/*****************************************
	 * Handle "count" changes
	 *****************************************/
	if (dev->state == DEV_STATE_IDLE)
	{
		/* Device is idle */
		if (dev->actual_count == dev->previous_count)
		{
			/* Switch closures were detected but in the end, after becoming
			 * stable, the count did not change.  This is OK, perhaps
			 * there is some vibration...
			 * Also, when transitioning back to idle after a device kick,
			 * we repoll the switches one extra time and if all is well,
			 * we'll end up here.
			 */
		}
		else if (dev->actual_count < dev->previous_count)
		{
			/* Also unusual in that a ball came out of the device without
			 * explicitly kicking it.  (Although this can happen in test mode.)
			 * Note that the number of balls in play went up */
			if (in_game)
			{
				device_call_op (dev, surprise_release);
				device_add_live ();
			}
		}
		else if (dev->actual_count > dev->previous_count)
		{
			/* More typical : when idle, the count should only go up.
			 * Treat this as an enter event (or multiple events, if the
			 * count goes up by more than 1). */
			U8 enter_count = dev->actual_count - dev->previous_count;
			if (!trough_dev_p (dev))
				set_valid_playfield ();
			while (enter_count > 0)
			{
				callset_invoke (any_device_enter);
				device_call_op (dev, enter);
				enter_count--;
			}
		}
	}
	else if ((dev->state == DEV_STATE_RELEASING) && (dev->kicks_needed > 0))
	{
		/* Device is in the middle of a release cycle.
		 * See if the count changed. */
		if (unlikely (dev->actual_count >= dev->previous_count))
		{
			/* After attempting a release, the count did not go down ... the kick
			 * probably failed, and we should retry up to a point.  Since dev->state
			 * is unchanged below, the kick attempt will get reinvoked. */

			/* Note: during multiball, it is possible for a second ball to enter
			the device immediately after the kick.  The kick didn't
			really fail, but there's no way to tell the difference. */

			dev->kick_errors++;
			dbprintf ("Kick error %d\n", dev->kick_errors);
			device_call_op (dev, kick_failure);

			if (dev->kick_errors == (trough_dev_p (dev) ? 20 : 7))
			{
				/* OK, we tried too many times and still no ball came out.
				 * Cancel all kick requests for this device unless it is
				 * the trough */
				nonfatal (ERR_FAILED_KICK);
				dev->kicks_needed = 0;
				dev->state = DEV_STATE_IDLE;
			}
			else
			{
				/* Wait awhile before trying again.  The more we fail, the longer
				the delay in between tries. */
				if (dev->kick_errors <= 3)
					task_sleep_sec (1);
				else
					task_sleep_sec (5);
			}
		}
		else if (dev->actual_count < dev->previous_count)
		{
			/* The count decreased as expected.
			 * As we only kick 1 ball at a time, then really it
			 * only should have gone down by 1, but the logic
			 * should work even if more than 1 ball is ejected. */
			U8 kicked_balls = dev->previous_count - dev->actual_count;

			/* If too many balls were kicked, throw an error.
			Only process as many as were requested. */
			if (kicked_balls > dev->kicks_needed)
			{
				nonfatal (ERR_KICK_TOO_MANY); /* akin to ERR_IDLE_BALL_LOST */
				kicked_balls = dev->kicks_needed;
			}

			/* Throw a kick success event for each ball that was kicked */
			while (kicked_balls > 0)
			{
				device_call_op (dev, kick_success);
				dev->kicks_needed--;
				kicked_balls--;
			}

			/* Go back to idle state.  If there are more kicks left, we will
			switch back to DEV_STATE_RELEASING again later.  The point is not to
			stay in DEV_STATE_RELEASING when we have not actually kicked the ball;
			if the request is held up for some reason, we want switch closures to
			be processed correctly. */
			dev->state = DEV_STATE_IDLE;
		}
	}

	/*****************************************
	 * Handle global count changes
	 *****************************************/
	device_update_globals ();

	/************************************************
	 * Handle counts larger than the device supports
	 ************************************************/
	if (dev->kicks_needed == 0 && dev->actual_count > dev->max_count)
	{
		/* When there are more balls in the device than we normally want
		to keep here, we must kick one of them out.  If multiple kicks
		are needed, this check will occur again in the future. */
		dev->kicks_needed++;
		dev->kick_errors = 0;
		/* TODO - device_request_kick (dev); would be more appropriate,
		 * but that doesn't work when called from device context due
		 * to live balls getting bumped */
	}

	/************************************************
	 * Handle kicking out balls
	 ************************************************/
	if (dev->kicks_needed > 0)
	{
		if (dev->actual_count == 0)
		{
			/* Container has fewer balls in it than we
			 * would like */
			dbprintf ("Can't kick when no balls available!\n");
			dev->kicks_needed = 0;
		}
		else if (kickout_locked_p () && !trough_dev_p (dev))
		{
			/* Container ready to kick, but 1 or more
			 * locks are held so we must wait. */
			goto start_update;
		}
		else if (!device_call_boolean_op (dev, kick_request))
		{
			/* Inform other modules that a kick was requested.
			These handlers can return FALSE to delay (but not
			cancel) the kick. */
			goto start_update;
		}
			/* TODO - if multiple devices want to kick at the same time,
			 * they should be staggered a bit.  Another case should be
			 * added here. */
		else
		{
			/* The container is ready to kick */

			/* Mark state as releasing if still idle */
			if (dev->state == DEV_STATE_IDLE)
				dev->state = DEV_STATE_RELEASING;

			/* TODO - keep track of all pending kick attempts.  Use a
			bit variable per device.  If other devices are in the
			process of kicking, wait */

			/* Generate events that a kick attempt is coming */
			callset_invoke (any_kick_attempt);
			device_call_op (dev, kick_attempt);

			/* Pulse the solenoid. */
			sol_request (dev->props->sol);

			/* In timed games, a device kick will pause the game timer.
			 * TODO : this should be a global event that other modules
			 * can catch as well.  Deal with this like we do slowtimers. */
			timed_game_pause (TIME_1S);

			/* We don't know if the kick was successful or not yet, so wait
			and see what happens. */
			goto wait_and_recount;
		}
	}

	/* Just before exiting this task, poll the switches one more time,
	and see if something has changed during the update.  It is important
	that no task context switches take place here, otherwise there would
	be a race condition where a switch closure gets missed. */
	device_recount (dev);
	if (dev->actual_count != dev->previous_count)
		goto start_update;

	task_exit ();
}