#include "global.h"
#include "Player.h"
#include "GameConstantsAndTypes.h"
#include "RageUtil.h"
#include "PrefsManager.h"
#include "GameConstantsAndTypes.h"
#include "GameManager.h"
#include "InputMapper.h"
#include "SongManager.h"
#include "GameState.h"
#include "ScoreKeeperMAX2.h"
#include "RageLog.h"
#include "RageMath.h"
#include "RageTimer.h"
#include "RageDisplay.h"
#include "ThemeManager.h"
#include "Combo.h"
#include "ScoreDisplay.h"
#include "LifeMeter.h"
#include "CombinedLifeMeter.h"
#include "PlayerAI.h"
#include "NoteFieldPositioning.h"
#include "NoteDataUtil.h"
#include "ScreenGameplay.h" /* for SM_ComboStopped */
#include "ScreenManager.h"
#include "StageStats.h"
#include "ArrowEffects.h"
#include "Game.h"
#include "NetworkSyncManager.h"	//used for sending timing offset
#include "DancingCharacters.h"

CachedThemeMetricF	GRAY_ARROWS_Y_STANDARD		("Player","ReceptorArrowsYStandard");
CachedThemeMetricF	GRAY_ARROWS_Y_REVERSE		("Player","ReceptorArrowsYReverse");

#define JUDGMENT_X( p, both_sides )			THEME->GetMetricF("Player",both_sides ? CString("JudgmentXOffsetBothSides") : ssprintf("JudgmentXOffsetOneSideP%d",p+1))
#define JUDGMENT_Y					THEME->GetMetricF("Player","JudgmentY")
#define JUDGMENT_Y_REVERSE				THEME->GetMetricF("Player","JudgmentYReverse")
#define COMBO_X( p, both_sides )			THEME->GetMetricF("Player",both_sides ? CString("ComboXOffsetBothSides") : ssprintf("ComboXOffsetOneSideP%d",p+1))
#define COMBO_Y						THEME->GetMetricF("Player","ComboY")
#define COMBO_Y_REVERSE					THEME->GetMetricF("Player","ComboYReverse")
#define ATTACK_DISPLAY_X( p, both_sides )		THEME->GetMetricF("Player",both_sides ? CString("AttackDisplayXOffsetBothSides") : ssprintf("AttackDisplayXOffsetOneSideP%d",p+1))
#define ATTACK_DISPLAY_Y				THEME->GetMetricF("Player","AttackDisplayY")
#define ATTACK_DISPLAY_Y_REVERSE			THEME->GetMetricF("Player","AttackDisplayYReverse")

// If we're using converge, the judgements end up on top of the receptor. So specify a new set of co-ordinates
// for when converge is used
#define JUDGMENT_Y_CENTERED				THEME->GetMetricF("Player","JudgmentYCentered")
#define COMBO_Y_CENTERED				THEME->GetMetricF("Player","ComboYCentered")

CachedThemeMetricF	HOLD_JUDGMENT_Y_STANDARD	("Player","HoldJudgmentYStandard");
CachedThemeMetricF	HOLD_JUDGMENT_Y_REVERSE		("Player","HoldJudgmentYReverse");
CachedThemeMetricF	HOLD_JUDGMENT_Y_CENTERED	("Player","HoldJudgmentYCentered");

CachedThemeMetricF	ROLL_JUDGMENT_Y_STANDARD	("Player","RollJudgmentYStandard");
CachedThemeMetricF	ROLL_JUDGMENT_Y_REVERSE		("Player","RollJudgmentYReverse");
CachedThemeMetricF	ROLL_JUDGMENT_Y_CENTERED	("Player","RollJudgmentYCentered");

CachedThemeMetricI	BRIGHT_GHOST_COMBO_THRESHOLD	("Player","BrightGhostComboThreshold");

CachedThemeMetricB	TAP_JUDGMENTS_UNDER_FIELD	("Player","TapJudgmentsUnderField");
CachedThemeMetricB	HOLD_JUDGMENTS_UNDER_FIELD	("Player","HoldJudgmentsUnderField");
CachedThemeMetricB	ROLL_JUDGMENTS_UNDER_FIELD	("Player","RollJudgmentsUnderField");
CachedThemeMetricB	COMBO_UNDER_FIELD		("Player","HoldJudgmentsUnderField");

#define START_DRAWING_AT_PIXELS				THEME->GetMetricI("Player","StartDrawingAtPixels")
#define STOP_DRAWING_AT_PIXELS				THEME->GetMetricI("Player","StopDrawingAtPixels")
#define MAX_PRO_TIMING_ERROR				THEME->GetMetricI("Player","MaxProTimingError")

// PIU HOLD DISABLER
//#define PIU_HOLDS					THEME->GetMetricB("Player","UsePIUStyleHolds")
#define PIU_HOLDS					false
#define DISABLE_PIU_HOLD_JUDGMENT			THEME->GetMetricB("Player","DisableOKandNGforPIUHolds")
#define	PIU_HOLD_DAMPER					THEME->GetMetricB("Player","UseDamperEffectAtStartOfPIUHolds")

CachedThemeMetricB	CUSTOM_COMBO_PER_ROW		("CustomScoring","ComboIsPerRow");
CachedThemeMetricB	MISS_COMBO_ON_BREAK		("Combo","MissComboIncreasesOnComboBreak");
CachedThemeMetricB	COMBO_BREAK_ON_GOOD		("Combo","ComboBreakOnGood");

CachedThemeMetricB	OVERRIDE_COMBO_INCREMENT	("ScoringOverride","OverrideComboIncrementValues");
CachedThemeMetricB	OVERRIDE_COMBO_PER_ROW		("ScoringOverride","ComboIsPerRow");

// Distance to search for a note in Step().
// Units?
static const float StepSearchDistanceBackwards = 1.0f;
static const float StepSearchDistanceForwards = 1.0f;

// The former is used only by a special condition for rolls
#define NON_ADJUSTED_WINDOW( judge ) (PREFSMAN->m_fJudgeWindowSeconds##judge)
#define ADJUSTED_WINDOW( judge ) ((PREFSMAN->m_fJudgeWindowSeconds##judge * PREFSMAN->m_fJudgeWindowScale) + PREFSMAN->m_fJudgeWindowAdd)

#define CHECKPOINT_DAMPER				ADJUSTED_WINDOW(Great)

PlayerMinus::PlayerMinus()
{
	GRAY_ARROWS_Y_STANDARD.Refresh();
	GRAY_ARROWS_Y_REVERSE.Refresh();
	HOLD_JUDGMENT_Y_STANDARD.Refresh();
	HOLD_JUDGMENT_Y_REVERSE.Refresh();
	ROLL_JUDGMENT_Y_STANDARD.Refresh();
	ROLL_JUDGMENT_Y_REVERSE.Refresh();
	BRIGHT_GHOST_COMBO_THRESHOLD.Refresh();
	TAP_JUDGMENTS_UNDER_FIELD.Refresh();
	HOLD_JUDGMENTS_UNDER_FIELD.Refresh();
	ROLL_JUDGMENTS_UNDER_FIELD.Refresh();
	COMBO_UNDER_FIELD.Refresh();

	HOLD_JUDGMENT_Y_CENTERED.Refresh();
	ROLL_JUDGMENT_Y_CENTERED.Refresh();

	CUSTOM_COMBO_PER_ROW.Refresh();
	MISS_COMBO_ON_BREAK.Refresh();
	COMBO_BREAK_ON_GOOD.Refresh();

	OVERRIDE_COMBO_INCREMENT.Refresh();
	OVERRIDE_COMBO_PER_ROW.Refresh();

	m_PlayerNumber = PLAYER_INVALID;
	m_fNoteFieldHeight = 0;

	m_pLifeMeter = NULL;
	m_pCombinedLifeMeter = NULL;
	m_pScoreDisplay = NULL;
	m_pSecondaryScoreDisplay = NULL;
	m_pPrimaryScoreKeeper = NULL;
	m_pSecondaryScoreKeeper = NULL;
	m_pInventory = NULL;
	
	m_iOffsetSample = 0;

	this->AddChild( &m_ArrowBackdrop );
	this->AddChild( &m_ProTimingDisplay );
	this->AddChild( &m_Combo );
	this->AddChild( &m_AttackDisplay );

	for( int c=0; c<MAX_NOTE_TRACKS; c++ )
	{
		this->AddChild( &m_HoldJudgment[c] );
		this->AddChild( &m_RollJudgment[c] );
	}


	PlayerAI::InitFromDisk();
}

PlayerMinus::~PlayerMinus()
{
}

void PlayerMinus::InitJudgment( PlayerNumber pn, Difficulty dc )
{
	m_Judgment.SetName( "Judgment" );
	m_Judgment.Load( dc );
	this->AddChild( &m_Judgment );
}

void PlayerMinus::Load( PlayerNumber pn, const NoteData* pNoteData, LifeMeter* pLM, CombinedLifeMeter* pCombinedLM, ScoreDisplay* pScoreDisplay, ScoreDisplay* pSecondaryScoreDisplay, Inventory* pInventory, ScoreKeeper* pPrimaryScoreKeeper, ScoreKeeper* pSecondaryScoreKeeper, NoteField* pNoteField )
{
	m_iDCState = AS2D_IDLE;
	//LOG->Trace( "PlayerMinus::Load()", );

	GAMESTATE->ResetNoteSkinsForPlayer( pn );
	
	m_PlayerNumber = pn;
	m_pLifeMeter = pLM;
	m_pCombinedLifeMeter = pCombinedLM;
	m_pScoreDisplay = pScoreDisplay;
	m_pSecondaryScoreDisplay = pSecondaryScoreDisplay;
	m_pInventory = pInventory;
	m_pPrimaryScoreKeeper = pPrimaryScoreKeeper;
	m_pSecondaryScoreKeeper = pSecondaryScoreKeeper;
	m_pNoteField = pNoteField;
	m_iRowLastCrossed = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ) - 1;	// why this?
	m_iMineRowLastCrossed = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ) - 1;	// why this?
	m_iPIUHoldHeadRowLastCrossed = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ) - 1;	// why this?

	// Ensure that this is up-to-date.
	GAMESTATE->m_pPosition->Load(pn);

	const Style* pStyle = GAMESTATE->GetCurrentStyle();

	// init scoring
	NoteDataWithScoring::Init();

	// copy note data
	this->CopyAll( pNoteData );
	if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY  &&  g_CurStageStats.bFailed[pn] )	// Oni dead
		this->ClearAll();

	/* The editor reuses Players ... so we really need to make sure everything
	 * is reset and not tweening.  Perhaps ActorFrame should recurse to subactors;
	 * then we could just this->StopTweening()? -glenn */
	m_Judgment.StopTweening();
//	m_Combo.Reset();				// don't reset combos between songs in a course!
	m_Combo.Init( pn );
	m_Combo.SetCombo( g_CurStageStats.iCurCombo[m_PlayerNumber], g_CurStageStats.iCurMissCombo[m_PlayerNumber]  );	// combo can persist between songs and games
	m_AttackDisplay.Init( pn );
	m_Judgment.Reset();

	// Don't re-init this; that'll reload graphics.  Add a separate Reset() call
	// if some ScoreDisplays need it.
//	if( m_pScore )
//		m_pScore->Init( pn );

	// Apply transforms.
	NoteDataUtil::TransformNoteData( *this, GAMESTATE->m_PlayerOptions[pn], GAMESTATE->GetCurrentStyle()->m_StepsType );
	
	switch( GAMESTATE->m_PlayMode )
	{
		case PLAY_MODE_RAVE:
		case PLAY_MODE_BATTLE:
			{
				// ugly, ugly, ugly.  Works only w/ dance.

				// Because of this introducing bugs in other games, limit it to dance modes only.
				// Hopefully someone can fix this later. ~ Mike
				if( GAMESTATE->m_pCurGame == GAMEMAN->GetDefaultGame() && PREFSMAN->m_bDanceRaveShufflesNotes )	// Default game is GAME_DANCE
				{
					NoteDataUtil::TransformNoteData( *this, GAMESTATE->m_PlayerOptions[pn], GAMESTATE->GetCurrentStyle()->m_StepsType );
					
					// shuffle either p1 or p2
					static int count = 0;
					switch( count )
					{
					case 0:
					case 3:
						NoteDataUtil::Turn( *this, STEPS_TYPE_DANCE_SINGLE, NoteDataUtil::left);
						break;
					case 1:
					case 2:
						NoteDataUtil::Turn( *this, STEPS_TYPE_DANCE_SINGLE, NoteDataUtil::right);
						break;
					default:
						ASSERT(0);
					}
					count++;
					count %= 4;
				}
			}
			break;
	}

	int iStartDrawingAtPixels = GAMESTATE->m_bEditing ? -100 : START_DRAWING_AT_PIXELS;
	int iStopDrawingAtPixels = GAMESTATE->m_bEditing ? 400 : STOP_DRAWING_AT_PIXELS;

	m_ArrowBackdrop.Unload();
	CString BackdropName = g_NoteFieldMode[pn].m_Backdrop;

	if( !BackdropName.empty() )
		m_ArrowBackdrop.LoadFromAniDir( THEME->GetPathToB( BackdropName ) );

	float fNoteFieldMidde = (GRAY_ARROWS_Y_STANDARD+GRAY_ARROWS_Y_REVERSE)/2;
	m_pNoteField->SetY( fNoteFieldMidde );
	m_fNoteFieldHeight = GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD;
	m_pNoteField->Load( this, pn, iStartDrawingAtPixels, iStopDrawingAtPixels, m_fNoteFieldHeight );
	m_ArrowBackdrop.SetPlayer( pn );

	const bool bReverse = GAMESTATE->m_PlayerOptions[pn].GetReversePercentForColumn(0) == 1;
	const bool bCentered = GAMESTATE->m_PlayerOptions[pn].m_fScrolls[PlayerOptions::SCROLL_CENTERED] == 1;

	bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyle()->m_StyleType==Style::ONE_PLAYER_TWO_CREDITS;
	
	m_AttackDisplay.SetX( ATTACK_DISPLAY_X(m_PlayerNumber,bPlayerUsingBothSides) );
	m_AttackDisplay.SetY( bReverse ? ATTACK_DISPLAY_Y_REVERSE : ATTACK_DISPLAY_Y );

	m_Judgment.SetX( JUDGMENT_X(m_PlayerNumber,bPlayerUsingBothSides) );
	m_Combo.SetX( COMBO_X(m_PlayerNumber,bPlayerUsingBothSides) );
	
	if( bReverse )
	{
		m_Judgment.SetY( JUDGMENT_Y_REVERSE );
		m_Combo.SetY( COMBO_Y_REVERSE );
	}
	else if( bCentered)
	{
		m_Judgment.SetY( JUDGMENT_Y_CENTERED );
		m_Combo.SetY( COMBO_Y_CENTERED );
	}
	else
	{
		m_Judgment.SetY( JUDGMENT_Y );
		m_Combo.SetY( COMBO_Y);
	}

	m_ProTimingDisplay.SetX( JUDGMENT_X(m_PlayerNumber,bPlayerUsingBothSides) );
	m_ProTimingDisplay.SetY( bReverse ? SCREEN_BOTTOM-JUDGMENT_Y : SCREEN_TOP+JUDGMENT_Y );

	// These commands add to the above positioning, and are usually empty.
	m_Judgment.Command( g_NoteFieldMode[pn].m_JudgmentCmd );
	m_ProTimingDisplay.Command( g_NoteFieldMode[pn].m_JudgmentCmd );
	m_Combo.Command( g_NoteFieldMode[pn].m_ComboCmd );
	m_AttackDisplay.Command( g_NoteFieldMode[pn].m_AttackDisplayCmd );

	int c;
	for( c=0; c<pStyle->m_iColsPerPlayer; c++ )
	{
		NoteFieldMode &mode = g_NoteFieldMode[pn];
		m_HoldJudgment[c].Command( mode.m_HoldJudgmentCmd[c] );
		m_RollJudgment[c].Command( mode.m_RollJudgmentCmd[c] );
	}

	// Need to set Y positions of all these elements in Update since
	// they change depending on PlayerOptions.
	RageSoundParams p;
	m_soundMine.Load( THEME->GetPathToS("Player mine"), true );

	// Attacks can be launched in course modes and in battle modes.  They both come
	// here to play, but allow loading a different sound for different modes.
	switch( GAMESTATE->m_PlayMode )
	{
	case PLAY_MODE_RAVE:
	case PLAY_MODE_BATTLE:
		m_soundAttackLaunch.Load( THEME->GetPathToS("Player battle attack launch"), true );
		m_soundAttackEnding.Load( THEME->GetPathToS("Player battle attack ending"), true );
		break;
	default:
		m_soundAttackLaunch.Load( THEME->GetPathToS("Player course attack launch"), true );
		m_soundAttackEnding.Load( THEME->GetPathToS("Player course attack ending"), true );
		break;
	}

	if( GAMESTATE->GetNumPlayersEnabled() == 2 )
	{
		// Two players are active.  Play sounds on this player's side.
		p.m_Balance = (m_PlayerNumber == PLAYER_1)? -1.0f:1.0f;
	}
	m_soundMine.SetParams( p );
	m_soundAttackLaunch.SetParams( p );
	m_soundAttackEnding.SetParams( p );

	m_bActiveRandomAttack = false;
	m_fActiveRandomAttackStart = 0.0f;

	// Set up random/mine attack array
	for( unsigned i=0; i < 5; i++ )
		m_iLastUsedAttacks[i] = -1;

	for( int i=0; i < GetNumHoldNotes(); i++ )
	{
		const HoldNote &hn = GetHoldNote(i);
		m_bHoldColumnButtonDown[hn] = false;
	}
	
	//m_iNumNotesThisCheckpoint = 0;
	m_iNumHoldsThisCheckpoint = 0;

	// Reset these vectors
	for( unsigned i=0; i < PIU_HOLD_CHECK_ADVANCE; i++ )
	{
		//m_iNumNotesNextCheckpoint[i] = 0;
		m_iNumHoldsNextCheckpoint[i] = 0;
		m_iNextCheckpointRow[i] = 0;
	}

	//m_iNumHitNotesForCheckpoint = 0;
	m_iNumHitHoldsForCheckpoint = 0;

	m_bNeedCheckpoint = false;
	m_bPrepareNextCheckpoint = false;
	m_bInitialCheckpointSet = false;
	m_bCheckpointAlreadyChecked = true;

	// Tickcount segments
	m_iCurrentTickSegment = CurrentSegmentForPreloading( BeatToNoteRow(GAMESTATE->m_fSongBeat) );
}

void PlayerMinus::Update( float fDeltaTime )
{
	//LOG->Trace( "PlayerMinus::Update(%f)", fDeltaTime );

	if( GAMESTATE->m_pCurSong==NULL )
		return;

	if( GAMESTATE->m_bAttackBeganThisUpdate[m_PlayerNumber] && PREFSMAN->m_bPlayAttackSounds )
		m_soundAttackLaunch.Play();
	if( GAMESTATE->m_bAttackEndedThisUpdate[m_PlayerNumber] && PREFSMAN->m_bPlayAttackSounds )
		m_soundAttackEnding.Play();

	//float fPositionSeconds = GAMESTATE->m_fMusicSeconds;

	//
	// Random Attack Mod
	//
	if( GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_bRandomAttacks )
	{
		float fCurrentGameTime = g_CurStageStats.fGameplaySeconds;
		float fAttackRunTime = PREFSMAN->m_fRandomAttackLength;

		// Don't start until 1 seconds into game, minimum
		if( fCurrentGameTime > 1)
		{
			// Update the attack if there are no others currently running
			if( !m_bActiveRandomAttack )
			{
				m_bActiveRandomAttack = true;
				m_fActiveRandomAttackStart = fCurrentGameTime;

				Attack attRandomAttack;
				attRandomAttack.sModifier = ApplyRandomAttack();
				attRandomAttack.fSecsRemaining = fAttackRunTime;
				GAMESTATE->LaunchAttack( m_PlayerNumber, attRandomAttack);
			}

			// Check to see when we should disable the flag for an active random attack
			// Note that we have a new one activate a little early; this is to have a bit of overlap ratehr than
			// an abrupt change
			if( (fCurrentGameTime - m_fActiveRandomAttackStart) > (fAttackRunTime - 0.5) )
				m_bActiveRandomAttack = false;
		}
	}

	const float fSongBeat = GAMESTATE->m_fSongBeat;
	const int iSongRow = BeatToNoteRow( fSongBeat );

	m_pNoteField->Update( fDeltaTime );

	//
	// Update Y positions
	//
	{
		for( int c=0; c<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; c++ )
		{
			float fPercentReverse = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].GetReversePercentForColumn(c);
			bool bReverse = fPercentReverse != 0;

			float fPercentCentered = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fScrolls[PlayerOptions::SCROLL_CENTERED];
			bool bCentered = fPercentCentered != 0;

			float fHoldJudgeYPos;
			float fRollJudgeYPos;

			if( bReverse )
			{
				fHoldJudgeYPos = SCALE( fPercentReverse, 0.f, 1.f, HOLD_JUDGMENT_Y_STANDARD, HOLD_JUDGMENT_Y_REVERSE );
				fRollJudgeYPos = SCALE( fPercentReverse, 0.f, 1.f, ROLL_JUDGMENT_Y_STANDARD, ROLL_JUDGMENT_Y_REVERSE );
				// float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_REVERSE );
			}
			else if( bCentered )
			{
				fHoldJudgeYPos = SCALE( fPercentCentered, 0.f, 1.f, HOLD_JUDGMENT_Y_STANDARD, HOLD_JUDGMENT_Y_CENTERED );
				fRollJudgeYPos = SCALE( fPercentCentered, 0.f, 1.f, ROLL_JUDGMENT_Y_STANDARD, ROLL_JUDGMENT_Y_CENTERED );
				// float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_CENTERED );
			}
			else
			{
				fHoldJudgeYPos = HOLD_JUDGMENT_Y_STANDARD;
				fRollJudgeYPos = ROLL_JUDGMENT_Y_STANDARD;
				// float fGrayYPos = GRAY_ARROWS_Y_STANDARD;
			}

			const float fX = ArrowGetXPos( m_PlayerNumber, c, 0 );
			const float fZ = ArrowGetZPos( m_PlayerNumber, c, 0 );

			m_HoldJudgment[c].SetX( fX );
			m_HoldJudgment[c].SetY( fHoldJudgeYPos );
			m_HoldJudgment[c].SetZ( fZ );

			m_RollJudgment[c].SetX( fX );
			m_RollJudgment[c].SetY( fRollJudgeYPos );
			m_RollJudgment[c].SetZ( fZ );
		}
	}

	float fPercentReverse = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].GetReversePercentForColumn(0);
	float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_REVERSE );
	m_ArrowBackdrop.SetY( fGrayYPos );

	//
	// Mini & Tiny Mods
	//
	float fMiniPercent = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fEffects[PlayerOptions::EFFECT_MINI];
	float fTinyPercent = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fEffects[PlayerOptions::EFFECT_TINY];

	float fJudgmentZoom = min( powf(0.5f, fMiniPercent+fTinyPercent), 1.0f );
	m_Judgment.SetZoom( fJudgmentZoom );
	m_pNoteField->SetZoom( fJudgmentZoom );

	//
	// Check for TapNote misses
	//
	UpdateTapNotesMissedOlderThan( GetMaxStepDistanceSeconds() );

	//
	// update pressed flag
	//
	const int iNumCols = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer;
	ASSERT_M( iNumCols < MAX_COLS_PER_PLAYER, ssprintf("%i >= %i", iNumCols, MAX_COLS_PER_PLAYER) );
	for( int col=0; col < iNumCols; ++col )
	{
		CHECKPOINT_M( ssprintf("%i %i", col, iNumCols) );

		const StyleInput StyleI( m_PlayerNumber, col );
		const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI );
		bool bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI );

		// TODO: Make this work for non-human-controlled players
		if( bIsHoldingButton && !GAMESTATE->m_bDemonstrationOrJukebox && GAMESTATE->m_PlayerController[m_PlayerNumber]==PC_HUMAN )
			m_pNoteField->SetPressed( col );
	}

	//
	// update hold checkpoints
	//
	if( PIU_HOLDS )
	{
		m_bNeedCheckpoint = false;

		//m_iNumNotesThisCheckpoint = 0;
		m_iNumHoldsThisCheckpoint = 0;

		//m_iNumHitNotesForCheckpoint = 0;
		m_iNumHitHoldsForCheckpoint = 0;

		// Set the checkpoints up starting from row zero
		if( !m_bInitialCheckpointSet )
		{
			// Set all of the checkpoints in the initial part of the song
			SetInitialCheckpoints( 0 );

			m_bInitialCheckpointSet = true;
		}
			
		// Check to see if we need a checkpoint
		if( iSongRow >= m_iNextCheckpointRow[0] )
			m_bNeedCheckpoint = true;

		if( m_bNeedCheckpoint )
		{
			m_bCheckpointAlreadyChecked = false;

			// Take the data for this checkpoint from the start of the array; everything will slowly be bumped forward in it
			//m_iNumNotesThisCheckpoint = m_iNumNotesNextCheckpoint[0];
			m_iNumHoldsThisCheckpoint = m_iNumHoldsNextCheckpoint[0];

			// Don't bother checking this checkpoint if there isn't a hold note to check for!
			if( m_iNumHoldsThisCheckpoint == 0 )
				m_bCheckpointAlreadyChecked = true;

			m_bPrepareNextCheckpoint = true;
		}

		if( m_bPrepareNextCheckpoint )
			UpdateCheckpoints();
	}

	//
	// Update PIU Hold Heads
	//
	if( PIU_HOLDS )
	{
		// TRICKY: 
		float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
		const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : 0;
		const int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
		
		if( iRowNow >= 0 )
		{
			for( ; m_iPIUHoldHeadRowLastCrossed <= iRowNow; m_iPIUHoldHeadRowLastCrossed++ )  // for each index we crossed since the last update
			{
				if( GAMESTATE->IsPlayerEnabled(m_PlayerNumber) )
					CrossedPIUHoldHeadRow( m_iPIUHoldHeadRowLastCrossed );
			}
		}
	}
	
	//
	// update HoldNotes logic
	//

	// We're using Pump It Up style holds
	if( PIU_HOLDS )
	{
		UpdatePIUHoldNotes( fDeltaTime, iSongRow );
		UpdatePIUHoldScoring();
	}

	// We're using DDR style holds
	if( !PIU_HOLDS )	
		UpdateHoldNotes( fDeltaTime, iSongRow );

	//
	// update RollNotes logic
	//
	UpdateRollNotes( fDeltaTime, iSongRow );

	//
	// Update non-holds and non-rolls
	//
	{
		// Why was this originally "BeatToNoteRowNotRounded"?  It should be rounded.  -Chris
		/* We want to send the crossed row message exactly when we cross the row--not
		 * .5 before the row.  Use a very slow song (around 2 BPM) as a test case: without
		 * rounding, autoplay steps early. -glenn */
		const int iRowNow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat );

		if( iRowNow >= 0 )
		{
			for( ; m_iRowLastCrossed <= iRowNow; m_iRowLastCrossed++ )  // for each index we crossed since the last update
			{
				if( GAMESTATE->IsPlayerEnabled(m_PlayerNumber) )
					CrossedRow( m_iRowLastCrossed );
			}
		}
	}

	//
	// Update mines
	//
	{
		// TRICKY: 
		float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
		fPositionSeconds -= PREFSMAN->m_fPadStickSeconds;
		const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : 0;
		const int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
		
		if( iRowNow >= 0 )
		{
			for( ; m_iMineRowLastCrossed <= iRowNow; m_iMineRowLastCrossed++ )  // for each index we crossed since the last update
			{
				if( GAMESTATE->IsPlayerEnabled(m_PlayerNumber) )
					CrossedMineRow( m_iMineRowLastCrossed );
			}
		}
	}

	// process transforms that are waiting to be applied
	ApplyWaitingTransforms();

	/* Cache any newly-used note skins.  Normally, the only new skins cached now are
	 * when we're adding course modifiers at the start of a song.  If this is spending
	 * time loading skins in the middle of a song, something is wrong. */
	m_pNoteField->CacheAllUsedNoteSkins();

	ActorFrame::Update( fDeltaTime );
}

void PlayerMinus::ApplyWaitingTransforms()
{
	GAMESTATE->m_bTransformsFromAttacks = true;

	for( unsigned j=0; j<GAMESTATE->m_ModsToApply[m_PlayerNumber].size(); j++ )
	{
		const Attack &mod = GAMESTATE->m_ModsToApply[m_PlayerNumber][j];
		PlayerOptions po;
		// Should this default to "" always? need it blank so we know if mod.sModifier changes the note skin.
		po.m_sNoteSkin = "";
		po.FromString( mod.sModifier );

		float fStartBeat, fEndBeat;
		mod.GetAttackBeats( GAMESTATE->m_pCurSong, m_PlayerNumber, fStartBeat, fEndBeat );
		fEndBeat = min( fEndBeat, GetNumBeats() );

		LOG->Trace( "Applying transform '%s' from %f to %f to '%s'", mod.sModifier.c_str(), fStartBeat, fEndBeat,
			GAMESTATE->m_pCurSong->GetTranslitMainTitle().c_str() );
		if( po.m_sNoteSkin != "" )
			GAMESTATE->SetNoteSkinForBeatRange( m_PlayerNumber, po.m_sNoteSkin, fStartBeat, fEndBeat );

		NoteDataUtil::TransformNoteData( *this, po, GAMESTATE->GetCurrentStyle()->m_StepsType, fStartBeat, fEndBeat );
		m_pNoteField->CopyRange( this, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), BeatToNoteRow(fStartBeat) );
	}
	GAMESTATE->m_ModsToApply[m_PlayerNumber].clear();

	GAMESTATE->m_bTransformsFromAttacks = false;
}

void PlayerMinus::DrawPrimitives()
{
	// May have both players in doubles (for battle play); only draw primary player.
	if( GAMESTATE->GetCurrentStyle()->m_StyleType == Style::ONE_PLAYER_TWO_CREDITS  &&
		m_PlayerNumber != GAMESTATE->m_MasterPlayerNumber )
		return;

	// Draw these below everything else.
	m_ArrowBackdrop.Draw();
	m_AttackDisplay.Draw();

	if( TAP_JUDGMENTS_UNDER_FIELD )
		DrawTapJudgments();

	if( HOLD_JUDGMENTS_UNDER_FIELD )
		DrawHoldJudgments();

	if( ROLL_JUDGMENTS_UNDER_FIELD )
		DrawRollJudgments();

	if( COMBO_UNDER_FIELD )
		if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind == 0 )
			m_Combo.Draw();

	float fTilt = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fPerspectiveTilt;
	float fSkew = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fSkew;
	bool bReverse = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].GetReversePercentForColumn(0)>0.5;


	DISPLAY->CameraPushMatrix();
	DISPLAY->PushMatrix();

	float fCenterY = (GRAY_ARROWS_Y_STANDARD+GRAY_ARROWS_Y_REVERSE)/2;
//	float fHeight = GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD;

	DISPLAY->LoadMenuPerspective( 45, SCALE(fSkew,0.f,1.f,this->GetX(),CENTER_X), fCenterY );

	float fOriginalY = 	m_pNoteField->GetY();
	float fTiltDegrees = SCALE(fTilt,-1.f,+1.f,+30,-30) * (bReverse?-1:1);
	float fZoom = SCALE( GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fEffects[PlayerOptions::EFFECT_TINY], 0.f, 1.f, 1.f, 0.5f );
	
	if( fTilt > 0 )
		fZoom *= SCALE( fTilt, 0.f, 1.f, 1.f, 0.9f );
	else
		fZoom *= SCALE( fTilt, 0.f, -1.f, 1.f, 0.9f );

	float fYOffset;
	
	if( fTilt > 0 )
		fYOffset = SCALE( fTilt, 0.f, 1.f, 0.f, -45.f ) * (bReverse?-1:1);
	else
		fYOffset = SCALE( fTilt, 0.f, -1.f, 0.f, -20.f ) * (bReverse?-1:1);

	m_pNoteField->SetY( fOriginalY + fYOffset );
	m_pNoteField->SetZoom( fZoom );
	m_pNoteField->SetRotationX( fTiltDegrees );
	m_pNoteField->Draw();

	m_pNoteField->SetY( fOriginalY );

	DISPLAY->CameraPopMatrix();
	DISPLAY->PopMatrix();

	if( !TAP_JUDGMENTS_UNDER_FIELD )
		DrawTapJudgments();

	if( !TAP_JUDGMENTS_UNDER_FIELD )
		DrawHoldJudgments();

	if( !TAP_JUDGMENTS_UNDER_FIELD )
		DrawRollJudgments();

	if( !COMBO_UNDER_FIELD )
		if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind == 0 )
			m_Combo.Draw();
}

void PlayerMinus::DrawTapJudgments()
{
	if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind > 0 )
		return;

	if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_bProTiming )
		m_ProTimingDisplay.Draw();
	else
		m_Judgment.Draw();
}

void PlayerMinus::DrawHoldJudgments()
{
	if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind > 0 )
		return;

	for( int c=0; c<GetNumTracks(); c++ )
	{
		g_NoteFieldMode[m_PlayerNumber].BeginDrawTrack(c);

		m_HoldJudgment[c].Draw();

		g_NoteFieldMode[m_PlayerNumber].EndDrawTrack(c);
	}
}

void PlayerMinus::DrawRollJudgments()
{
	if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind > 0 )
		return;

	for( int c=0; c<GetNumTracks(); c++ )
	{
		g_NoteFieldMode[m_PlayerNumber].BeginDrawTrack(c);

		m_RollJudgment[c].Draw();

		g_NoteFieldMode[m_PlayerNumber].EndDrawTrack(c);
	}
}

// It's OK for this function to search a little more than was requested.
int PlayerMinus::GetClosestNoteDirectional( int col, float fBeat, float fMaxBeatsDistance, int iDirection  ) const
{
	// look for the closest matching step
	const int iIndexStartLookingAt = BeatToNoteRow( fBeat );

	// Number of elements to examine on either end of iIndexStartLookingAt. Make sure we always round up.
	const int iNumElementsToExamine = BeatToNoteRow( fMaxBeatsDistance + 1 );

	// For skipped section check
	const TimingData timingSeg = GAMESTATE->m_pCurSong->m_Timing;

	// Start at iIndexStartLookingAt and search outward.
	for( int delta=0; delta < iNumElementsToExamine; delta++ )
	{
		int iCurrentIndex = iIndexStartLookingAt + (iDirection * delta);

		if( iCurrentIndex < 0) 
			continue;
		if( GetTapNote(col, iCurrentIndex).type == TapNote::empty) 
			continue;	// no note here
		if( GetTapNoteScore(col, iCurrentIndex) != TNS_NONE ) 
			continue;	// this note has a score already

		return iCurrentIndex;
	}

	return -1;
}

int PlayerMinus::GetClosestNote( int col, float fBeat, float fMaxBeatsAhead, float fMaxBeatsBehind ) const
{
	int Fwd = GetClosestNoteDirectional(col, fBeat, fMaxBeatsAhead, 1);
	int Back = GetClosestNoteDirectional(col, fBeat, fMaxBeatsBehind, -1);

	if(Fwd == -1 && Back == -1) return -1;
	if(Fwd == -1) return Back;
	if(Back == -1) return Fwd;

	// Figure out which row is closer.
	const float DistToFwd = fabsf(fBeat-NoteRowToBeat(Fwd));
	const float DistToBack = fabsf(fBeat-NoteRowToBeat(Back));
	
	if( DistToFwd > DistToBack )
		return Back;

	return Fwd;
}

void PlayerMinus::Step( int col, RageTimer tm )
{
	if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY  &&  g_CurStageStats.bFailed[m_PlayerNumber] )	// Oni dead
		return;	// do nothing

	//LOG->Trace( "PlayerMinus::HandlePlayerStep()" );

	ASSERT( col >= 0  &&  col <= GetNumTracks() );

	float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
	fPositionSeconds -= tm.Ago();
	const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat;

	//
	// Check for step on a TapNote
	//
	int iIndexOverlappingNote = GetClosestNote( col, fSongBeat, 
						   StepSearchDistanceForwards * GAMESTATE->m_fCurBPS * GAMESTATE->m_SongOptions.m_fMusicRate,
						   StepSearchDistanceBackwards * GAMESTATE->m_fCurBPS * GAMESTATE->m_SongOptions.m_fMusicRate );
	
	// Determine TapNoteType
	TapNote tn = GetTapNote(col,iIndexOverlappingNote);

	// Calculate TapNoteScore
	TapNoteScore score = TNS_NONE;

	if( iIndexOverlappingNote != -1)
	{
		// compute the score for this hit
		const float fStepBeat = NoteRowToBeat( (float)iIndexOverlappingNote );
		const float fStepSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fStepBeat);

		/* We actually stepped on the note this long ago: */
		const float fTimeSinceStep = tm.Ago();

		/* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure
		* out what the music time is as of now. */
		const float fCurrentMusicSeconds = GAMESTATE->m_fMusicSeconds + (GAMESTATE->m_LastBeatUpdate.Ago()*GAMESTATE->m_SongOptions.m_fMusicRate);

		/* ... which means it happened at this point in the music: */
		const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.m_fMusicRate;

		// The offset from the actual step in seconds:
		float fNoteOffset = (fStepSeconds - fMusicSeconds) / GAMESTATE->m_SongOptions.m_fMusicRate;   // account for music rate

		const float fSecondsFromPerfect = fabsf( fNoteOffset );
		bool bSteppedEarly = -fNoteOffset < 0;

		switch( GAMESTATE->m_PlayerController[m_PlayerNumber] )
		{
		case PC_HUMAN:
			switch( tn.type )
			{
			case TapNote::lift: // So, you've stepped directly onto a lift note...
				if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Marvelous) )  
					score = TNS_MISS;
				break;
			case TapNote::mine:
				// stepped too close to mine?
				if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Mine) )
				{
					score = TNS_HIT_MINE;

					if( PREFSMAN->m_bPlayMineSound )
						m_soundMine.Play();

					// Attack Mines!
					if( GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_bTransforms[PlayerOptions::TRANSFORM_ATTACK_MINES] )
					{
						Attack attMineAttack;
						attMineAttack.sModifier = ApplyRandomAttack();
						attMineAttack.level = (AttackLevel)1;
						attMineAttack.fStartSecond = -1;
						attMineAttack.fSecsRemaining = PREFSMAN->m_fAttackMinesLength;
						GAMESTATE->LaunchAttack( m_PlayerNumber, attMineAttack);
					}
					else
					{
						if( m_pLifeMeter )
							m_pLifeMeter->ChangeLifeMine();
						if( m_pCombinedLifeMeter )
							m_pCombinedLifeMeter->ChangeLifeMine(m_PlayerNumber);
					}
				m_pNoteField->SetTapNote(col, iIndexOverlappingNote, TAP_EMPTY);    // remove from NoteField
				m_pNoteField->DidTapNote( col, score, false );
				}
				break;
			case TapNote::attack:
				score = TNS_NONE;   // don't score this as anything

				if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Attack) )
				{
					m_soundAttackLaunch.Play();
					// put attack in effect
					Attack attack = this->GetAttackAt( col, iIndexOverlappingNote );
					GAMESTATE->LaunchAttack( OPPOSITE_PLAYER[m_PlayerNumber], attack );
				
					// remove all TapAttacks on this row
					for( int t=0; t<this->GetNumTracks(); t++ )
					{
						TapNote tn = this->GetTapNote(t, iIndexOverlappingNote);

						if( tn.type == TapNote::attack )
						{
							this->SetTapNote(col, iIndexOverlappingNote, TAP_EMPTY);    // remove from NoteField
							m_pNoteField->SetTapNote(col, iIndexOverlappingNote, TAP_EMPTY);    // remove from NoteField
						}
					}
				}
				break;
			case TapNote::hidden:
				if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Hidden) )
				{
					score = TNS_HIDDEN;
					bSteppedEarly = false;
				}
				break;
			default:
				if(      fSecondsFromPerfect <= ADJUSTED_WINDOW(Marvelous) )    score = TNS_MARVELOUS;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Perfect) )      score = TNS_PERFECT;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Great) )        score = TNS_GREAT;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Good) )         score = TNS_GOOD;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Boo) )          score = TNS_BOO;
				else    score = TNS_NONE;
				break;
			}
			break;
	        
		case PC_CPU:
		case PC_AUTOPLAY:
			switch( GAMESTATE->m_PlayerController[m_PlayerNumber] )
			{
			case PC_CPU:
				if( tn.type != TapNote::hidden )	// Computer ignores hidden notes
				{
					score = PlayerAI::GetTapNoteScore( m_PlayerNumber );

					// This generates computer early/late markers
					if( score != TNS_MARVELOUS )
					{
						float fRand = randomf(0,1);

						if( fRand < 0.5f )
							bSteppedEarly = true;
						else
							bSteppedEarly = false;
					}
				}
				break;
			case PC_AUTOPLAY:
				if( tn.type != TapNote::hidden )	// Computer ignores hidden notes
					score = TNS_MARVELOUS;
				break;
			}

			// TRICKY:  We're asking the AI to judge mines. Consider TNS_GOOD and below
			// as "mine was hit" and everything else as "mine was avoided"
			if( tn.type == TapNote::mine )
			{
				// The CPU hits a lot of mines.  Only consider hitting the 
				// first mine for a row.  We know we're the first mine if 
				// there are are no mines to the left of us.
				for( int t=0; t<col; t++ )
				{
					if( GetTapNote(t,iIndexOverlappingNote).type == TapNote::mine ) // there's a mine to the left of us
						return; // avoid
				}

				// The CPU hits a lot of mines.  Make it less likely to hit 
				// mines that don't have a tap note on the same row.
				bool bTapsOnRow = IsThereATapOrHoldOrRollHeadAtRow( iIndexOverlappingNote );
				TapNoteScore get_to_avoid = bTapsOnRow ? TNS_GREAT : TNS_GOOD;

				if( score >= get_to_avoid )
					return; // avoided
				else
				{
					score = TNS_HIT_MINE;

					if( PREFSMAN->m_bPlayMineSound )
						m_soundMine.Play();

					// Attack Mines!
					if( GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_bTransforms[PlayerOptions::TRANSFORM_ATTACK_MINES] )
					{
						Attack attMineAttack;
						attMineAttack.sModifier = ApplyRandomAttack();
						attMineAttack.level = (AttackLevel)1;
						attMineAttack.fStartSecond = -1;
						attMineAttack.fSecsRemaining = PREFSMAN->m_fAttackMinesLength;
						GAMESTATE->LaunchAttack( m_PlayerNumber, attMineAttack);
					}
					else
					{
						if( m_pLifeMeter )
							m_pLifeMeter->ChangeLifeMine();
						if( m_pCombinedLifeMeter )
							m_pCombinedLifeMeter->ChangeLifeMine(m_PlayerNumber);

						m_pNoteField->SetTapNote(col, iIndexOverlappingNote, TAP_EMPTY);    // remove from NoteField
						m_pNoteField->DidTapNote( col, score, false );
					}
				}
			}

			/* AI will generate misses here.  Don't handle a miss like a regular note because
			* we want the judgment animation to appear delayed.  Instead, return early if
			* AI generated a miss, and let UpdateMissedTapNotesOlderThan() detect and handle the 
			* misses. */
			if( score == TNS_MISS || score == TNS_MISS_HIDDEN )
				return;

			if( tn.type == TapNote::attack && score > TNS_GOOD )
			{
				m_soundAttackLaunch.Play();
				score = TNS_NONE;   // don't score this as anything
	                
				// put attack in effect
				Attack attack = this->GetAttackAt( col, iIndexOverlappingNote );
				GAMESTATE->LaunchAttack( OPPOSITE_PLAYER[m_PlayerNumber], attack );
	                
				// remove all TapAttacks on this row
				for( int t=0; t<this->GetNumTracks(); t++ )
				{
					TapNote tn = this->GetTapNote(t, iIndexOverlappingNote);

					if( tn.type == TapNote::attack )
					{
						this->SetTapNote(col, iIndexOverlappingNote, TAP_EMPTY);    // remove from NoteField
						m_pNoteField->SetTapNote(col, iIndexOverlappingNote, TAP_EMPTY);    // remove from NoteField
					}
				}
			}
			break;
			
		default:
			ASSERT(0);
			score = TNS_NONE;
			break;
		}

		// Do game-specific score mapping.
		const Game* pGame = GAMESTATE->GetCurrentGame();
		if( score == TNS_MARVELOUS )    score = pGame->m_mapMarvelousTo;
		if( score == TNS_PERFECT )      score = pGame->m_mapPerfectTo;
		if( score == TNS_GREAT )        score = pGame->m_mapGreatTo;
		if( score == TNS_GOOD )         score = pGame->m_mapGoodTo;
		if( score == TNS_BOO )          score = pGame->m_mapBooTo;

		if( score != TNS_NONE && score != TNS_MISS_HIDDEN && score != TNS_MISS_PIU_HOLD && score != TNS_HIT_PIU_HOLD && score != TNS_MISS )
		{
			int ms_error = (int) roundf( fSecondsFromPerfect * 1000 );
			ms_error = min( ms_error, MAX_PRO_TIMING_ERROR );

			g_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;

			if (!GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind)
				m_ProTimingDisplay.SetJudgment( ms_error, score );
		}

		if( score == TNS_MARVELOUS && !GAMESTATE->ShowMarvelous() )
			score = TNS_PERFECT;

		/* if( score >= TNS_BOO && score <= TNS_MARVELOUS && PIU_HOLDS )
			m_iNumHitNotesForCheckpoint++; */

		LOG->Trace("XXX: %i col %i, at %f, music at %f, step was at %f, off by %f", score, col, fStepSeconds, fCurrentMusicSeconds, fMusicSeconds, fNoteOffset );

		SetTapNoteScore(col, iIndexOverlappingNote, score);

		if( score != TNS_NONE )
			SetTapNoteOffset(col, iIndexOverlappingNote, -fNoteOffset);

		if( GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_HUMAN  && score >= TNS_GREAT ) 
			HandleAutosync(fNoteOffset);

		//Keep this here so we get the same data as Autosync
		NSMAN->ReportTiming(fNoteOffset,m_PlayerNumber);
	        
		if( m_pPrimaryScoreKeeper )
			m_pPrimaryScoreKeeper->HandleTapScore( score );
		if( m_pSecondaryScoreKeeper )
			m_pSecondaryScoreKeeper->HandleTapScore( score );

		switch( GAMESTATE->m_PlayerController[m_PlayerNumber] )
		{
		case PC_HUMAN:
			switch( tn.type )
			{
			case TapNote::tap:
			case TapNote::hold_head:
			case TapNote::roll_head:
			case TapNote::hidden:
				// don't judge the row if this note is a mine or tap attack
				if( IsRowCompletelyJudged(iIndexOverlappingNote) )
					OnRowCompletelyJudged( iIndexOverlappingNote, bSteppedEarly, false );
			}
			break;

		case PC_CPU:
		case PC_AUTOPLAY:
			switch( tn.type )
			{
			case TapNote::tap:
			case TapNote::hold_head:
			case TapNote::roll_head:
			case TapNote::hidden:
			case TapNote::lift:         // For CPU, judge Lift Notes here to make them work
				// don't judge the row if this note is a mine or tap attack
				if( IsRowCompletelyJudged(iIndexOverlappingNote) )
					OnRowCompletelyJudged( iIndexOverlappingNote, bSteppedEarly, false );
			}
			break;
		}

		if( score == TNS_MISS || score == TNS_MISS_PIU_HOLD || score == TNS_BOO )
			m_iDCState = AS2D_MISS;
		if( score == TNS_GOOD || score == TNS_GREAT )
			m_iDCState = AS2D_GOOD;
		if( score == TNS_PERFECT || score == TNS_MARVELOUS || score == TNS_HIT_PIU_HOLD )
		{
			m_iDCState = AS2D_GREAT;

			if( m_pLifeMeter && m_pLifeMeter->GetLife() == 1.0f) // full life
				m_iDCState = AS2D_FEVER; // super celebrate time :)
		}

		if( score == TNS_HIDDEN )
			m_iDCState = AS2D_FEVER;	// Found the hidden notes! Super celebrate!

		m_pNoteField->Step( col, score );
	}
	else
		m_pNoteField->Step( col, TNS_NONE );
}

void PlayerMinus::Release( int col, RageTimer tm )
{
	if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY && g_CurStageStats.bFailed[m_PlayerNumber] )	// Oni dead
		return;	// do nothing

	ASSERT( col >= 0 && col <= GetNumTracks() );

	float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
	fPositionSeconds -= tm.Ago();
	const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat;

	// Check for the closest note
	int iIndexOverlappingNote = GetClosestNote( col, fSongBeat, 
						   StepSearchDistanceForwards * GAMESTATE->m_fCurBPS * GAMESTATE->m_SongOptions.m_fMusicRate,
						   StepSearchDistanceBackwards * GAMESTATE->m_fCurBPS * GAMESTATE->m_SongOptions.m_fMusicRate );

	// Determine TapNoteType
	TapNote tn = GetTapNote(col,iIndexOverlappingNote);

	// Calculate TapNoteScore
	TapNoteScore score = TNS_NONE;

	if( iIndexOverlappingNote != -1)
	{
		// We're only interested in this for Lift Notes
		if( tn.type == TapNote::lift )
		{
			// compute the score for this hit
			const float fReleaseBeat = NoteRowToBeat( (float)iIndexOverlappingNote );
			const float fReleaseSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fReleaseBeat);

			/* We actually released the note this long ago: */
			const float fTimeSinceRelease = tm.Ago();

			/* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure
			* out what the music time is as of now. */
			const float fCurrentMusicSeconds = GAMESTATE->m_fMusicSeconds + (GAMESTATE->m_LastBeatUpdate.Ago()*GAMESTATE->m_SongOptions.m_fMusicRate);

			/* ... which means it happened at this point in the music: */
			const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceRelease * GAMESTATE->m_SongOptions.m_fMusicRate;

			// The offset from the actual release in seconds:
			const float fNoteOffset = (fReleaseSeconds - fMusicSeconds) / GAMESTATE->m_SongOptions.m_fMusicRate;	// account for music rate

			const float fSecondsFromPerfect = fabsf( fNoteOffset );
			bool	bSteppedEarly = -fNoteOffset < 0;

			switch( GAMESTATE->m_PlayerController[m_PlayerNumber] )
			{
			case PC_HUMAN:
				if(		 fSecondsFromPerfect <= ADJUSTED_WINDOW(Marvelous) )	score = TNS_MARVELOUS;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Perfect) )		score = TNS_PERFECT;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Great) )		score = TNS_GREAT;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Good) )			score = TNS_GOOD;
				else if( fSecondsFromPerfect <= ADJUSTED_WINDOW(Boo) )			score = TNS_BOO;
				else	score = TNS_NONE;
				break;
			default:
				ASSERT(0);
				score = TNS_NONE;
				break;
			}
			
			// Do game-specific score mapping.
			const Game* pGame = GAMESTATE->GetCurrentGame();
			if( score == TNS_MARVELOUS )	score = pGame->m_mapMarvelousTo;
			if( score == TNS_PERFECT )		score = pGame->m_mapPerfectTo;
			if( score == TNS_GREAT )		score = pGame->m_mapGreatTo;
			if( score == TNS_GOOD )			score = pGame->m_mapGoodTo;
			if( score == TNS_BOO )			score = pGame->m_mapBooTo;

			if( score != TNS_NONE && score != TNS_MISS )
			{
				int ms_error = (int) roundf( fSecondsFromPerfect * 1000 );
				ms_error = min( ms_error, MAX_PRO_TIMING_ERROR );

				g_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;
				if (!GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind)
					m_ProTimingDisplay.SetJudgment( ms_error, score );
			}

			if( score==TNS_MARVELOUS && !GAMESTATE->ShowMarvelous() )
				score = TNS_PERFECT;

			LOG->Trace("XXX: %i col %i, at %f, music at %f, step was at %f, off by %f",
				score, col, fReleaseSeconds, fCurrentMusicSeconds, fMusicSeconds, fNoteOffset );
			
			SetTapNoteScore(col, iIndexOverlappingNote, score);

			if( score != TNS_NONE )
				SetTapNoteOffset(col, iIndexOverlappingNote, -fNoteOffset);

			if( GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_HUMAN  && score >= TNS_GREAT ) 
				HandleAutosync(fNoteOffset);

			//Keep this here so we get the same data as Autosync
			NSMAN->ReportTiming(fNoteOffset,m_PlayerNumber);
			
			if( m_pPrimaryScoreKeeper )
				m_pPrimaryScoreKeeper->HandleTapScore( score );
			if( m_pSecondaryScoreKeeper )
				m_pSecondaryScoreKeeper->HandleTapScore( score );

			switch( tn.type )
			{
			case TapNote::lift:
				// don't judge the row if this note is a mine or tap attack
				if( IsRowCompletelyJudged(iIndexOverlappingNote) )
					OnRowCompletelyJudged( iIndexOverlappingNote, bSteppedEarly, false );
			}

			if( score == TNS_MISS || score == TNS_MISS_PIU_HOLD || score == TNS_BOO )
			{
				m_iDCState = AS2D_MISS;
			}
			if( score == TNS_GOOD || score == TNS_GREAT )
			{
				m_iDCState = AS2D_GOOD;
			}
			if( score == TNS_PERFECT || score == TNS_MARVELOUS || score == TNS_HIT_PIU_HOLD )
			{
				m_iDCState = AS2D_GREAT;

				if( m_pLifeMeter && m_pLifeMeter->GetLife() == 1.0f) // full life
					m_iDCState = AS2D_FEVER; // super celebrate time :)
			}
			if( score == TNS_HIDDEN )
				m_iDCState = AS2D_FEVER;	// Found the hidden notes! Super celebrate!

			m_pNoteField->Step( col, score );
		}
	}
	else
		m_pNoteField->Step( col, TNS_NONE, true );
}

void PlayerMinus::HandleAutosync(float fNoteOffset)
{
	if( !GAMESTATE->m_SongOptions.m_bAutoSync )
		return;

	m_fOffset[m_iOffsetSample++] = fNoteOffset;
	if (m_iOffsetSample < SAMPLE_COUNT) 
		return; /* need more */

	const float mean = calc_mean(m_fOffset, m_fOffset+SAMPLE_COUNT);
	const float stddev = calc_stddev(m_fOffset, m_fOffset+SAMPLE_COUNT);

	//If they stepped with less than .03 error
	if (stddev < .03 && stddev < fabsf(mean)) 
	{
		GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += mean;
		LOG->Trace("Offset corrected by %f. Error in steps: %f seconds.", mean, stddev);
		SCREENMAN->SystemMessage( "Autosync correction applied." );
	}
	//If we're outside the deviation -and- not synced properly - Mark
	else if ( stddev >= .03 )
	{
		LOG->Trace("Offset NOT corrected. Average offset: %f seconds. Error: %f seconds.", mean, stddev);
		SCREENMAN->SystemMessage( "Autosync correction NOT applied: too much deviation." );
	}

	m_iOffsetSample = 0;
}


void PlayerMinus::OnRowCompletelyJudged( int iIndexThatWasSteppedOn, bool bSteppedEarly, bool bHolds )
{
//	LOG->Trace( "PlayerMinus::OnRowCompletelyJudged" );
	
	// bHolds is true if we only had hold notes at the checkpoint
	//if( !bHolds )
	//{
		/* Find the minimum score of the row.  This will never be TNS_NONE, since this
		* function is only called when a row is completed. */
		/* Instead, use the last tap score (ala DDR).  Using the minimum results in 
		* slightly more harsh scoring than DDR */
		/* I'm not sure this is right, either.  Can you really jump a boo and a perfect
		* and get scored for a perfect?  (That's so loose, you can gallop jumps.) -glenn */
		/* Instead of grading individual columns, DDR sets a "was pressed recently" 
		* countdown every time you step on a column.  When you step on the first note of 
		* the jump, it sets the first "was pressed recently" timer.  Then, when you do 
		* the 2nd step of the jump, it sets another column's timer then AND's the jump 
		* columns with the "was pressed recently" columns to see whether or not you hit 
		* all the columns of the jump.  -Chris */

	//	TapNoteScore score = MinTapNoteScore(iIndexThatWasSteppedOn);
		TapNoteScore score = LastTapNoteScore(iIndexThatWasSteppedOn);

		ASSERT(score != TNS_NONE);
		ASSERT(score != TNS_HIT_MINE);

		// Check hold checkpoints now (in this case, we only check to change the score from what it was to
		// a miss if applicable). Combo will be adjusted in HandleTapRowScore(), both in Player.cpp and
		// ScoreKeeperMAX2.cpp.
		GAMESTATE->m_iComboTransfer = 0;

		//if( PIU_HOLDS )
		//{
		//	if( m_iNumHoldsThisCheckpoint > 0 && !m_bCheckpointAlreadyChecked )
		//	{
		//		// CASE 3 - All holds were held, and there were other tap notes
		//		// This is taken care of in HandleTapRowScore()

		//		// CASE 4 - Not all holds were held, and there were other tap notes
		//		if( m_iNumHitHoldsForCheckpoint != m_iNumHoldsThisCheckpoint && m_iNumNotesThisCheckpoint > m_iNumHoldsThisCheckpoint )
		//		{
		//			// We're now checking, so set this flag to true to avoid checking on this row again
		//			m_bCheckpointAlreadyChecked = true;

		//			score = TNS_MISS;
		//		}
		//	}
		//}

		// If the whole row was hit with perfects or greats, remove the row from the NoteField, so it disappears.
		for( int c=0; c<GetNumTracks(); c++ )	// for each column
		{
			TapNote tn = GetTapNote(c, iIndexThatWasSteppedOn);

			if( tn.type == TapNote::empty )	continue; // no note in this col
			if( tn.type == TapNote::mine )	continue; // don't flash on mines b/c they're supposed to be missed

			// If the score is great or better, remove the note from the screen to 
			// indicate success.  (Or always if blind is on.)
			if( score == TNS_HIDDEN || score >= TNS_GREAT || GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind )
				m_pNoteField->SetTapNote(c, iIndexThatWasSteppedOn, TAP_EMPTY);

			// show the ghost arrow for this column
			if (GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind)
				m_pNoteField->DidTapNote( c, TNS_MARVELOUS, false );
			else
			{
				switch( score )
				{
				case TNS_HIDDEN:	// Always flash the bright ghost if they hit a hidden note
					m_pNoteField->DidTapNote( c, score, true );
					break;
				case TNS_GREAT:
				case TNS_PERFECT:
				case TNS_MARVELOUS:
					bool bBright = g_CurStageStats.iCurCombo[m_PlayerNumber]>=(int)BRIGHT_GHOST_COMBO_THRESHOLD;
					m_pNoteField->DidTapNote( c, score, bBright );
					break;
				}
			}
		}
		
		HandleTapRowScore( iIndexThatWasSteppedOn );	// update score

		m_Judgment.SetJudgment( score, bSteppedEarly );
	// }
}


void PlayerMinus::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
{
	//LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
	int iMissIfOlderThanThisIndex;
	{
		const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds;
		bool bFreeze;
		float fMissIfOlderThanThisBeat;
		float fThrowAway;
		GAMESTATE->m_pCurSong->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze );

		iMissIfOlderThanThisIndex = BeatToNoteRow( fMissIfOlderThanThisBeat );
		if( bFreeze )
		{
			/* If there is a freeze on iMissIfOlderThanThisIndex, include this index too.
			 * Otherwise we won't show misses for tap notes on freezes until the
			 * freeze finishes. */
			iMissIfOlderThanThisIndex++;
		}
	}

	// Since this is being called every frame, let's not check the whole array every time.
	// Instead, only check 10 elements back.  Even 10 is overkill.
	const int iStartCheckingAt = max( 0, iMissIfOlderThanThisIndex-10 );

	//LOG->Trace( "iStartCheckingAt: %d   iMissIfOlderThanThisIndex:  %d", iStartCheckingAt, iMissIfOlderThanThisIndex );

	int iNumMissesFound = 0;
	for( int r=iStartCheckingAt; r<iMissIfOlderThanThisIndex; r++ )
	{
		bool MissedNoteOnThisRow = false;
		for( int t=0; t<GetNumTracks(); t++ )
		{
			switch( GetTapNote(t, r).type )
			{
				case TapNote::empty:	// No note existed here
				case TapNote::attack:	// No note existed here
				case TapNote::mine:		// We don't want Mines to have an impact
					continue;
			}

			if( GetTapNoteScore(t, r) != TNS_NONE ) // note here is already hit
				continue; 
			else
			{
				if( GetTapNote(t, r).type == TapNote::hidden )
				{
					MissedNoteOnThisRow = false;	// We don't count hidden notes as normal notes
					SetTapNoteScore(t, r, TNS_MISS_HIDDEN);
				}
				else
				{
					// A normal note.  Penalize for not stepping on it.
					MissedNoteOnThisRow = true;
					SetTapNoteScore(t, r, TNS_MISS);
					g_CurStageStats.iTotalError[m_PlayerNumber] += MAX_PRO_TIMING_ERROR;
					m_ProTimingDisplay.SetJudgment( MAX_PRO_TIMING_ERROR, TNS_MISS );
				}
			}
		}

		if( MissedNoteOnThisRow )
		{
			iNumMissesFound++;
			HandleTapRowScore( r );
		}
	}

	if( iNumMissesFound > 0 )
		m_Judgment.SetJudgment( TNS_MISS, false );	// You can't be early on a miss...
}


void PlayerMinus::CrossedRow( int iNoteRow )
{
	// If we're doing random vanish, randomise notes on the fly.
	if(GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fAppearances[PlayerOptions::APPEARANCE_RANDOMVANISH]==1)
		RandomiseNotes( iNoteRow );

	// check to see if there's a note at the crossed row
	RageTimer now;
	if( GAMESTATE->m_PlayerController[m_PlayerNumber] != PC_HUMAN )
	{
		for( int t=0; t<GetNumTracks(); t++ )
		{
			// We want to use PlayerMinus::Step() for everything, including lift notes
			// PlayerMinus::Release() is for human players only!!!

			if( GetTapNote(t, iNoteRow).type != TapNote::empty )
			{
				if( GetTapNoteScore(t, iNoteRow) == TNS_NONE )
					Step( t, now );
			}
		}
	}
}

void PlayerMinus::CrossedMineRow( int iNoteRow )
{
	// Hold the panel while crossing a mine will cause the mine to explode
	RageTimer now;
	for( int t=0; t<GetNumTracks(); t++ )
	{
		if( GetTapNote(t,iNoteRow).type == TapNote::mine )
		{
			const StyleInput StyleI( m_PlayerNumber, t );
			const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI );
			if( PREFSMAN->m_fPadStickSeconds > 0 )
			{
				float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI );
				if( fSecsHeld >= PREFSMAN->m_fPadStickSeconds )
					Step( t, now+(-PREFSMAN->m_fPadStickSeconds) );
			}
			else
			{
				bool bIsDown = INPUTMAPPER->IsButtonDown( GameI );
				if( bIsDown )
					Step( t, now );
			}
		}
	}
}

void PlayerMinus::CrossedPIUHoldHeadRow( int iNoteRow )
{
	// Hold the panel while crossing a hold head will trigger the hold
	RageTimer now;
	for( int t=0; t<GetNumTracks(); t++ )
	{
		if( GetTapNote(t,iNoteRow).type == TapNote::hold_head )
		{
			const StyleInput StyleI( m_PlayerNumber, t );
			const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI );

			if( PREFSMAN->m_fPadStickSeconds > 0 )
			{
				float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI );

				if( fSecsHeld >= PREFSMAN->m_fPadStickSeconds )
					Step( t, now+(-PREFSMAN->m_fPadStickSeconds) );
			}
			else
			{
				bool bIsDown = INPUTMAPPER->IsButtonDown( GameI );

				if( bIsDown )
					Step( t, now );
			}
		}
	}
}

void PlayerMinus::RandomiseNotes( int iNoteRow )
{
	// change the row to look ahead from based upon their speed mod
	/* This is incorrect: if m_fScrollSpeed is 0.5, we'll never change
	 * any odd rows, and if it's 2, we'll shuffle each row twice. */
	int iNewNoteRow = iNoteRow + ROWS_PER_BEAT*2;
	iNewNoteRow = int( iNewNoteRow / GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fScrollSpeed );

	int iNumOfTracks = GetNumTracks();
	for( int t=0; t+1 < iNumOfTracks; t++ )
	{
		const int iSwapWith = RandomInt( 0, iNumOfTracks-1 );

		/* Only swap a tap and an empty. */
		const TapNote t1 = GetTapNote(t, iNewNoteRow);
		if( t1.type != TapNote::tap )
			continue;

		const TapNote t2 = GetTapNote( iSwapWith, iNewNoteRow );
		if( t2.type != TapNote::empty )
			continue;

		/* Make sure the destination row isn't in the middle of a hold. */
		bool bHoldSkip = false;
		for( int i = 0; !bHoldSkip && i < GetNumHoldNotes(); ++i )
		{
			const HoldNote &hn = GetHoldNote(i);
			if( hn.iTrack == iSwapWith && hn.RowIsInRange(iNewNoteRow) )
				bHoldSkip = true;
		}
		if( bHoldSkip )
			continue;

		/* Make sure the destination row isn't in the middle of a roll. */
		bool bRollSkip = false;
		for( int i = 0; !bRollSkip && i < GetNumRollNotes(); ++i )
		{
			const RollNote &rn = GetRollNote(i);
			if( rn.iTrack == iSwapWith && rn.RowIsInRange(iNewNoteRow) )
				bRollSkip = true;
		}
		if( bRollSkip )
			continue;
		
		SetTapNote( t, iNewNoteRow, t2 );
		SetTapNote( iSwapWith, iNewNoteRow, t1 );

		const TapNote nft1 = m_pNoteField->GetTapNote( t, iNewNoteRow );
		const TapNote nft2 = m_pNoteField->GetTapNote( iSwapWith, iNewNoteRow );
		m_pNoteField->SetTapNote( t, iNewNoteRow, nft2 );
		m_pNoteField->SetTapNote( iSwapWith, iNewNoteRow, nft1 );
	}
}

void PlayerMinus::HandleTapRowScore( unsigned row )
{
	TapNoteScore scoreOfLastTap = LastTapNoteScore(row);
	int iNumTapsInRow = this->GetNumTracksWithTapOrHiddenOrHoldOrRollHead(row);
	ASSERT(iNumTapsInRow > 0);

	// True if a jump/hand/quad is one to combo, false if combo is purely based on tap count.
	bool ComboIsPerRow = true;

	switch( PREFSMAN->m_iScoringType )
	{
	case PrefsManager::SCORING_MAX2:
	case PrefsManager::SCORING_HYBRID:
		ComboIsPerRow = (GAMESTATE->m_PlayMode == PLAY_MODE_ONI);
		break;
	case PrefsManager::SCORING_5TH:
	case PrefsManager::SCORING_NOVA:
	case PrefsManager::SCORING_NOVA2:
	case PrefsManager::SCORING_PIU:
		ComboIsPerRow = true;
		break;
	case PrefsManager::SCORING_CUSTOM:
		ComboIsPerRow = CUSTOM_COMBO_PER_ROW;
		break;
	default:
		ASSERT(0);
	}

	// Override the default scoring values
	if( OVERRIDE_COMBO_INCREMENT )
	{
		if( OVERRIDE_COMBO_PER_ROW )
			ComboIsPerRow = true;
		else
			ComboIsPerRow = false;
	}

	int ComboCountIfMissed = ComboIsPerRow? 1: iNumTapsInRow;

	// Adjust for checkpoints if not ComboIsPerRow
	//if( PIU_HOLDS )
	//{
	//	// CASE 3 - All holds were held, and there were other tap notes
	//	if( m_iNumHitHoldsForCheckpoint == m_iNumHoldsThisCheckpoint && m_iNumNotesThisCheckpoint > m_iNumHoldsThisCheckpoint )
	//	{
	//		// We're now checking, so set this flag to true to avoid checking on this row again
	//		m_bCheckpointAlreadyChecked = true;

	//		TapNoteScore MinScoreToContinueCombo = GAMESTATE->m_PlayMode == PLAY_MODE_ONI? TNS_PERFECT:TNS_GREAT;

	//		// Do the combo adjustment here, then send over to ScoreKeeper via Gamestate
	//		if( scoreOfLastTap >= MinScoreToContinueCombo && !ComboIsPerRow )
	//		{
	//			int ComboCountIfHit = m_iNumHoldsThisCheckpoint;

	//			// Fix combo double counting on hold head!
	//			for( int i=0; i < GetNumHoldNotes(); i++ )
	//			{
	//				const HoldNote &hn = GetHoldNote(i);

	//				if( m_iLastCheckpointRow == hn.iStartRow )	// Only interested in those that started this checkpoint
	//				{
	//					// Correct for every hold note meeting this criteria
	//					ComboCountIfHit -= 1;
	//				}
	//			}

	//			// Check to make sure we don't crash the game with a bad miss combo by accident due to a correction.
	//			// Shouldn't ever occur, but lets be safe about this.
	//			if( ComboCountIfHit < 0 )
	//				ComboCountIfHit = 0;

	//			// Reset values
	//			// This is fixed for AutoPlay in ScoreKeeperMAX2.cpp
	//			GAMESTATE->m_iComboTransfer = ComboCountIfHit;
	//		}

	//		// Reset values
	//		m_iNumNotesThisCheckpoint = 0;
	//		m_iNumHoldsThisCheckpoint = 0;
	//	}
	//	// CASE 4 - The remainder of the code to handle combo effects
	//	if( scoreOfLastTap == TNS_MISS && !ComboIsPerRow )
	//	{
	//		ComboCountIfMissed += m_iNumHoldsThisCheckpoint;

	//		// Fix combo double counting on hold head!
	//		for( int i=0; i < GetNumHoldNotes(); i++ )
	//		{
	//			const HoldNote &hn = GetHoldNote(i);

	//			if( m_iLastCheckpointRow == hn.iStartRow )	// Only interested in those that started this checkpoint
	//			{
	//				// Correct for every hold note meeting this criteria
	//				ComboCountIfMissed -= 1;
	//			}
	//		}

	//		// Check to make sure we don't crash the game with a bad miss combo by accident due to a correction.
	//		// Shouldn't ever occur, but lets be safe about this.
	//		if( ComboCountIfMissed < 0 )
	//			ComboCountIfMissed = 0;

	//		// Reset values
	//		m_iNumNotesThisCheckpoint = 0;
	//		m_iNumHoldsThisCheckpoint = 0;
	//	}
	//}

	bool NoCheating = true;
#ifdef DEBUG
	NoCheating = false;
#endif

	if(GAMESTATE->m_bDemonstrationOrJukebox)
		NoCheating = false;

	// don't accumulate points if AutoPlay is on.
	if( NoCheating && GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_AUTOPLAY )
	{
		// We won't throw this flag on AutoPlay until we reach the first tap note
		if( PREFSMAN->m_bAutoPlay )
			GAMESTATE->m_iNotesInAutoPlay++;

		return;
	}

	// Update miss combo, and handle "combo stopped" messages.
	int &iCurCombo = g_CurStageStats.iCurCombo[m_PlayerNumber];
	int &iCurComboScoring = g_CurStageStats.iCurComboScoring[m_PlayerNumber];

	switch( scoreOfLastTap )
	{
	case TNS_MARVELOUS:
	case TNS_PERFECT:
	case TNS_GREAT:
	case TNS_HIT_PIU_HOLD:
		g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;
		SCREENMAN->PostMessageToTopScreen( SM_MissComboAborted, 0 );
		break;

	case TNS_MISS:
	case TNS_MISS_PIU_HOLD:
		iCurCombo = 0;
		iCurComboScoring = 0;
		g_CurStageStats.iCurMissCombo[m_PlayerNumber] += ComboCountIfMissed;

		m_iDCState = AS2D_MISS; // update dancing 2d characters that may have missed a note
		// fall through

	case TNS_BOO:
		if( iCurCombo > 50 )
			SCREENMAN->PostMessageToTopScreen( SM_ComboStopped, 0 );

		if( MISS_COMBO_ON_BREAK && scoreOfLastTap == TNS_BOO && iCurCombo > 0 )
			g_CurStageStats.iCurMissCombo[m_PlayerNumber] += ComboCountIfMissed;
		else if( !MISS_COMBO_ON_BREAK && scoreOfLastTap == TNS_BOO )
		{
			g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;
			SCREENMAN->PostMessageToTopScreen( SM_MissComboAborted, 0 );
		}

		iCurCombo = 0;
		iCurComboScoring = 0;
		break;

	case TNS_GOOD:
		if( COMBO_BREAK_ON_GOOD )
		{
			if( iCurCombo > 50 )
				SCREENMAN->PostMessageToTopScreen( SM_ComboStopped, 0 );

			if( MISS_COMBO_ON_BREAK && iCurCombo > 0 )
				g_CurStageStats.iCurMissCombo[m_PlayerNumber] += ComboCountIfMissed;
			else if( !MISS_COMBO_ON_BREAK )
			{
				g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;
				SCREENMAN->PostMessageToTopScreen( SM_MissComboAborted, 0 );
			}

			iCurCombo = 0;
			iCurComboScoring = 0;
		}
		else
		{
			g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;
			SCREENMAN->PostMessageToTopScreen( SM_MissComboAborted, 0 );
		}

		break;

	case TNS_HIDDEN:
	case TNS_MISS_HIDDEN:
		break;	// Do nothing to the combo

	default:
		ASSERT( 0 );
	}

	// The score keeper updates the hit combo.  Remember the old combo for handling announcers.
	const int iOldCombo = g_CurStageStats.iCurCombo[m_PlayerNumber];

	if(m_pPrimaryScoreKeeper)
		m_pPrimaryScoreKeeper->HandleTapRowScore(scoreOfLastTap, iNumTapsInRow );
	if(m_pSecondaryScoreKeeper)
		m_pSecondaryScoreKeeper->HandleTapRowScore(scoreOfLastTap, iNumTapsInRow );

	m_Combo.SetCombo( g_CurStageStats.iCurCombo[m_PlayerNumber], g_CurStageStats.iCurMissCombo[m_PlayerNumber] );

#define CROSSED( x ) (iOldCombo<x && iCurCombo>=x)
	if ( CROSSED(100) )	
		SCREENMAN->PostMessageToTopScreen( SM_100Combo, 0 );
	else if( CROSSED(200) )	
		SCREENMAN->PostMessageToTopScreen( SM_200Combo, 0 );
	else if( CROSSED(300) )	
		SCREENMAN->PostMessageToTopScreen( SM_300Combo, 0 );
	else if( CROSSED(400) )	
		SCREENMAN->PostMessageToTopScreen( SM_400Combo, 0 );
	else if( CROSSED(500) )	
		SCREENMAN->PostMessageToTopScreen( SM_500Combo, 0 );
	else if( CROSSED(600) )	
		SCREENMAN->PostMessageToTopScreen( SM_600Combo, 0 );
	else if( CROSSED(700) )	
		SCREENMAN->PostMessageToTopScreen( SM_700Combo, 0 );
	else if( CROSSED(800) )	
		SCREENMAN->PostMessageToTopScreen( SM_800Combo, 0 );
	else if( CROSSED(900) )	
		SCREENMAN->PostMessageToTopScreen( SM_900Combo, 0 );
	else if( CROSSED(1000))	
		SCREENMAN->PostMessageToTopScreen( SM_1000Combo, 0 );
	else if( (iOldCombo / 100) < (iCurCombo / 100) && iCurCombo > 1000 )
		SCREENMAN->PostMessageToTopScreen( SM_ComboContinuing, 0 );
#undef CROSSED

	// new max combo
	g_CurStageStats.iMaxCombo[m_PlayerNumber] = max(g_CurStageStats.iMaxCombo[m_PlayerNumber], iCurComboScoring);
	
	// These two modes depend on this max combo, so use this instead
	if( PREFSMAN->m_iScoringType == PrefsManager::SCORING_PIU || PREFSMAN->m_iScoringType == PrefsManager::SCORING_CUSTOM )
		g_CurStageStats.iMaxCombo[m_PlayerNumber] = max(g_CurStageStats.iMaxCombo[m_PlayerNumber], iCurCombo);

	/* Use the real current beat, not the beat we've been passed.  That's because we
	 * want to record the current life/combo to the current time; eg. if it's a MISS,
	 * the beat we're registering is in the past, but the life is changing now. */
	g_CurStageStats.UpdateComboList( m_PlayerNumber, g_CurStageStats.fAliveSeconds[m_PlayerNumber], false );

	float life = -1;
	if( m_pLifeMeter )
		life = m_pLifeMeter->GetLife();
	else if( m_pCombinedLifeMeter )
	{
		life = GAMESTATE->m_fTugLifePercentP1;
		if( m_PlayerNumber == PLAYER_2 )
			life = 1.0f - life;
	}
	if( life != -1 )
		g_CurStageStats.SetLifeRecordAt( m_PlayerNumber, life, g_CurStageStats.fAliveSeconds[m_PlayerNumber] );

	if (m_pScoreDisplay)
		m_pScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
	if (m_pSecondaryScoreDisplay)
		m_pSecondaryScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);

	if( m_pLifeMeter ) {
		m_pLifeMeter->ChangeLife( scoreOfLastTap );
		m_pLifeMeter->OnDancePointsChange();    // update oni life meter
	}
	if( m_pCombinedLifeMeter ) {
		m_pCombinedLifeMeter->ChangeLife( m_PlayerNumber, scoreOfLastTap );
		m_pCombinedLifeMeter->OnDancePointsChange( m_PlayerNumber );    // update oni life meter
	}
}

void PlayerMinus::HandleHoldScore( HoldNoteScore holdScore, TapNoteScore tapScore )
{
	bool NoCheating = true;
#ifdef DEBUG
	NoCheating = false;
#endif

	if(GAMESTATE->m_bDemonstrationOrJukebox)
		NoCheating = false;
	// don't accumulate points if AutoPlay is on.
	if( NoCheating && GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_AUTOPLAY )
		return;

	if(m_pPrimaryScoreKeeper)
		m_pPrimaryScoreKeeper->HandleHoldScore(holdScore, tapScore );
	if(m_pSecondaryScoreKeeper)
		m_pSecondaryScoreKeeper->HandleHoldScore(holdScore, tapScore );

	if (m_pScoreDisplay)
		m_pScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
	if (m_pSecondaryScoreDisplay)
		m_pSecondaryScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);

	if( m_pLifeMeter ) {
		m_pLifeMeter->ChangeLife( holdScore, tapScore );
		m_pLifeMeter->OnDancePointsChange();
	}
	if( m_pCombinedLifeMeter ) {
		m_pCombinedLifeMeter->ChangeLife( m_PlayerNumber, holdScore, tapScore );
		m_pCombinedLifeMeter->OnDancePointsChange( m_PlayerNumber );
	}
}

void PlayerMinus::HandleRollScore( RollNoteScore rollScore, TapNoteScore tapScore )
{
	bool NoCheating = true;
#ifdef DEBUG
	NoCheating = false;
#endif

	if(GAMESTATE->m_bDemonstrationOrJukebox)
		NoCheating = false;
	// don't accumulate points if AutoPlay is on.
	if( NoCheating && GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_AUTOPLAY )
		return;

	if(m_pPrimaryScoreKeeper)
		m_pPrimaryScoreKeeper->HandleRollScore(rollScore, tapScore );
	if(m_pSecondaryScoreKeeper)
		m_pSecondaryScoreKeeper->HandleRollScore(rollScore, tapScore );

	if (m_pScoreDisplay)
		m_pScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
	if (m_pSecondaryScoreDisplay)
		m_pSecondaryScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);

	if( m_pLifeMeter ) {
		m_pLifeMeter->ChangeLife( rollScore, tapScore );
		m_pLifeMeter->OnDancePointsChange();
	}
	if( m_pCombinedLifeMeter ) {
		m_pCombinedLifeMeter->ChangeLife( m_PlayerNumber, rollScore, tapScore );
		m_pCombinedLifeMeter->OnDancePointsChange( m_PlayerNumber );
	}
}

float PlayerMinus::GetMaxStepDistanceSeconds()
{
	return GAMESTATE->m_SongOptions.m_fMusicRate * ADJUSTED_WINDOW(Boo);
}

void PlayerMinus::FadeToFail()
{
	m_pNoteField->FadeToFail();
}

/* XXX: Why's m_NoteField in a separate class, again?  Is that still needed? */
void Player::Load( PlayerNumber player_no, const NoteData* pNoteData, LifeMeter* pLM, CombinedLifeMeter* pCombinedLM, ScoreDisplay* pScoreDisplay, ScoreDisplay* pSecondaryScoreDisplay, Inventory* pInventory, ScoreKeeper* pPrimaryScoreKeeper, ScoreKeeper* pSecondaryScoreKeeper )
{
	PlayerMinus::Load( player_no, pNoteData, pLM, pCombinedLM, pScoreDisplay, pSecondaryScoreDisplay, pInventory, pPrimaryScoreKeeper, pSecondaryScoreKeeper, &m_NoteField );
}

CString PlayerMinus::ApplyRandomAttack()
{
	int iAttackVectorSize = GAMESTATE->m_RandomAttacks.size();

	if( iAttackVectorSize < 1 )
	{
		LOG->Warn( "Attempting to use Random Modifiers, but none specified in file. Attacks will be nullified." );
		return "";
	}

	bool bCantBreak = false;
	int iAttackToUse = -1;

	do {
		bCantBreak = false;
		iAttackToUse = rand() % iAttackVectorSize;

		for( unsigned i=0; (int)i < PREFSMAN->m_iAttackCantRepeatForX; i++ )
		{
			if( iAttackToUse == m_iLastUsedAttacks[i] )
				bCantBreak = true;
		}
	} while( bCantBreak );

	// These are used below
	int iTempValue = -1;
	int iTempValue2 = -1;
	
	for( unsigned i=0; (int)i < PREFSMAN->m_iAttackCantRepeatForX; i++ )
	{
		if( m_iLastUsedAttacks[i] == -1 )
		{
			m_iLastUsedAttacks[i] = iAttackToUse;
			break;
		}
		else
		{
			// Okay, so this is hack-ish...
			iTempValue = m_iLastUsedAttacks[i];

			if( i == 0 )
				m_iLastUsedAttacks[i] = iAttackToUse;
			else
				m_iLastUsedAttacks[i] = iTempValue2;

			iTempValue2 = iTempValue;
		}
	}

	return GAMESTATE->m_RandomAttacks[iAttackToUse];
}

int PlayerMinus::CheckDistanceBetweenCheckpoints( int iRow )
{
	TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;
	const int iTickcount = timing.GetTickcountAtBeat( NoteRowToBeat( iRow ) );
	int iValue = 48;

	// That's it, we're only doing factors of 48 now. Screw this other crap!
	if( iTickcount <= 1 )				// iTickcount = negative infinity to 1
		iValue = 48;
	else if( iTickcount == 2 )			// iTickcount = 2
		iValue = 24;
	else if( iTickcount == 3 )			// iTickcount = 3
		iValue = 16;
	else if( iTickcount >= 4 && iTickcount <= 5 )	// iTickcount = 4, 5
		iValue = 12;
	else if( iTickcount >= 6 && iTickcount <= 7 )	// iTickcount = 6, 7
		iValue = 8;
	else if( iTickcount >= 8 && iTickcount <= 11 )	// iTickcount = 8, 9, 10, 11
		iValue = 6;
	else if( iTickcount >= 12 && iTickcount <= 15 )	// iTickcount = 12, 13, 14, 15
		iValue = 4;
	else if( iTickcount >= 16 && iTickcount <= 23 )	// iTickcount = 16, 17, 17, 19, 20, 21, 22, 23
		iValue = 3;
	else if( iTickcount >= 24 && iTickcount <= 47 )	// iTickcount = 24 to 47
		iValue = 2;
	else						// iTickcount = 48 to infinity
		iValue = 1;

	// Old method, I wonder if this is what is causing all the problems
	//int iValue = (int)((float)ROWS_PER_BEAT/(float)iTickcount);

	// ROWS_PER_BEAT = 48, as defined in NoteTypes.h
	//if( iTickcount >= ROWS_PER_BEAT )	// Correction for large tickcount values
	//	iValue = 1;

	return iValue;
}

bool PlayerMinus::StartOfNewTickSegment( int iRow, int iTickSegment )
{
	TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;

	// If we're at the end of the segment vector, then stop checking!
	if( iTickSegment == (int)(timing.m_TickSegments.size()-1) )
		return false;

	const int iStartOfNextSegment = StartOfTickSegment( iTickSegment+1 );

	if( iRow >= iStartOfNextSegment )
		return true;

	return false;
}

int PlayerMinus::StartOfTickSegment( int iTickSegment )
{
	TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;
	return BeatToNoteRow( timing.GetTickSegmentStart(iTickSegment) );
}

int PlayerMinus::StartOfTickSegmentZero()
{
	return StartOfTickSegment(0);
}

int PlayerMinus::CurrentSegmentForPreloading( int iRow )
{
	if( iRow < StartOfTickSegmentZero() )
		return 0;

	TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;
	return timing.GetTickSegmentNumber( NoteRowToBeat(iRow) );
}

// This is ugly, but it works so leave it as is!
void PlayerMinus::SetInitialCheckpoints( int iCurrentRow )
{
	int iTickSegment = CurrentSegmentForPreloading( iCurrentRow );
	const int iLastSongRow = BeatToNoteRow( GAMESTATE->m_pCurSong->m_fLastBeat );

	for( unsigned i=0; i <= PIU_HOLD_CHECK_ADVANCE; i++ )
	{
		int iNeedCheckpointEveryXRows = CheckDistanceBetweenCheckpoints( iCurrentRow );
		
		if( i > 0 )
			iCurrentRow += iNeedCheckpointEveryXRows;

		// Make sure to look for segment changes
		if( StartOfNewTickSegment( iCurrentRow, iTickSegment ) && iCurrentRow != StartOfTickSegmentZero() )
		{
			iTickSegment++;
			int iStartRow = StartOfTickSegment( iTickSegment );

			if( iCurrentRow > iStartRow )
				iCurrentRow = iStartRow;
		}

		// Reset status of the next element
		//m_iNumNotesNextCheckpoint[i] = 0;
		m_iNumHoldsNextCheckpoint[i] = 0;
		m_iNextCheckpointRow[i] = (iLastSongRow * 2);	// Set arbitrarily high; it'll get reset later anyways

		//int iNumNotes = 0;
		int iNumHolds = 0;

		// If we're beyond the end of the song, there's no reason to keep checking. Might happen in very
		// short songs, although unlikely. We don't break, however, as we still want to zero all the values
		// left in the array.
		if( iCurrentRow > iLastSongRow )
			continue;

		for( int c=0; c<GetNumTracks(); c++ )	// for each column
		{
			//if( TapNoteOnCheckpoint( c, iCurrentRow ) )
			//	iNumNotes++;

			// Check for hold notes
			for( int i=0; i < GetNumHoldNotes(); i++ )		// for each HoldNote
			{
				const HoldNote &hn = GetHoldNote(i);

				if( c != hn.iTrack )	// The hold note is not on this track
					continue;

				if( iCurrentRow > hn.iStartRow && iCurrentRow <= hn.iEndRow )
				{
					if( PIU_HOLD_DAMPER )
					{
						// Dampen the impact of being late on hitting a hold head
						float fPositionSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(NoteRowToBeat(hn.iStartRow)) + CHECKPOINT_DAMPER;
						int iDampenRow = BeatToNoteRow(GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ));

						if( iCurrentRow > iDampenRow )
						{
							//iNumNotes++;
							iNumHolds++;
						}
					}
					else
					{
						//iNumNotes++;
						iNumHolds++;
					}
				}
			}
		}

		//m_iNumNotesNextCheckpoint[i] = iNumNotes;
		m_iNumHoldsNextCheckpoint[i] = iNumHolds;
		m_iNextCheckpointRow[i] = iCurrentRow;
	}
}

void PlayerMinus::UpdateCheckpoints()
{
	const int iLastSongRow = BeatToNoteRow( GAMESTATE->m_pCurSong->m_fLastBeat );

	// Bump all the data in the array forward, don't check the last element in the array!
	// It won't have anything to compare to, and crash out
	for( unsigned i=0; i < (PIU_HOLD_CHECK_ADVANCE-1); i++ )
	{
		//m_iNumNotesNextCheckpoint[i] = m_iNumNotesNextCheckpoint[i+1];
		m_iNumHoldsNextCheckpoint[i] = m_iNumHoldsNextCheckpoint[i+1];
		m_iNextCheckpointRow[i] = m_iNextCheckpointRow[i+1];
	}

	// Reset status of final element in each array
	//m_iNumNotesNextCheckpoint[PIU_HOLD_CHECK_ADVANCE-1] = 0;
	m_iNumHoldsNextCheckpoint[PIU_HOLD_CHECK_ADVANCE-1] = 0;
	m_iNextCheckpointRow[PIU_HOLD_CHECK_ADVANCE-1] = (iLastSongRow * 2);	// Set arbitrarily high; it'll get reset later anyways

	int iLastCheckpointRow = m_iNextCheckpointRow[PIU_HOLD_CHECK_ADVANCE-2];

	//int iNumNotes = 0;
	int iNumHolds = 0;

	int iTickSegment = CurrentSegmentForPreloading( iLastCheckpointRow );
	int iNeedCheckpointEveryXRows = CheckDistanceBetweenCheckpoints( iLastCheckpointRow );
	int iCheckpointRow = iLastCheckpointRow + iNeedCheckpointEveryXRows;

	// Make sure to look for segment changes
	if( StartOfNewTickSegment( iCheckpointRow, iTickSegment ) && iCheckpointRow != StartOfTickSegmentZero() )
	{
		iTickSegment++;
		int iStartRow = StartOfTickSegment( iTickSegment );

		if( iCheckpointRow > iStartRow )
			iCheckpointRow = iStartRow;
	}

	// If we're beyond the end of the song, there's no reason to keep checking
	if( iCheckpointRow <= iLastSongRow )
	{
		for( int c=0; c<GetNumTracks(); c++ )	// for each column
		{
			//if( TapNoteOnCheckpoint( c, iCheckpointRow ) )
			//	iNumNotes++;

			// Check for hold notes
			for( int i=0; i < GetNumHoldNotes(); i++ )		// for each HoldNote
			{
				const HoldNote &hn = GetHoldNote(i);

				if( c != hn.iTrack )	// The hold note is not on this track
					continue;

				if( iCheckpointRow > hn.iStartRow && iCheckpointRow <= hn.iEndRow )
				{
					if( PIU_HOLD_DAMPER )
					{
						// Dampen the impact of being late on hitting a hold head
						float fPositionSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(NoteRowToBeat(hn.iStartRow)) + CHECKPOINT_DAMPER;
						int iDampenRow = BeatToNoteRow(GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ));

						if( iCheckpointRow > iDampenRow )
						{
							//iNumNotes++;
							iNumHolds++;
						}
					}
					else
					{
						//iNumNotes++;
						iNumHolds++;
					}
				}
			}
		}

		//m_iNumNotesNextCheckpoint[PIU_HOLD_CHECK_ADVANCE-1] = iNumNotes;
		m_iNumHoldsNextCheckpoint[PIU_HOLD_CHECK_ADVANCE-1] = iNumHolds;
		m_iNextCheckpointRow[PIU_HOLD_CHECK_ADVANCE-1] = iCheckpointRow;
	}

	// We've prepared the next checkpoint, so reset this switch
	m_bPrepareNextCheckpoint = false;
}

bool PlayerMinus::TapNoteOnCheckpoint( int iTrack, int iRow )
{
	bool bTapNotes = false;

	// Check for tap notes
	TapNote tn = GetTapNote(iTrack, iRow);

	if( tn.type == TapNote::tap )
		bTapNotes = true;
	if( tn.type == TapNote::lift )
		bTapNotes = true;
	if( tn.type == TapNote::hold_head )
		bTapNotes = true;
	if( tn.type == TapNote::roll_head )
		bTapNotes = true;

	return bTapNotes;
}

void PlayerMinus::UpdateHoldNotes( float fDeltaTime, int iSongRow )
{
	for( int i=0; i < GetNumHoldNotes(); i++ )		// for each HoldNote
	{
		const HoldNote &hn = GetHoldNote(i);
		HoldNoteScore hns = GetHoldNoteScore(hn);

		m_pNoteField->m_HeldHoldNotes[hn] = false;	// set hold flag so NoteField can do intelligent drawing
		m_pNoteField->m_ActiveHoldNotes[hn] = false;	// set hold flag so NoteField can do intelligent drawing

		if( iSongRow < hn.iStartRow )
			continue;				// hold hasn't happened yet
		if( hns != HNS_NONE )				// if this HoldNote already has a result
			continue;				// we don't need to update the logic for this one

		const StyleInput StyleI( m_PlayerNumber, hn.iTrack );
		const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI );

		// If they got a bad score or haven't stepped on the corresponding tap yet
		const TapNoteScore tns = GetTapNoteScore( hn.iTrack, hn.iStartRow );
		bool bSteppedOnTapNote = tns > TNS_MISS;	// did they step on the start of this hold?

		float fLife = GetHoldNoteLife(hn);

		// If the song beat is in the range of this hold:
		if( hn.iStartRow <= iSongRow && iSongRow <= hn.iEndRow )
		{
			bool bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI );
			
			// TODO: Make the CPU miss sometimes.
			if( GAMESTATE->m_PlayerController[m_PlayerNumber] != PC_HUMAN )
				bIsHoldingButton = true;

			// set hold flag so NoteField can do intelligent drawing
			m_pNoteField->m_HeldHoldNotes[hn] = bIsHoldingButton && bSteppedOnTapNote;
			m_pNoteField->m_ActiveHoldNotes[hn] = bSteppedOnTapNote;

			if( bSteppedOnTapNote )
			{
				// This hold note is not judged and we stepped on its head.  Update iLastHeldRow.
				HoldNoteResult *hnr = m_pNoteField->CreateHoldNoteResult( hn );
				hnr->iLastHeldRow = min( iSongRow, hn.iEndRow );

				hnr = this->CreateHoldNoteResult( hn );
				hnr->iLastHeldRow = min( iSongRow, hn.iEndRow );
			}

			if( bSteppedOnTapNote && bIsHoldingButton )
			{
				// Increase life
				fLife = 1;

				m_pNoteField->DidHoldNote( hn.iTrack );		// update the "electric ghost" effect
			}
			else
			{
				/* What is this conditional for?  It causes a problem: if a hold note
				* begins on a freeze, you can tap it and then release it for the
				* duration of the freeze; life doesn't count down until we're
				* past the first beat. */
//				if( fSongBeat-hn.fStartBeat > GAMESTATE->m_fCurBPS * GetMaxStepDistanceSeconds() )
//				{
					// Decrease life
					fLife -= fDeltaTime/ADJUSTED_WINDOW(Hold);
					fLife = max( fLife, 0 );	// clamp
//				}
			}
		}

		// check for NG.  If the head was missed completely, don't count an NG.
		if( bSteppedOnTapNote && fLife == 0 )	// the player has not pressed the button for a long time!
			hns = HNS_NG;

		// check for OK
		if( iSongRow >= hn.iEndRow && bSteppedOnTapNote && fLife > 0 )	// if this HoldNote is in the past
		{
			fLife = 1;
			hns = HNS_OK;
			m_pNoteField->DidTapNote( StyleI.col, TNS_MARVELOUS, true );	// bright ghost flash
		}

		if( hns != HNS_NONE )
		{
			// this note has been judged
			HandleHoldScore( hns, tns );
			m_HoldJudgment[hn.iTrack].SetHoldJudgment( hns );

			int ms_error = (hns == HNS_OK)? 0:MAX_PRO_TIMING_ERROR;

			g_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;

			if( hns == HNS_NG ) // don't show a 0 for an OK
				m_ProTimingDisplay.SetJudgment( ms_error, TNS_MISS );
		}

		m_pNoteField->SetHoldNoteLife(hn, fLife);	// update the NoteField display
		m_pNoteField->SetHoldNoteScore(hn, hns);	// update the NoteField display

		SetHoldNoteLife(hn, fLife);
		SetHoldNoteScore(hn, hns);
	}
}

void PlayerMinus::UpdateRollNotes( float fDeltaTime, int iSongRow )
{
	for( int i=0; i < GetNumRollNotes(); i++ )		// for each RollNote
	{
		const RollNote &rn = GetRollNote(i);
		RollNoteScore rns = GetRollNoteScore(rn);

		m_pNoteField->m_HeldRollNotes[rn] = false;	// set hold flag so NoteField can do intelligent drawing
		m_pNoteField->m_ActiveRollNotes[rn] = false;	// set hold flag so NoteField can do intelligent drawing

		if( rns != RNS_NONE )				// if this RollNote already has a result
			continue;				// we don't need to update the logic for this one
		if( iSongRow < rn.iStartRow )
			continue;				// roll hasn't happened yet

		const StyleInput StyleI( m_PlayerNumber, rn.iTrack );
		const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI );

		// if they got a bad score or haven't stepped on the corresponding tap yet
		const TapNoteScore tns = GetTapNoteScore( rn.iTrack, rn.iStartRow );
		const bool bSteppedOnTapNote = tns > TNS_MISS;	// did they step on the start of this roll?

		float fLife = GetRollNoteLife(rn);
		bool bIsHoldingButton = false;

		// If the song beat is in the range of this roll:
		if( rn.iStartRow <= iSongRow && iSongRow <= rn.iEndRow )
		{
			bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI );

			// TODO: Make the CPU miss sometimes
			if( GAMESTATE->m_PlayerController[m_PlayerNumber] != PC_HUMAN )
			{
				// Define this explicitly!
				bIsHoldingButton = false;

				// HACK: First is to make the rolls look more realistic when hit by the CPU (since a player
				// doesn't release instantly) by keeping it 'held' for a bit longer
				// The second if makes the CPU rehit the roll to keep it alive
				if( fLife > CPU_ROLL_LETGO_LIFE )
					bIsHoldingButton = true;
				else if( fLife < CPU_ROLL_REHIT_LIFE )
				{
					bIsHoldingButton = true;
					m_bMustRelease[rn] = false;
				}
			}
			// Okay, so we have a human player. Make this check. 
			else
			{
				if( !bIsHoldingButton )
					m_bMustRelease[rn] = false;	
			}

			// set hold flag so NoteField can do intelligent drawing
			m_pNoteField->m_HeldRollNotes[rn] = bIsHoldingButton && bSteppedOnTapNote;
			m_pNoteField->m_ActiveRollNotes[rn] = bSteppedOnTapNote;

			if( bSteppedOnTapNote )
			{
				// This roll note is not judged and we stepped on its head.  Update iLastHeldRow.
				RollNoteResult *rnr = m_pNoteField->CreateRollNoteResult( rn );
				rnr->iLastHeldRow = min( iSongRow, rn.iEndRow );

				rnr = this->CreateRollNoteResult( rn );
				rnr->iLastHeldRow = min( iSongRow, rn.iEndRow );
			}

			if( bSteppedOnTapNote && bIsHoldingButton && !m_bMustRelease[rn] )
			{
				// Increase life
				fLife = 1;
				m_bMustRelease[rn] = true;

				m_pNoteField->DidRollNote( rn.iTrack );		// update the "electric ghost" effect
			}
			else
			{
				// Decrease life
				// JudgeWindowSecondsRoll takes effect here. - Mark
				if( PREFSMAN->m_bRollTapTimingIsAffectedByJudge )
					fLife -= fDeltaTime/ADJUSTED_WINDOW(Roll);
				else
					fLife -= fDeltaTime/NON_ADJUSTED_WINDOW(Roll);

				fLife = max( fLife, 0 );	// clamp
			}
		}

		// check for NG.  If the head was missed completely, don't count a NG.
		if( bSteppedOnTapNote && fLife == 0 )	// the player has not pressed the button for a long time!
			rns = RNS_NG;

		// check for OK
		if( iSongRow >= rn.iEndRow && bSteppedOnTapNote && fLife > 0 )	// if this RollNote is in the past
		{
			fLife = 1;
			rns = RNS_OK;
			m_pNoteField->DidTapNote( StyleI.col, TNS_PERFECT, true );	// bright ghost flash
		}

		if( rns != RNS_NONE )
		{
			// this note has been judged
			HandleRollScore( rns, tns );
			m_RollJudgment[rn.iTrack].SetRollJudgment( rns );

			int ms_error = (rns == RNS_OK)? 0:MAX_PRO_TIMING_ERROR;

			g_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;
			if( rns == RNS_NG ) // don't show a 0 for an OK
				m_ProTimingDisplay.SetJudgment( ms_error, TNS_MISS );
		}

		m_pNoteField->SetRollNoteLife(rn, fLife);	// update the NoteField display
		m_pNoteField->SetRollNoteScore(rn, rns);	// update the NoteField display

		SetRollNoteLife(rn, fLife);
		SetRollNoteScore(rn, rns);
	}
}

void PlayerMinus::UpdatePIUHoldNotes( float fDeltaTime, int iSongRow )
{
	for( int i=0; i < GetNumHoldNotes(); i++ )		// for each HoldNote
	{
		const HoldNote &hn = GetHoldNote(i);
		HoldNoteScore hns = GetHoldNoteScore(hn);

		m_pNoteField->m_HeldHoldNotes[hn] = false;	// set hold flag so NoteField can do intelligent drawing
		m_pNoteField->m_ActiveHoldNotes[hn] = false;	// set hold flag so NoteField can do intelligent drawing

		if( iSongRow < hn.iStartRow )
			continue;				// hold hasn't happened yet
		if( hns != HNS_NONE )				// if this HoldNote already has a result
			continue;				// we don't need to update the logic for this one

		const StyleInput StyleI( m_PlayerNumber, hn.iTrack );
		const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI );

		const TapNoteScore tns = GetTapNoteScore( hn.iTrack, hn.iStartRow );
		bool bSteppedOnTapNote = tns > TNS_MISS;	// did they step on the start of this hold?

		bool bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI );

		float fLife = GetHoldNoteLife(hn);

		// If the song beat is in the range of this hold:
		if( hn.iStartRow <= iSongRow && iSongRow <= hn.iEndRow )
		{
			// TODO: Make the CPU miss sometimes.
			if( GAMESTATE->m_PlayerController[m_PlayerNumber] != PC_HUMAN )
				bIsHoldingButton = true;

			if( bIsHoldingButton || bSteppedOnTapNote )
				m_bHoldColumnButtonDown[hn] = true;

			if( m_bHoldColumnButtonDown[hn] )
			{
				m_pNoteField->m_HeldHoldNotes[hn] = bIsHoldingButton && (fLife > 0);
				m_pNoteField->m_ActiveHoldNotes[hn] = (fLife > 0);
			}

			if( fLife > 0 && m_bHoldColumnButtonDown[hn] )
			{
				HoldNoteResult *hnr = m_pNoteField->CreateHoldNoteResult( hn );
				hnr->iLastHeldRow = min( iSongRow, hn.iEndRow );

				hnr = this->CreateHoldNoteResult( hn );
				hnr->iLastHeldRow = min( iSongRow, hn.iEndRow );
			}

			float fLifeForCheckpoint = 0.0f;

			if( bIsHoldingButton )
			{
				// Increase hold life
				fLife = 1;
				fLifeForCheckpoint = 1.0f;
				m_pNoteField->DidHoldNote( hn.iTrack );		// update the "electric ghost" effect
			}
			else
			{			
				fLife -= fDeltaTime/ADJUSTED_WINDOW(Hold);
				fLife = max( fLife, 0 );	// clamp

				fLifeForCheckpoint = fLife;

				// If the hold has yet to be activated, don't allow them to score at all here
				if( !m_bHoldColumnButtonDown[hn] )
					fLifeForCheckpoint = 0.0f;
			}

			// Exclude the very start of a hold, since it isn't included in the checkpoints
			if( fLifeForCheckpoint > 0.0f )
			{
				if( PIU_HOLD_DAMPER )
				{
					// Dampen the impact of being late on hitting a hold head
					float fPositionSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(NoteRowToBeat(hn.iStartRow)) + CHECKPOINT_DAMPER;
					int iDampenRow = BeatToNoteRow(GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ));

					if( iSongRow > iDampenRow )
						m_iNumHitHoldsForCheckpoint++;
				}
				else
				{
					if( iSongRow > hn.iStartRow )
						m_iNumHitHoldsForCheckpoint++;
				}
			}
		}

		// Check for the end of the hold for scoring
		if( iSongRow >= hn.iEndRow )
		{
			// check for NG. The hold is NG if the hold life was 0 when the hold ended.
			if( fLife <= 0.0f )
				hns = HNS_NG;

			// check for OK
			if( fLife > 0.0f )
			{
				fLife = 1.0f;
				hns = HNS_OK;

				if( GAMESTATE->ShowMarvelous() )
					m_pNoteField->DidTapNote( StyleI.col, TNS_MARVELOUS, true );	// bright ghost flash
				else
					m_pNoteField->DidTapNote( StyleI.col, TNS_PERFECT, true );	// bright ghost flash
			}
		}

		// We're not judging the holds themselves anymore in PIU Holds mode if we've disabled it through the
		// metrics (this is for more accurate PIU style behaviour).
		if( hns != HNS_NONE )
		{
			const TapNoteScore tns = GetTapNoteScore( hn.iTrack, hn.iStartRow );

			// this note has been judged
			if( PREFSMAN->m_iScoringType != PrefsManager::SCORING_PIU && PREFSMAN->m_iScoringType != PrefsManager::SCORING_CUSTOM )
				HandleHoldScore( hns, tns );

			if( !DISABLE_PIU_HOLD_JUDGMENT )
				m_HoldJudgment[hn.iTrack].SetHoldJudgment( hns );

			int ms_error = (hns == HNS_OK)? 0:MAX_PRO_TIMING_ERROR;

			g_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;

			if( hns == HNS_NG && !DISABLE_PIU_HOLD_JUDGMENT ) // don't show a 0 for an OK
				m_ProTimingDisplay.SetJudgment( ms_error, TNS_MISS );
		}

		m_pNoteField->SetHoldNoteLife(hn, fLife);	// update the NoteField display
		m_pNoteField->SetHoldNoteScore(hn, hns);	// update the NoteField display

		SetHoldNoteLife(hn, fLife);
		SetHoldNoteScore(hn, hns);
	}
}

void PlayerMinus::UpdatePIUHoldScoring()
{
	// Checkpoint scoring, now independent of other notes (and, only two possible outcomes! Woo!)
	if( m_iNumHoldsThisCheckpoint > 0 )
	{
		if( !m_bCheckpointAlreadyChecked )
		{
			// True if a jump/hand/quad is one to combo, false if combo is purely based on tap count.
			bool bComboIsPerRow = true;

			switch( PREFSMAN->m_iScoringType )
			{
			case PrefsManager::SCORING_MAX2:
			case PrefsManager::SCORING_HYBRID:
				bComboIsPerRow = (GAMESTATE->m_PlayMode == PLAY_MODE_ONI);
				break;
			case PrefsManager::SCORING_5TH:
			case PrefsManager::SCORING_NOVA:
			case PrefsManager::SCORING_NOVA2:
			case PrefsManager::SCORING_PIU:
				bComboIsPerRow = true;
				break;
			case PrefsManager::SCORING_CUSTOM:
				bComboIsPerRow = CUSTOM_COMBO_PER_ROW;
				break;
			default:
				ASSERT(0);
			}

			// Override the default scoring values
			if( OVERRIDE_COMBO_INCREMENT )
			{
				if( OVERRIDE_COMBO_PER_ROW )
					bComboIsPerRow = true;
				else
					bComboIsPerRow = false;
			}

			// CASE 1 - All holds were held, no other tap notes (the latter part is now ignored)
			//if( m_iNumHitHoldsForCheckpoint == m_iNumHoldsThisCheckpoint && m_iNumNotesThisCheckpoint == m_iNumHoldsThisCheckpoint )
			if( m_iNumHitHoldsForCheckpoint == m_iNumHoldsThisCheckpoint || PREFSMAN->m_bAutoPlay )
			{
				// We're now checking, so set this flag to true to avoid checking on this row again
				m_bCheckpointAlreadyChecked = true;

				if( GAMESTATE->m_bEditing || GAMESTATE->UsedAutoPlay() || PREFSMAN->m_bAutoPlay )
				{
					if( PREFSMAN->m_bAutoPlay )
						GAMESTATE->m_iNotesInAutoPlay++;

					g_CurStageStats.iCurCombo[m_PlayerNumber] = 0;
					g_CurStageStats.iCurComboScoring[m_PlayerNumber] = 0;
					g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;

					m_Judgment.SetJudgment( TNS_HIT_PIU_HOLD, false );

					// Reset
					m_iNumHoldsThisCheckpoint = 0;

					return;
				}

				int iOldCombo = g_CurStageStats.iCurCombo[m_PlayerNumber];

				if( bComboIsPerRow )
					g_CurStageStats.iCurCombo[m_PlayerNumber] += 1;
				else
					g_CurStageStats.iCurCombo[m_PlayerNumber] += m_iNumHitHoldsForCheckpoint;

				g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;

				// The score keeper updates the hit combo. Remember the old combo for handling announcers.
				const int iCurCombo = g_CurStageStats.iCurCombo[m_PlayerNumber];

#define CROSSED( x ) ( iOldCombo<x && iCurCombo>=x )
				if ( CROSSED(100) )	
					SCREENMAN->PostMessageToTopScreen( SM_100Combo, 0 );
				else if( CROSSED(200) )	
					SCREENMAN->PostMessageToTopScreen( SM_200Combo, 0 );
				else if( CROSSED(300) )	
					SCREENMAN->PostMessageToTopScreen( SM_300Combo, 0 );
				else if( CROSSED(400) )	
					SCREENMAN->PostMessageToTopScreen( SM_400Combo, 0 );
				else if( CROSSED(500) )	
					SCREENMAN->PostMessageToTopScreen( SM_500Combo, 0 );
				else if( CROSSED(600) )	
					SCREENMAN->PostMessageToTopScreen( SM_600Combo, 0 );
				else if( CROSSED(700) )	
					SCREENMAN->PostMessageToTopScreen( SM_700Combo, 0 );
				else if( CROSSED(800) )	
					SCREENMAN->PostMessageToTopScreen( SM_800Combo, 0 );
				else if( CROSSED(900) )	
					SCREENMAN->PostMessageToTopScreen( SM_900Combo, 0 );
				else if( CROSSED(1000))	
					SCREENMAN->PostMessageToTopScreen( SM_1000Combo, 0 );
				else if( (iOldCombo / 100) < (iCurCombo / 100) && iCurCombo > 1000 )
					SCREENMAN->PostMessageToTopScreen( SM_ComboContinuing, 0 );
#undef CROSSED

				if( PREFSMAN->m_iScoringType == PrefsManager::SCORING_PIU || PREFSMAN->m_iScoringType == PrefsManager::SCORING_CUSTOM )
					g_CurStageStats.iMaxCombo[m_PlayerNumber] = max(g_CurStageStats.iMaxCombo[m_PlayerNumber], g_CurStageStats.iCurCombo[m_PlayerNumber]);
				
				g_CurStageStats.UpdateComboList( m_PlayerNumber, g_CurStageStats.fAliveSeconds[m_PlayerNumber], false );

				m_Combo.SetCombo( g_CurStageStats.iCurCombo[m_PlayerNumber], g_CurStageStats.iCurMissCombo[m_PlayerNumber] );
				m_Judgment.SetJudgment( TNS_HIT_PIU_HOLD, false );

				if( m_pLifeMeter )
				{
					m_pLifeMeter->ChangeLife( TNS_HIT_PIU_HOLD );
					m_pLifeMeter->OnDancePointsChange();    // update oni life meter
				}

				if( m_pCombinedLifeMeter )
				{
					m_pCombinedLifeMeter->ChangeLife( m_PlayerNumber, TNS_HIT_PIU_HOLD );
					m_pCombinedLifeMeter->OnDancePointsChange( m_PlayerNumber );    // update oni life meter
				}

				if( PREFSMAN->m_iScoringType == PrefsManager::SCORING_PIU || PREFSMAN->m_iScoringType == PrefsManager::SCORING_CUSTOM )
				{
					if(m_pPrimaryScoreKeeper)
						m_pPrimaryScoreKeeper->HandleTapRowScoreCheckpoint(TNS_HIT_PIU_HOLD, m_iNumHitHoldsForCheckpoint);
					if(m_pSecondaryScoreKeeper)
						m_pSecondaryScoreKeeper->HandleTapRowScoreCheckpoint(TNS_HIT_PIU_HOLD, m_iNumHitHoldsForCheckpoint);

					if (m_pScoreDisplay)
						m_pScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
					if (m_pSecondaryScoreDisplay)
						m_pSecondaryScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
				}

				// Reset values
				//m_iNumNotesThisCheckpoint = 0;
				m_iNumHoldsThisCheckpoint = 0;
			}
			// CASE 2 - Not all holds were held, and there are no other tap notes (again, latter doesn't matter)
			//else if( m_iNumHitHoldsForCheckpoint != m_iNumHoldsThisCheckpoint && m_iNumNotesThisCheckpoint == m_iNumHoldsThisCheckpoint )
			else
			{
				// We're now checking, so set this flag to true to avoid checking on this row again
				m_bCheckpointAlreadyChecked = true;

				if( GAMESTATE->m_bEditing || GAMESTATE->UsedAutoPlay() || PREFSMAN->m_bAutoPlay )
				{
					g_CurStageStats.iCurCombo[m_PlayerNumber] = 0;
					g_CurStageStats.iCurComboScoring[m_PlayerNumber] = 0;
					g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;

					m_Judgment.SetJudgment( TNS_MISS_PIU_HOLD, false );

					// Reset
					m_iNumHoldsThisCheckpoint = 0;

					return;
				}

				g_CurStageStats.iCurCombo[m_PlayerNumber] = 0;
				g_CurStageStats.iCurComboScoring[m_PlayerNumber] = 0;

				if( bComboIsPerRow )
					g_CurStageStats.iCurMissCombo[m_PlayerNumber] += 1;
				else
					g_CurStageStats.iCurMissCombo[m_PlayerNumber] += m_iNumHoldsThisCheckpoint;

				if( GAMESTATE->UsedAutoPlay() || GAMESTATE->m_bEditing )
				{
					g_CurStageStats.iCurCombo[m_PlayerNumber] = 0;
					g_CurStageStats.iCurComboScoring[m_PlayerNumber] = 0;
					g_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;
				}

				g_CurStageStats.iMaxCombo[m_PlayerNumber] = max(g_CurStageStats.iMaxCombo[m_PlayerNumber], g_CurStageStats.iCurCombo[m_PlayerNumber]);
				g_CurStageStats.UpdateComboList( m_PlayerNumber, g_CurStageStats.fAliveSeconds[m_PlayerNumber], false );

				m_Combo.SetCombo( g_CurStageStats.iCurCombo[m_PlayerNumber], g_CurStageStats.iCurMissCombo[m_PlayerNumber] );
				m_Judgment.SetJudgment( TNS_MISS_PIU_HOLD, false );

				if( !GAMESTATE->UsedAutoPlay() )
				{
					if( m_pLifeMeter )
					{
						m_pLifeMeter->ChangeLife( TNS_MISS_PIU_HOLD );
						m_pLifeMeter->OnDancePointsChange();    // update oni life meter
					}

					if( m_pCombinedLifeMeter )
					{
						m_pCombinedLifeMeter->ChangeLife( m_PlayerNumber, TNS_MISS_PIU_HOLD );
						m_pCombinedLifeMeter->OnDancePointsChange( m_PlayerNumber );    // update oni life meter
					}

					if( PREFSMAN->m_iScoringType == PrefsManager::SCORING_PIU || PREFSMAN->m_iScoringType == PrefsManager::SCORING_CUSTOM )
					{
						if(m_pPrimaryScoreKeeper)
							m_pPrimaryScoreKeeper->HandleTapRowScoreCheckpoint(TNS_MISS_PIU_HOLD, m_iNumHoldsThisCheckpoint ); // - m_iNumHitNotesForCheckpoint);
						if(m_pSecondaryScoreKeeper)
							m_pSecondaryScoreKeeper->HandleTapRowScoreCheckpoint(TNS_MISS_PIU_HOLD, m_iNumHoldsThisCheckpoint ); // - m_iNumHitNotesForCheckpoint);

						if (m_pScoreDisplay)
							m_pScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
						if (m_pSecondaryScoreDisplay)
							m_pSecondaryScoreDisplay->SetScore(g_CurStageStats.iScore[m_PlayerNumber]);
					}
				}

				// Reset values
				//m_iNumNotesThisCheckpoint = 0;
				m_iNumHoldsThisCheckpoint = 0;
			}
			// These cases are now commented out
			// CASE 3 - All holds were held, and there were other tap notes
			// CASE 4 - Not all holds were held, and there were other tap notes
			// These are handled in the OnRowCompletelyJudged() function
		}		
	}
}

/*
 * (c) 2001-2004 Chris Danford
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
