Files
itgmania212121/stepmania/src/NoteField.cpp
T

810 lines
27 KiB
C++
Raw Normal View History

2003-02-16 04:01:45 +00:00
#include "global.h"
2002-04-16 17:31:00 +00:00
#include "NoteField.h"
#include "RageUtil.h"
2002-05-01 19:14:55 +00:00
#include "GameConstantsAndTypes.h"
2002-04-16 17:31:00 +00:00
#include "ArrowEffects.h"
#include "GameManager.h"
2002-07-23 01:41:40 +00:00
#include "GameState.h"
2002-07-27 19:29:51 +00:00
#include "RageException.h"
#include "RageTimer.h"
#include "RageLog.h"
2003-07-04 18:03:20 +00:00
#include "RageMath.h"
#include "ThemeManager.h"
2003-04-02 21:58:27 +00:00
#include "NoteFieldPositioning.h"
#include "NoteSkinManager.h"
#include "song.h"
#include "ScreenDimensions.h"
#include "PlayerState.h"
2005-01-15 02:01:26 +00:00
#include "Style.h"
2005-03-18 18:56:35 +00:00
#include "CommonMetrics.h"
#include <float.h>
2005-06-04 21:22:50 +00:00
#include "BackgroundUtil.h"
2005-07-30 19:32:31 +00:00
#include "Course.h"
2002-04-16 17:31:00 +00:00
NoteField::NoteField()
{
m_pNoteData = NULL;
2006-01-12 21:44:10 +00:00
m_pCurDisplay = NULL;
2005-02-06 03:32:53 +00:00
m_textMeasureNumber.LoadFromFont( THEME->GetPathF("Common","normal") );
2002-04-16 17:31:00 +00:00
m_textMeasureNumber.SetZoom( 1.0f );
m_textMeasureNumber.SetShadowLength( 2 );
m_textMeasureNumber.SetWrapWidthPixels( 300 );
2002-04-16 17:31:00 +00:00
2003-02-18 23:15:38 +00:00
m_rectMarkerBar.SetEffectDiffuseShift( 2, RageColor(1,1,1,0.5f), RageColor(0.5f,0.5f,0.5f,0.5f) );
2002-04-16 17:31:00 +00:00
2005-02-06 03:32:53 +00:00
m_sprBars.Load( THEME->GetPathG("NoteField","bars") );
m_sprBars.StopAnimating();
m_iBeginMarker = m_iEndMarker = -1;
2002-06-27 17:49:10 +00:00
2002-08-01 21:55:40 +00:00
m_fPercentFadeToFail = -1;
2002-04-16 17:31:00 +00:00
}
NoteField::~NoteField()
{
Unload();
}
void NoteField::Unload()
{
2006-01-22 01:00:06 +00:00
for( map<RString, NoteDisplayCols *>::iterator it = m_NoteDisplays.begin();
it != m_NoteDisplays.end(); ++it )
delete it->second;
m_NoteDisplays.clear();
2006-01-12 21:44:10 +00:00
m_pCurDisplay = NULL;
}
2006-01-22 01:00:06 +00:00
void NoteField::CacheNoteSkin( const RString &sNoteSkin_ )
{
2006-01-22 01:00:06 +00:00
RString sNoteSkin = sNoteSkin_;
sNoteSkin.ToLower();
if( m_NoteDisplays.find(sNoteSkin) != m_NoteDisplays.end() )
return;
LockNoteSkin l( sNoteSkin );
LOG->Trace("NoteField::CacheNoteSkin: cache %s", sNoteSkin.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 );
2003-11-28 00:45:31 +00:00
m_NoteDisplays[ sNoteSkin ] = nd;
}
2002-04-16 17:31:00 +00:00
2006-02-01 06:25:00 +00:00
void NoteField::UncacheNoteSkin( const RString &sNoteSkin_ )
{
RString sNoteSkin( sNoteSkin_ );
sNoteSkin.ToLower();
LOG->Trace("NoteField::CacheNoteSkin: release %s", sNoteSkin.c_str() );
ASSERT_M( m_NoteDisplays.find(sNoteSkin) != m_NoteDisplays.end(), sNoteSkin );
delete m_NoteDisplays[sNoteSkin];
m_NoteDisplays.erase( sNoteSkin );
}
void NoteField::CacheAllUsedNoteSkins()
2003-10-25 07:50:30 +00:00
{
/* 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). */
2006-02-01 05:57:29 +00:00
vector<RString> asSkins;
GAMESTATE->GetAllUsedNoteSkins( asSkins );
2006-02-01 05:57:30 +00:00
asSkins.push_back( m_pPlayerState->m_PlayerOptions.m_sNoteSkin );
2006-02-01 05:57:29 +00:00
for( unsigned i=0; i < asSkins.size(); ++i )
CacheNoteSkin( asSkins[i] );
2006-02-01 06:25:00 +00:00
/* 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 )
{
if( find(asSkins.begin(), asSkins.end(), d->first) == asSkins.end() )
setNoteSkinsToUnload.insert( d->first );
}
FOREACHS( RString, setNoteSkinsToUnload, s )
UncacheNoteSkin( *s );
2003-10-25 07:50:30 +00:00
}
void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels )
{
m_pPlayerState = pPlayerState;
m_fYReverseOffsetPixels = fYReverseOffsetPixels;
CacheAllUsedNoteSkins();
}
void NoteField::Load(
const NoteData *pNoteData,
int iFirstPixelToDraw,
int iLastPixelToDraw )
2002-04-16 17:31:00 +00:00
{
m_pNoteData = pNoteData;
m_iStartDrawingPixel = iFirstPixelToDraw;
m_iEndDrawingPixel = iLastPixelToDraw;
2002-07-28 20:28:37 +00:00
2002-08-01 21:55:40 +00:00
m_fPercentFadeToFail = -1;
2005-10-02 19:21:05 +00:00
//int i1 = m_pNoteData->GetNumTracks();
//int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer;
ASSERT_M( m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
ssprintf("%d = %d",m_pNoteData->GetNumTracks(), GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer) );
// The note skin may have changed at the beginning of a new course song.
2006-01-22 01:00:06 +00:00
map<RString, NoteDisplayCols *>::iterator it = m_NoteDisplays.find( m_pPlayerState->m_PlayerOptions.m_sNoteSkin );
ASSERT_M( it != m_NoteDisplays.end(), m_pPlayerState->m_PlayerOptions.m_sNoteSkin );
2006-01-12 21:44:10 +00:00
m_pCurDisplay = it->second;
2003-04-22 04:54:04 +00:00
}
2002-07-28 20:28:37 +00:00
void NoteField::Update( float fDeltaTime )
2002-04-16 17:31:00 +00:00
{
ActorFrame::Update( fDeltaTime );
2002-04-16 17:31:00 +00:00
m_rectMarkerBar.Update( fDeltaTime );
2003-11-28 00:45:31 +00:00
2006-01-12 21:44:10 +00:00
NoteDisplayCols *cur = m_pCurDisplay;
2003-12-22 10:30:10 +00:00
cur->m_ReceptorArrowRow.Update( fDeltaTime );
2003-11-28 00:45:31 +00:00
cur->m_GhostArrowRow.Update( fDeltaTime );
2002-08-01 21:55:40 +00:00
if( m_fPercentFadeToFail >= 0 )
2002-09-05 20:42:49 +00:00
m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/1.5f, 1 ); // take 1.5 seconds to totally fade
2005-05-09 02:58:30 +00:00
// Update fade to failed
2006-01-12 21:44:10 +00:00
m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_fPercentFadeToFail );
2005-05-09 02:58:30 +00:00
/*
* 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->m_MasterPlayerNumber )
NoteDisplay::Update( fDeltaTime );
2002-04-16 17:31:00 +00:00
}
void NoteField::ProcessMessages( float fDeltaTime )
{
ActorFrame::ProcessMessages( fDeltaTime );
2006-01-12 21:44:10 +00:00
/* If m_pCurDisplay is NULL, we're receiving a message before Load() was called. */
if( m_pCurDisplay != NULL )
2005-10-11 21:42:06 +00:00
{
2006-01-12 21:44:10 +00:00
m_pCurDisplay->m_ReceptorArrowRow.ProcessMessages( fDeltaTime );
m_pCurDisplay->m_GhostArrowRow.ProcessMessages( fDeltaTime );
2005-10-11 21:42:06 +00:00
}
}
2006-01-30 22:51:01 +00:00
float NoteField::GetWidth() const
2002-04-16 17:31:00 +00:00
{
2004-06-28 07:26:00 +00:00
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;
2003-02-18 23:15:38 +00:00
}
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 = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
2002-04-16 17:31:00 +00:00
float fAlpha;
int iState;
if( bIsMeasure )
2003-02-18 23:15:38 +00:00
{
fAlpha = 1;
iState = 0;
2003-02-18 23:15:38 +00:00
}
else
2003-02-18 23:15:38 +00:00
{
float fScrollSpeed = m_pPlayerState->m_CurrentPlayerOptions.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();
2003-02-18 23:15:38 +00:00
if( bIsMeasure )
{
m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) );
m_textMeasureNumber.SetHorizAlign( Actor::align_right );
2003-02-18 23:15:38 +00:00
m_textMeasureNumber.SetText( ssprintf("%d", iMeasureNoDisplay) );
m_textMeasureNumber.SetXY( -fWidth/2, fYPos );
2003-02-18 23:15:38 +00:00
m_textMeasureNumber.Draw();
}
2002-04-16 17:31:00 +00:00
}
void NoteField::DrawMarkerBar( int iBeat )
2002-04-16 17:31:00 +00:00
{
float fBeat = NoteRowToBeat( iBeat );
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
2002-04-16 17:31:00 +00:00
m_rectMarkerBar.StretchTo( RectF(-GetWidth()/2, fYPos-ARROW_SIZE/2, GetWidth()/2, fYPos+ARROW_SIZE/2) );
2002-04-16 17:31:00 +00:00
m_rectMarkerBar.Draw();
}
void NoteField::DrawAreaHighlight( int iStartBeat, int iEndBeat )
2003-02-18 23:15:38 +00:00
{
float fStartBeat = NoteRowToBeat( iStartBeat );
float fEndBeat = NoteRowToBeat( iEndBeat );
float fYStartOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fStartBeat );
float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYStartOffset, m_fYReverseOffsetPixels );
float fYEndOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fEndBeat );
float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYEndOffset, m_fYReverseOffsetPixels );
// The caller should have clamped these to reasonable values
ASSERT( fYStartPos > -1000 );
ASSERT( fYEndPos < +5000 );
2003-02-18 23:15:38 +00:00
2005-01-25 05:02:35 +00:00
m_rectAreaHighlight.StretchTo( RectF(-GetWidth()/2, fYStartPos, GetWidth()/2, fYEndPos) );
2003-02-18 23:15:38 +00:00
m_rectAreaHighlight.SetDiffuse( RageColor(1,0,0,0.3f) );
m_rectAreaHighlight.Draw();
}
void NoteField::DrawBPMText( const float fBeat, const float fBPM )
2002-06-29 11:59:09 +00:00
{
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 );
2002-06-29 11:59:09 +00:00
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( Actor::align_right );
m_textMeasureNumber.SetDiffuse( RageColor(1,0,0,1) );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f", fBPM) );
m_textMeasureNumber.SetXY( -GetWidth()/2 - 60*fZoom, fYPos );
2002-06-29 11:59:09 +00:00
m_textMeasureNumber.Draw();
}
void NoteField::DrawFreezeText( const float fBeat, const float fSecs )
2002-06-29 11:59:09 +00:00
{
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 );
2002-06-29 11:59:09 +00:00
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( Actor::align_right );
m_textMeasureNumber.SetDiffuse( RageColor(0.8f,0.8f,0,1) );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f", fSecs) );
m_textMeasureNumber.SetXY( -GetWidth()/2.f - 10*fZoom, fYPos );
2002-06-29 11:59:09 +00:00
m_textMeasureNumber.Draw();
}
2005-07-30 19:32:31 +00:00
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 );
2005-07-30 19:32:31 +00:00
m_textMeasureNumber.SetZoom( fZoom );
2005-07-30 21:11:18 +00:00
m_textMeasureNumber.SetHorizAlign( Actor::align_left );
2005-07-30 19:32:31 +00:00
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 );
2005-07-30 19:32:31 +00:00
m_textMeasureNumber.Draw();
}
2006-01-22 01:00:06 +00:00
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( Actor::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();
}
// CPU OPTIMIZATION OPPORTUNITY:
// change this probing to binary search
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iFirstPixelToDraw )
{
float fFirstBeatToDraw = GAMESTATE->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes.
/* In Boomerang, we'll usually have two sections of notes: before and after
* the peak. We always start drawing before the peak, and end after it, or
* we may falsely detect the off-screen portion as the end (or beginning)
* of the stream. */
bool bBoomerang;
{
const float* fAccels = pPlayerState->m_CurrentPlayerOptions.m_fAccels;
bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0);
}
while( fFirstBeatToDraw < GAMESTATE->m_fSongBeat )
{
bool bIsPastPeakYOffset;
float fPeakYOffset;
float fYOffset = ArrowEffects::GetYOffset( pPlayerState, 0, fFirstBeatToDraw, fPeakYOffset, bIsPastPeakYOffset, true );
if( bBoomerang && bIsPastPeakYOffset )
break; // stop probing
else if( fYOffset < iFirstPixelToDraw ) // off screen
fFirstBeatToDraw += 0.1f; // move toward fSongBeat
else // on screen
break; // stop probing
}
fFirstBeatToDraw -= 0.1f; // rewind if we intentionally overshot
return fFirstBeatToDraw;
}
float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iLastPixelToDraw )
{
2003-09-16 07:22:54 +00:00
//
// Probe for last note to draw.
// worst case is 0.25x + boost. Adjust search distance to
// so that notes don't pop onto the screen.
//
2003-09-18 04:23:58 +00:00
float fSearchDistance = 10;
2003-09-16 07:22:54 +00:00
float fLastBeatToDraw = GAMESTATE->m_fSongBeat+fSearchDistance;
2003-09-17 22:25:29 +00:00
2003-09-18 04:23:58 +00:00
const int NUM_ITERATIONS = 20;
2003-09-16 07:22:54 +00:00
bool bBoomerang;
{
const float* fAccels = pPlayerState->m_CurrentPlayerOptions.m_fAccels;
bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0);
}
2003-09-16 07:22:54 +00:00
for( int i=0; i<NUM_ITERATIONS; i++ )
{
bool bIsPastPeakYOffset;
float fPeakYOffset;
float fYOffset = ArrowEffects::GetYOffset( pPlayerState, 0, fLastBeatToDraw, fPeakYOffset, bIsPastPeakYOffset, true );
2003-09-16 07:22:54 +00:00
if( bBoomerang && !bIsPastPeakYOffset )
fLastBeatToDraw += fSearchDistance;
else if( fYOffset > iLastPixelToDraw ) // off screen
2003-09-16 07:22:54 +00:00
fLastBeatToDraw -= fSearchDistance;
else // on screen
2003-09-16 07:22:54 +00:00
fLastBeatToDraw += fSearchDistance;
fSearchDistance /= 2;
}
2003-09-16 07:22:54 +00:00
return fLastBeatToDraw;
}
2006-01-30 22:51:01 +00:00
bool NoteField::IsOnScreen( float fBeat, int iFirstPixelToDraw, int iLastPixelToDraw ) const
{
// TRICKY: If boomerang is on, then ones in the range
// [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible.
// Test to see if this beat is visible before drawing.
float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
if( fYOffset > iLastPixelToDraw ) // off screen
return false;
if( fYOffset < iFirstPixelToDraw ) // off screen
return false;
return true;
}
2002-05-19 01:59:48 +00:00
void NoteField::DrawPrimitives()
2002-04-16 17:31:00 +00:00
{
//LOG->Trace( "NoteField::DrawPrimitives()" );
2002-04-16 17:31:00 +00:00
/* This should be filled in on the first update. */
2006-01-12 21:44:10 +00:00
ASSERT( m_pCurDisplay != NULL );
2003-01-25 11:05:12 +00:00
ArrowEffects::Update();
2006-01-12 21:44:10 +00:00
NoteDisplayCols *cur = m_pCurDisplay;
2003-12-22 10:30:10 +00:00
cur->m_ReceptorArrowRow.Draw();
2003-11-27 05:18:28 +00:00
const PlayerOptions &current_po = m_pPlayerState->m_CurrentPlayerOptions;
2004-08-29 05:56:51 +00:00
2003-04-01 19:31:27 +00:00
//
// Adjust draw range depending on some effects
//
int iFirstPixelToDraw = m_iStartDrawingPixel;
2004-08-29 07:12:37 +00:00
// HACK: if boomerang and centered are on, then we want to draw much
2004-08-29 05:56:51 +00:00
// earlier to that the notes don't pop on screen.
2004-08-29 07:12:37 +00:00
float fCenteredTimesBoomerang =
current_po.m_fScrolls[PlayerOptions::SCROLL_CENTERED] *
2004-08-29 05:56:51 +00:00
current_po.m_fAccels[PlayerOptions::ACCEL_BOOMERANG];
iFirstPixelToDraw += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 ));
int iLastPixelToDraw = m_iEndDrawingPixel;
2003-04-01 19:31:27 +00:00
float fDrawScale = 1;
2004-08-29 05:56:51 +00:00
fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt );
2003-04-17 21:16:56 +00:00
2005-01-15 01:38:42 +00:00
iFirstPixelToDraw = (int)(iFirstPixelToDraw * fDrawScale);
iLastPixelToDraw = (int)(iLastPixelToDraw * fDrawScale);
2003-04-01 19:31:27 +00:00
// Probe for first and last notes on the screen
float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, iFirstPixelToDraw );
float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, iLastPixelToDraw );
2003-01-25 11:05:12 +00:00
m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw;
2003-01-25 11:05:12 +00:00
const int iFirstIndexToDraw = BeatToNoteRow(fFirstBeatToDraw);
const int iLastIndexToDraw = BeatToNoteRow(fLastBeatToDraw);
2002-04-16 17:31:00 +00:00
2003-01-25 11:05:12 +00:00
// LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat );
2002-09-11 04:49:07 +00:00
// LOG->Trace( "Drawing elements %d through %d", iFirstIndexToDraw, iLastIndexToDraw );
2002-04-16 17:31:00 +00:00
#define IS_ON_SCREEN( fBeat ) IsOnScreen( fBeat, iFirstPixelToDraw, iLastPixelToDraw )
if( GAMESTATE->IsEditing() )
2002-04-16 17:31:00 +00:00
{
2003-01-31 02:37:23 +00:00
ASSERT(GAMESTATE->m_pCurSong);
2002-06-29 11:59:09 +00:00
//
2003-02-18 23:15:38 +00:00
// Draw beat bars
2002-06-29 11:59:09 +00:00
//
2003-02-18 23:15:38 +00:00
{
2004-10-24 17:44:51 +00:00
float fStartDrawingMeasureBars = max( 0, Quantize(fFirstBeatToDraw-0.25f,0.25f) );
2003-02-18 23:15:38 +00:00
for( float f=fStartDrawingMeasureBars; f<fLastBeatToDraw; f+=0.25f )
{
if( IS_ON_SCREEN(f) )
DrawBeatBar( f );
}
2003-02-18 23:15:38 +00:00
}
2002-04-16 17:31:00 +00:00
2002-06-29 11:59:09 +00:00
//
// BPM text
//
2006-01-30 22:51:01 +00:00
const vector<BPMSegment> &aBPMSegments = GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments;
2004-09-21 07:53:39 +00:00
for( unsigned i=0; i<aBPMSegments.size(); i++ )
2003-01-30 20:12:39 +00:00
{
if( aBPMSegments[i].m_iStartIndex >= iFirstIndexToDraw &&
aBPMSegments[i].m_iStartIndex <= iLastIndexToDraw)
{
float fBeat = NoteRowToBeat(aBPMSegments[i].m_iStartIndex);
if( IS_ON_SCREEN(fBeat) )
DrawBPMText( fBeat, aBPMSegments[i].GetBPM() );
}
2003-01-30 20:12:39 +00:00
}
2005-07-30 19:32:31 +00:00
2002-06-29 11:59:09 +00:00
//
// Freeze text
//
2006-01-30 22:51:01 +00:00
const vector<StopSegment> &aStopSegments = GAMESTATE->m_pCurSong->m_Timing.m_StopSegments;
2004-09-21 07:53:39 +00:00
for( unsigned i=0; i<aStopSegments.size(); i++ )
2003-01-30 20:12:39 +00:00
{
if( aStopSegments[i].m_iStartRow >= iFirstIndexToDraw &&
aStopSegments[i].m_iStartRow <= iLastIndexToDraw)
{
float fBeat = NoteRowToBeat(aStopSegments[i].m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawFreezeText( fBeat, aStopSegments[i].m_fStopSeconds );
}
2003-01-30 20:12:39 +00:00
}
2002-06-29 11:59:09 +00:00
2005-07-30 19:32:31 +00:00
//
// Course mods text
//
2006-01-30 22:51:01 +00:00
const Course *pCourse = GAMESTATE->m_pCurCourse;
2005-07-30 19:32:31 +00:00
if( pCourse )
{
2005-08-14 02:12:19 +00:00
const CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
2005-07-30 19:32:31 +00:00
FOREACH_CONST( Attack, ce.attacks, a )
{
float fSecond = a->fStartSecond;
float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond );
if( BeatToNoteRow(fBeat) >= iFirstIndexToDraw &&
BeatToNoteRow(fBeat) <= iLastIndexToDraw)
{
if( IS_ON_SCREEN(fBeat) )
DrawAttackText( fBeat, *a );
}
}
}
//
// BGChange text
//
2005-07-14 05:16:32 +00:00
switch( GAMESTATE->m_EditMode )
2003-01-30 20:12:39 +00:00
{
case EditMode_Home:
case EditMode_Practice:
break;
case EditMode_Full:
{
2005-06-04 21:22:50 +00:00
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
FOREACH_BackgroundLayer( i )
iter[i] = GAMESTATE->m_pCurSong->GetBackgroundChanges(i).begin();
while( 1 )
2005-03-18 18:56:35 +00:00
{
float fLowestBeat = FLT_MAX;
2005-06-04 21:22:50 +00:00
vector<BackgroundLayer> viLowestIndex;
2005-06-04 21:22:50 +00:00
FOREACH_BackgroundLayer( i )
{
2005-06-04 21:22:50 +00:00
if( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() )
continue;
float fBeat = iter[i]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
viLowestIndex.push_back( i );
}
else if( fBeat == fLowestBeat )
{
viLowestIndex.push_back( i );
}
}
if( viLowestIndex.empty() )
2005-06-05 09:45:00 +00:00
{
FOREACH_BackgroundLayer( i )
ASSERT( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() );
break;
2005-06-05 09:45:00 +00:00
}
if( IS_ON_SCREEN(fLowestBeat) )
{
2006-01-22 01:00:06 +00:00
vector<RString> vsBGChanges;
2005-06-04 21:22:50 +00:00
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
2005-05-26 09:35:57 +00:00
{
2005-06-04 21:22:50 +00:00
ASSERT( iter[*i] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*i).end() );
const BackgroundChange& change = *iter[*i];
2006-01-22 01:00:06 +00:00
RString s = change.GetTextDescription();
2005-07-30 19:32:31 +00:00
if( *i!=0 )
s = ssprintf("%d: ",*i) + s;
vsBGChanges.push_back( s );
2005-05-26 09:35:57 +00:00
}
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
}
2005-06-04 21:22:50 +00:00
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
iter[*i]++;
2005-03-18 18:56:35 +00:00
}
}
break;
default:
ASSERT(0);
2003-01-30 20:12:39 +00:00
}
2002-06-29 11:59:09 +00:00
//
// Draw marker bars
//
if( m_iBeginMarker != -1 && m_iEndMarker != -1 )
2005-03-30 04:27:13 +00:00
{
int iBegin = m_iBeginMarker;
int iEnd = m_iEndMarker;
CLAMP( iBegin, iFirstIndexToDraw, iLastIndexToDraw );
CLAMP( iEnd, iFirstIndexToDraw, iLastIndexToDraw );
2005-03-30 04:27:13 +00:00
DrawAreaHighlight( iBegin, iEnd );
}
else if( m_iBeginMarker != -1 )
2005-03-30 04:27:13 +00:00
{
if( m_iBeginMarker >= iFirstIndexToDraw &&
m_iBeginMarker <= iLastIndexToDraw )
DrawMarkerBar( m_iBeginMarker );
}
else if( m_iEndMarker != -1 )
2005-03-30 04:27:13 +00:00
{
if( m_iEndMarker >= iFirstIndexToDraw &&
m_iEndMarker <= iLastIndexToDraw )
DrawMarkerBar( m_iEndMarker );
2005-03-30 04:27:13 +00:00
}
2002-04-16 17:31:00 +00:00
}
2002-06-29 11:59:09 +00:00
2002-04-16 17:31:00 +00:00
//
// 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( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f );
2003-02-19 06:27:20 +00:00
for( int c=0; c<m_pNoteData->GetNumTracks(); c++ ) // for each arrow column
2002-04-28 20:42:32 +00:00
{
NoteFieldMode::BeginDrawTrack( m_pPlayerState, c );
2003-04-02 21:58:27 +00:00
2003-11-15 08:51:47 +00:00
//
2002-06-14 22:25:22 +00:00
// Draw all HoldNotes in this column (so that they appear under the tap notes)
2004-09-21 07:53:39 +00:00
//
2002-04-16 17:31:00 +00:00
{
2005-01-25 05:02:35 +00:00
NoteData::TrackMap::const_iterator begin, end;
m_pNoteData->GetTapNoteRangeInclusive( c, iFirstIndexToDraw, iLastIndexToDraw+1, begin, end );
2005-01-25 05:02:35 +00:00
for( ; begin != end; ++begin )
{
const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, i);
if( tn.type != TapNote::hold_head )
continue; // skip
const HoldNoteResult &Result = tn.HoldResult;
if( Result.hns == HNS_Held ) // if this HoldNote was completed
2005-01-25 05:02:35 +00:00
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
// [iFirstIndexToDraw,iLastIndexToDraw] 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, NoteRowToBeat(iStartRow), fThrowAway, bStartIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iEndRow), fThrowAway, bEndIsPastPeak );
bool bTailIsOnVisible = iFirstPixelToDraw <= fEndYOffset && fEndYOffset <= iLastPixelToDraw;
bool bHeadIsVisible = iFirstPixelToDraw <= fStartYOffset && fStartYOffset <= iLastPixelToDraw;
2005-04-07 08:28:41 +00:00
bool bStraddlingVisible = fStartYOffset <= iFirstPixelToDraw && iLastPixelToDraw <= fEndYOffset;
bool bStaddlingPeak = bStartIsPastPeak && !bEndIsPastPeak;
if( !(bTailIsOnVisible || bHeadIsVisible || bStraddlingVisible || bStaddlingPeak) )
2005-01-25 05:02:35 +00:00
{
//LOG->Trace( "skip drawing this hold." );
2005-01-25 05:02:35 +00:00
continue; // skip
}
2005-01-25 05:02:35 +00:00
const bool bIsActive = tn.HoldResult.bActive;
const bool bIsHoldingNote = tn.HoldResult.bHeld;
if( bIsActive )
2006-01-12 21:44:10 +00:00
m_pCurDisplay->m_GhostArrowRow.SetHoldIsActive( c );
2005-01-25 05:02:35 +00:00
ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) );
2002-08-01 03:15:27 +00:00
2005-01-25 05:02:35 +00:00
bool bIsInSelectionRange = false;
if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker);
2002-04-16 17:31:00 +00:00
2006-01-12 21:44:10 +00:00
m_pCurDisplay->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, bIsActive, Result, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, false, m_fYReverseOffsetPixels, (float) iFirstPixelToDraw, (float) iLastPixelToDraw );
2005-01-25 05:02:35 +00:00
}
2002-04-16 17:31:00 +00:00
}
2002-06-14 22:25:22 +00:00
2003-11-15 08:51:47 +00:00
//
2004-05-24 04:17:19 +00:00
// Draw all TapNotes in this column
2003-11-15 08:51:47 +00:00
//
2003-09-16 07:22:54 +00:00
// draw notes from furthest to closest
2005-01-22 03:11:29 +00:00
NoteData::TrackMap::const_iterator begin, end;
m_pNoteData->GetTapNoteRange( c, iFirstIndexToDraw, iLastIndexToDraw+1, begin, end );
2005-01-22 03:11:29 +00:00
for( ; begin != end; ++begin )
2002-06-14 22:25:22 +00:00
{
2005-01-22 03:11:29 +00:00
int i = begin->first;
const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, i);
2005-01-25 05:02:35 +00:00
switch( tn.type )
{
case TapNote::empty: // no note here
case TapNote::hold_head:
continue; // skip
2005-01-25 05:02:35 +00:00
}
/* Don't draw hidden (fully judged) steps. */
if( tn.result.bHidden )
continue;
// TRICKY: If boomerang is on, then all notes in the range
// [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible.
// Test every note to make sure it's on screen before drawing
if( !IS_ON_SCREEN(NoteRowToBeat(i)) )
continue; // skip
ASSERT_M( NoteRowToBeat(i) > -2000, ssprintf("%i %i %i, %f %f", i, iLastIndexToDraw, iFirstIndexToDraw, GAMESTATE->m_fSongBeat, GAMESTATE->m_fMusicSeconds) );
// See if there is a hold step that begins on this index. Only do this
// if the note skin cares.
bool bHoldNoteBeginsOnThisBeat = false;
2006-01-12 21:44:10 +00:00
if( m_pCurDisplay->display[c].DrawHoldHeadForTapsOnSameRow() )
2002-06-14 22:25:22 +00:00
{
for( int c2=0; c2<m_pNoteData->GetNumTracks(); c2++ )
2002-06-14 22:25:22 +00:00
{
if( m_pNoteData->GetTapNote(c2, i).type == TapNote::hold_head)
{
bHoldNoteBeginsOnThisBeat = true;
break;
}
2002-06-14 22:25:22 +00:00
}
}
2003-02-18 23:15:38 +00:00
bool bIsInSelectionRange = false;
if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
2005-01-25 05:02:35 +00:00
bIsInSelectionRange = m_iBeginMarker<=i && i<m_iEndMarker;
bool bIsAddition = (tn.source == TapNote::addition);
bool bIsMine = (tn.type == TapNote::mine);
bool bIsAttack = (tn.type == TapNote::attack);
2003-11-15 23:19:13 +00:00
if( bIsAttack )
{
2003-11-15 23:19:13 +00:00
Sprite sprite;
2005-02-06 03:32:53 +00:00
sprite.Load( THEME->GetPathG("NoteField","attack "+tn.sAttackModifiers) );
2003-11-15 23:19:13 +00:00
float fBeat = NoteRowToBeat(i);
2006-01-12 21:44:10 +00:00
m_pCurDisplay->display[c].DrawActor( &sprite, c, fBeat, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, 1, m_fYReverseOffsetPixels, false, NotePart_Tap );
2003-11-15 23:19:13 +00:00
}
else
{
2006-01-12 21:44:10 +00:00
m_pCurDisplay->display[c].DrawTap( c, NoteRowToBeat(i), bHoldNoteBeginsOnThisBeat, bIsAddition, bIsMine, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, 1, m_fYReverseOffsetPixels );
}
}
2003-04-02 21:58:27 +00:00
2003-11-15 08:51:47 +00:00
2005-01-15 02:01:26 +00:00
NoteFieldMode::EndDrawTrack( c );
2002-04-16 17:31:00 +00:00
}
2002-04-28 20:42:32 +00:00
2003-11-28 00:45:31 +00:00
cur->m_GhostArrowRow.Draw();
2002-04-16 17:31:00 +00:00
}
2002-08-01 21:55:40 +00:00
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
2002-11-16 09:12:55 +00:00
}
2003-11-28 00:45:31 +00:00
2006-01-12 21:44:10 +00:00
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 ); }
2004-06-08 00:08:04 +00:00
/*
* (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.
*/