0c62e5fb9e
NoteField board was already an AutoActor, but code was requiring it to be a derivative of Sprite. Allow any ActorClass to be used. NoteField could already be accessed from below, using GetParent() on the NoteField Board. For ease of allowing the board to be used for player specific purposes, make notefield an actual child of Player, so the Board can be accessed per player from the top down.
1412 lines
53 KiB
C++
1412 lines
53 KiB
C++
#include "global.h"
|
|
#include "NoteField.h"
|
|
#include "RageUtil.h"
|
|
#include "GameConstantsAndTypes.h"
|
|
#include "ArrowEffects.h"
|
|
#include "GameManager.h"
|
|
#include "GameState.h"
|
|
#include "RageTimer.h"
|
|
#include "RageLog.h"
|
|
#include "RageMath.h"
|
|
#include "ThemeManager.h"
|
|
#include "NoteSkinManager.h"
|
|
#include "Song.h"
|
|
#include "ScreenDimensions.h"
|
|
#include "PlayerState.h"
|
|
#include "Style.h"
|
|
#include "CommonMetrics.h"
|
|
#include <float.h>
|
|
#include "BackgroundUtil.h"
|
|
#include "Course.h"
|
|
#include "NoteData.h"
|
|
#include "RageDisplay.h"
|
|
|
|
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels );
|
|
float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels );
|
|
|
|
static ThemeMetric<bool> SHOW_BOARD( "NoteField", "ShowBoard" );
|
|
static ThemeMetric<bool> SHOW_BEAT_BARS( "NoteField", "ShowBeatBars" );
|
|
static ThemeMetric<float> FADE_BEFORE_TARGETS_PERCENT( "NoteField", "FadeBeforeTargetsPercent" );
|
|
static ThemeMetric<float> BAR_MEASURE_ALPHA( "NoteField", "BarMeasureAlpha" );
|
|
static ThemeMetric<float> BAR_4TH_ALPHA( "NoteField", "Bar4thAlpha" );
|
|
static ThemeMetric<float> BAR_8TH_ALPHA( "NoteField", "Bar8thAlpha" );
|
|
static ThemeMetric<float> BAR_16TH_ALPHA( "NoteField", "Bar16thAlpha" );
|
|
static ThemeMetric<float> FADE_FAIL_TIME( "NoteField", "FadeFailTime" );
|
|
|
|
static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); }
|
|
static ThemeMetric1D<RString> ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS );
|
|
|
|
NoteField::NoteField()
|
|
{
|
|
m_pNoteData = NULL;
|
|
m_pCurDisplay = NULL;
|
|
|
|
m_textMeasureNumber.LoadFromFont( THEME->GetPathF("NoteField","MeasureNumber") );
|
|
m_textMeasureNumber.SetZoom( 1.0f );
|
|
m_textMeasureNumber.SetShadowLength( 2 );
|
|
m_textMeasureNumber.SetWrapWidthPixels( 300 );
|
|
|
|
m_rectMarkerBar.SetEffectDiffuseShift( 2, RageColor(1,1,1,0.5f), RageColor(0.5f,0.5f,0.5f,0.5f) );
|
|
|
|
m_sprBoard.Load( THEME->GetPathG("NoteField","board") );
|
|
m_sprBoard->SetName("Board");
|
|
m_sprBoard->PlayCommand( "On" );
|
|
this->AddChild( m_sprBoard );
|
|
|
|
m_fBoardOffsetPixels = 0;
|
|
m_fCurrentBeatLastUpdate = -1;
|
|
m_fYPosCurrentBeatLastUpdate = -1;
|
|
this->SubscribeToMessage( Message_CurrentSongChanged );
|
|
|
|
m_sprBeatBars.Load( THEME->GetPathG("NoteField","bars") );
|
|
m_sprBeatBars.StopAnimating();
|
|
|
|
m_iBeginMarker = m_iEndMarker = -1;
|
|
|
|
m_fPercentFadeToFail = -1;
|
|
}
|
|
|
|
NoteField::~NoteField()
|
|
{
|
|
Unload();
|
|
}
|
|
|
|
void NoteField::Unload()
|
|
{
|
|
for( map<RString, NoteDisplayCols *>::iterator it = m_NoteDisplays.begin();
|
|
it != m_NoteDisplays.end(); ++it )
|
|
delete it->second;
|
|
m_NoteDisplays.clear();
|
|
m_pCurDisplay = NULL;
|
|
memset( m_pDisplays, 0, sizeof(m_pDisplays) );
|
|
}
|
|
|
|
void NoteField::CacheNoteSkin( const RString &sNoteSkin_ )
|
|
{
|
|
RString sNoteSkinLower = sNoteSkin_;
|
|
sNoteSkinLower.MakeLower();
|
|
|
|
if( m_NoteDisplays.find(sNoteSkinLower) != m_NoteDisplays.end() )
|
|
return;
|
|
|
|
LockNoteSkin l( sNoteSkinLower );
|
|
|
|
LOG->Trace("NoteField::CacheNoteSkin: cache %s", sNoteSkinLower.c_str() );
|
|
NoteDisplayCols *nd = new NoteDisplayCols( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer );
|
|
|
|
for( int c=0; c<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; c++ )
|
|
nd->display[c].Load( c, m_pPlayerState, m_fYReverseOffsetPixels );
|
|
nd->m_ReceptorArrowRow.Load( m_pPlayerState, m_fYReverseOffsetPixels );
|
|
nd->m_GhostArrowRow.Load( m_pPlayerState, m_fYReverseOffsetPixels );
|
|
|
|
m_NoteDisplays[ sNoteSkinLower ] = nd;
|
|
}
|
|
|
|
void NoteField::UncacheNoteSkin( const RString &sNoteSkin_ )
|
|
{
|
|
RString sNoteSkinLower = sNoteSkin_;
|
|
sNoteSkinLower.MakeLower();
|
|
|
|
LOG->Trace("NoteField::CacheNoteSkin: release %s", sNoteSkinLower.c_str() );
|
|
ASSERT_M( m_NoteDisplays.find(sNoteSkinLower) != m_NoteDisplays.end(), sNoteSkinLower );
|
|
delete m_NoteDisplays[sNoteSkinLower];
|
|
m_NoteDisplays.erase( sNoteSkinLower );
|
|
}
|
|
|
|
void NoteField::CacheAllUsedNoteSkins()
|
|
{
|
|
// If we're in Routine mode, apply our per-player noteskins.
|
|
if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_TwoPlayersSharedSides )
|
|
{
|
|
FOREACH_EnabledPlayer( pn )
|
|
GAMESTATE->ApplyStageModifiers( pn, ROUTINE_NOTESKIN.GetValue(pn) );
|
|
}
|
|
|
|
/* Cache all note skins that we might need for the whole song, course or battle
|
|
* play, so we don't have to load them later (such as between course songs). */
|
|
vector<RString> asSkinsLower;
|
|
GAMESTATE->GetAllUsedNoteSkins( asSkinsLower );
|
|
asSkinsLower.push_back( m_pPlayerState->m_PlayerOptions.GetStage().m_sNoteSkin );
|
|
FOREACH( RString, asSkinsLower, s )
|
|
s->MakeLower();
|
|
|
|
for( unsigned i=0; i < asSkinsLower.size(); ++i )
|
|
CacheNoteSkin( asSkinsLower[i] );
|
|
|
|
/* If we're changing note skins in the editor, we can have old note skins lying
|
|
* around. Remove them so they don't accumulate. */
|
|
set<RString> setNoteSkinsToUnload;
|
|
FOREACHM( RString, NoteDisplayCols *, m_NoteDisplays, d )
|
|
{
|
|
bool unused = find(asSkinsLower.begin(), asSkinsLower.end(), d->first) == asSkinsLower.end();
|
|
if( unused )
|
|
setNoteSkinsToUnload.insert( d->first );
|
|
}
|
|
FOREACHS( RString, setNoteSkinsToUnload, s )
|
|
UncacheNoteSkin( *s );
|
|
|
|
RString sCurrentNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin;
|
|
sCurrentNoteSkinLower.MakeLower();
|
|
|
|
map<RString, NoteDisplayCols *>::iterator it = m_NoteDisplays.find( sCurrentNoteSkinLower );
|
|
ASSERT_M( it != m_NoteDisplays.end(), sCurrentNoteSkinLower );
|
|
m_pCurDisplay = it->second;
|
|
memset( m_pDisplays, 0, sizeof(m_pDisplays) );
|
|
|
|
FOREACH_EnabledPlayer( pn )
|
|
{
|
|
RString sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin;
|
|
sNoteSkinLower.MakeLower();
|
|
it = m_NoteDisplays.find( sNoteSkinLower );
|
|
ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower );
|
|
m_pDisplays[pn] = it->second;
|
|
}
|
|
}
|
|
|
|
void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels )
|
|
{
|
|
m_pPlayerState = pPlayerState;
|
|
m_fYReverseOffsetPixels = fYReverseOffsetPixels;
|
|
CacheAllUsedNoteSkins();
|
|
}
|
|
|
|
void NoteField::Load(
|
|
const NoteData *pNoteData,
|
|
int iDrawDistanceAfterTargetsPixels,
|
|
int iDrawDistanceBeforeTargetsPixels )
|
|
{
|
|
ASSERT( pNoteData != NULL );
|
|
m_pNoteData = pNoteData;
|
|
m_iDrawDistanceAfterTargetsPixels = iDrawDistanceAfterTargetsPixels;
|
|
m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels;
|
|
ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels );
|
|
|
|
m_fPercentFadeToFail = -1;
|
|
|
|
//int i1 = m_pNoteData->GetNumTracks();
|
|
//int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer;
|
|
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
|
|
ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
|
|
GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer));
|
|
|
|
// The NoteSkin may have changed at the beginning of a new course song.
|
|
RString sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin;
|
|
|
|
/* XXX: Combination of good idea and bad idea to ensure courses load
|
|
* regardless of noteskin content. This may take a while to fix. */
|
|
NoteDisplayCols *badIdea = m_pCurDisplay;
|
|
|
|
if(sNoteSkinLower.empty())
|
|
{
|
|
sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetPreferred().m_sNoteSkin;
|
|
|
|
if(sNoteSkinLower.empty())
|
|
{
|
|
sNoteSkinLower = "default";
|
|
}
|
|
m_NoteDisplays.insert(pair<RString, NoteDisplayCols *> (sNoteSkinLower, badIdea));
|
|
}
|
|
|
|
sNoteSkinLower.MakeLower();
|
|
map<RString, NoteDisplayCols *>::iterator it = m_NoteDisplays.find( sNoteSkinLower );
|
|
ASSERT_M( it != m_NoteDisplays.end(), ssprintf("iterator != m_NoteDisplays.end() [sNoteSkinLower = %s]",sNoteSkinLower.c_str()) );
|
|
memset( m_pDisplays, 0, sizeof(m_pDisplays) );
|
|
FOREACH_EnabledPlayer( pn )
|
|
{
|
|
sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin;
|
|
|
|
// XXX: Re-setup sNoteSkinLower. Unsure if inserting the skin again is needed.
|
|
if(sNoteSkinLower.empty())
|
|
{
|
|
sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_sNoteSkin;
|
|
|
|
if(sNoteSkinLower.empty())
|
|
{
|
|
sNoteSkinLower = "default";
|
|
}
|
|
m_NoteDisplays.insert(pair<RString, NoteDisplayCols *> (sNoteSkinLower, badIdea));
|
|
}
|
|
|
|
sNoteSkinLower.MakeLower();
|
|
it = m_NoteDisplays.find( sNoteSkinLower );
|
|
ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower );
|
|
m_pDisplays[pn] = it->second;
|
|
}
|
|
}
|
|
|
|
void NoteField::Update( float fDeltaTime )
|
|
{
|
|
if( m_bFirstUpdate )
|
|
{
|
|
m_pCurDisplay->m_ReceptorArrowRow.PlayCommand( "On" );
|
|
}
|
|
|
|
ActorFrame::Update( fDeltaTime );
|
|
|
|
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
|
|
const float fCurrentBeat = m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
|
|
bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK
|
|
if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 )
|
|
{
|
|
const float fYOffsetLast = ArrowEffects::GetYOffset( m_pPlayerState, 0, m_fCurrentBeatLastUpdate );
|
|
const float fYPosLast = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffsetLast, m_fYReverseOffsetPixels );
|
|
const float fPixelDifference = fYPosLast - m_fYPosCurrentBeatLastUpdate;
|
|
|
|
//LOG->Trace( "speed = %f, %f, %f, %f, %f, %f", fSpeed, fYOffsetAtCurrent, fYOffsetAtNext, fSecondsAtCurrent, fSecondsAtNext, fPixelDifference, fSecondsDifference );
|
|
|
|
m_fBoardOffsetPixels += fPixelDifference;
|
|
wrap( m_fBoardOffsetPixels, m_sprBoard->GetUnzoomedHeight() );
|
|
}
|
|
m_fCurrentBeatLastUpdate = fCurrentBeat;
|
|
const float fYOffsetCurrent = ArrowEffects::GetYOffset( m_pPlayerState, 0, m_fCurrentBeatLastUpdate );
|
|
m_fYPosCurrentBeatLastUpdate = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffsetCurrent, m_fYReverseOffsetPixels );
|
|
|
|
m_rectMarkerBar.Update( fDeltaTime );
|
|
|
|
NoteDisplayCols *cur = m_pCurDisplay;
|
|
|
|
cur->m_ReceptorArrowRow.Update( fDeltaTime );
|
|
cur->m_GhostArrowRow.Update( fDeltaTime );
|
|
|
|
if( m_fPercentFadeToFail >= 0 )
|
|
m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/FADE_FAIL_TIME, 1 );
|
|
|
|
// Update fade to failed
|
|
m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_fPercentFadeToFail );
|
|
|
|
NoteDisplay::Update( fDeltaTime );
|
|
/* Update all NoteDisplays. Hack: We need to call this once per frame, not
|
|
* once per player. */
|
|
// TODO: Remove use of PlayerNumber.
|
|
|
|
PlayerNumber pn = m_pPlayerState->m_PlayerNumber;
|
|
if( pn == GAMESTATE->GetMasterPlayerNumber() )
|
|
NoteDisplay::Update( fDeltaTime );
|
|
}
|
|
|
|
float NoteField::GetWidth() const
|
|
{
|
|
const Style* pStyle = GAMESTATE->GetCurrentStyle();
|
|
float fMinX, fMaxX;
|
|
// TODO: Remove use of PlayerNumber.
|
|
pStyle->GetMinAndMaxColX( m_pPlayerState->m_PlayerNumber, fMinX, fMaxX );
|
|
|
|
const float fYZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
return (fMaxX - fMinX + ARROW_SIZE) * fYZoom;
|
|
}
|
|
|
|
void NoteField::DrawBeatBar( const float fBeat, BeatBarType type, int iMeasureIndex )
|
|
{
|
|
bool bIsMeasure = type == measure;
|
|
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
|
|
float fAlpha;
|
|
int iState;
|
|
|
|
if( bIsMeasure )
|
|
{
|
|
fAlpha = BAR_MEASURE_ALPHA;
|
|
iState = 0;
|
|
}
|
|
else
|
|
{
|
|
float fScrollSpeed = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
|
|
if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing > 0 )
|
|
fScrollSpeed = 4;
|
|
switch( type )
|
|
{
|
|
DEFAULT_FAIL( type );
|
|
case measure: // handled above
|
|
case beat: // fall through
|
|
fAlpha = BAR_4TH_ALPHA;
|
|
iState = 1;
|
|
break;
|
|
case half_beat:
|
|
fAlpha = SCALE(fScrollSpeed,1.0f,2.0f,0.0f,BAR_8TH_ALPHA);
|
|
iState = 2;
|
|
break;
|
|
case quarter_beat:
|
|
fAlpha = SCALE(fScrollSpeed,2.0f,4.0f,0.0f,BAR_16TH_ALPHA);
|
|
iState = 3;
|
|
break;
|
|
}
|
|
CLAMP( fAlpha, 0, 1 );
|
|
}
|
|
|
|
float fWidth = GetWidth();
|
|
float fFrameWidth = m_sprBeatBars.GetUnzoomedWidth();
|
|
|
|
m_sprBeatBars.SetX( 0 );
|
|
m_sprBeatBars.SetY( fYPos );
|
|
m_sprBeatBars.SetDiffuse( RageColor(1,1,1,fAlpha) );
|
|
m_sprBeatBars.SetState( iState );
|
|
m_sprBeatBars.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_sprBeatBars.SetZoomX( fWidth/m_sprBeatBars.GetUnzoomedWidth() );
|
|
m_sprBeatBars.Draw();
|
|
|
|
|
|
if( GAMESTATE->IsEditing() && bIsMeasure )
|
|
{
|
|
int iMeasureNoDisplay = iMeasureIndex;
|
|
|
|
m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) );
|
|
m_textMeasureNumber.SetHorizAlign( align_right );
|
|
m_textMeasureNumber.SetText( ssprintf("%d", iMeasureNoDisplay) );
|
|
m_textMeasureNumber.SetXY( -fWidth/2, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
}
|
|
|
|
void NoteField::DrawBoard( int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels )
|
|
{
|
|
// todo: make this an AutoActor instead? -aj
|
|
Sprite *pSprite = dynamic_cast<Sprite *>( (Actor*)m_sprBoard );
|
|
if( pSprite == NULL )
|
|
{
|
|
m_sprBoard->Draw();
|
|
}
|
|
else
|
|
{
|
|
// Draw the board centered on fYPosAt0 so that the board doesn't slide as
|
|
// the draw distance changes with modifiers.
|
|
const float fYPosAt0 = ArrowEffects::GetYPos( m_pPlayerState, 0, 0, m_fYReverseOffsetPixels );
|
|
|
|
RectF rect = *pSprite->GetCurrentTextureCoordRect();
|
|
const float fBoardGraphicHeightPixels = pSprite->GetUnzoomedHeight();
|
|
float fTexCoordOffset = m_fBoardOffsetPixels / fBoardGraphicHeightPixels;
|
|
|
|
// top half
|
|
const float fHeight = iDrawDistanceBeforeTargetsPixels - iDrawDistanceAfterTargetsPixels;
|
|
const float fY = fYPosAt0 - ((iDrawDistanceBeforeTargetsPixels + iDrawDistanceAfterTargetsPixels) / 2.0f);
|
|
|
|
pSprite->ZoomToHeight( fHeight );
|
|
pSprite->SetY( fY );
|
|
|
|
// handle tex coord offset and fade
|
|
rect.top = -fTexCoordOffset-(iDrawDistanceBeforeTargetsPixels/fBoardGraphicHeightPixels);
|
|
rect.bottom = -fTexCoordOffset+(-iDrawDistanceAfterTargetsPixels/fBoardGraphicHeightPixels);
|
|
pSprite->SetCustomTextureRect( rect );
|
|
float fFadeTop = FADE_BEFORE_TARGETS_PERCENT * iDrawDistanceBeforeTargetsPixels / (iDrawDistanceBeforeTargetsPixels-iDrawDistanceAfterTargetsPixels);
|
|
pSprite->SetFadeTop( fFadeTop );
|
|
pSprite->Draw();
|
|
}
|
|
}
|
|
|
|
void NoteField::DrawMarkerBar( int iBeat )
|
|
{
|
|
float fBeat = NoteRowToBeat( iBeat );
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
|
|
m_rectMarkerBar.StretchTo( RectF(-GetWidth()/2, fYPos-ARROW_SIZE/2, GetWidth()/2, fYPos+ARROW_SIZE/2) );
|
|
m_rectMarkerBar.Draw();
|
|
}
|
|
|
|
static ThemeMetric<RageColor> AREA_HIGHLIGHT_COLOR("NoteField", "AreaHighlightColor");
|
|
void NoteField::DrawAreaHighlight( int iStartBeat, int iEndBeat )
|
|
{
|
|
float fStartBeat = NoteRowToBeat( iStartBeat );
|
|
float fEndBeat = NoteRowToBeat( iEndBeat );
|
|
float fDrawDistanceAfterTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fStartBeat );
|
|
float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fDrawDistanceAfterTargetsPixels, m_fYReverseOffsetPixels );
|
|
float fDrawDistanceBeforeTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fEndBeat );
|
|
float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels );
|
|
|
|
// The caller should have clamped these to reasonable values
|
|
ASSERT( fYStartPos > -1000 );
|
|
ASSERT( fYEndPos < +5000 );
|
|
|
|
m_rectAreaHighlight.StretchTo( RectF(-GetWidth()/2, fYStartPos, GetWidth()/2, fYEndPos) );
|
|
m_rectAreaHighlight.SetDiffuse( AREA_HIGHLIGHT_COLOR );
|
|
m_rectAreaHighlight.Draw();
|
|
}
|
|
|
|
// todo: add DrawWarpAreaBG? -aj
|
|
|
|
static ThemeMetric<RageColor> BPM_COLOR ( "NoteField", "BPMColor" );
|
|
static ThemeMetric<RageColor> STOP_COLOR ( "NoteField", "StopColor" );
|
|
static ThemeMetric<RageColor> DELAY_COLOR ( "NoteField", "DelayColor" );
|
|
static ThemeMetric<RageColor> WARP_COLOR ( "NoteField", "WarpColor" );
|
|
static ThemeMetric<RageColor> TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignatureColor" );
|
|
static ThemeMetric<RageColor> TICKCOUNT_COLOR ( "NoteField", "TickcountColor" );
|
|
static ThemeMetric<RageColor> COMBO_COLOR ( "NoteField", "ComboColor" );
|
|
static ThemeMetric<RageColor> LABEL_COLOR ( "NoteField", "LabelColor" );
|
|
static ThemeMetric<RageColor> SPEED_COLOR ( "NoteField", "SpeedColor" );
|
|
static ThemeMetric<RageColor> SCROLL_COLOR ( "NoteField", "ScrollColor" );
|
|
static ThemeMetric<RageColor> FAKE_COLOR ("NoteField", "FakeColor" );
|
|
static ThemeMetric<bool> BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" );
|
|
static ThemeMetric<bool> STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" );
|
|
static ThemeMetric<bool> DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" );
|
|
static ThemeMetric<bool> WARP_IS_LEFT_SIDE ( "NoteField", "WarpIsLeftSide" );
|
|
static ThemeMetric<bool> TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatureIsLeftSide" );
|
|
static ThemeMetric<bool> TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" );
|
|
static ThemeMetric<bool> COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" );
|
|
static ThemeMetric<bool> LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" );
|
|
static ThemeMetric<bool> SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" );
|
|
static ThemeMetric<bool> SCROLL_IS_LEFT_SIDE ( "NoteField", "ScrollIsLeftSide" );
|
|
static ThemeMetric<bool> FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" );
|
|
static ThemeMetric<float> BPM_OFFSETX ( "NoteField", "BPMOffsetX" );
|
|
static ThemeMetric<float> STOP_OFFSETX ( "NoteField", "StopOffsetX" );
|
|
static ThemeMetric<float> DELAY_OFFSETX ( "NoteField", "DelayOffsetX" );
|
|
static ThemeMetric<float> WARP_OFFSETX ( "NoteField", "WarpOffsetX" );
|
|
static ThemeMetric<float> TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOffsetX" );
|
|
static ThemeMetric<float> TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" );
|
|
static ThemeMetric<float> COMBO_OFFSETX ( "NoteField", "ComboOffsetX" );
|
|
static ThemeMetric<float> LABEL_OFFSETX ( "NoteField", "LabelOffsetX" );
|
|
static ThemeMetric<float> SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" );
|
|
static ThemeMetric<float> SCROLL_OFFSETX ( "NoteField", "ScrollOffsetX" );
|
|
static ThemeMetric<float> FAKE_OFFSETX ( "NoteField", "FakeOffsetX" );
|
|
|
|
void NoteField::DrawBPMText( const float fBeat, const float fBPM )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = BPM_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( BPM_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( BPM_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( FloatToString(fBPM) );
|
|
m_textMeasureNumber.SetXY( (BPM_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawFreezeText( const float fBeat, const float fSecs )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = STOP_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( STOP_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( STOP_COLOR );
|
|
m_textMeasureNumber.SetXY( (STOP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( FloatToString(fSecs) );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawDelayText( const float fBeat, const float fSecs )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = DELAY_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( DELAY_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( DELAY_COLOR );
|
|
m_textMeasureNumber.SetXY( (DELAY_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( FloatToString(fSecs) );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawWarpText( const float fBeat, const float fNewBeat )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = WARP_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( WARP_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( WARP_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( FloatToString(fNewBeat) );
|
|
m_textMeasureNumber.SetXY( (WARP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = TIME_SIGNATURE_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( TIME_SIGNATURE_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( TIME_SIGNATURE_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( ssprintf("%d\n--\n%d", iNumerator, iDenominator) );
|
|
m_textMeasureNumber.SetXY( (TIME_SIGNATURE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawTickcountText( const float fBeat, int iTicks )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = TICKCOUNT_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( TICKCOUNT_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( TICKCOUNT_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( ssprintf("%d", iTicks) );
|
|
m_textMeasureNumber.SetXY( (TICKCOUNT_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawComboText( const float fBeat, int iCombo, int iMiss )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = COMBO_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( COMBO_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( COMBO_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( ssprintf("%d/%d", iCombo, iMiss) );
|
|
m_textMeasureNumber.SetXY( (COMBO_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawLabelText( const float fBeat, RString sLabel )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = LABEL_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( LABEL_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( LABEL_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( sLabel.c_str() );
|
|
m_textMeasureNumber.SetXY( (LABEL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawSpeedText( const float fBeat, float fPercent, float fWait, int iMode )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = SPEED_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( SPEED_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( SPEED_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( ssprintf("%s\n%s\n%s", FloatToString(fPercent).c_str(), (iMode == 1 ? "S" : "B"), FloatToString(fWait).c_str()) );
|
|
m_textMeasureNumber.SetXY( (SPEED_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawScrollText( const float fBeat, float fPercent )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = SCROLL_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( SCROLL_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( SCROLL_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( FloatToString(fPercent) );
|
|
m_textMeasureNumber.SetXY( (SCROLL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawFakeText( const float fBeat, const float fNewBeat )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
const float xBase = GetWidth()/2.f;
|
|
const float xOffset = FAKE_OFFSETX * fZoom;
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( FAKE_IS_LEFT_SIDE ? align_right : align_left );
|
|
m_textMeasureNumber.SetDiffuse( FAKE_COLOR );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( FloatToString(fNewBeat) );
|
|
m_textMeasureNumber.SetXY( (FAKE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawAttackText( const float fBeat, const Attack &attack )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( align_left );
|
|
m_textMeasureNumber.SetDiffuse( RageColor(0,0.8f,0.8f,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( attack.GetTextDescription() );
|
|
m_textMeasureNumber.SetXY( +GetWidth()/2.f + 10*fZoom, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
void NoteField::DrawBGChangeText( const float fBeat, const RString sNewBGName )
|
|
{
|
|
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
|
|
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
|
|
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
|
|
|
|
m_textMeasureNumber.SetZoom( fZoom );
|
|
m_textMeasureNumber.SetHorizAlign( align_left );
|
|
m_textMeasureNumber.SetDiffuse( RageColor(0,1,0,1) );
|
|
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
|
|
m_textMeasureNumber.SetText( sNewBGName );
|
|
m_textMeasureNumber.SetXY( +GetWidth()/2.f, fYPos );
|
|
m_textMeasureNumber.Draw();
|
|
}
|
|
|
|
static CacheNoteStat GetNumNotesFromBeginning( const PlayerState *pPlayerState, float beat )
|
|
{
|
|
// XXX: I realized that I have copied and pasted my binary search code 3 times already.
|
|
// how can we abstract this?
|
|
const vector<CacheNoteStat> &data = pPlayerState->m_CacheNoteStat;
|
|
int max = data.size() - 1;
|
|
int l = 0, r = max;
|
|
while( l <= r )
|
|
{
|
|
int m = ( l + r ) / 2;
|
|
if( ( m == 0 || data[m].beat <= beat ) && ( m == max || beat < data[m + 1].beat ) )
|
|
{
|
|
return data[m];
|
|
}
|
|
else if( data[m].beat <= beat )
|
|
{
|
|
l = m + 1;
|
|
}
|
|
else
|
|
{
|
|
r = m - 1;
|
|
}
|
|
}
|
|
CacheNoteStat dummy = { 0, 0, 0 };
|
|
return dummy;
|
|
}
|
|
|
|
static int GetNumNotesRange( const PlayerState* pPlayerState, float fLow, float fHigh )
|
|
{
|
|
CacheNoteStat low = GetNumNotesFromBeginning( pPlayerState, fLow );
|
|
CacheNoteStat high = GetNumNotesFromBeginning( pPlayerState, fHigh );
|
|
return high.notesUpper - low.notesLower;
|
|
}
|
|
|
|
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels )
|
|
{
|
|
|
|
float fLow = 0, fHigh = pPlayerState->GetDisplayedPosition().m_fSongBeat;
|
|
|
|
bool bHasCache = pPlayerState->m_CacheNoteStat.size() > 0;
|
|
|
|
if( !bHasCache )
|
|
{
|
|
fLow = fHigh - 4.0f;
|
|
}
|
|
|
|
const int NUM_ITERATIONS = 24;
|
|
const int MAX_NOTES_AFTER = 64;
|
|
|
|
float fFirstBeatToDraw = fLow;
|
|
|
|
for( int i = 0; i < NUM_ITERATIONS; i ++ )
|
|
{
|
|
|
|
float fMid = (fLow + fHigh) / 2.0f;
|
|
|
|
bool bIsPastPeakYOffset;
|
|
float fPeakYOffset;
|
|
float fYOffset = ArrowEffects::GetYOffset( pPlayerState, 0, fMid, fPeakYOffset, bIsPastPeakYOffset, true );
|
|
|
|
if( fYOffset < iDrawDistanceAfterTargetsPixels || ( bHasCache && GetNumNotesRange( pPlayerState, fMid, pPlayerState->GetDisplayedPosition().m_fSongBeat ) > MAX_NOTES_AFTER ) ) // off screen / too many notes
|
|
{
|
|
fFirstBeatToDraw = fMid; // move towards fSongBeat
|
|
fLow = fMid;
|
|
}
|
|
else // on screen, move away!!
|
|
{
|
|
fHigh = fMid;
|
|
}
|
|
|
|
}
|
|
|
|
return fFirstBeatToDraw;
|
|
|
|
}
|
|
|
|
float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels )
|
|
{
|
|
// Probe for last note to draw. Worst case is 0.25x + boost.
|
|
// Adjust search distance so that notes don't pop onto the screen.
|
|
float fSearchDistance = 10;
|
|
float fLastBeatToDraw = pPlayerState->GetDisplayedPosition().m_fSongBeat+fSearchDistance;
|
|
float fSpeedMultiplier = pPlayerState->GetDisplayedTiming().GetDisplayedSpeedPercent(pPlayerState->GetDisplayedPosition().m_fSongBeatVisible, pPlayerState->GetDisplayedPosition().m_fMusicSecondsVisible);
|
|
|
|
const int NUM_ITERATIONS = 20;
|
|
|
|
bool bBoomerang;
|
|
{
|
|
const float* fAccels = pPlayerState->m_PlayerOptions.GetCurrent().m_fAccels;
|
|
bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0);
|
|
}
|
|
|
|
for( int i=0; i<NUM_ITERATIONS; i++ )
|
|
{
|
|
bool bIsPastPeakYOffset;
|
|
float fPeakYOffset;
|
|
float fYOffset = ArrowEffects::GetYOffset( pPlayerState, 0, fLastBeatToDraw, fPeakYOffset, bIsPastPeakYOffset, true );
|
|
|
|
if( bBoomerang && !bIsPastPeakYOffset )
|
|
fLastBeatToDraw += fSearchDistance;
|
|
else if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen
|
|
fLastBeatToDraw -= fSearchDistance;
|
|
else // on screen
|
|
fLastBeatToDraw += fSearchDistance;
|
|
|
|
fSearchDistance /= 2;
|
|
}
|
|
|
|
if( fSpeedMultiplier < 0.75 )
|
|
{
|
|
fLastBeatToDraw = min(fLastBeatToDraw, pPlayerState->GetDisplayedPosition().m_fSongBeat + 16);
|
|
}
|
|
|
|
return fLastBeatToDraw;
|
|
}
|
|
|
|
inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow )
|
|
{
|
|
return NoteRowToBeat(iRow);
|
|
}
|
|
|
|
bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
|
|
{
|
|
// TRICKY: If boomerang is on, then ones in the range
|
|
// [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
|
|
// Test to see if this beat is visible before drawing.
|
|
float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat );
|
|
if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen
|
|
return false;
|
|
if( fYOffset < iDrawDistanceAfterTargetsPixels ) // off screen
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void NoteField::DrawPrimitives()
|
|
{
|
|
//LOG->Trace( "NoteField::DrawPrimitives()" );
|
|
|
|
// This should be filled in on the first update.
|
|
ASSERT( m_pCurDisplay != NULL );
|
|
|
|
ArrowEffects::Update();
|
|
|
|
NoteDisplayCols *cur = m_pCurDisplay;
|
|
|
|
const PlayerOptions ¤t_po = m_pPlayerState->m_PlayerOptions.GetCurrent();
|
|
|
|
// Adjust draw range depending on some effects
|
|
int iDrawDistanceAfterTargetsPixels = m_iDrawDistanceAfterTargetsPixels;
|
|
// HACK: If boomerang and centered are on, then we want to draw much
|
|
// earlier so that the notes don't pop on screen.
|
|
float fCenteredTimesBoomerang =
|
|
current_po.m_fScrolls[PlayerOptions::SCROLL_CENTERED] *
|
|
current_po.m_fAccels[PlayerOptions::ACCEL_BOOMERANG];
|
|
iDrawDistanceAfterTargetsPixels += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 ));
|
|
int iDrawDistanceBeforeTargetsPixels = m_iDrawDistanceBeforeTargetsPixels;
|
|
|
|
float fDrawScale = 1;
|
|
fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt );
|
|
fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_MINI] );
|
|
|
|
iDrawDistanceAfterTargetsPixels = (int)(iDrawDistanceAfterTargetsPixels * fDrawScale);
|
|
iDrawDistanceBeforeTargetsPixels = (int)(iDrawDistanceBeforeTargetsPixels * fDrawScale);
|
|
|
|
|
|
// Probe for first and last notes on the screen
|
|
float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, iDrawDistanceAfterTargetsPixels );
|
|
float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, iDrawDistanceBeforeTargetsPixels );
|
|
|
|
m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw;
|
|
|
|
const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw);
|
|
const int iLastRowToDraw = BeatToNoteRow(fLastBeatToDraw);
|
|
|
|
//LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat );
|
|
//LOG->Trace( "Drawing elements %d through %d", iFirstRowToDraw, iLastRowToDraw );
|
|
|
|
#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) )
|
|
|
|
// Draw board
|
|
if( SHOW_BOARD )
|
|
{
|
|
DrawBoard( iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels );
|
|
}
|
|
|
|
// Draw Receptors
|
|
{
|
|
cur->m_ReceptorArrowRow.Draw();
|
|
}
|
|
|
|
const TimingData *pTiming = &m_pPlayerState->GetDisplayedTiming();
|
|
const vector<TimingSegment*>* segs[NUM_TimingSegmentType];
|
|
|
|
FOREACH_TimingSegmentType( tst )
|
|
segs[tst] = &(pTiming->GetTimingSegments(tst));
|
|
|
|
unsigned i = 0;
|
|
// Draw beat bars
|
|
if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL )
|
|
{
|
|
const vector<TimingSegment *> &tSigs = *segs[SEGMENT_TIME_SIG];
|
|
int iMeasureIndex = 0;
|
|
for (i = 0; i < tSigs.size(); i++)
|
|
{
|
|
const TimeSignatureSegment *ts = ToTimeSignature(tSigs[i]);
|
|
int iSegmentEndRow = (i + 1 == tSigs.size()) ? iLastRowToDraw : tSigs[i+1]->GetRow();
|
|
|
|
// beat bars every 16th note
|
|
int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4;
|
|
|
|
// In 4/4, every 16th beat bar is a measure
|
|
int iMeasureBarFrequency = ts->GetNum() * 4;
|
|
int iBeatBarsDrawn = 0;
|
|
|
|
for( int j=ts->GetRow(); j < iSegmentEndRow; j += iDrawBeatBarsEveryRows )
|
|
{
|
|
bool bMeasureBar = iBeatBarsDrawn % iMeasureBarFrequency == 0;
|
|
BeatBarType type = quarter_beat;
|
|
if( bMeasureBar )
|
|
type = measure;
|
|
else if( iBeatBarsDrawn % 4 == 0 )
|
|
type = beat;
|
|
else if( iBeatBarsDrawn % 2 == 0 )
|
|
type = half_beat;
|
|
float fBeat = NoteRowToBeat(j);
|
|
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
{
|
|
DrawBeatBar( fBeat, type, iMeasureIndex );
|
|
}
|
|
|
|
iBeatBarsDrawn++;
|
|
if( bMeasureBar )
|
|
iMeasureIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( GAMESTATE->IsEditing() && pTiming != NULL )
|
|
{
|
|
ASSERT(GAMESTATE->m_pCurSong != NULL);
|
|
|
|
const TimingData &timing = *pTiming;
|
|
|
|
// Scroll text
|
|
for (i = 0; i < segs[SEGMENT_SCROLL]->size(); i++)
|
|
{
|
|
ScrollSegment *seg = ToScroll( segs[SEGMENT_SCROLL]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawScrollText( fBeat, seg->GetRatio() );
|
|
}
|
|
}
|
|
|
|
// BPM text
|
|
for (i = 0; i < segs[SEGMENT_BPM]->size(); i++)
|
|
{
|
|
const BPMSegment *seg = ToBPM( segs[SEGMENT_BPM]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawBPMText( fBeat, seg->GetBPM() );
|
|
}
|
|
}
|
|
|
|
// Freeze text
|
|
for (i = 0; i < segs[SEGMENT_STOP]->size(); i++)
|
|
{
|
|
const StopSegment *seg = ToStop( segs[SEGMENT_STOP]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawFreezeText( fBeat, seg->GetPause() );
|
|
}
|
|
}
|
|
|
|
// Delay text
|
|
for (i = 0; i < segs[SEGMENT_DELAY]->size(); i++)
|
|
{
|
|
const DelaySegment *seg = ToDelay( segs[SEGMENT_DELAY]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawDelayText( fBeat, seg->GetPause() );
|
|
}
|
|
}
|
|
|
|
// Warp text
|
|
for (i = 0; i < segs[SEGMENT_WARP]->size(); i++)
|
|
{
|
|
const WarpSegment *seg = ToWarp( segs[SEGMENT_WARP]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawWarpText( fBeat, seg->GetLength() );
|
|
}
|
|
}
|
|
|
|
// Time Signature text
|
|
for (i = 0; i < segs[SEGMENT_TIME_SIG]->size(); i++)
|
|
{
|
|
const TimeSignatureSegment *seg = ToTimeSignature( segs[SEGMENT_TIME_SIG]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawTimeSignatureText( fBeat, seg->GetNum(), seg->GetDen() );
|
|
}
|
|
}
|
|
|
|
// Tickcount text
|
|
for (i = 0; i < segs[SEGMENT_TICKCOUNT]->size(); i++)
|
|
{
|
|
const TickcountSegment *seg = ToTickcount( segs[SEGMENT_TICKCOUNT]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawTickcountText( fBeat, seg->GetTicks() );
|
|
}
|
|
}
|
|
|
|
// Combo text
|
|
for (i = 0; i < segs[SEGMENT_COMBO]->size(); i++)
|
|
{
|
|
const ComboSegment *seg = ToCombo( segs[SEGMENT_COMBO]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawComboText( fBeat, seg->GetCombo(), seg->GetMissCombo() );
|
|
}
|
|
}
|
|
|
|
// Label text
|
|
for (i = 0; i < segs[SEGMENT_LABEL]->size(); i++)
|
|
{
|
|
const LabelSegment *seg = ToLabel( segs[SEGMENT_LABEL]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawLabelText( fBeat, seg->GetLabel() );
|
|
}
|
|
}
|
|
|
|
// Speed text
|
|
for (i = 0; i < segs[SEGMENT_SPEED]->size(); i++)
|
|
{
|
|
const SpeedSegment *seg = ToSpeed( segs[SEGMENT_SPEED]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawSpeedText(fBeat, seg->GetRatio(),
|
|
seg->GetDelay(), seg->GetUnit() );
|
|
}
|
|
}
|
|
|
|
// Fake text
|
|
for (i = 0; i < segs[SEGMENT_FAKE]->size(); i++)
|
|
{
|
|
const FakeSegment *seg = ToFake( segs[SEGMENT_FAKE]->at(i) );
|
|
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
|
|
{
|
|
float fBeat = seg->GetBeat();
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawFakeText( fBeat, seg->GetLength() );
|
|
}
|
|
}
|
|
|
|
// Course mods text
|
|
const Course *pCourse = GAMESTATE->m_pCurCourse;
|
|
if( pCourse )
|
|
{
|
|
ASSERT_M( GAMESTATE->m_iEditCourseEntryIndex >= 0 && GAMESTATE->m_iEditCourseEntryIndex < (int)pCourse->m_vEntries.size(),
|
|
ssprintf("%i",GAMESTATE->m_iEditCourseEntryIndex.Get()) );
|
|
const CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
|
|
FOREACH_CONST( Attack, ce.attacks, a )
|
|
{
|
|
float fSecond = a->fStartSecond;
|
|
float fBeat = timing.GetBeatFromElapsedTime( fSecond );
|
|
|
|
if( BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
|
|
BeatToNoteRow(fBeat) <= iLastRowToDraw)
|
|
{
|
|
if( IS_ON_SCREEN(fBeat) )
|
|
DrawAttackText( fBeat, *a );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
|
|
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks :
|
|
GAMESTATE->m_pCurSong->m_Attacks;
|
|
// XXX: We're somehow getting here when attacks is null. Find the actual cause later.
|
|
if (&attacks)
|
|
{
|
|
FOREACH_CONST(Attack, attacks, a)
|
|
{
|
|
float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond);
|
|
if (BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
|
|
BeatToNoteRow(fBeat) <= iLastRowToDraw &&
|
|
IS_ON_SCREEN(fBeat))
|
|
{
|
|
this->DrawAttackText(fBeat, *a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !GAMESTATE->m_bIsUsingStepTiming )
|
|
{
|
|
// BGChange text
|
|
EditMode mode = GAMESTATE->m_EditMode;
|
|
switch( mode )
|
|
{
|
|
case EditMode_Home:
|
|
case EditMode_CourseMods:
|
|
case EditMode_Practice:
|
|
break;
|
|
case EditMode_Full:
|
|
{
|
|
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
|
|
FOREACH_BackgroundLayer( j )
|
|
iter[j] = GAMESTATE->m_pCurSong->GetBackgroundChanges(j).begin();
|
|
|
|
while( 1 )
|
|
{
|
|
float fLowestBeat = FLT_MAX;
|
|
vector<BackgroundLayer> viLowestIndex;
|
|
|
|
FOREACH_BackgroundLayer( j )
|
|
{
|
|
if( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() )
|
|
continue;
|
|
|
|
float fBeat = iter[j]->m_fStartBeat;
|
|
if( fBeat < fLowestBeat )
|
|
{
|
|
fLowestBeat = fBeat;
|
|
viLowestIndex.clear();
|
|
viLowestIndex.push_back( j );
|
|
}
|
|
else if( fBeat == fLowestBeat )
|
|
{
|
|
viLowestIndex.push_back( j );
|
|
}
|
|
}
|
|
|
|
if( viLowestIndex.empty() )
|
|
{
|
|
FOREACH_BackgroundLayer( j )
|
|
ASSERT( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() );
|
|
break;
|
|
}
|
|
|
|
if( IS_ON_SCREEN(fLowestBeat) )
|
|
{
|
|
vector<RString> vsBGChanges;
|
|
FOREACH_CONST( BackgroundLayer, viLowestIndex, bl )
|
|
{
|
|
ASSERT( iter[*bl] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*bl).end() );
|
|
const BackgroundChange& change = *iter[*bl];
|
|
RString s = change.GetTextDescription();
|
|
if( *bl!=0 )
|
|
s = ssprintf("%d: ",*bl) + s;
|
|
vsBGChanges.push_back( s );
|
|
}
|
|
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
|
|
}
|
|
FOREACH_CONST( BackgroundLayer, viLowestIndex, bl )
|
|
iter[*bl]++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
FAIL_M(ssprintf("Invalid edit mode: %i", mode));
|
|
}
|
|
}
|
|
|
|
// Draw marker bars
|
|
if( m_iBeginMarker != -1 && m_iEndMarker != -1 )
|
|
{
|
|
int iBegin = m_iBeginMarker;
|
|
int iEnd = m_iEndMarker;
|
|
CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw );
|
|
CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw );
|
|
DrawAreaHighlight( iBegin, iEnd );
|
|
}
|
|
else if( m_iBeginMarker != -1 )
|
|
{
|
|
if( m_iBeginMarker >= iFirstRowToDraw &&
|
|
m_iBeginMarker <= iLastRowToDraw )
|
|
DrawMarkerBar( m_iBeginMarker );
|
|
}
|
|
else if( m_iEndMarker != -1 )
|
|
{
|
|
if( m_iEndMarker >= iFirstRowToDraw &&
|
|
m_iEndMarker <= iLastRowToDraw )
|
|
DrawMarkerBar( m_iEndMarker );
|
|
}
|
|
}
|
|
|
|
// Optimization is very important here because there are so many arrows to draw.
|
|
// Draw the arrows in order of column. This minimizes texture switches and
|
|
// lets us draw in big batches.
|
|
|
|
float fSelectedRangeGlow = SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f );
|
|
|
|
const Style* pStyle = GAMESTATE->GetCurrentStyle();
|
|
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
|
|
ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
|
|
GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer));
|
|
|
|
for( int j=0; j<m_pNoteData->GetNumTracks(); j++ ) // for each arrow column
|
|
{
|
|
const int c = pStyle->m_iColumnDrawOrder[j];
|
|
|
|
bool bAnyUpcomingInThisCol = false;
|
|
|
|
// Draw all HoldNotes in this column (so that they appear under the tap notes)
|
|
{
|
|
NoteData::TrackMap::const_iterator begin, end;
|
|
m_pNoteData->GetTapNoteRangeInclusive( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end );
|
|
|
|
for( ; begin != end; ++begin )
|
|
{
|
|
const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, j);
|
|
if( tn.type != TapNote::hold_head )
|
|
continue; // skip
|
|
|
|
const HoldNoteResult &Result = tn.HoldResult;
|
|
if( Result.hns == HNS_Held ) // if this HoldNote was completed
|
|
continue; // don't draw anything
|
|
|
|
int iStartRow = begin->first;
|
|
int iEndRow = iStartRow + tn.iDuration;
|
|
|
|
// TRICKY: If boomerang is on, then all notes in the range
|
|
// [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
|
|
// Test every note to make sure it's on screen before drawing
|
|
float fThrowAway;
|
|
bool bStartIsPastPeak = false;
|
|
bool bEndIsPastPeak = false;
|
|
float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak );
|
|
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak );
|
|
|
|
bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels;
|
|
bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels;
|
|
bool bStraddlingVisible = fStartYOffset <= iDrawDistanceAfterTargetsPixels && iDrawDistanceBeforeTargetsPixels <= fEndYOffset;
|
|
bool bStaddlingPeak = bStartIsPastPeak && !bEndIsPastPeak;
|
|
if( !(bTailIsOnVisible || bHeadIsVisible || bStraddlingVisible || bStaddlingPeak) )
|
|
{
|
|
//LOG->Trace( "skip drawing this hold." );
|
|
continue; // skip
|
|
}
|
|
|
|
bool bIsAddition = (tn.source == TapNote::addition);
|
|
bool bIsHopoPossible = (tn.bHopoPossible);
|
|
bool bUseAdditionColoring = bIsAddition || bIsHopoPossible;
|
|
const bool bHoldGhostShowing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0;
|
|
const bool bIsHoldingNote = tn.HoldResult.bHeld;
|
|
if( bHoldGhostShowing )
|
|
m_pCurDisplay->m_GhostArrowRow.SetHoldShowing( c, tn );
|
|
|
|
ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) );
|
|
|
|
bool bIsInSelectionRange = false;
|
|
if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
|
|
bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker);
|
|
|
|
NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn];
|
|
displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
|
|
m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT );
|
|
|
|
bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
|
|
bAnyUpcomingInThisCol |= bNoteIsUpcoming;
|
|
}
|
|
}
|
|
|
|
// Draw all TapNotes in this column
|
|
|
|
// draw notes from furthest to closest
|
|
NoteData::TrackMap::const_iterator begin, end;
|
|
m_pNoteData->GetTapNoteRange( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end );
|
|
for( ; begin != end; ++begin )
|
|
{
|
|
int q = begin->first;
|
|
const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, q);
|
|
|
|
// Switch modified by Wolfman2000, tested by Saturn2888
|
|
// Fixes hold head overlapping issue, but not the rolls.
|
|
switch( tn.type )
|
|
{
|
|
case TapNote::empty: // no note here
|
|
{
|
|
continue;
|
|
}
|
|
case TapNote::hold_head:
|
|
{
|
|
//if (tn.subType == TapNote::hold_head_roll)
|
|
continue; // skip
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
// Don't draw hidden (fully judged) steps.
|
|
if( tn.result.bHidden )
|
|
continue;
|
|
|
|
// TRICKY: If boomerang is on, then all notes in the range
|
|
// [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
|
|
// Test every note to make sure it's on screen before drawing.
|
|
if( !IsOnScreen( NoteRowToBeat(q), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) )
|
|
continue; // skip
|
|
|
|
ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw,
|
|
iFirstRowToDraw, m_pPlayerState->GetDisplayedPosition().m_fSongBeat, m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds) );
|
|
|
|
// See if there is a hold step that begins on this index.
|
|
// Only do this if the noteskin cares.
|
|
bool bHoldNoteBeginsOnThisBeat = false;
|
|
if( m_pCurDisplay->display[c].DrawHoldHeadForTapsOnSameRow() )
|
|
{
|
|
for( int c2=0; c2<m_pNoteData->GetNumTracks(); c2++ )
|
|
{
|
|
const TapNote &tmp = m_pNoteData->GetTapNote(c2, q);
|
|
if(tmp.type == TapNote::hold_head &&
|
|
tmp.subType == TapNote::hold_head_hold)
|
|
{
|
|
bHoldNoteBeginsOnThisBeat = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// do the same for a roll.
|
|
bool bRollNoteBeginsOnThisBeat = false;
|
|
if (m_pCurDisplay->display[c].DrawRollHeadForTapsOnSameRow() )
|
|
{
|
|
for( int c2=0; c2<m_pNoteData->GetNumTracks(); c2++ )
|
|
{
|
|
const TapNote &tmp = m_pNoteData->GetTapNote(c2, q);
|
|
if(tmp.type == TapNote::hold_head &&
|
|
tmp.subType == TapNote::hold_head_roll)
|
|
{
|
|
bRollNoteBeginsOnThisBeat = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bIsInSelectionRange = false;
|
|
if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
|
|
bIsInSelectionRange = m_iBeginMarker<=q && q<m_iEndMarker;
|
|
|
|
bool bIsAddition = (tn.source == TapNote::addition);
|
|
bool bIsHopoPossible = (tn.bHopoPossible);
|
|
bool bUseAdditionColoring = bIsAddition || bIsHopoPossible;
|
|
NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn];
|
|
displayCols->display[c].DrawTap(tn, c, NoteRowToVisibleBeat(m_pPlayerState, q),
|
|
bHoldNoteBeginsOnThisBeat, bRollNoteBeginsOnThisBeat,
|
|
bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
|
|
m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels,
|
|
FADE_BEFORE_TARGETS_PERCENT );
|
|
|
|
bool bNoteIsUpcoming = NoteRowToBeat(q) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
|
|
bAnyUpcomingInThisCol |= bNoteIsUpcoming;
|
|
|
|
DISPLAY->ClearZBuffer();
|
|
}
|
|
|
|
cur->m_ReceptorArrowRow.SetNoteUpcoming( c, bAnyUpcomingInThisCol );
|
|
}
|
|
|
|
cur->m_GhostArrowRow.Draw();
|
|
}
|
|
|
|
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
|
|
}
|
|
void NoteField::Step( int iCol, TapNoteScore score ) { m_pCurDisplay->m_ReceptorArrowRow.Step( iCol, score ); }
|
|
void NoteField::SetPressed( int iCol ) { m_pCurDisplay->m_ReceptorArrowRow.SetPressed( iCol ); }
|
|
void NoteField::DidTapNote( int iCol, TapNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidTapNote( iCol, score, bBright ); }
|
|
void NoteField::DidHoldNote( int iCol, HoldNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidHoldNote( iCol, score, bBright ); }
|
|
|
|
void NoteField::HandleMessage( const Message &msg )
|
|
{
|
|
if( msg == Message_CurrentSongChanged )
|
|
{
|
|
m_fCurrentBeatLastUpdate = -1;
|
|
m_fYPosCurrentBeatLastUpdate = -1;
|
|
}
|
|
|
|
ActorFrame::HandleMessage( msg );
|
|
}
|
|
|
|
/*
|
|
* (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.
|
|
*/
|