Known Bugs: The only major bug i've experienced with this is for some reason the arrows continue past the grey bar even if you get great, perfect or marvellous. This doesn't affect the score though, I can't quite figure this one out. Added: Hidden and Vanish codes for PIU.
442 lines
14 KiB
C++
442 lines
14 KiB
C++
#include "global.h"
|
|
/*
|
|
-----------------------------------------------------------------------------
|
|
Class: NoteField
|
|
|
|
Desc: See header.
|
|
|
|
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
|
|
Chris Danford
|
|
-----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "NoteField.h"
|
|
#include "RageUtil.h"
|
|
#include "GameConstantsAndTypes.h"
|
|
#include "PrefsManager.h"
|
|
#include "ArrowEffects.h"
|
|
#include "PrefsManager.h"
|
|
#include "GameManager.h"
|
|
#include "GameState.h"
|
|
#include "RageException.h"
|
|
#include "RageTimer.h"
|
|
#include "RageLog.h"
|
|
#include <math.h>
|
|
#include "ThemeManager.h"
|
|
#include "RageDisplay.h"
|
|
#include "NoteFieldPositioning.h"
|
|
|
|
|
|
const float HOLD_NOTE_BITS_PER_BEAT = 6;
|
|
const float HOLD_NOTE_BITS_PER_ROW = HOLD_NOTE_BITS_PER_BEAT / ROWS_PER_BEAT;
|
|
const float ROWS_BETWEEN_HOLD_BITS = 1 / HOLD_NOTE_BITS_PER_ROW;
|
|
|
|
NoteField::NoteField()
|
|
{
|
|
m_textMeasureNumber.LoadFromFont( THEME->GetPathToF("Common normal") );
|
|
m_textMeasureNumber.SetZoom( 1.0f );
|
|
|
|
m_rectMarkerBar.EnableShadow( false );
|
|
m_rectMarkerBar.SetEffectDiffuseShift( 2, RageColor(1,1,1,0.5f), RageColor(0.5f,0.5f,0.5f,0.5f) );
|
|
|
|
m_sprBars.Load( THEME->GetPathToG("NoteField bars") );
|
|
m_sprBars.StopAnimating();
|
|
|
|
m_fBeginMarker = m_fEndMarker = -1;
|
|
|
|
m_fPercentFadeToFail = -1;
|
|
}
|
|
|
|
|
|
void NoteField::Load( NoteData* pNoteData, PlayerNumber pn, int iFirstPixelToDraw, int iLastPixelToDraw )
|
|
{
|
|
m_PlayerNumber = pn;
|
|
m_iFirstPixelToDraw = iFirstPixelToDraw;
|
|
m_iLastPixelToDraw = iLastPixelToDraw;
|
|
|
|
m_fPercentFadeToFail = -1;
|
|
|
|
NoteDataWithScoring::Init();
|
|
|
|
m_bIsHoldingHoldNote.clear();
|
|
m_bIsHoldingHoldNote.insert(m_bIsHoldingHoldNote.end(), pNoteData->GetNumTapNotes(), false);
|
|
|
|
this->CopyAll( pNoteData );
|
|
|
|
// init note displays
|
|
for( int c=0; c<GetNumTracks(); c++ )
|
|
m_NoteDisplay[c].Load( c, pn );
|
|
|
|
ASSERT( GetNumTracks() == GAMESTATE->GetCurrentStyleDef()->m_iColsPerPlayer );
|
|
}
|
|
|
|
void NoteField::ReloadNoteSkin()
|
|
{
|
|
// init note displays
|
|
for( int c=0; c<GetNumTracks(); c++ )
|
|
m_NoteDisplay[c].Load( c, m_PlayerNumber );
|
|
}
|
|
|
|
void NoteField::Update( float fDeltaTime )
|
|
{
|
|
m_rectMarkerBar.Update( fDeltaTime );
|
|
|
|
if( m_fPercentFadeToFail >= 0 )
|
|
m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/1.5f, 1 ); // take 1.5 seconds to totally fade
|
|
}
|
|
|
|
float NoteField::GetWidth()
|
|
{
|
|
const StyleDef* pStyleDef = GAMESTATE->GetCurrentStyleDef();
|
|
float fMinX, fMaxX;
|
|
pStyleDef->GetMinAndMaxColX( m_PlayerNumber, fMinX, fMaxX );
|
|
|
|
return fMaxX - fMinX + ARROW_SIZE;
|
|
}
|
|
|
|
void NoteField::DrawBeatBar( const float fBeat )
|
|
{
|
|
bool bIsMeasure = fmodf( fBeat, (float)BEATS_PER_MEASURE ) == 0;
|
|
int iMeasureIndex = (int)fBeat / BEATS_PER_MEASURE;
|
|
int iMeasureNoDisplay = iMeasureIndex+1;
|
|
|
|
NoteType nt = BeatToNoteType( fBeat );
|
|
|
|
const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat );
|
|
const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset );
|
|
|
|
float fAlpha;
|
|
int iState;
|
|
|
|
if( bIsMeasure )
|
|
{
|
|
fAlpha = 1;
|
|
iState = 0;
|
|
}
|
|
else
|
|
{
|
|
float fScrollSpeed = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fScrollSpeed;
|
|
switch( nt )
|
|
{
|
|
default: ASSERT(0);
|
|
case NOTE_TYPE_4TH: fAlpha = 1; iState = 1; break;
|
|
case NOTE_TYPE_8TH: fAlpha = SCALE(fScrollSpeed,1.f,2.f,0.f,1.f); iState = 2; break;
|
|
case NOTE_TYPE_16TH:fAlpha = SCALE(fScrollSpeed,2.f,4.f,0.f,1.f); iState = 3; break;
|
|
}
|
|
CLAMP( fAlpha, 0, 1 );
|
|
}
|
|
|
|
float fWidth = GetWidth();
|
|
float fFrameWidth = m_sprBars.GetUnzoomedWidth();
|
|
|
|
m_sprBars.SetX( 0 );
|
|
m_sprBars.SetY( fYPos );
|
|
m_sprBars.SetDiffuse( RageColor(1,1,1,fAlpha) );
|
|
m_sprBars.SetState( iState );
|
|
m_sprBars.SetCustomTextureRect( RectF(0,SCALE(iState,0.f,4.f,0.f,1.f), fWidth/fFrameWidth, SCALE(iState+1,0.f,4.f,0.f,1.f)) );
|
|
m_sprBars.SetZoomX( fWidth/m_sprBars.GetUnzoomedWidth() );
|
|
m_sprBars.Draw();
|
|
|
|
|
|
if( bIsMeasure )
|
|
{
|
|
m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) );
|
|
m_textMeasureNumber.SetHorizAlign( Actor::align_right );
|
|
m_textMeasureNumber.SetText( ssprintf("%d", iMeasureNoDisplay) );
|
|
m_textMeasureNumber.SetXY( -fWidth/2, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
}
|
|
|
|
void NoteField::DrawMarkerBar( const float fBeat )
|
|
{
|
|
const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat );
|
|
const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset );
|
|
|
|
|
|
m_rectMarkerBar.StretchTo( RectF(-GetWidth()/2, fYPos-ARROW_SIZE/2, GetWidth()/2, fYPos+ARROW_SIZE/2) );
|
|
m_rectMarkerBar.Draw();
|
|
}
|
|
|
|
void NoteField::DrawAreaHighlight( const float fStartBeat, const float fEndBeat )
|
|
{
|
|
float fYStartOffset = ArrowGetYOffset( m_PlayerNumber, fStartBeat );
|
|
float fYStartPos = ArrowGetYPos( m_PlayerNumber, fYStartOffset );
|
|
float fYEndOffset = ArrowGetYOffset( m_PlayerNumber, fEndBeat );
|
|
float fYEndPos = ArrowGetYPos( m_PlayerNumber, fYEndOffset );
|
|
|
|
// Something in OpenGL crashes if this is values are too large. Strange. -Chris
|
|
fYStartPos = max( fYStartPos, -1000 );
|
|
fYEndPos = min( fYEndPos, +5000 );
|
|
|
|
m_rectAreaHighlight.StretchTo( RectF(-GetWidth()/2, fYStartPos-ARROW_SIZE/2, GetWidth()/2, fYEndPos+ARROW_SIZE/2) );
|
|
m_rectAreaHighlight.SetDiffuse( RageColor(1,0,0,0.3f) );
|
|
m_rectAreaHighlight.Draw();
|
|
}
|
|
|
|
|
|
|
|
void NoteField::DrawBPMText( const float fBeat, const float fBPM )
|
|
{
|
|
const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat );
|
|
const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset );
|
|
|
|
m_textMeasureNumber.SetHorizAlign( Actor::align_right );
|
|
m_textMeasureNumber.SetDiffuse( RageColor(1,0,0,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,cosf(RageTimer::GetTimeSinceStart()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( ssprintf("%.2f", fBPM) );
|
|
m_textMeasureNumber.SetXY( -GetWidth()/2.f - 60, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawFreezeText( const float fBeat, const float fSecs )
|
|
{
|
|
const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat );
|
|
const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset );
|
|
|
|
m_textMeasureNumber.SetHorizAlign( Actor::align_right );
|
|
m_textMeasureNumber.SetDiffuse( RageColor(0.8f,0.8f,0,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,cosf(RageTimer::GetTimeSinceStart()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( ssprintf("%.2f", fSecs) );
|
|
m_textMeasureNumber.SetXY( -GetWidth()/2.f - 10, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawBGChangeText( const float fBeat, const CString sNewBGName )
|
|
{
|
|
const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat );
|
|
const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset );
|
|
|
|
m_textMeasureNumber.SetHorizAlign( Actor::align_left );
|
|
m_textMeasureNumber.SetDiffuse( RageColor(0,1,0,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,cosf(RageTimer::GetTimeSinceStart()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( sNewBGName );
|
|
m_textMeasureNumber.SetXY( +GetWidth()/2.f, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawPrimitives()
|
|
{
|
|
//LOG->Trace( "NoteField::DrawPrimitives()" );
|
|
|
|
const float fSongBeat = GAMESTATE->m_fSongBeat;
|
|
|
|
//
|
|
// Adjust draw range depending on some effects
|
|
//
|
|
int iFirstPixelToDraw = m_iFirstPixelToDraw;
|
|
int iLastPixelToDraw = m_iLastPixelToDraw;
|
|
|
|
float fDrawScale = 1;
|
|
fDrawScale *= 1 + 0.5f * fabsf( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fPerspectiveTilt );
|
|
fDrawScale *= 1 + fabsf( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fEffects[PlayerOptions::EFFECT_MINI] );
|
|
|
|
float fFirstDrawScale = g_NoteFieldMode[m_PlayerNumber].m_fFirstPixelToDrawScale;
|
|
float fLastDrawScale = g_NoteFieldMode[m_PlayerNumber].m_fLastPixelToDrawScale;
|
|
|
|
iFirstPixelToDraw = (int)(iFirstPixelToDraw * fFirstDrawScale * fDrawScale);
|
|
iLastPixelToDraw = (int)(iLastPixelToDraw * fLastDrawScale * fDrawScale);
|
|
|
|
|
|
// CPU OPTIMIZATION OPPORTUNITY:
|
|
// change this probing to binary search
|
|
|
|
// probe for first note on the screen
|
|
float fFirstBeatToDraw = fSongBeat-4; // Adjust to balance of performance and showing enough notes.
|
|
while( fFirstBeatToDraw<fSongBeat )
|
|
{
|
|
float fYOffset = ArrowGetYOffset(m_PlayerNumber, fFirstBeatToDraw);
|
|
float fYPosWOReverse = ArrowGetYPosWithoutReverse(m_PlayerNumber, fYOffset );
|
|
if( fYPosWOReverse < iFirstPixelToDraw ) // off screen
|
|
fFirstBeatToDraw += 0.1f; // move toward fSongBeat
|
|
else // on screen
|
|
break; // stop probing
|
|
}
|
|
fFirstBeatToDraw -= 0.1f; // rewind if we intentionally overshot
|
|
|
|
// probe for last note to draw
|
|
float fLastBeatToDraw = fSongBeat+20; // worst case is 0.25x + boost. Adjust to balance of performance and showing enough notes.
|
|
while( fLastBeatToDraw>fSongBeat )
|
|
{
|
|
float fYOffset = ArrowGetYOffset(m_PlayerNumber, fLastBeatToDraw);
|
|
float fYPosWOReverse = ArrowGetYPosWithoutReverse(m_PlayerNumber, fYOffset );
|
|
if( fYPosWOReverse > iLastPixelToDraw ) // off screen
|
|
fLastBeatToDraw -= 0.1f; // move toward fSongBeat
|
|
else // on screen
|
|
break; // stop probing
|
|
}
|
|
fLastBeatToDraw += 0.1f; // fast forward since we intentionally overshot
|
|
|
|
|
|
const int iFirstIndexToDraw = BeatToNoteRow(fFirstBeatToDraw);
|
|
const int iLastIndexToDraw = BeatToNoteRow(fLastBeatToDraw);
|
|
|
|
// LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat );
|
|
// LOG->Trace( "Drawing elements %d through %d", iFirstIndexToDraw, iLastIndexToDraw );
|
|
|
|
if( GAMESTATE->m_bEditing )
|
|
{
|
|
ASSERT(GAMESTATE->m_pCurSong);
|
|
|
|
unsigned i;
|
|
|
|
//
|
|
// Draw beat bars
|
|
//
|
|
{
|
|
float fStartDrawingMeasureBars = max( 0, froundf(fFirstBeatToDraw-0.25f,0.25f) );
|
|
for( float f=fStartDrawingMeasureBars; f<fLastBeatToDraw; f+=0.25f )
|
|
DrawBeatBar( f );
|
|
}
|
|
|
|
//
|
|
// BPM text
|
|
//
|
|
vector<BPMSegment> &aBPMSegments = GAMESTATE->m_pCurSong->m_BPMSegments;
|
|
for( i=0; i<aBPMSegments.size(); i++ )
|
|
{
|
|
if(aBPMSegments[i].m_fStartBeat >= fFirstBeatToDraw &&
|
|
aBPMSegments[i].m_fStartBeat <= fLastBeatToDraw)
|
|
DrawBPMText( aBPMSegments[i].m_fStartBeat, aBPMSegments[i].m_fBPM );
|
|
}
|
|
//
|
|
// Freeze text
|
|
//
|
|
vector<StopSegment> &aStopSegments = GAMESTATE->m_pCurSong->m_StopSegments;
|
|
for( i=0; i<aStopSegments.size(); i++ )
|
|
{
|
|
if(aStopSegments[i].m_fStartBeat >= fFirstBeatToDraw &&
|
|
aStopSegments[i].m_fStartBeat <= fLastBeatToDraw)
|
|
DrawFreezeText( aStopSegments[i].m_fStartBeat, aStopSegments[i].m_fStopSeconds );
|
|
}
|
|
|
|
//
|
|
// BGChange text
|
|
//
|
|
vector<BackgroundChange> &aBackgroundChanges = GAMESTATE->m_pCurSong->m_BackgroundChanges;
|
|
for( i=0; i<aBackgroundChanges.size(); i++ )
|
|
{
|
|
if(aBackgroundChanges[i].m_fStartBeat >= fFirstBeatToDraw &&
|
|
aBackgroundChanges[i].m_fStartBeat <= fLastBeatToDraw)
|
|
{
|
|
const BackgroundChange& change = aBackgroundChanges[i];
|
|
CString sChangeText = ssprintf("%s\n%.0f%%%s%s%s",
|
|
change.m_sBGName.c_str(),
|
|
change.m_fRate*100,
|
|
change.m_bFadeLast ? " Fade" : "",
|
|
change.m_bRewindMovie ? " Rewind" : "",
|
|
change.m_bLoop ? " Loop" : "" );
|
|
|
|
DrawBGChangeText( change.m_fStartBeat, sChangeText );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Draw marker bars
|
|
//
|
|
if( m_fBeginMarker != -1 && m_fEndMarker != -1 )
|
|
DrawAreaHighlight( m_fBeginMarker, m_fEndMarker );
|
|
else if( m_fBeginMarker != -1 )
|
|
DrawMarkerBar( m_fBeginMarker );
|
|
else if( m_fEndMarker != -1 )
|
|
DrawMarkerBar( m_fEndMarker );
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Optimization is very important here because there are so many arrows to draw.
|
|
// Draw the arrows in order of column. This minimize texture switches and let us
|
|
// draw in big batches.
|
|
//
|
|
|
|
float fSelectedRangeGlow = SCALE( cosf(RageTimer::GetTimeSinceStart()*2), -1, 1, 0.1f, 0.3f );
|
|
|
|
|
|
for( int c=0; c<GetNumTracks(); c++ ) // for each arrow column
|
|
{
|
|
g_NoteFieldMode[m_PlayerNumber].BeginDrawTrack(c);
|
|
|
|
/////////////////////////////////
|
|
// Draw all HoldNotes in this column (so that they appear under the tap notes)
|
|
/////////////////////////////////
|
|
int i;
|
|
for( i=0; i < GetNumHoldNotes(); i++ )
|
|
{
|
|
const HoldNote &hn = GetHoldNote(i);
|
|
const HoldNoteScore hns = GetHoldNoteScore(i);
|
|
const float fLife = GetHoldNoteLife(i);
|
|
const bool bIsHoldingNote = (i < int(m_bIsHoldingHoldNote.size()))?
|
|
m_bIsHoldingHoldNote[i]: false;
|
|
|
|
if( hns == HNS_OK ) // if this HoldNote was completed
|
|
continue; // don't draw anything
|
|
|
|
if( hn.iTrack != c ) // this HoldNote doesn't belong to this column
|
|
continue;
|
|
|
|
// If no part of this HoldNote is on the screen, skip it
|
|
if( !( fFirstBeatToDraw <= hn.fEndBeat && hn.fEndBeat <= fLastBeatToDraw ||
|
|
fFirstBeatToDraw <= hn.fStartBeat && hn.fStartBeat <= fLastBeatToDraw ||
|
|
hn.fStartBeat < fFirstBeatToDraw && hn.fEndBeat > fLastBeatToDraw ) )
|
|
{
|
|
continue; // skip
|
|
}
|
|
|
|
bool bIsInSelectionRange = false;
|
|
if( m_fBeginMarker!=-1 && m_fEndMarker!=-1 )
|
|
bIsInSelectionRange = m_fBeginMarker<=hn.fStartBeat && hn.fStartBeat<=m_fEndMarker && m_fBeginMarker<=hn.fEndBeat && hn.fEndBeat<=m_fEndMarker;
|
|
|
|
m_NoteDisplay[c].DrawHold( hn, bIsHoldingNote, fLife, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail );
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
// Draw all TapNotes in this column
|
|
///////////////////////////////////
|
|
for( i=iFirstIndexToDraw; i<=iLastIndexToDraw; i++ ) // for each row
|
|
{
|
|
if( GetTapNote(c, i) == TAP_EMPTY ) // no note here
|
|
continue; // skip
|
|
|
|
if( GetTapNote(c, i) == TAP_HOLD_HEAD ) // this is a HoldNote begin marker. Grade it, but don't draw
|
|
continue; // skip
|
|
|
|
// See if there is a hold step that begins on this index.
|
|
bool bHoldNoteBeginsOnThisBeat = false;
|
|
for( int c2=0; c2<GetNumTracks(); c2++ )
|
|
{
|
|
if( GetTapNote(c2, i) == TAP_HOLD_HEAD )
|
|
{
|
|
bHoldNoteBeginsOnThisBeat = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool bIsInSelectionRange = false;
|
|
if( m_fBeginMarker!=-1 && m_fEndMarker!=-1 )
|
|
{
|
|
float fBeat = NoteRowToBeat(i);
|
|
bIsInSelectionRange = m_fBeginMarker<=fBeat && fBeat<=m_fEndMarker;
|
|
}
|
|
m_NoteDisplay[c].DrawTap( c, NoteRowToBeat(i), bHoldNoteBeginsOnThisBeat, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail );
|
|
}
|
|
|
|
g_NoteFieldMode[m_PlayerNumber].EndDrawTrack(c);
|
|
}
|
|
|
|
}
|
|
|
|
void NoteField::RemoveTapNoteRow( int iIndex )
|
|
{
|
|
for( int c=0; c<GetNumTracks(); c++ )
|
|
SetTapNote(c, iIndex, TAP_EMPTY);
|
|
}
|
|
|
|
void NoteField::FadeToFail()
|
|
{
|
|
m_fPercentFadeToFail = max( 0.0f, m_fPercentFadeToFail ); // this will slowly increase every Update()
|
|
// don't fade all over again if this is called twice
|
|
}
|