Beispiel #1
0
int NoteData::GetNumHands( float fStartBeat, float fEndBeat ) const
{
	/* Count the number of times you have to use your hands.  This includes
	 * three taps at the same time, a tap while two hold notes are being held,
	 * etc.  Only count rows that have at least one tap note (hold heads count).
	 * Otherwise, every row of hold notes counts, so three simultaneous hold
	 * notes will count as hundreds of "hands". */
	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	/* Clamp to known-good ranges. */
	iStartIndex = max( iStartIndex, 0 );
	iEndIndex = min( iEndIndex, GetNumRows()-1 );

	int iNum = 0;
	for( int i=iStartIndex; i<=iEndIndex; i++ )
	{
		if( !RowNeedsHands(i) )
			continue;

		iNum++;
	}

	return iNum;
}
Beispiel #2
0
int NoteData::GetNumN( int MinTaps, float fStartBeat, float fEndBeat ) const
{
	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	/* Clamp to known-good ranges. */
	iStartIndex = max( iStartIndex, 0 );
	iEndIndex = min( iEndIndex, GetNumRows()-1 );

	int iNum = 0;
	for( int i=iStartIndex; i<=iEndIndex; i++ )
	{
		int iNumNotesThisIndex = 0;
		for( int t=0; t<m_iNumTracks; t++ )
		{
			TapNote tn = GetTapNoteX(t, i);
			if( tn.type != TapNote::mine  &&  tn.type != TapNote::empty )	// mines don't count
				iNumNotesThisIndex++;
		}
		if( iNumNotesThisIndex >= MinTaps )
			iNum++;
	}
	
	return iNum;
}
Beispiel #3
0
int NoteData::GetNumTapNotes( float fStartBeat, float fEndBeat ) const
{
	int iNumNotes = 0;

	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	/* Clamp to known-good ranges. */
	iStartIndex = max( iStartIndex, 0 );
	iEndIndex = min( iEndIndex, GetNumRows()-1 );
	
	for( int t=0; t<m_iNumTracks; t++ )
	{
		for( int i=iStartIndex; i<=iEndIndex; i++ )
		{
			TapNote tn = GetTapNoteX(t, i);
			if( tn.type != TapNote::empty  &&  tn.type != TapNote::mine )
				iNumNotes++;
		}
	}
	
	return iNumNotes;
}
int NoteDataWithScoring::GetNumNWithScore( TapNoteScore tns, int MinTaps, const float fStartBeat, float fEndBeat ) const
{
	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	iStartIndex = max( iStartIndex, 0 );
	iEndIndex = min( iEndIndex, GetNumRows()-1 );

	int iNumSuccessfulDoubles = 0;
	for( int i=iStartIndex; i<=iEndIndex; i++ )
	{
		int iNumNotesThisIndex = 0;
		TapNoteScore	minTapNoteScore = TNS_MARVELOUS;
		for( int t=0; t<GetNumTracks(); t++ )
		{
			switch( GetTapNote(t, i).type )
			{
			case TapNote::tap:		
			case TapNote::hold_head: 
				iNumNotesThisIndex++;
				minTapNoteScore = min( minTapNoteScore, GetTapNoteScore(t, i) );
			}
		}
		if( iNumNotesThisIndex >= MinTaps && minTapNoteScore >= tns )
			iNumSuccessfulDoubles++;
	}
	
	return iNumSuccessfulDoubles;
}
/* See NoteData::GetNumHands(). */
int NoteDataWithScoring::GetSuccessfulHands( float fStartBeat, float fEndBeat ) const
{
	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	/* Clamp to known-good ranges. */
	iStartIndex = max( iStartIndex, 0 );
	iEndIndex = min( iEndIndex, GetNumRows()-1 );

	int iNum = 0;
	for( int i=iStartIndex; i<=iEndIndex; i++ )
	{
		if( !RowNeedsHands(i) )
			continue;

		bool Missed = false;
		for( int t=0; t<GetNumTracks(); t++ )
		{
			TapNote tn = GetTapNoteX(t, i);
			if( tn.type == TapNote::empty )
				continue;
			if( tn.type == TapNote::mine ) // mines don't count
				continue;
			if( GetTapNoteScore(t, i) <= TNS_BOO )
				Missed = true;
		}

		if( Missed )
			continue;

		/* Check hold scores. */
		for( int j=0; j<GetNumHoldNotes(); j++ )
		{
			const HoldNote &hn = GetHoldNote(j);
			HoldNoteResult hnr = GetHoldNoteResult( hn );

			/* Check if the row we're checking is in range. */
			if( !hn.RowIsInRange(i) )
				continue;

			/* If a hold is released *after* a hands containing it, the hands is
			 * still good.  So, ignore the judgement and only examine iLastHeldRow
			 * to be sure that the hold was still held at the point of this row.
			 * (Note that if the hold head tap was missed, then iLastHeldRow == i
			 * and this won't fail--but the tap check above will have already failed.) */
			if( hnr.iLastHeldRow < i )
				Missed = true;
		}

		if( !Missed )
			iNum++;
	}

	return iNum;
}
Beispiel #6
0
int NoteData::GetNumRowsWithTapOrHoldHead( float fStartBeat, float fEndBeat ) const
{
	int iNumNotes = 0;

	if(fEndBeat == -1) fEndBeat = GetNumBeats();
	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );
	
	for( int i=iStartIndex; i<=iEndIndex; i++ )
		if( IsThereATapOrHoldHeadAtRow(i) )
			iNumNotes++;
	
	return iNumNotes;
}
void KSFLoader::RemoveHoles( NoteData &out, const Song &song )
{
    /* Start at the second BPM segment; the first one is already aligned. */
    for( unsigned seg = 1; seg < song.m_Timing.m_BPMSegments.size(); ++seg )
    {
//		const float FromBeat = song.m_Timing.m_BPMSegments[seg].m_fStartBeat;
        const float FromBeat = song.m_Timing.m_BPMSegments[seg].m_fStartBeat * song.m_BPMSegments[seg].m_fBPM / song.m_BPMSegments[0].m_fBPM;
        const int FromRow = (int) BeatToNoteRow(FromBeat);
        const int ToRow = (int) BeatToNoteRow(song.m_Timing.m_BPMSegments[seg].m_fStartBeat);

        LOG->Trace("from %f (%i) to (%i)", FromBeat, FromRow, ToRow);
//		const int ToRow = (int) roundf(FromRow * song.m_Timing.m_BPMSegments[0].m_fBPM / song.m_BPMSegments[seg].m_fBPM);
//		const int Rows = out.GetLastRow() - FromRow + 1;
//		int LastRow;
//		if(seg+1 < song.m_Timing.m_BPMSegments().size())
//			LastRow = (int) NoteRowToBeat( song.m_Timing.m_BPMSegments[seg+1].m_fStartBeat ) - 1;
//		else
//			LastRow = out.GetLastRow();
        NoteData tmp;
        tmp.SetNumTracks(out.GetNumTracks());
        tmp.CopyRange( &out, FromRow, out.GetLastRow() );
        out.ClearRange( FromRow, out.GetLastRow() );
        out.CopyRange( &tmp, 0, tmp.GetLastRow(), ToRow );
    }

    /*
    	out.ConvertHoldNotesTo2sAnd3s();
    	for( t = 0; t < notedata.GetNumTracks(); ++t )
    	{
    		const float CurBPM = song.GetBPMAtBeat( NoteRowToBeat(row) );
    		song.m_Timing.m_BPMSegments.size()
    		for( int row = 0; row <= notedata.GetLastRow(); ++row )
    		{
    			TapNote tn = notedata.GetTapNote(t, row);
    			if( tn == TAP_EMPTY )
    				continue;

    			const int RealRow = (int) roundf(row * OrigBPM / CurBPM);
    			if( RealRow == row )
    				continue;
    			LOG->Trace("from %i to %i", row, RealRow);
    			notedata.SetTapNote( t, RealRow, tn );
    			notedata.SetTapNote( t, row, TAP_EMPTY );
    		}
    	}
    	out.Convert2sAnd3sToHoldNotes();
    */
}
Beispiel #8
0
void SSCLoader::ProcessLabels( TimingData &out, const RString sParam )
{
	vector<RString> arrayLabelExpressions;
	split( sParam, ",", arrayLabelExpressions );
	
	for( unsigned b=0; b<arrayLabelExpressions.size(); b++ )
	{
		vector<RString> arrayLabelValues;
		split( arrayLabelExpressions[b], "=", arrayLabelValues );
		if( arrayLabelValues.size() != 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #LABELS value \"%s\" (must have exactly one '='), ignored.",
				     arrayLabelExpressions[b].c_str() );
			continue;
		}
		
		const float fBeat = StringToFloat( arrayLabelValues[0] );
		RString sLabel = arrayLabelValues[1];
		TrimRight(sLabel);
		if( fBeat >= 0.0f )
			out.AddSegment( LabelSegment(BeatToNoteRow(fBeat), sLabel) );
		else 
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid Label at beat %f called %s.",
				     fBeat, sLabel.c_str() );
		}
		
	}
}
Beispiel #9
0
void SSCLoader::ProcessStops( TimingData &out, const RString sParam )
{
	vector<RString> arrayStopExpressions;
	split( sParam, ",", arrayStopExpressions );
	
	for( unsigned b=0; b<arrayStopExpressions.size(); b++ )
	{
		vector<RString> arrayStopValues;
		split( arrayStopExpressions[b], "=", arrayStopValues );
		if( arrayStopValues.size() != 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
				     arrayStopExpressions[b].c_str() );
			continue;
		}
		
		const float fBeat = StringToFloat( arrayStopValues[0] );
		const float fNewStop = StringToFloat( arrayStopValues[1] );
		if( fBeat >= 0 && fNewStop > 0 )
			out.AddSegment( StopSegment(BeatToNoteRow(fBeat), fNewStop) );
		else
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid Stop at beat %f, length %f.",
				     fBeat, fNewStop );
		}
	}
}
Beispiel #10
0
int NoteData::GetNumHoldNotes( float fStartBeat, float fEndBeat ) const
{
	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();
	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	int iNumHolds = 0;
	for( int i=0; i<GetNumHoldNotes(); i++ )
	{
		const HoldNote &hn = GetHoldNote(i);
		if( iStartIndex <= hn.iStartRow &&  hn.iEndRow <= iEndIndex )
			iNumHolds++;
	}
	return iNumHolds;
}
Beispiel #11
0
static int GetMeasureStartRow( const MeasureToTimeSig_t &sigs, int iMeasureNo, const MeasureToTimeSig_t &sigAdjustments )
{
	int iRowNo = 0;
	for( int i = 0; i < iMeasureNo; ++i )
		iRowNo += BeatToNoteRow( GetBeatsPerMeasure(sigs, i, sigAdjustments) );
	return iRowNo;
}
Beispiel #12
0
void SSCLoader::ProcessScrolls( TimingData &out, const RString sParam )
{
	vector<RString> vs1;
	split( sParam, ",", vs1 );
	
	FOREACH_CONST( RString, vs1, s1 )
	{
		vector<RString> vs2;
		split( *s1, "=", vs2 );
		
		if( vs2.size() < 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an scroll change with %i values.",
				     static_cast<int>(vs2.size()) );
			continue;
		}

		const float fBeat = StringToFloat( vs2[0] );
		const float fRatio = StringToFloat( vs2[1] );

		if( fBeat < 0 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an scroll change with beat %f.",
				     fBeat );
			continue;
		}

		out.AddSegment( ScrollSegment(BeatToNoteRow(fBeat), fRatio) );
	}
Beispiel #13
0
static void HandleBunki( TimingData &timing, const float fEarlyBPM, 
			const float fCurBPM, const float fGap, 
			const float fPos )
{
	const float BeatsPerSecond = fEarlyBPM / 60.0f;
	const float beat = (fPos + fGap) * BeatsPerSecond;
	LOG->Trace( "BPM %f, BPS %f, BPMPos %f, beat %f",
		   fEarlyBPM, BeatsPerSecond, fPos, beat );
	timing.AddSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) );
}
int NoteDataWithScoring::GetNumHoldNotesWithScore( HoldNoteScore hns, const float fStartBeat, float fEndBeat ) const
{
	int iNumSuccessfulHolds = 0;

	if(fEndBeat == -1)
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	for( int i=0; i<GetNumHoldNotes(); i++ )
	{
		const HoldNote &hn = GetHoldNote(i);
		if( iStartIndex > hn.iStartRow ||  hn.iEndRow > iEndIndex )
			continue;
		if( GetHoldNoteScore(hn) == hns )
			iNumSuccessfulHolds++;
	}
	return iNumSuccessfulHolds;
}
int NoteDataWithScoring::GetNumTapNotesWithScore( TapNoteScore tns, const float fStartBeat, float fEndBeat ) const
{ 
	int iNumSuccessfulTapNotes = 0;

	if(fEndBeat == -1)
		fEndBeat = GetNumBeats()+1;

	unsigned iStartIndex = BeatToNoteRow( fStartBeat );
	unsigned iEndIndex = BeatToNoteRow( fEndBeat );

	for( unsigned i=iStartIndex; i<min(float(iEndIndex), float(m_TapNoteScores[0].size())); i++ )
	{
		for( int t=0; t<GetNumTracks(); t++ )
		{
			if( this->GetTapNote(t, i).type != TapNote::empty && GetTapNoteScore(t, i) >= tns )
				iNumSuccessfulTapNotes++;
		}
	}
	
	return iNumSuccessfulTapNotes;
}
int NoteDataWithScoring::GetSuccessfulMines( float fStartBeat, float fEndBeat ) const
{
	if( fEndBeat == -1 )
		fEndBeat = GetNumBeats();

	int iStartIndex = BeatToNoteRow( fStartBeat );
	int iEndIndex = BeatToNoteRow( fEndBeat );

	iStartIndex = max( iStartIndex, 0 );
	iEndIndex = min( iEndIndex, GetNumRows()-1 );

	int iNumSuccessfulMinesNotes = 0;
	for( int i=iStartIndex; i<=iEndIndex; i++ )
	{
		for( int t=0; t<GetNumTracks(); t++ )
		{
			if( this->GetTapNote(t,i).type == TapNote::mine  &&  GetTapNoteScore(t, i) != TNS_HIT_MINE )
				iNumSuccessfulMinesNotes++;
		}
	}
	
	return iNumSuccessfulMinesNotes;
}
void SoundEffectControl::Update( float fDeltaTime )
{
	if( SOUND_PROPERTY == "" )
		return;

	float fLevel = INPUTMAPPER->GetLevel( GAME_BUTTON_EFFECT_UP, m_pPlayerState->m_PlayerNumber );
	fLevel -= INPUTMAPPER->GetLevel( GAME_BUTTON_EFFECT_DOWN, m_pPlayerState->m_PlayerNumber );
	CLAMP( fLevel, -1.0f, +1.0f );

	if( LOCK_TO_HOLD )
	{
		int iRow = BeatToNoteRow( GAMESTATE->m_Position.m_fSongBeat );
		int iHoldsHeld, iHoldsLetGo;
		HoldsBeingHeld( iRow, iHoldsHeld, iHoldsLetGo );

		/* If no holds are being held, or any have been missed, lock the effect off until
		 * the button has been released. */
		if( iHoldsLetGo > 0 || iHoldsHeld == 0 )
			m_bLocked = true;

		/* If the button is released, unlock it when the level crosses or reaches 0. */
		if( m_bLocked )
		{
			if( (fLevel <= 0.0f && m_fLastLevel >= 0.0f) ||
				(fLevel >= 0.0f && m_fLastLevel <= 0.0f) )
				m_bLocked = false;
		}
	}

	m_fLastLevel = fLevel;

	if( m_bLocked )
		fLevel = 0.0f;

	m_fSample = fLevel;
	m_pPlayerState->m_EffectHistory.AddSample( m_fSample, fDeltaTime );

	float fPropertyMin = PROPERTY_MIN;
	float fPropertyCenter = PROPERTY_CENTER;
	float fPropertyMax = PROPERTY_MAX;

	float fCurrent;
	if( m_fSample < 0 )
		fCurrent = SCALE( m_fSample, 0.0f, -1.0f, fPropertyCenter, fPropertyMin );
	else
		fCurrent = SCALE( m_fSample, 0.0f, +1.0f, fPropertyCenter, fPropertyMax );

	if( m_pSoundReader )
		m_pSoundReader->SetProperty( SOUND_PROPERTY, fCurrent );
}
Beispiel #18
0
void SSCLoader::ProcessWarps( TimingData &out, const RString sParam, const float fVersion )
{
	vector<RString> arrayWarpExpressions;
	split( sParam, ",", arrayWarpExpressions );
	
	for( unsigned b=0; b<arrayWarpExpressions.size(); b++ )
	{
		vector<RString> arrayWarpValues;
		split( arrayWarpExpressions[b], "=", arrayWarpValues );
		if( arrayWarpValues.size() != 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #WARPS value \"%s\" (must have exactly one '='), ignored.",
				     arrayWarpExpressions[b].c_str() );
			continue;
		}
		
		const float fBeat = StringToFloat( arrayWarpValues[0] );
		const float fNewBeat = StringToFloat( arrayWarpValues[1] );
		// Early versions were absolute in beats. They should be relative.
		if( ( fVersion < VERSION_SPLIT_TIMING && fNewBeat > fBeat ) )
		{
			out.AddSegment( WarpSegment(BeatToNoteRow(fBeat), fNewBeat - fBeat) );
		}
		else if( fNewBeat > 0 )
			out.AddSegment( WarpSegment(BeatToNoteRow(fBeat), fNewBeat) );
		else
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid Warp at beat %f, BPM %f.",
				     fBeat, fNewBeat );
		}
	}
}
Beispiel #19
0
void SSCLoader::ProcessCombos( TimingData &out, const RString line, const int rowsPerBeat )
{
	vector<RString> arrayComboExpressions;
	split( line, ",", arrayComboExpressions );
	
	for( unsigned f=0; f<arrayComboExpressions.size(); f++ )
	{
		vector<RString> arrayComboValues;
		split( arrayComboExpressions[f], "=", arrayComboValues );
		unsigned size = arrayComboValues.size();
		if( size < 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #COMBOS value \"%s\" (must have at least one '='), ignored.",
				     arrayComboExpressions[f].c_str() );
			continue;
		}
		const float fComboBeat = StringToFloat( arrayComboValues[0] );
		const int iCombos = StringToInt( arrayComboValues[1] );
		const int iMisses = (size == 2 ? iCombos : StringToInt(arrayComboValues[2]));
		out.AddSegment( ComboSegment( BeatToNoteRow(fComboBeat), iCombos, iMisses ) );
	}
}
Beispiel #20
0
/* For visibility testing: if bAbsolute is false, random modifiers must return the
 * minimum possible scroll speed. */
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
{
	// Default values that are returned if boomerang is off.
	fPeakYOffsetOut = FLT_MAX;
	bIsPastPeakOut = true;


	float fYOffset = 0;

	/* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or
	 * entirely time spacing (respectively).  Occasionally, we tween between them. */
	if( pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing != 1.0f )
	{
		float fSongBeat = GAMESTATE->m_fSongBeat;
		float fBeatsUntilStep = fNoteBeat - fSongBeat;
		float fYOffsetBeatSpacing = fBeatsUntilStep * ARROW_SPACING;
		fYOffset += fYOffsetBeatSpacing * (1-pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing);
	}

	if( pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing != 0.0f )
	{
		float fSongSeconds = GAMESTATE->m_fMusicSeconds;
		float fNoteSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fNoteBeat);
		float fSecondsUntilStep = fNoteSeconds - fSongSeconds;
		float fBPM = pPlayerState->m_CurrentPlayerOptions.m_fScrollBPM;
		float fBPS = fBPM/60.f;
		float fYOffsetTimeSpacing = fSecondsUntilStep * fBPS * ARROW_SPACING;
		fYOffset += fYOffsetTimeSpacing * pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing;
	}

	// don't mess with the arrows after they've crossed 0
	if( fYOffset < 0 )
		return fYOffset * pPlayerState->m_CurrentPlayerOptions.m_fScrollSpeed;

	const float* fAccels = pPlayerState->m_CurrentPlayerOptions.m_fAccels;
	//const float* fEffects = pPlayerState->m_CurrentPlayerOptions.m_fEffects;


	float fYAdjust = 0;	// fill this in depending on PlayerOptions

	if( fAccels[PlayerOptions::ACCEL_BOOST] != 0 )
	{
		float fEffectHeight = GetNoteFieldHeight(pPlayerState);
		float fNewYOffset = fYOffset * 1.5f / ((fYOffset+fEffectHeight/1.2f)/fEffectHeight); 
		float fAccelYAdjust =	fAccels[PlayerOptions::ACCEL_BOOST] * (fNewYOffset - fYOffset);
		// TRICKY:	Clamp this value, or else BOOST+BOOMERANG will draw a ton of arrows on the screen.
		CLAMP( fAccelYAdjust, -400.f, 400.f );
		fYAdjust += fAccelYAdjust;
	}
	if( fAccels[PlayerOptions::ACCEL_BRAKE] != 0 )
	{
		float fEffectHeight = GetNoteFieldHeight(pPlayerState);
		float fScale = SCALE( fYOffset, 0.f, fEffectHeight, 0, 1.f );
		float fNewYOffset = fYOffset * fScale; 
		float fBrakeYAdjust = fAccels[PlayerOptions::ACCEL_BRAKE] * (fNewYOffset - fYOffset);
		// TRICKY:	Clamp this value the same way as BOOST so that in BOOST+BRAKE, BRAKE doesn't overpower BOOST
		CLAMP( fBrakeYAdjust, -400.f, 400.f );
		fYAdjust += fBrakeYAdjust;
	}
	if( fAccels[PlayerOptions::ACCEL_WAVE] != 0 )
		fYAdjust +=	fAccels[PlayerOptions::ACCEL_WAVE] * 20.0f*RageFastSin( fYOffset/38.0f );

	fYOffset += fYAdjust;

	//
	// Factor in boomerang
	//
	if( fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0 )
	{
		float fOriginalYOffset = fYOffset;

		fYOffset = (-1*fOriginalYOffset*fOriginalYOffset/SCREEN_HEIGHT) + 1.5f*fOriginalYOffset;
		float fPeakAtYOffset = SCREEN_HEIGHT * 0.75f;	// zero point of function above
		fPeakYOffsetOut = (-1*fPeakAtYOffset*fPeakAtYOffset/SCREEN_HEIGHT) + 1.5f*fPeakAtYOffset;
		bIsPastPeakOut = fOriginalYOffset < fPeakAtYOffset;
	}

	//
	// Factor in scroll speed
	//
	float fScrollSpeed = pPlayerState->m_CurrentPlayerOptions.m_fScrollSpeed;
	if( pPlayerState->m_CurrentPlayerOptions.m_fRandomSpeed > 0 && !bAbsolute )
	{
		int seed = GAMESTATE->m_iStageSeed + ( BeatToNoteRow( fNoteBeat ) << 8 ) + (iCol * 100);

		/* Temporary hack: the first call to RandomFloat isn't "random"; it takes an extra
		 * call to get the RNG rolling. */
		RandomFloat( seed );
		float fRandom = RandomFloat( seed );

		/* Random speed always increases speed: a random speed of 10 indicates [1,11].
		 * This keeps it consistent with other mods: 0 means no effect. */
		fScrollSpeed *=
				SCALE( fRandom,
						0.0f, 1.0f,
						1.0f, pPlayerState->m_CurrentPlayerOptions.m_fRandomSpeed + 1.0f );
	}	


	if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 )
	{
		/* Timers can't be global, since they'll be initialized before SDL. */
		static RageTimer timerExpand;
		if( !GAMESTATE->m_bFreeze )
			g_fExpandSeconds += timerExpand.GetDeltaTime();
		else
			timerExpand.GetDeltaTime();	// throw away
		float fExpandMultiplier = SCALE( RageFastCos(g_fExpandSeconds*3), -1, 1, 0.75f, 1.75f );
		fScrollSpeed *=	SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], 0.f, 1.f, 1.f, fExpandMultiplier );
	}

	fYOffset *= fScrollSpeed;
	fPeakYOffsetOut *= fScrollSpeed;

	return fYOffset;
}
Beispiel #21
0
static NoteData ParseNoteData(RString &step1, RString &step2,
			      Steps &out, const RString &path)
{
	g_mapDanceNoteToNoteDataColumn.clear();
	switch( out.m_StepsType )
	{
		case StepsType_dance_single:
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
			break;
		case StepsType_dance_double:
		case StepsType_dance_couple:
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7;
			break;
		case StepsType_dance_solo:
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5;
			break;
			DEFAULT_FAIL( out.m_StepsType );
	}
	
	NoteData newNoteData;
	newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() );
	
	for( int pad=0; pad<2; pad++ )		// foreach pad
	{
		RString sStepData;
		switch( pad )
		{
			case 0:
				sStepData = step1;
				break;
			case 1:
				if( step2 == "" )	// no data
					continue;	// skip
				sStepData = step2;
				break;
				DEFAULT_FAIL( pad );
		}
		
		sStepData.Replace("\n", "");
		sStepData.Replace("\r", "");
		sStepData.Replace("\t", "");
		sStepData.Replace(" ", "");
		
		double fCurrentBeat = 0;
		double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
		
		for( size_t i=0; i<sStepData.size(); )
		{
			char c = sStepData[i++];
			switch( c )
			{
					// begins a series
				case '(':
					fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
					break;
				case '[':
					fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
					break;
				case '{':
					fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
					break;
				case '`':
					fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
					break;
					
					// ends a series
				case ')':
				case ']':
				case '}':
				case '\'':
				case '>':
					fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
					break;
					
				default:	// this is a note character
				{
					if( c == '!' )
					{
						LOG->UserLog(
							     "Song file",
							     path,
							     "has an unexpected character: '!'." );
						continue;
					}
					
					bool jump = false;
					if( c == '<' )
					{
						/* Arr.  Is this a jump or a 1/192 marker? */
						if( Is192( sStepData, i ) )
						{
							fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
							break;
						}
						
						/* It's a jump.
						 * We need to keep reading notes until we hit a >. */
						jump = true;
						i++;
					}
					
					const int iIndex = BeatToNoteRow( (float)fCurrentBeat );
					i--;
					do {
						c = sStepData[i++];
						
						if( jump && c == '>' )
							break;
						
						int iCol1, iCol2;
						DWIcharToNoteCol(
								 c,
								 (GameController)pad,
								 iCol1,
								 iCol2,
								 path );
						
						if( iCol1 != -1 )
							newNoteData.SetTapNote(iCol1,
									       iIndex,
									       TAP_ORIGINAL_TAP);
						if( iCol2 != -1 )
							newNoteData.SetTapNote(iCol2,
									       iIndex,
									       TAP_ORIGINAL_TAP);
						
						if(i>=sStepData.length())
						{
							break;
							//we ran out of data
							//while looking for the ending > mark
						}
						
						if( sStepData[i] == '!' )
						{
							i++;
							const char holdChar = sStepData[i++];
							
							DWIcharToNoteCol(holdChar,
									 (GameController)pad,
									 iCol1,
									 iCol2,
									 path );
							
							if( iCol1 != -1 )
								newNoteData.SetTapNote(iCol1,
										       iIndex,
										       TAP_ORIGINAL_HOLD_HEAD);
							if( iCol2 != -1 )
								newNoteData.SetTapNote(iCol2,
										       iIndex,
										       TAP_ORIGINAL_HOLD_HEAD);
						}
					}
					while( jump );
					fCurrentBeat += fCurrentIncrementer;
				}
					break;
			}
		}
	}
	
	/* Fill in iDuration. */
	for( int t=0; t<newNoteData.GetNumTracks(); ++t )
	{
		FOREACH_NONEMPTY_ROW_IN_TRACK( newNoteData, t, iHeadRow )
		{
			TapNote tn = newNoteData.GetTapNote( t, iHeadRow  );
			if( tn.type != TapNote::hold_head )
				continue;
			
			int iTailRow = iHeadRow;
			bool bFound = false;
			while( !bFound && newNoteData.GetNextTapNoteRowForTrack(t, iTailRow) )
			{
				const TapNote &TailTap = newNoteData.GetTapNote( t, iTailRow );
				if( TailTap.type == TapNote::empty )
					continue;
				
				newNoteData.SetTapNote( t, iTailRow, TAP_EMPTY );
				tn.iDuration = iTailRow - iHeadRow;
				newNoteData.SetTapNote( t, iHeadRow, tn );
				bFound = true;
			}
			
			if( !bFound )
			{
				/* The hold was never closed.  */
				LOG->UserLog("Song file",
					     path,
					     "failed to close a hold note in \"%s\" on track %i", 
					     DifficultyToString(out.GetDifficulty()).c_str(),
					     t);
				
				newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY );
			}
		}
	}
Beispiel #22
0
static void WriteDWINotesField( RageFile &f, const Steps &out, int start )
{
	NoteData notedata;
	out.GetNoteData( notedata );
	NoteDataUtil::InsertHoldTails( notedata );

	const int iLastMeasure = int( notedata.GetLastBeat()/BEATS_PER_MEASURE );
	for( int m=0; m<=iLastMeasure; m++ )	// foreach measure
	{
		NoteType nt = NoteDataUtil::GetSmallestNoteTypeForMeasure( notedata, m );

		double fCurrentIncrementer = 0;
		switch( nt )
		{
		case NOTE_TYPE_4TH:
		case NOTE_TYPE_8TH:	
			fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_12TH:
		case NOTE_TYPE_24TH:
			f.Write( "[" );
			fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_16TH:
			f.Write( "(" );
			fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_32ND:
		case NOTE_TYPE_64TH:
			f.Write( "{" );
			fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_48TH:
		case NOTE_TYPE_192ND:
		case NoteType_Invalid:
			// since, for whatever reason, the only way to do
			// 48ths is through a block of 192nds...
			f.Write(  "`" );
			fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
			break;
		default:
			ASSERT_M(0, ssprintf("nt = %d",nt) );
			break;
		}

		double fFirstBeatInMeasure = m * BEATS_PER_MEASURE;
		double fLastBeatInMeasure = (m+1) * BEATS_PER_MEASURE;

		for( double b=fFirstBeatInMeasure; b<=fLastBeatInMeasure-1/64.0f; b+=fCurrentIncrementer )	// need the -0.0001 to account for rounding errors
		{
			int row = BeatToNoteRow( (float)b );

			RString str;
			switch( out.m_StepsType )
			{
			case StepsType_dance_single:
			case StepsType_dance_couple:
			case StepsType_dance_double:
				str = NotesToDWIString( 
					notedata.GetTapNote(start+0, row), 
					notedata.GetTapNote(start+1, row),
					notedata.GetTapNote(start+2, row),
					notedata.GetTapNote(start+3, row) );

				// Blank out the notes so we don't write them again if the incrementer is small
				notedata.SetTapNote(start+0, row, TAP_EMPTY);
				notedata.SetTapNote(start+1, row, TAP_EMPTY);
				notedata.SetTapNote(start+2, row, TAP_EMPTY);
				notedata.SetTapNote(start+3, row, TAP_EMPTY);
				break;
			case StepsType_dance_solo:
				str = NotesToDWIString( 
					notedata.GetTapNote(0, row),
					notedata.GetTapNote(1, row),
					notedata.GetTapNote(2, row),
					notedata.GetTapNote(3, row),
					notedata.GetTapNote(4, row),
					notedata.GetTapNote(5, row) );

				// Blank out the notes so we don't write them again if the incrementer is small
				notedata.SetTapNote(start+0, row, TAP_EMPTY);
				notedata.SetTapNote(start+1, row, TAP_EMPTY);
				notedata.SetTapNote(start+2, row, TAP_EMPTY);
				notedata.SetTapNote(start+3, row, TAP_EMPTY);
				notedata.SetTapNote(start+4, row, TAP_EMPTY);
				notedata.SetTapNote(start+5, row, TAP_EMPTY);
				break;
			default:
				ASSERT(0);	// not a type supported by DWI.  We shouldn't have called in here if that's the case
			}
			f.Write( str );
		}

		switch( nt )
		{
		case NOTE_TYPE_4TH:
		case NOTE_TYPE_8TH:	
			break;
		case NOTE_TYPE_12TH:
		case NOTE_TYPE_24TH:
			f.Write( "]" );
			break;
		case NOTE_TYPE_16TH:
			f.Write( ")" );
			break;
		case NOTE_TYPE_32ND:
		case NOTE_TYPE_64TH:
			f.Write( "}" );
			break;
		case NOTE_TYPE_48TH:
		case NOTE_TYPE_192ND:
		case NoteType_Invalid:
			f.Write( "'" );
			break;
		default:
			ASSERT(0);
			// fall though
		}
		f.PutLine( "" );
	}
}
bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &song )
{
    LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );

    MsdFile msd;
    if( !msd.ReadFile( sPath ) )
        RageException::Throw( "Error opening file '%s'.", sPath.c_str() );

    int iTickCount = -1;	// this is the value we read for TICKCOUNT
    CStringArray asRows;

    for( unsigned i=0; i<msd.GetNumValues(); i++ )
    {
        const MsdFile::value_t &sParams = msd.GetValue(i);
        CString sValueName = sParams[0];

        // handle the data
        if( 0==stricmp(sValueName,"TICKCOUNT") )
            iTickCount = atoi(sParams[1]);

        else if( 0==stricmp(sValueName,"STEP") )
        {
            CString step = sParams[1];
            TrimLeft(step);
            split( step, "\n", asRows, true );
        }
        else if( 0==stricmp(sValueName,"DIFFICULTY") )
            out.SetMeter(atoi(sParams[1]));
    }

    if( iTickCount == -1 )
    {
        iTickCount = 2;
        LOG->Warn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount );
    }

    NoteData notedata;	// read it into here

    {
        CString sDir, sFName, sExt;
        splitpath( sPath, sDir, sFName, sExt );
        sFName.MakeLower();

        out.SetDescription(sFName);
        if( sFName.Find("crazy")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_HARD);
            if(!out.GetMeter()) out.SetMeter(8);
        }
        else if( sFName.Find("hard")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_MEDIUM);
            if(!out.GetMeter()) out.SetMeter(5);
        }
        else if( sFName.Find("easy")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_EASY);
            if(!out.GetMeter()) out.SetMeter(2);
        }
        else
        {
            out.SetDifficulty(DIFFICULTY_MEDIUM);
            if(!out.GetMeter()) out.SetMeter(5);
        }

        notedata.SetNumTracks( 5 );
        out.m_StepsType = STEPS_TYPE_PUMP_SINGLE;

        /* Check for "halfdouble" before "double". */
        if( sFName.Find("halfdouble") != -1 || sFName.Find("h_double") != -1 )
        {
            notedata.SetNumTracks( 6 );
            out.m_StepsType = STEPS_TYPE_PUMP_HALFDOUBLE;
        }
        else if( sFName.Find("double") != -1 )
        {
            notedata.SetNumTracks( 10 );
            out.m_StepsType = STEPS_TYPE_PUMP_DOUBLE;
        }
        else if( sFName.Find("_2") != -1 )
        {
            notedata.SetNumTracks( 10 );
            out.m_StepsType = STEPS_TYPE_PUMP_COUPLE;
        }
    }

    int iHoldStartRow[13];
    int t;
    for( t=0; t<13; t++ )
        iHoldStartRow[t] = -1;

    for( unsigned r=0; r<asRows.size(); r++ )
    {
        CString& sRowString = asRows[r];
        StripCrnl( sRowString );

        if( sRowString == "" )
            continue;	// skip

        /* All 2s indicates the end of the song. */
        if( sRowString == "2222222222222" )
            break;

        if(sRowString.size() != 13)
        {
            LOG->Warn("File %s had a RowString with an improper length (\"%s\"); corrupt notes ignored",
                      sPath.c_str(), sRowString.c_str());
            return false;
        }

        /* Half-doubles is offset; "0011111100000". */
        if( out.m_StepsType == STEPS_TYPE_PUMP_HALFDOUBLE )
            sRowString.erase( 0, 2 );

        // the length of a note in a row depends on TICKCOUNT
        float fBeatThisRow = r/(float)iTickCount;
        int row = BeatToNoteRow(fBeatThisRow);
        for( int t=0; t < notedata.GetNumTracks(); t++ )
        {
            if( sRowString[t] == '4' )
            {
                /* Remember when each hold starts; ignore the middle. */
                if( iHoldStartRow[t] == -1 )
                    iHoldStartRow[t] = r;

                continue;
            }

            if( iHoldStartRow[t] != -1 )	// this ends the hold
            {
                HoldNote hn (
                    t, /* button */
                    BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */
                    BeatToNoteRow((r-1)/(float)iTickCount) /* end */
                );
                notedata.AddHoldNote( hn );
                iHoldStartRow[t] = -1;
            }

            TapNote tap;
            switch(sRowString[t])
            {
            case '0':
                tap = TAP_EMPTY;
                break;
            case '1':
                tap = TAP_ORIGINAL_TAP;
                break;
            default:
                ASSERT(0);
                tap = TAP_EMPTY;
                break;
            }

            notedata.SetTapNote(t, row, tap);
        }
    }

    /* We need to remove holes where the BPM increases. */
//	if( song.m_Timing.m_BPMSegments.size() > 1 )
//		RemoveHoles( notedata, song );

    out.SetNoteData(&notedata);

    out.TidyUpData();

    return true;
}
Beispiel #24
0
void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change )
{
	int iStartRow = BeatToNoteRow(fFirstBeat);
	int iEndRow = BeatToNoteRow(fEndBeat);

	const TimingData &timing = m_pSong->m_SongTiming;

	// change BG every time signature change or 4 measures
	const vector<TimingSegment *> &tSigs = timing.GetTimingSegments(SEGMENT_TIME_SIG);

	for (unsigned i = 0; i < tSigs.size(); i++)
	{
		TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[i]);
		int iSegmentEndRow = (i + 1 == tSigs.size()) ? iEndRow : tSigs[i+1]->GetRow();


		for(int j=max(ts->GetRow(),iStartRow);
			j<min(iEndRow,iSegmentEndRow);
			j+=4*ts->GetNoteRowsPerMeasure())
		{
			// Don't fade. It causes frame rate dip, especially on slower machines.
			BackgroundDef bd = m_Layer[0].CreateRandomBGA(m_pSong,
														  change.m_def.m_sEffect,
														  m_RandomBGAnimations, this);
			if( !bd.IsEmpty() )
			{
				BackgroundChange c = change;
				c.m_def = bd;
				c.m_fStartBeat = NoteRowToBeat(j);
				m_Layer[0].m_aBGChanges.push_back( c );
			}
		}
	}

	// change BG every BPM change that is at the beginning of a measure
	const vector<TimingSegment *> &bpms = timing.GetTimingSegments(SEGMENT_BPM);
	for( unsigned i=0; i<bpms.size(); i++ )
	{
		bool bAtBeginningOfMeasure = false;
		for (unsigned j=0; j<tSigs.size(); j++)
		{
			TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[j]);
			if ((bpms[i]->GetRow() - ts->GetRow()) % ts->GetNoteRowsPerMeasure() == 0)
			{
				bAtBeginningOfMeasure = true;
				break;
			}
		}

		if( !bAtBeginningOfMeasure )
			continue; // skip

		// start so that we don't create a BGChange right on top of fEndBeat
		bool bInRange = bpms[i]->GetRow() >= iStartRow && bpms[i]->GetRow() < iEndRow;
		if( !bInRange )
			continue; // skip

		BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this );
		if( !bd.IsEmpty() )
		{
			BackgroundChange c = change;
			c.m_def.m_sFile1 = bd.m_sFile1;
			c.m_def.m_sFile2 = bd.m_sFile2;
			c.m_fStartBeat = bpms[i]->GetBeat();
			m_Layer[0].m_aBGChanges.push_back( c );
		}
	}
}
Beispiel #25
0
NoteType BeatToNoteType( float fBeat )
{ 
	return GetNoteType( BeatToNoteRow(fBeat) );
}
/* For visibility testing: if bAbsolute is false, random modifiers must return
 * the minimum possible scroll speed. */
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
{
	// Default values that are returned if boomerang is off.
	fPeakYOffsetOut = FLT_MAX;
	bIsPastPeakOut = true;

	float fYOffset = 0;
	const SongPosition &position = pPlayerState->GetDisplayedPosition();
	
	float fSongBeat = position.m_fSongBeatVisible;
	
	Steps *pCurSteps = GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber];

	/* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or
	 * entirely time spacing (respectively). Occasionally, we tween between them. */
	if( curr_options->m_fTimeSpacing != 1.0f )
	{
		if( GAMESTATE->m_bInStepEditor ) {
			// Use constant spacing in step editor
			fYOffset = fNoteBeat - fSongBeat;
		} else {
			fYOffset = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat);
			fYOffset *= pCurSteps->GetTimingData()->GetDisplayedSpeedPercent(
								     position.m_fSongBeatVisible,
								     position.m_fMusicSecondsVisible );
		}
		fYOffset *= 1 - curr_options->m_fTimeSpacing;
	}

	if( curr_options->m_fTimeSpacing != 0.0f )
	{
		float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible;
		float fNoteSeconds = pCurSteps->GetTimingData()->GetElapsedTimeFromBeat(fNoteBeat);
		float fSecondsUntilStep = fNoteSeconds - fSongSeconds;
		float fBPM = curr_options->m_fScrollBPM;
		float fBPS = fBPM/60.f / GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
		float fYOffsetTimeSpacing = fSecondsUntilStep * fBPS;
		fYOffset += fYOffsetTimeSpacing * curr_options->m_fTimeSpacing;
	}

	// TODO: If we allow noteskins to have metricable row spacing
	// (per issue 24), edit this to reflect that. -aj
	fYOffset *= ARROW_SPACING;

	// Factor in scroll speed
	float fScrollSpeed = curr_options->m_fScrollSpeed;
	if(curr_options->m_fMaxScrollBPM != 0)
	{
		fScrollSpeed= curr_options->m_fMaxScrollBPM /
			(pPlayerState->m_fReadBPM * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
	}
	
	// don't mess with the arrows after they've crossed 0
	if( fYOffset < 0 )
	{
		return fYOffset * fScrollSpeed;
	}

	const float* fAccels = curr_options->m_fAccels;
	//const float* fEffects = curr_options->m_fEffects;

	float fYAdjust = 0;	// fill this in depending on PlayerOptions

	if( fAccels[PlayerOptions::ACCEL_BOOST] != 0 )
	{
		float fEffectHeight = GetNoteFieldHeight();
		float fNewYOffset = fYOffset * 1.5f / ((fYOffset+fEffectHeight/1.2f)/fEffectHeight); 
		float fAccelYAdjust =	fAccels[PlayerOptions::ACCEL_BOOST] * (fNewYOffset - fYOffset);
		// TRICKY: Clamp this value, or else BOOST+BOOMERANG will draw a ton of arrows on the screen.
		CLAMP( fAccelYAdjust, BOOST_MOD_MIN_CLAMP, BOOST_MOD_MAX_CLAMP );
		fYAdjust += fAccelYAdjust;
	}
	if( fAccels[PlayerOptions::ACCEL_BRAKE] != 0 )
	{
		float fEffectHeight = GetNoteFieldHeight();
		float fScale = SCALE( fYOffset, 0.f, fEffectHeight, 0, 1.f );
		float fNewYOffset = fYOffset * fScale; 
		float fBrakeYAdjust = fAccels[PlayerOptions::ACCEL_BRAKE] * (fNewYOffset - fYOffset);
		// TRICKY: Clamp this value the same way as BOOST so that in BOOST+BRAKE, BRAKE doesn't overpower BOOST
		CLAMP( fBrakeYAdjust, BRAKE_MOD_MIN_CLAMP, BRAKE_MOD_MAX_CLAMP );
		fYAdjust += fBrakeYAdjust;
	}
	if( fAccels[PlayerOptions::ACCEL_WAVE] != 0 )
		fYAdjust +=	fAccels[PlayerOptions::ACCEL_WAVE] * WAVE_MOD_MAGNITUDE *RageFastSin( fYOffset/WAVE_MOD_HEIGHT );

	fYOffset += fYAdjust;

	// Factor in boomerang
	if( fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0 )
	{
		float fPeakAtYOffset = SCREEN_HEIGHT * BOOMERANG_PEAK_PERCENTAGE;	// zero point of boomerang function
		fPeakYOffsetOut = (-1*fPeakAtYOffset*fPeakAtYOffset/SCREEN_HEIGHT) + 1.5f*fPeakAtYOffset;
		bIsPastPeakOut = fYOffset < fPeakAtYOffset;

		fYOffset = (-1*fYOffset*fYOffset/SCREEN_HEIGHT) + 1.5f*fYOffset;
	}

	if( curr_options->m_fRandomSpeed > 0 && !bAbsolute )
	{
		// Generate a deterministically "random" speed for each arrow.
		unsigned seed = GAMESTATE->m_iStageSeed + ( BeatToNoteRow( fNoteBeat ) << 8 ) + (iCol * 100);

		for( int i = 0; i < 3; ++i )
			seed = ((seed * 1664525u) + 1013904223u) & 0xFFFFFFFF;
		float fRandom = seed / 4294967296.0f;

		/* Random speed always increases speed: a random speed of 10 indicates
		 * [1,11]. This keeps it consistent with other mods: 0 means no effect. */
		fScrollSpeed *=
				SCALE( fRandom,
						0.0f, 1.0f,
						1.0f, curr_options->m_fRandomSpeed + 1.0f );
	}

	if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 )
	{
		// TODO: Don't index by PlayerNumber.
		PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber];
	
		float fExpandMultiplier = SCALE( RageFastCos(data.m_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY),
						EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH,
						EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH );
		fScrollSpeed *=	SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], 
				      EXPAND_SPEED_SCALE_FROM_LOW, EXPAND_SPEED_SCALE_FROM_HIGH,
				      EXPAND_SPEED_SCALE_TO_LOW, fExpandMultiplier );
	}

	fYOffset *= fScrollSpeed;
	fPeakYOffsetOut *= fScrollSpeed;

	return fYOffset;
}
Beispiel #27
0
static bool LoadFromKSFFile( const RString &sPath, Steps &out, Song &song, bool bKIUCompliant )
{
	LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );

	MsdFile msd;
	if( !msd.ReadFile( sPath, false ) )  // don't unescape
	{
		LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() );
		return false;
	}

	// this is the value we read for TICKCOUNT
	int iTickCount = -1;
	// used to adapt weird tickcounts
	//float fScrollRatio = 1.0f; -- uncomment when ready to use.
	vector<RString> vNoteRows;

	// According to Aldo_MX, there is a default BPM and it's 60. -aj
	bool bDoublesChart = false;
	
	TimingData stepsTiming;
	float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1;

	for( unsigned i=0; i<msd.GetNumValues(); i++ )
	{
		const MsdFile::value_t &sParams = msd.GetValue( i );
		RString sValueName = sParams[0];
		sValueName.MakeUpper();

		/* handle the data...well, not this data: not related to steps.
		 * Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */
		if (sValueName=="TITLE" || EndsWith(sValueName, "INTRO")
		    || EndsWith(sValueName, "FILE") )
		{

		}
		else if( sValueName=="BPM" )
		{
			BPM1 = StringToFloat(sParams[1]);
			stepsTiming.AddSegment( BPMSegment(0, BPM1) );
		}
		else if( sValueName=="BPM2" )
		{
			if (bKIUCompliant)
			{
				BPM2 = StringToFloat( sParams[1] );
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="BPM3" )
		{
			if (bKIUCompliant)
			{
				BPM3 = StringToFloat( sParams[1] );
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="BUNKI" )
		{
			if (bKIUCompliant)
			{
				BPMPos2 = StringToFloat( sParams[1] ) / 100.0f;
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="BUNKI2" )
		{
			if (bKIUCompliant)
			{
				BPMPos3 = StringToFloat( sParams[1] ) / 100.0f;
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="STARTTIME" )
		{
			SMGap1 = -StringToFloat( sParams[1] )/100;
			stepsTiming.m_fBeat0OffsetInSeconds = SMGap1;
		}
		// This is currently required for more accurate KIU BPM changes.  
		else if( sValueName=="STARTTIME2" )
		{
			if (bKIUCompliant)
			{
				SMGap2 = -StringToFloat( sParams[1] )/100;
			}
			else
			{
				// LOG an error.
			}
		}
		else if ( sValueName=="STARTTIME3" )
		{
			// STARTTIME3 only ensures this is a KIU compliant simfile.
			bKIUCompliant = true;
		}
		
		else if( sValueName=="TICKCOUNT" )
		{
			iTickCount = StringToInt( sParams[1] );
			if( iTickCount <= 0 )
			{
				LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount );
				return false;
			}
			stepsTiming.AddSegment( TickcountSegment(0, iTickCount));
		}
		
		else if( sValueName=="DIFFICULTY" )
		{
			out.SetMeter( max(StringToInt(sParams[1]), 1) );
		}
		// new cases from Aldo_MX's fork:
		else if( sValueName=="PLAYER" )
		{
			RString sPlayer = sParams[1];
			sPlayer.MakeLower();
			if( sPlayer.find( "double" ) != string::npos )
				bDoublesChart = true;
		}
		// This should always be last.
		else if( sValueName=="STEP" )
		{
			RString theSteps = sParams[1];
			TrimLeft( theSteps );
			split( theSteps, "\n", vNoteRows, true );
		}
	}

	if( iTickCount == -1 )
	{
		iTickCount = 4;
		LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount );
	}
	
	// Prepare BPM stuff already if the file uses KSF syntax.
	if( bKIUCompliant )
	{
		if( BPM2 > 0 && BPMPos2 > 0 )
		{
			HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 );
		}
		
		if( BPM3 > 0 && BPMPos3 > 0 )
		{
			HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 );
		}
	}

	NoteData notedata;	// read it into here

	{
		RString sDir, sFName, sExt;
		splitpath( sPath, sDir, sFName, sExt );
		sFName.MakeLower();

		out.SetDescription(sFName);
		// Check another before anything else... is this okay? -DaisuMaster
		if( sFName.find("another") != string::npos )
		{
			out.SetDifficulty( Difficulty_Edit );
			if( !out.GetMeter() ) out.SetMeter( 25 );
		}
		else if(sFName.find("wild") != string::npos || 
			sFName.find("wd") != string::npos || 
			sFName.find("crazy+") != string::npos || 
			sFName.find("cz+") != string::npos || 
			sFName.find("hardcore") != string::npos )
		{
			out.SetDifficulty( Difficulty_Challenge );
			if( !out.GetMeter() ) out.SetMeter( 20 );
		}
		else if(sFName.find("crazy") != string::npos || 
			sFName.find("cz") != string::npos || 
			sFName.find("nightmare") != string::npos || 
			sFName.find("nm") != string::npos || 
			sFName.find("crazydouble") != string::npos )
		{
			out.SetDifficulty( Difficulty_Hard );
			if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR.
		}
		else if(sFName.find("hard") != string::npos || 
			sFName.find("hd") != string::npos || 
			sFName.find("freestyle") != string::npos || 
			sFName.find("fs") != string::npos || 
			sFName.find("double") != string::npos )
		{
			out.SetDifficulty( Difficulty_Medium );
			if( !out.GetMeter() ) out.SetMeter( 8 );
		}
		else if(sFName.find("easy") != string::npos || 
			sFName.find("ez") != string::npos || 
			sFName.find("normal") != string::npos )
		{
			// I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster
			out.SetDifficulty( Difficulty_Easy );
			if( !out.GetMeter() ) out.SetMeter( 4 );
		}
		else if(sFName.find("beginner") != string::npos || 
			sFName.find("practice") != string::npos || sFName.find("pr") != string::npos  )
		{
			out.SetDifficulty( Difficulty_Beginner );
			if( !out.GetMeter() ) out.SetMeter( 4 );
		}
		else
		{
			out.SetDifficulty( Difficulty_Hard );
			if( !out.GetMeter() ) out.SetMeter( 10 );
		}

		out.m_StepsType = StepsType_pump_single;

		// Check for "halfdouble" before "double".
		if(sFName.find("halfdouble") != string::npos || 
		   sFName.find("half-double") != string::npos || 
		   sFName.find("h_double") != string::npos || 
		   sFName.find("hdb") != string::npos )
			out.m_StepsType = StepsType_pump_halfdouble;
		// Handle bDoublesChart from above as well. -aj
		else if(sFName.find("double") != string::npos || 
			sFName.find("nightmare") != string::npos || 
			sFName.find("freestyle") != string::npos || 
			sFName.find("db") != string::npos || 
			sFName.find("nm") != string::npos || 
			sFName.find("fs") != string::npos || bDoublesChart )
			out.m_StepsType = StepsType_pump_double;
		else if( sFName.find("_1") != string::npos )
			out.m_StepsType = StepsType_pump_single;
		else if( sFName.find("_2") != string::npos )
			out.m_StepsType = StepsType_pump_couple;
	}

	switch( out.m_StepsType )
	{
	case StepsType_pump_single: notedata.SetNumTracks( 5 ); break;
	case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break;
	case StepsType_pump_double: notedata.SetNumTracks( 10 ); break;
	case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this?
	case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break;
	default: FAIL_M( ssprintf("%i", out.m_StepsType) );
	}

	int t = 0;
	int iHoldStartRow[13];
	for( t=0; t<13; t++ )
		iHoldStartRow[t] = -1;

	bool bTickChangeNeeded = false;
	int newTick = -1;
	float fCurBeat = 0.0f;
	float prevBeat = 0.0f; // Used for hold tails.

	for( unsigned r=0; r<vNoteRows.size(); r++ )
	{
		RString& sRowString = vNoteRows[r];
		StripCrnl( sRowString );

		if( sRowString == "" )
			continue;	// skip

		// All 2s indicates the end of the song.
		else if( sRowString == "2222222222222" )
		{
			// Finish any holds that didn't get...well, finished.
			for( t=0; t < notedata.GetNumTracks(); t++ )
			{
				if( iHoldStartRow[t] != -1 )	// this ends the hold
				{
					if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) )
						notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP );
					else
						notedata.AddHoldNote(t,
								     iHoldStartRow[t],
								     BeatToNoteRow(prevBeat),
								     TAP_ORIGINAL_HOLD_HEAD );
				}
			}
			/* have this row be the last moment in the song, unless
			 * a future step ends later. */
			//float curTime = stepsTiming.GetElapsedTimeFromBeat(fCurBeat);
			//if (curTime > song.GetSpecifiedLastSecond())
			//{
			//	song.SetSpecifiedLastSecond(curTime);
			//}

			song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 );

			break;
		}

		else if( BeginsWith(sRowString, "|") )
		{
			/*
			if (bKIUCompliant)
			{
				// Log an error, ignore the line.
				continue;
			}
			*/
			// gotta do something tricky here: if the bpm is below one then a couple of calculations
			// for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will
			// be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll
			// segment will then be added based on approximations. -DaisuMaster
			// eh better do it considering the tickcount (high tickcounts)

			// I'm making some experiments, please spare me...
			//continue;

			RString temp = sRowString.substr(2,sRowString.size()-3);
			float numTemp = StringToFloat(temp);
			if (BeginsWith(sRowString, "|T")) 
			{
				// duh
				iTickCount = static_cast<int>(numTemp);
				// I have been owned by the man -DaisuMaster
				stepsTiming.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) );
			}
			else if (BeginsWith(sRowString, "|B")) 
			{
				// BPM
				stepsTiming.SetBPMAtBeat( fCurBeat, numTemp );
			}
			else if (BeginsWith(sRowString, "|E"))
			{
				// DelayBeat
				float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
				fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
				stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay );
			}
			else if (BeginsWith(sRowString, "|D"))
			{
				// Delays
				float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) );
				fCurDelay += numTemp / 1000;
				stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay );
			}
			else if (BeginsWith(sRowString, "|M") || BeginsWith(sRowString, "|C"))
			{
				// multipliers/combo
				ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) );
				stepsTiming.AddSegment( seg );
			}
			else if (BeginsWith(sRowString, "|S"))
			{
				// speed segments
			}
			else if (BeginsWith(sRowString, "|F"))
			{
				// fakes
			}
			else if (BeginsWith(sRowString, "|X"))
			{
				// scroll segments
				ScrollSegment seg = ScrollSegment( BeatToNoteRow(fCurBeat), numTemp );
				stepsTiming.AddSegment( seg );
				//return true;
			}

			continue;
		}

		// Half-doubles is offset; "0011111100000".
		if( out.m_StepsType == StepsType_pump_halfdouble )
			sRowString.erase( 0, 2 );

		// Update TICKCOUNT for Direct Move files.
		if( bTickChangeNeeded )
		{
			iTickCount = newTick;
			bTickChangeNeeded = false;
		}

		for( t=0; t < notedata.GetNumTracks(); t++ )
		{
			if( sRowString[t] == '4' )
			{
				/* Remember when each hold starts; ignore the middle. */
				if( iHoldStartRow[t] == -1 )
					iHoldStartRow[t] = BeatToNoteRow(fCurBeat);
				continue;
			}

			if( iHoldStartRow[t] != -1 )	// this ends the hold
			{
				int iEndRow = BeatToNoteRow(prevBeat);
				if( iHoldStartRow[t] == iEndRow )
					notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP );
				else
				{
					//notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_PUMP_HEAD );
					notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_HOLD_HEAD );
				}
				iHoldStartRow[t] = -1;
			}

			TapNote tap;
			switch( sRowString[t] )
			{
			case '0':	tap = TAP_EMPTY;		break;
			case '1':	tap = TAP_ORIGINAL_TAP;		break;
				//allow setting more notetypes on ksf files, this may come in handy (it should) -DaisuMaster
			case 'M':
			case 'm':
						tap = TAP_ORIGINAL_MINE;
						break;
			case 'F':
			case 'f':
						tap = TAP_ORIGINAL_FAKE;
						break;
			case 'L':
			case 'l':
						tap = TAP_ORIGINAL_LIFT;
						break;
			default:
				LOG->UserLog( "Song file", sPath, "has an invalid row \"%s\"; corrupt notes ignored.",
					      sRowString.c_str() );
				//return false;
				tap = TAP_EMPTY;
				break;
			}

			notedata.SetTapNote(t, BeatToNoteRow(fCurBeat), tap);
		}
		prevBeat = fCurBeat;
		fCurBeat = prevBeat + 1.0f / iTickCount;
	}

	out.SetNoteData( notedata );
	out.m_Timing = stepsTiming;

	out.TidyUpData();

	out.SetSavedToDisk( true );	// we're loading from disk, so this is by definintion already saved

	return true;
}