Files
itgmania212121/src/NoteField.cpp
T

1293 lines
44 KiB
C++
Raw Normal View History

2011-03-17 01:47:30 -04:00
#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"
2011-03-17 01:47:30 -04:00
2011-06-12 19:20:43 -04:00
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels );
float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels );
2011-03-17 01:47:30 -04:00
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_drawing_board_primitive= false;
2011-03-17 01:47:30 -04:00
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();
// I decided to do it this way because I don't want to dig through
// ScreenEdit to change all the places it touches the markers. -Kyz
m_FieldRenderArgs.selection_begin_marker= &m_iBeginMarker;
m_FieldRenderArgs.selection_end_marker= &m_iEndMarker;
2011-03-17 01:47:30 -04:00
m_iBeginMarker = m_iEndMarker = -1;
m_FieldRenderArgs.fail_fade = -1;
m_StepCallback.SetFromNil();
m_SetPressedCallback.SetFromNil();
m_DidTapNoteCallback.SetFromNil();
m_DidHoldNoteCallback.SetFromNil();
2011-03-17 01:47:30 -04:00
}
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_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer );
2011-03-17 01:47:30 -04:00
for( int c=0; c<GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer; c++ )
2011-03-17 01:47:30 -04:00
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_pPlayerState->m_PlayerNumber)->m_StyleType == StyleType_TwoPlayersSharedSides )
2011-03-17 01:47:30 -04:00
{
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 )
{
NOTESKIN->ValidateNoteSkinName(*s);
2011-03-17 01:47:30 -04:00
s->MakeLower();
}
2011-03-17 01:47:30 -04:00
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;
NOTESKIN->ValidateNoteSkinName(sCurrentNoteSkinLower);
2011-03-17 01:47:30 -04:00
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;
NOTESKIN->ValidateNoteSkinName(sNoteSkinLower);
2011-03-17 01:47:30 -04:00
sNoteSkinLower.MakeLower();
it = m_NoteDisplays.find( sNoteSkinLower );
ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower );
m_pDisplays[pn] = it->second;
}
InitColumnRenderers();
2011-03-17 01:47:30 -04:00
}
void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels, bool use_states_zoom )
2011-03-17 01:47:30 -04:00
{
m_pPlayerState = pPlayerState;
m_fYReverseOffsetPixels = fYReverseOffsetPixels;
CacheAllUsedNoteSkins();
// Design change: Instead of having a flag in the style that toggles a
// fixed zoom that is only applied to the columns, ScreenGameplay now
// calculates a zoom factor to apply to the notefield and puts it in the
// PlayerState. -Kyz
// use_states_zoom flag exists because edit mode has to set its own special
// zoom factor. -Kyz
if(use_states_zoom)
{
SetZoom(pPlayerState->m_NotefieldZoom);
}
// Pass the player state info down to children so that they can set
// per-player things. For example, if a screen filter is in the notefield
// board, this tells it what player it's for. -Kyz
Message msg("PlayerStateSet");
msg.SetParam("PlayerNumber", pPlayerState->m_PlayerNumber);
HandleMessage(msg);
2011-03-17 01:47:30 -04:00
}
void NoteField::Load(
const NoteData *pNoteData,
int iDrawDistanceAfterTargetsPixels,
2011-03-17 01:47:30 -04:00
int iDrawDistanceBeforeTargetsPixels )
{
ASSERT( pNoteData != NULL );
2011-03-17 01:47:30 -04:00
m_pNoteData = pNoteData;
m_iDrawDistanceAfterTargetsPixels = iDrawDistanceAfterTargetsPixels;
m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels;
ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels );
m_FieldRenderArgs.fail_fade = -1;
2011-03-17 01:47:30 -04:00
//int i1 = m_pNoteData->GetNumTracks();
//int i2 = GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer;
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer,
ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer));
2011-07-03 15:10:00 -04:00
2011-03-17 01:47:30 -04:00
// 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;
2011-11-27 22:20:32 -06:00
if(sNoteSkinLower.empty())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetPreferred().m_sNoteSkin;
2011-11-27 22:28:50 -06:00
if(sNoteSkinLower.empty())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = "default";
}
m_NoteDisplays.insert(pair<RString, NoteDisplayCols *> (sNoteSkinLower, badIdea));
}
sNoteSkinLower.MakeLower();
map<RString, NoteDisplayCols *>::iterator it = m_NoteDisplays.find( sNoteSkinLower );
2011-11-27 22:20:32 -06:00
ASSERT_M( it != m_NoteDisplays.end(), ssprintf("iterator != m_NoteDisplays.end() [sNoteSkinLower = %s]",sNoteSkinLower.c_str()) );
2011-03-17 01:47:30 -04:00
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.
2011-11-27 22:28:50 -06:00
if(sNoteSkinLower.empty())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_sNoteSkin;
2011-11-27 22:28:50 -06:00
if(sNoteSkinLower.empty())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = "default";
}
m_NoteDisplays.insert(pair<RString, NoteDisplayCols *> (sNoteSkinLower, badIdea));
}
2011-11-27 22:28:50 -06:00
2011-03-17 01:47:30 -04:00
sNoteSkinLower.MakeLower();
it = m_NoteDisplays.find( sNoteSkinLower );
ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower );
m_pDisplays[pn] = it->second;
}
InitColumnRenderers();
}
void NoteField::InitColumnRenderers()
{
m_FieldRenderArgs.player_state= m_pPlayerState;
m_FieldRenderArgs.reverse_offset_pixels= m_fYReverseOffsetPixels;
m_FieldRenderArgs.receptor_row= &(m_pCurDisplay->m_ReceptorArrowRow);
m_FieldRenderArgs.ghost_row= &(m_pCurDisplay->m_GhostArrowRow);
m_FieldRenderArgs.note_data= m_pNoteData;
2015-01-27 18:03:41 -07:00
m_ColumnRenderers.resize(GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer);
for(size_t ncr= 0; ncr < m_ColumnRenderers.size(); ++ncr)
{
FOREACH_EnabledPlayer(pn)
{
m_ColumnRenderers[ncr].m_displays[pn]= &(m_pDisplays[pn]->display[ncr]);
}
m_ColumnRenderers[ncr].m_displays[PLAYER_INVALID]= &(m_pCurDisplay->display[ncr]);
m_ColumnRenderers[ncr].m_column= ncr;
m_ColumnRenderers[ncr].m_column_render_args.column= ncr;
m_ColumnRenderers[ncr].m_field_render_args= &m_FieldRenderArgs;
}
m_pCurDisplay->m_ReceptorArrowRow.SetColumnRenderers(m_ColumnRenderers);
m_pCurDisplay->m_GhostArrowRow.SetColumnRenderers(m_ColumnRenderers);
2011-03-17 01:47:30 -04:00
}
void NoteField::Update( float fDeltaTime )
{
if( m_bFirstUpdate )
{
m_pCurDisplay->m_ReceptorArrowRow.PlayCommand( "On" );
}
ActorFrame::Update( fDeltaTime );
ArrowEffects::SetCurrentOptions(&m_pPlayerState->m_PlayerOptions.GetCurrent());
2011-03-17 01:47:30 -04:00
for(size_t c= 0; c < m_ColumnRenderers.size(); ++c)
{
m_ColumnRenderers[c].Update(fDeltaTime);
}
2011-03-17 01:47:30 -04:00
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
const float fCurrentBeat = m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
2011-03-17 01:47:30 -04:00
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(0, fYOffsetLast, m_fYReverseOffsetPixels);
2011-03-17 01:47:30 -04:00
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(0, fYOffsetCurrent, m_fYReverseOffsetPixels);
2011-03-17 01:47:30 -04:00
m_rectMarkerBar.Update( fDeltaTime );
NoteDisplayCols *cur = m_pCurDisplay;
cur->m_ReceptorArrowRow.Update( fDeltaTime );
cur->m_GhostArrowRow.Update( fDeltaTime );
if( m_FieldRenderArgs.fail_fade >= 0 )
m_FieldRenderArgs.fail_fade = min( m_FieldRenderArgs.fail_fade + fDeltaTime/FADE_FAIL_TIME, 1 );
2011-03-17 01:47:30 -04:00
// Update fade to failed
m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_FieldRenderArgs.fail_fade );
2011-03-17 01:47:30 -04:00
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;
2011-06-06 20:40:11 -04:00
if( pn == GAMESTATE->GetMasterPlayerNumber() )
2011-03-17 01:47:30 -04:00
NoteDisplay::Update( fDeltaTime );
}
float NoteField::GetWidth() const
{
const Style* pStyle = GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber);
2011-03-17 01:47:30 -04:00
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(0, fYOffset, m_fYReverseOffsetPixels);
2011-03-17 01:47:30 -04:00
float fAlpha;
int iState;
if( bIsMeasure )
{
fAlpha = BAR_MEASURE_ALPHA;
iState = 0;
}
else
{
PlayerOptions const& curr_ops= m_pPlayerState->m_PlayerOptions.GetCurrent();
float fScrollSpeed = curr_ops.m_fScrollSpeed;
if(curr_ops.m_fTimeSpacing > 0)
{
2011-03-17 01:47:30 -04:00
fScrollSpeed = 4;
}
else if(curr_ops.m_fMaxScrollBPM != 0)
{
fScrollSpeed= curr_ops.m_fMaxScrollBPM / m_pPlayerState->m_fReadBPM;
}
2011-03-17 01:47:30 -04:00
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 )
{
2014-05-02 17:01:38 -05:00
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(0, 0, m_fYReverseOffsetPixels);
2014-05-02 17:01:38 -05:00
RectF rect = *pSprite->GetCurrentTextureCoordRect();
const float fBoardGraphicHeightPixels = pSprite->GetUnzoomedHeight();
float fTexCoordOffset = m_fBoardOffsetPixels / fBoardGraphicHeightPixels;
2011-03-17 01:47:30 -04:00
// 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(0, fYOffset, m_fYReverseOffsetPixels);
2011-03-17 01:47:30 -04:00
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");
2011-03-17 01:47:30 -04:00
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(0, fDrawDistanceAfterTargetsPixels, m_fYReverseOffsetPixels);
2011-03-17 01:47:30 -04:00
float fDrawDistanceBeforeTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fEndBeat );
float fYEndPos= ArrowEffects::GetYPos(0, fDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels);
2011-03-17 01:47:30 -04:00
// 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 );
2011-03-17 01:47:30 -04:00
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" );
2011-03-24 20:41:35 -04:00
static ThemeMetric<RageColor> WARP_COLOR ( "NoteField", "WarpColor" );
static ThemeMetric<RageColor> TIME_SIG_COLOR ( "NoteField", "TimeSignatureColor" );
2011-03-17 01:47:30 -04:00
static ThemeMetric<RageColor> TICKCOUNT_COLOR ( "NoteField", "TickcountColor" );
static ThemeMetric<RageColor> COMBO_COLOR ( "NoteField", "ComboColor" );
2011-04-05 00:35:45 -04:00
static ThemeMetric<RageColor> LABEL_COLOR ( "NoteField", "LabelColor" );
static ThemeMetric<RageColor> SPEED_COLOR ( "NoteField", "SpeedColor" );
2011-05-25 22:44:41 +07:00
static ThemeMetric<RageColor> SCROLL_COLOR ( "NoteField", "ScrollColor" );
2011-05-16 12:02:17 -04:00
static ThemeMetric<RageColor> FAKE_COLOR ("NoteField", "FakeColor" );
2011-03-17 01:47:30 -04:00
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" );
2011-03-24 20:41:35 -04:00
static ThemeMetric<bool> WARP_IS_LEFT_SIDE ( "NoteField", "WarpIsLeftSide" );
static ThemeMetric<bool> TIME_SIG_IS_LEFT_SIDE ( "NoteField", "TimeSignatureIsLeftSide" );
2011-03-17 01:47:30 -04:00
static ThemeMetric<bool> TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" );
static ThemeMetric<bool> COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" );
2011-04-05 00:35:45 -04:00
static ThemeMetric<bool> LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" );
static ThemeMetric<bool> SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" );
2011-05-25 22:44:41 +07:00
static ThemeMetric<bool> SCROLL_IS_LEFT_SIDE ( "NoteField", "ScrollIsLeftSide" );
2011-05-16 12:02:17 -04:00
static ThemeMetric<bool> FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" );
2011-03-17 01:47:30 -04:00
static ThemeMetric<float> BPM_OFFSETX ( "NoteField", "BPMOffsetX" );
static ThemeMetric<float> STOP_OFFSETX ( "NoteField", "StopOffsetX" );
static ThemeMetric<float> DELAY_OFFSETX ( "NoteField", "DelayOffsetX" );
2011-03-24 20:41:35 -04:00
static ThemeMetric<float> WARP_OFFSETX ( "NoteField", "WarpOffsetX" );
static ThemeMetric<float> TIME_SIG_OFFSETX ( "NoteField", "TimeSignatureOffsetX" );
2011-03-17 01:47:30 -04:00
static ThemeMetric<float> TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" );
static ThemeMetric<float> COMBO_OFFSETX ( "NoteField", "ComboOffsetX" );
2011-04-05 00:35:45 -04:00
static ThemeMetric<float> LABEL_OFFSETX ( "NoteField", "LabelOffsetX" );
static ThemeMetric<float> SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" );
2011-05-25 22:44:41 +07:00
static ThemeMetric<float> SCROLL_OFFSETX ( "NoteField", "ScrollOffsetX" );
2011-05-16 12:02:17 -04:00
static ThemeMetric<float> FAKE_OFFSETX ( "NoteField", "FakeOffsetX" );
void NoteField::set_text_measure_number_for_draw(
const float beat, const float side_sign, float x_offset,
const float horiz_align, const RageColor& color, const RageColor& glow)
2011-03-17 01:47:30 -04:00
{
const float y_offset= ArrowEffects::GetYOffset(m_pPlayerState, 0, beat);
const float y_pos= ArrowEffects::GetYPos(0, y_offset, m_fYReverseOffsetPixels);
const float zoom= ArrowEffects::GetZoom(m_pPlayerState);
const float x_base= GetWidth() * .5f;
x_offset*= zoom;
m_textMeasureNumber.SetZoom(zoom);
m_textMeasureNumber.SetHorizAlign(horiz_align);
m_textMeasureNumber.SetDiffuse(color);
m_textMeasureNumber.SetGlow(glow);
m_textMeasureNumber.SetXY((x_offset + x_base) * side_sign, y_pos);
2011-03-17 01:47:30 -04:00
}
void NoteField::draw_timing_segment_text(const RString& text,
const float beat, const float side_sign, float x_offset,
const float horiz_align, const RageColor& color, const RageColor& glow)
2011-03-17 01:47:30 -04:00
{
set_text_measure_number_for_draw(beat, side_sign, x_offset, horiz_align,
color, glow);
m_textMeasureNumber.SetText(text);
2011-03-17 01:47:30 -04:00
m_textMeasureNumber.Draw();
}
void NoteField::DrawAttackText(const float beat, const Attack &attack,
const RageColor& glow)
2011-04-05 00:35:45 -04:00
{
set_text_measure_number_for_draw(beat, 1, 10, align_left,
RageColor(0,0.8f,0.8f,1), glow);
2011-03-17 01:47:30 -04:00
m_textMeasureNumber.SetText( attack.GetTextDescription() );
m_textMeasureNumber.Draw();
}
void NoteField::DrawBGChangeText(const float beat, const RString new_bg_name,
const RageColor& glow)
2011-03-17 01:47:30 -04:00
{
set_text_measure_number_for_draw(beat, 1, 0, align_left, RageColor(0,1,0,1),
glow);
m_textMeasureNumber.SetText(new_bg_name);
2011-03-17 01:47:30 -04:00
m_textMeasureNumber.Draw();
}
static CacheNoteStat GetNumNotesFromBeginning( const PlayerState *pPlayerState, float beat )
2011-03-17 01:47:30 -04:00
{
// 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 )
2011-03-17 01:47:30 -04:00
{
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;
}
2011-03-17 01:47:30 -04:00
}
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;
}
2011-03-17 01:47:30 -04:00
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 ++ )
2011-03-17 01:47:30 -04:00
{
float fMid = (fLow + fHigh) / 2.0f;
2011-03-17 01:47:30 -04:00
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;
}
2011-03-17 01:47:30 -04:00
}
2011-03-17 01:47:30 -04:00
return fFirstBeatToDraw;
2011-03-17 01:47:30 -04:00
}
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);
2011-03-17 01:47:30 -04:00
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;
}
2011-05-24 12:44:48 +07:00
if( fSpeedMultiplier < 0.75 )
{
fLastBeatToDraw = min(fLastBeatToDraw, pPlayerState->GetDisplayedPosition().m_fSongBeat + 16);
2011-05-24 12:44:48 +07:00
}
2011-03-17 01:47:30 -04:00
return fLastBeatToDraw;
}
bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
{
// IMPORTANT: Do not modify this function without also modifying the
// version that is in NoteDisplay.cpp or coming up with a good way to
// merge them. -Kyz
2011-03-17 01:47:30 -04:00
// 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::CalcPixelsBeforeAndAfterTargets()
2011-03-17 01:47:30 -04:00
{
const PlayerOptions& curr_options= m_pPlayerState->m_PlayerOptions.GetCurrent();
2011-03-17 01:47:30 -04:00
// Adjust draw range depending on some effects
m_FieldRenderArgs.draw_pixels_after_targets= m_iDrawDistanceAfterTargetsPixels;
2011-03-17 01:47:30 -04:00
// HACK: If boomerang and centered are on, then we want to draw much
// earlier so that the notes don't pop on screen.
float centered_times_boomerang=
curr_options.m_fScrolls[PlayerOptions::SCROLL_CENTERED] *
curr_options.m_fAccels[PlayerOptions::ACCEL_BOOMERANG];
m_FieldRenderArgs.draw_pixels_after_targets +=
int(SCALE(centered_times_boomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2));
m_FieldRenderArgs.draw_pixels_before_targets =
m_iDrawDistanceBeforeTargetsPixels;
float draw_scale= 1;
draw_scale*= 1 + 0.5f * fabsf(curr_options.m_fPerspectiveTilt);
draw_scale*= 1 + fabsf(curr_options.m_fEffects[PlayerOptions::EFFECT_MINI]);
m_FieldRenderArgs.draw_pixels_after_targets=
(int)(m_FieldRenderArgs.draw_pixels_after_targets * draw_scale);
m_FieldRenderArgs.draw_pixels_before_targets=
(int)(m_FieldRenderArgs.draw_pixels_before_targets * draw_scale);
}
void NoteField::DrawPrimitives()
{
//LOG->Trace( "NoteField::DrawPrimitives()" );
2011-03-17 01:47:30 -04:00
// This should be filled in on the first update.
ASSERT( m_pCurDisplay != NULL );
2011-03-17 01:47:30 -04:00
// ArrowEffects::Update call moved because having it happen once per
// NoteField (which means twice in two player) seemed wasteful. -Kyz
2011-03-17 01:47:30 -04:00
if(m_drawing_board_primitive)
{
CalcPixelsBeforeAndAfterTargets();
DrawBoard(m_FieldRenderArgs.draw_pixels_after_targets,
m_FieldRenderArgs.draw_pixels_before_targets);
return;
}
// Some might prefer an else block, instead of returning from the if, but I
// don't want to bump the indent on the entire remaining section. -Kyz
ArrowEffects::SetCurrentOptions(&m_pPlayerState->m_PlayerOptions.GetCurrent());
2011-03-17 01:47:30 -04:00
CalcPixelsBeforeAndAfterTargets();
NoteDisplayCols *cur = m_pCurDisplay;
2011-03-17 01:47:30 -04:00
// Probe for first and last notes on the screen
float first_beat_to_draw= FindFirstDisplayedBeat(
m_pPlayerState, m_FieldRenderArgs.draw_pixels_after_targets);
float last_beat_to_draw= FindLastDisplayedBeat(
m_pPlayerState, m_FieldRenderArgs.draw_pixels_before_targets);
2011-03-17 01:47:30 -04:00
m_pPlayerState->m_fLastDrawnBeat = last_beat_to_draw;
2011-03-17 01:47:30 -04:00
m_FieldRenderArgs.first_row = BeatToNoteRow(first_beat_to_draw);
m_FieldRenderArgs.last_row = BeatToNoteRow(last_beat_to_draw);
2011-03-17 01:47:30 -04:00
//LOG->Trace( "start = %f.1, end = %f.1", first_beat_to_draw-fSongBeat, last_beat_to_draw-fSongBeat );
//LOG->Trace( "Drawing elements %d through %d", m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row );
2011-03-17 01:47:30 -04:00
#define IS_ON_SCREEN(fBeat) (first_beat_to_draw <= (fBeat) && (fBeat) <= last_beat_to_draw && IsOnScreen(fBeat, 0, m_FieldRenderArgs.draw_pixels_after_targets, m_FieldRenderArgs.draw_pixels_before_targets))
2011-03-17 01:47:30 -04:00
// 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));
2011-07-14 14:33:07 -04:00
unsigned i = 0;
2011-03-17 01:47:30 -04:00
// Draw beat bars
if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL )
2011-03-17 01:47:30 -04:00
{
const vector<TimingSegment *> &tSigs = *segs[SEGMENT_TIME_SIG];
2011-03-17 01:47:30 -04:00
int iMeasureIndex = 0;
2011-07-14 14:33:07 -04:00
for (i = 0; i < tSigs.size(); i++)
2011-03-17 01:47:30 -04:00
{
const TimeSignatureSegment *ts = ToTimeSignature(tSigs[i]);
int iSegmentEndRow = (i + 1 == tSigs.size()) ? m_FieldRenderArgs.last_row : tSigs[i+1]->GetRow();
2011-08-24 12:54:30 -05:00
2011-03-17 01:47:30 -04:00
// beat bars every 16th note
2011-07-14 14:33:07 -04:00
int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4;
2011-03-17 01:47:30 -04:00
// In 4/4, every 16th beat bar is a measure
2011-07-14 14:33:07 -04:00
int iMeasureBarFrequency = ts->GetNum() * 4;
2011-03-17 01:47:30 -04:00
int iBeatBarsDrawn = 0;
2011-07-14 14:33:07 -04:00
for( int j=ts->GetRow(); j < iSegmentEndRow; j += iDrawBeatBarsEveryRows )
2011-03-17 01:47:30 -04:00
{
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;
2011-07-14 14:33:07 -04:00
float fBeat = NoteRowToBeat(j);
2011-03-17 01:47:30 -04:00
if( IS_ON_SCREEN(fBeat) )
{
DrawBeatBar( fBeat, type, iMeasureIndex );
}
iBeatBarsDrawn++;
if( bMeasureBar )
iMeasureIndex++;
}
}
}
if( GAMESTATE->IsEditing() && pTiming != NULL )
2011-03-17 01:47:30 -04:00
{
ASSERT(GAMESTATE->m_pCurSong != NULL);
2011-03-17 01:47:30 -04:00
const TimingData &timing = *pTiming;
const RageColor text_glow= RageColor(1,1,1,std::cos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f);
float horiz_align= align_right;
float side_sign= 1;
#define draw_all_segments(str_exp, name, caps_name) \
horiz_align= caps_name##_IS_LEFT_SIDE ? align_right : align_left; \
side_sign= caps_name##_IS_LEFT_SIDE ? -1 : 1; \
for(unsigned int i= 0; i < segs[SEGMENT_##caps_name]->size(); ++i) \
{ \
const name##Segment* seg= To##name((*segs[SEGMENT_##caps_name])[i]); \
if(seg->GetRow() >= m_FieldRenderArgs.first_row && \
seg->GetRow() <= m_FieldRenderArgs.last_row && \
IS_ON_SCREEN(seg->GetBeat())) \
{ \
draw_timing_segment_text(str_exp, seg->GetBeat(), side_sign, \
caps_name##_OFFSETX, horiz_align, caps_name##_COLOR, text_glow); \
} \
2011-03-17 01:47:30 -04:00
}
2011-08-24 12:54:30 -05:00
draw_all_segments(FloatToString(seg->GetRatio()), Scroll, SCROLL);
draw_all_segments(FloatToString(seg->GetBPM()), BPM, BPM);
draw_all_segments(FloatToString(seg->GetPause()), Stop, STOP);
draw_all_segments(FloatToString(seg->GetPause()), Delay, DELAY);
draw_all_segments(FloatToString(seg->GetLength()), Warp, WARP);
draw_all_segments(ssprintf("%d\n--\n%d", seg->GetNum(), seg->GetDen()),
TimeSignature, TIME_SIG);
draw_all_segments(ssprintf("%d", seg->GetTicks()), Tickcount, TICKCOUNT);
draw_all_segments(
ssprintf("%d/%d", seg->GetCombo(), seg->GetMissCombo()), Combo, COMBO);
draw_all_segments(seg->GetLabel(), Label, LABEL);
draw_all_segments(ssprintf("%s\n%s\n%s",
FloatToString(seg->GetRatio()).c_str(),
(seg->GetUnit() == 1 ? "S" : "B"),
FloatToString(seg->GetDelay()).c_str()), Speed, SPEED);
draw_all_segments(FloatToString(seg->GetLength()), Fake, FAKE);
#undef draw_all_segments
2011-03-17 01:47:30 -04:00
// 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;
2011-05-09 21:19:40 -04:00
float fBeat = timing.GetBeatFromElapsedTime( fSecond );
2011-03-17 01:47:30 -04:00
if( BeatToNoteRow(fBeat) >= m_FieldRenderArgs.first_row &&
BeatToNoteRow(fBeat) <= m_FieldRenderArgs.last_row &&
IS_ON_SCREEN(fBeat))
2011-03-17 01:47:30 -04:00
{
DrawAttackText(fBeat, *a, text_glow);
2011-03-17 01:47:30 -04:00
}
}
}
2011-06-24 18:07:47 -04:00
else
{
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks :
GAMESTATE->m_pCurSong->m_Attacks;
2011-10-17 21:37:30 -04:00
// XXX: We're somehow getting here when attacks is null. Find the actual cause later.
if (&attacks)
2011-06-24 18:07:47 -04:00
{
2011-10-17 21:37:30 -04:00
FOREACH_CONST(Attack, attacks, a)
2011-06-24 18:07:47 -04:00
{
2011-10-17 21:37:30 -04:00
float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond);
if (BeatToNoteRow(fBeat) >= m_FieldRenderArgs.first_row &&
BeatToNoteRow(fBeat) <= m_FieldRenderArgs.last_row &&
2011-10-17 21:37:30 -04:00
IS_ON_SCREEN(fBeat))
{
this->DrawAttackText(fBeat, *a, text_glow);
2011-10-17 21:37:30 -04:00
}
2011-06-24 18:07:47 -04:00
}
}
}
2011-08-24 12:54:30 -05:00
2011-05-17 00:07:43 -04:00
if( !GAMESTATE->m_bIsUsingStepTiming )
2011-03-17 01:47:30 -04:00
{
// BGChange text
2012-12-27 16:59:35 -05:00
EditMode mode = GAMESTATE->m_EditMode;
switch( mode )
{
case EditMode_Home:
case EditMode_CourseMods:
case EditMode_Practice:
break;
case EditMode_Full:
2011-03-17 01:47:30 -04:00
{
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
2011-07-14 14:33:07 -04:00
FOREACH_BackgroundLayer( j )
iter[j] = GAMESTATE->m_pCurSong->GetBackgroundChanges(j).begin();
2011-08-24 12:54:30 -05:00
while( 1 )
2011-03-17 01:47:30 -04:00
{
float fLowestBeat = FLT_MAX;
vector<BackgroundLayer> viLowestIndex;
2011-08-24 12:54:30 -05:00
2011-07-14 14:33:07 -04:00
FOREACH_BackgroundLayer( j )
2011-03-17 01:47:30 -04:00
{
2011-07-14 14:33:07 -04:00
if( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() )
continue;
2011-08-24 12:54:30 -05:00
2011-07-14 14:33:07 -04:00
float fBeat = iter[j]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
2011-07-14 14:33:07 -04:00
viLowestIndex.push_back( j );
}
else if( fBeat == fLowestBeat )
{
2011-07-14 14:33:07 -04:00
viLowestIndex.push_back( j );
}
2011-03-17 01:47:30 -04:00
}
if( viLowestIndex.empty() )
2011-03-17 01:47:30 -04:00
{
2011-07-14 14:33:07 -04:00
FOREACH_BackgroundLayer( j )
ASSERT( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() );
break;
2011-03-17 01:47:30 -04:00
}
if( IS_ON_SCREEN(fLowestBeat) )
2011-03-17 01:47:30 -04:00
{
vector<RString> vsBGChanges;
2011-07-14 14:33:07 -04:00
FOREACH_CONST( BackgroundLayer, viLowestIndex, bl )
{
2011-07-14 14:33:07 -04:00
ASSERT( iter[*bl] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*bl).end() );
const BackgroundChange& change = *iter[*bl];
RString s = change.GetTextDescription();
2011-07-14 14:33:07 -04:00
if( *bl!=0 )
s = ssprintf("%d: ",*bl) + s;
vsBGChanges.push_back( s );
}
DrawBGChangeText(fLowestBeat, join("\n",vsBGChanges), text_glow);
2011-03-17 01:47:30 -04:00
}
2011-07-14 14:33:07 -04:00
FOREACH_CONST( BackgroundLayer, viLowestIndex, bl )
iter[*bl]++;
2011-03-17 01:47:30 -04:00
}
}
break;
default:
2012-12-27 16:59:35 -05:00
FAIL_M(ssprintf("Invalid edit mode: %i", mode));
}
2011-03-17 01:47:30 -04:00
}
// Draw marker bars
if( m_iBeginMarker != -1 && m_iEndMarker != -1 )
{
int iBegin = m_iBeginMarker;
int iEnd = m_iEndMarker;
CLAMP( iBegin, m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row );
CLAMP( iEnd, m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row );
2011-03-17 01:47:30 -04:00
DrawAreaHighlight( iBegin, iEnd );
}
else if( m_iBeginMarker != -1 )
{
if( m_iBeginMarker >= m_FieldRenderArgs.first_row &&
m_iBeginMarker <= m_FieldRenderArgs.last_row )
2011-03-17 01:47:30 -04:00
DrawMarkerBar( m_iBeginMarker );
}
else if( m_iEndMarker != -1 )
{
if( m_iEndMarker >= m_FieldRenderArgs.first_row &&
m_iEndMarker <= m_FieldRenderArgs.last_row )
2011-03-17 01:47:30 -04:00
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.
const Style* pStyle = GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber);
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer,
2015-01-27 18:03:41 -07:00
ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer));
2011-03-17 01:47:30 -04:00
if(*m_FieldRenderArgs.selection_begin_marker != -1 &&
*m_FieldRenderArgs.selection_end_marker != -1)
{
m_FieldRenderArgs.selection_glow= SCALE(
std::cos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f);
}
m_FieldRenderArgs.fade_before_targets= FADE_BEFORE_TARGETS_PERCENT;
2011-07-14 14:33:07 -04:00
for( int j=0; j<m_pNoteData->GetNumTracks(); j++ ) // for each arrow column
2011-03-17 01:47:30 -04:00
{
2011-07-14 14:33:07 -04:00
const int c = pStyle->m_iColumnDrawOrder[j];
m_ColumnRenderers[c].Draw();
2011-03-17 01:47:30 -04:00
}
cur->m_GhostArrowRow.Draw();
}
void NoteField::DrawBoardPrimitive()
{
if(!SHOW_BOARD)
{
return;
}
m_drawing_board_primitive= true;
Draw();
m_drawing_board_primitive= false;
}
2011-03-17 01:47:30 -04:00
void NoteField::FadeToFail()
{
m_FieldRenderArgs.fail_fade = max( 0.0f, m_FieldRenderArgs.fail_fade ); // this will slowly increase every Update()
2011-03-17 01:47:30 -04:00
// don't fade all over again if this is called twice
}
// A few functions and macros to take care of processing the callback
// return values, since the code would be identical in all of them. -Kyz
#define OPEN_CALLBACK_BLOCK(member_name) \
if(!from_lua && !member_name.IsNil()) \
{ \
Lua* L= LUA->Get(); \
member_name.PushSelf(L);
#define OPEN_RUN_BLOCK(arg_count) \
RString error= "Error running callback: "; \
if(LuaHelpers::RunScriptOnStack(L, error, arg_count, arg_count, true)) \
{
#define CLOSE_RUN_AND_CALLBACK_BLOCKS } lua_settop(L, 0); LUA->Release(L); }
#define PUSH_COLUMN lua_pushnumber(L, col+1)
2014-12-10 18:14:00 -07:00
static void get_returned_column(Lua* L, PlayerNumber pn, int index, int& col)
{
if(lua_isnumber(L, index))
{
// 1-indexed columns in lua
int tmpcol= lua_tonumber(L, index) - 1;
2014-12-10 18:14:00 -07:00
if(tmpcol < 0 || tmpcol >= GAMESTATE->GetCurrentStyle(pn)->m_iColsPerPlayer)
{
LuaHelpers::ReportScriptErrorFmt(
"Column returned by callback must be between 1 and %d "
"(GAMESTATE:GetCurrentStyle():ColumnsPerPlayer()).",
2014-12-10 18:14:00 -07:00
GAMESTATE->GetCurrentStyle(pn)->m_iColsPerPlayer);
}
else
{
col= tmpcol;
}
}
}
// Templated so it can be used for TNS and HNS. -Kyz
template<class T> static void get_returned_score(Lua* L, int index, T& score)
{
T maybe_score= Enum::Check<T>(L, index, true, true);
if(maybe_score != EnumTraits<T>::Invalid)
{
score= maybe_score;
}
}
static void get_returned_bright(Lua* L, int index, bool& bright)
{
if(lua_isboolean(L, index))
{
bright= lua_toboolean(L, index);
}
}
void NoteField::Step(int col, TapNoteScore score, bool from_lua)
{
OPEN_CALLBACK_BLOCK(m_StepCallback);
PUSH_COLUMN;
Enum::Push(L, score);
OPEN_RUN_BLOCK(2);
2014-12-10 18:14:00 -07:00
get_returned_column(L, m_pPlayerState->m_PlayerNumber, 1, col);
get_returned_score(L, 2, score);
CLOSE_RUN_AND_CALLBACK_BLOCKS;
m_pCurDisplay->m_ReceptorArrowRow.Step(col, score);
}
void NoteField::SetPressed(int col, bool from_lua)
{
OPEN_CALLBACK_BLOCK(m_SetPressedCallback);
PUSH_COLUMN;
OPEN_RUN_BLOCK(1);
2014-12-10 18:14:00 -07:00
get_returned_column(L, m_pPlayerState->m_PlayerNumber, 1, col);
CLOSE_RUN_AND_CALLBACK_BLOCKS;
m_pCurDisplay->m_ReceptorArrowRow.SetPressed(col);
}
void NoteField::DidTapNote(int col, TapNoteScore score, bool bright, bool from_lua)
{
OPEN_CALLBACK_BLOCK(m_DidTapNoteCallback);
PUSH_COLUMN;
Enum::Push(L, score);
lua_pushboolean(L, bright);
OPEN_RUN_BLOCK(3);
2014-12-10 18:14:00 -07:00
get_returned_column(L, m_pPlayerState->m_PlayerNumber, 1, col);
get_returned_score(L, 2, score);
get_returned_bright(L, 3, bright);
CLOSE_RUN_AND_CALLBACK_BLOCKS;
m_pCurDisplay->m_GhostArrowRow.DidTapNote(col, score, bright);
}
void NoteField::DidHoldNote(int col, HoldNoteScore score, bool bright, bool from_lua)
{
OPEN_CALLBACK_BLOCK(m_DidHoldNoteCallback);
PUSH_COLUMN;
Enum::Push(L, score);
lua_pushboolean(L, bright);
OPEN_RUN_BLOCK(3);
2014-12-10 18:14:00 -07:00
get_returned_column(L, m_pPlayerState->m_PlayerNumber, 1, col);
get_returned_score(L, 2, score);
get_returned_bright(L, 3, bright);
CLOSE_RUN_AND_CALLBACK_BLOCKS;
m_pCurDisplay->m_GhostArrowRow.DidHoldNote(col, score, bright);
}
#undef OPEN_CALLBACK_BLOCK
#undef OPEN_RUN_BLOCK
#undef CLOSE_RUN_AND_CALLBACK_BLOCKS
#undef PUSH_COLUMN
2011-03-17 01:47:30 -04:00
void NoteField::HandleMessage( const Message &msg )
{
if( msg == Message_CurrentSongChanged )
{
m_fCurrentBeatLastUpdate = -1;
m_fYPosCurrentBeatLastUpdate = -1;
}
ActorFrame::HandleMessage( msg );
}
// lua start
#include "LuaBinding.h"
/** @brief Allow Lua to have access to the Notefield. */
class LunaNoteField: public Luna<NoteField>
{
public:
#define SET_CALLBACK_GENERIC(callback_name, member_name) \
static int callback_name(T* p, lua_State* L) \
{ \
if(lua_isnoneornil(L, 1)) \
{ \
p->member_name.SetFromNil(); \
} \
else if(lua_isfunction(L, 1)) \
{ \
p->member_name.SetFromStack(L); \
} \
else \
{ \
luaL_error(L, #callback_name "Callback argument must be nil (to clear the callback) or a function (to set the callback)."); \
} \
return 0; \
}
SET_CALLBACK_GENERIC(set_step_callback, m_StepCallback);
SET_CALLBACK_GENERIC(set_set_pressed_callback, m_SetPressedCallback);
SET_CALLBACK_GENERIC(set_did_tap_note_callback, m_DidTapNoteCallback);
SET_CALLBACK_GENERIC(set_did_hold_note_callback, m_DidHoldNoteCallback);
#undef SET_CALLBACK_GENERIC
2014-12-10 18:14:00 -07:00
static int check_column(lua_State* L, int index, PlayerNumber pn)
{
// 1-indexed columns in lua
int col= IArg(1)-1;
2014-12-10 18:14:00 -07:00
if(col < 0 || col >= GAMESTATE->GetCurrentStyle(pn)->m_iColsPerPlayer)
{
luaL_error(L, "Column must be between 1 and %d "
2014-12-10 18:14:00 -07:00
"(GAMESTATE:GetCurrentStyle(pn):ColumnsPerPlayer()).",
GAMESTATE->GetCurrentStyle(pn)->m_iColsPerPlayer);
}
return col;
}
static int step(T* p, lua_State* L)
{
2014-12-10 18:14:00 -07:00
int col= check_column(L, 1, p->GetPlayerState()->m_PlayerNumber);
TapNoteScore tns= Enum::Check<TapNoteScore>(L, 2);
p->Step(col, tns, true);
return 0;
}
static int set_pressed(T* p, lua_State* L)
{
2014-12-10 18:14:00 -07:00
int col= check_column(L, 1, p->GetPlayerState()->m_PlayerNumber);
p->SetPressed(col, true);
return 0;
}
static int did_tap_note(T* p, lua_State* L)
{
2014-12-10 18:14:00 -07:00
int col= check_column(L, 1, p->GetPlayerState()->m_PlayerNumber);
TapNoteScore tns= Enum::Check<TapNoteScore>(L, 2);
bool bright= BArg(3);
p->DidTapNote(col, tns, bright, true);
return 0;
}
static int did_hold_note(T* p, lua_State* L)
{
2014-12-10 18:14:00 -07:00
int col= check_column(L, 1, p->GetPlayerState()->m_PlayerNumber);
HoldNoteScore hns= Enum::Check<HoldNoteScore>(L, 2);
bool bright= BArg(3);
p->DidHoldNote(col, hns, bright, true);
return 0;
}
static int get_column_actors(T* p, lua_State* L)
{
lua_createtable(L, p->m_ColumnRenderers.size(), 0);
for(size_t i= 0; i < p->m_ColumnRenderers.size(); ++i)
{
p->m_ColumnRenderers[i].PushSelf(L);
lua_rawseti(L, -2, i+1);
}
return 1;
}
LunaNoteField()
{
ADD_METHOD(set_step_callback);
ADD_METHOD(set_set_pressed_callback);
ADD_METHOD(set_did_tap_note_callback);
ADD_METHOD(set_did_hold_note_callback);
ADD_METHOD(step);
ADD_METHOD(set_pressed);
ADD_METHOD(did_tap_note);
ADD_METHOD(did_hold_note);
ADD_METHOD(get_column_actors);
}
};
LUA_REGISTER_DERIVED_CLASS(NoteField, ActorFrame)
// lua end
2011-03-17 01:47:30 -04: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.
*/