diff --git a/stepmania/src/GameState.cpp b/stepmania/src/GameState.cpp index 12af28dd35..af76b5fa0c 100644 --- a/stepmania/src/GameState.cpp +++ b/stepmania/src/GameState.cpp @@ -521,6 +521,19 @@ void GameState::RestoreSelectedOptions() m_SongOptions = m_StoredSongOptions; } +void GameState::ResetNoteSkins() +{ + for( int pn = 0; pn < NUM_PLAYERS; ++pn ) + { + m_BeatToNoteSkin[pn].clear(); + m_BeatToNoteSkin[pn][-1000] = GAMESTATE->m_PlayerOptions[pn].m_sNoteSkin; + } + + m_BeatToNoteSkinRev = 0; +} + +/* From NoteField: */ +extern float g_fNoteFieldLastBeatToDraw; void GameState::LaunchAttack( PlayerNumber target, Attack a ) { @@ -538,6 +551,44 @@ void GameState::LaunchAttack( PlayerNumber target, Attack a ) m_TransformsToApply[target].push_back( po.m_Transform ); } + if( po.m_sNoteSkin != "" ) + { + map &BeatToNoteSkin = m_BeatToNoteSkin[target]; + /* Add it in the future, past what's currently on screen, so new arrows will scroll + * on screen with this skin. */ + const float CurBeat = this->m_fSongBeat; + /* If reasonable, push the attack forward so notes on screen don't change suddenly. */ + const float AddBeat = min( CurBeat+16, g_fNoteFieldLastBeatToDraw ); + + const float AddSecond = this->m_pCurSong->GetElapsedTimeFromBeat( CurBeat ); + const float EndSecond = AddSecond + a.fSecsRemaining; + const float EndBeat = this->m_pCurSong->GetBeatFromElapsedTime( EndSecond ); + + /* If there are any note skins after the point we're adding, remove them. We probably + * have overlapping note skin attacks. */ + map::iterator it = BeatToNoteSkin.begin(); + while( it != BeatToNoteSkin.end() ) + { + map::iterator next = it; + ++next; + if( it->first >= AddBeat ) + { + LOG->Trace( "erase old %f", it->first ); + BeatToNoteSkin.erase( it ); + } + it = next; + } + + /* Add the skin to m_BeatToNoteSkin. */ + BeatToNoteSkin[AddBeat] = po.m_sNoteSkin; + + /* Return to the default note skin after the duration. */ + BeatToNoteSkin[EndBeat] = GAMESTATE->m_PlayerOptions[target].m_sNoteSkin; + + ++m_BeatToNoteSkinRev; + po.m_sNoteSkin = ""; + } + // search for an open slot for( int s=0; s + class Song; class Steps; class Course; @@ -152,6 +154,11 @@ public: float m_fCurBPS; bool m_bFreeze; // in the middle of a freeze bool m_bPastHereWeGo; + + map m_BeatToNoteSkin[NUM_PLAYERS]; + int m_BeatToNoteSkinRev; /* hack: incremented whenever m_BeatToNoteSkin changes */ + void ResetNoteSkins(); + static const float MUSIC_SECONDS_INVALID; void ResetMusicStatistics(); // Call this when it's time to play a new song. Clears the values above. diff --git a/stepmania/src/NoteDisplay.cpp b/stepmania/src/NoteDisplay.cpp index 55ce5daea0..e70a81918e 100644 --- a/stepmania/src/NoteDisplay.cpp +++ b/stepmania/src/NoteDisplay.cpp @@ -43,35 +43,35 @@ Actor* MakeModelOrSprite( CString sFile ) } } -#define DRAW_HOLD_HEAD_FOR_TAPS_ON_SAME_ROW NOTESKIN->GetMetricB(pn,name,"DrawHoldHeadForTapsOnSameRow") -#define TAP_NOTE_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"TapNoteAnimationLengthInBeats") -#define TAP_ADDITION_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"TapAdditionAnimationLengthInBeats") -#define TAP_MINE_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"TapMineAnimationLengthInBeats") -#define HOLD_HEAD_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"HoldHeadAnimationLengthInBeats") -#define HOLD_TOPCAP_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"HoldTopCapAnimationLengthInBeats") -#define HOLD_BODY_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"HoldBodyAnimationLengthInBeats") -#define HOLD_BOTTOMCAP_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"HoldBottomCapAnimationLengthInBeats") -#define HOLD_TAIL_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(pn,name,"HoldTailAnimationLengthInBeats") -#define TAP_NOTE_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"TapNoteAnimationIsVivid") -#define TAP_ADDITION_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"TapAdditionAnimationIsVivid") -#define TAP_MINE_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"TapMineAnimationIsVivid") -#define HOLD_HEAD_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"HoldHeadAnimationIsVivid") -#define HOLD_TOPCAP_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"HoldTopCapAnimationIsVivid") -#define HOLD_BODY_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"HoldBodyAnimationIsVivid") -#define HOLD_BOTTOMCAP_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"HoldBottomCapAnimationIsVivid") -#define HOLD_TAIL_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(pn,name,"HoldTailAnimationIsVivid") -#define TAP_NOTE_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(pn,name,"TapNoteAnimationIsNoteColor") -#define HOLD_HEAD_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(pn,name,"HoldHeadAnimationIsNoteColor") -#define HOLD_TOPCAP_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(pn,name,"HoldTopCapAnimationIsNoteColor") -#define HOLD_BODY_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(pn,name,"HoldBodyAnimationIsNoteColor") -#define HOLD_BOTTOMCAP_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(pn,name,"HoldBottomCapAnimationIsNoteColor") -#define HOLD_TAIL_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(pn,name,"HoldTailAnimationIsNoteColor") -#define HOLD_HEAD_IS_ABOVE_WAVY_PARTS NOTESKIN->GetMetricB(pn,name,"HoldHeadIsAboveWavyParts") -#define HOLD_TAIL_IS_ABOVE_WAVY_PARTS NOTESKIN->GetMetricB(pn,name,"HoldTailIsAboveWavyParts") -#define START_DRAWING_HOLD_BODY_OFFSET_FROM_HEAD NOTESKIN->GetMetricI(pn,name,"StartDrawingHoldBodyOffsetFromHead") -#define STOP_DRAWING_HOLD_BODY_OFFSET_FROM_TAIL NOTESKIN->GetMetricI(pn,name,"StopDrawingHoldBodyOffsetFromTail") -#define HOLD_NG_GRAY_PERCENT NOTESKIN->GetMetricF(pn,name,"HoldNGGrayPercent") -#define USE_LIGHTING NOTESKIN->GetMetricB(pn,name,"UseLighting") +#define DRAW_HOLD_HEAD_FOR_TAPS_ON_SAME_ROW NOTESKIN->GetMetricB(skin,name,"DrawHoldHeadForTapsOnSameRow") +#define TAP_NOTE_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"TapNoteAnimationLengthInBeats") +#define TAP_ADDITION_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"TapAdditionAnimationLengthInBeats") +#define TAP_MINE_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"TapMineAnimationLengthInBeats") +#define HOLD_HEAD_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"HoldHeadAnimationLengthInBeats") +#define HOLD_TOPCAP_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"HoldTopCapAnimationLengthInBeats") +#define HOLD_BODY_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"HoldBodyAnimationLengthInBeats") +#define HOLD_BOTTOMCAP_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"HoldBottomCapAnimationLengthInBeats") +#define HOLD_TAIL_ANIMATION_LENGTH_IN_BEATS NOTESKIN->GetMetricF(skin,name,"HoldTailAnimationLengthInBeats") +#define TAP_NOTE_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"TapNoteAnimationIsVivid") +#define TAP_ADDITION_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"TapAdditionAnimationIsVivid") +#define TAP_MINE_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"TapMineAnimationIsVivid") +#define HOLD_HEAD_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"HoldHeadAnimationIsVivid") +#define HOLD_TOPCAP_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"HoldTopCapAnimationIsVivid") +#define HOLD_BODY_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"HoldBodyAnimationIsVivid") +#define HOLD_BOTTOMCAP_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"HoldBottomCapAnimationIsVivid") +#define HOLD_TAIL_ANIMATION_IS_VIVID NOTESKIN->GetMetricB(skin,name,"HoldTailAnimationIsVivid") +#define TAP_NOTE_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(skin,name,"TapNoteAnimationIsNoteColor") +#define HOLD_HEAD_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(skin,name,"HoldHeadAnimationIsNoteColor") +#define HOLD_TOPCAP_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(skin,name,"HoldTopCapAnimationIsNoteColor") +#define HOLD_BODY_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(skin,name,"HoldBodyAnimationIsNoteColor") +#define HOLD_BOTTOMCAP_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(skin,name,"HoldBottomCapAnimationIsNoteColor") +#define HOLD_TAIL_ANIMATION_IS_NOTE_COLOR NOTESKIN->GetMetricB(skin,name,"HoldTailAnimationIsNoteColor") +#define HOLD_HEAD_IS_ABOVE_WAVY_PARTS NOTESKIN->GetMetricB(skin,name,"HoldHeadIsAboveWavyParts") +#define HOLD_TAIL_IS_ABOVE_WAVY_PARTS NOTESKIN->GetMetricB(skin,name,"HoldTailIsAboveWavyParts") +#define START_DRAWING_HOLD_BODY_OFFSET_FROM_HEAD NOTESKIN->GetMetricI(skin,name,"StartDrawingHoldBodyOffsetFromHead") +#define STOP_DRAWING_HOLD_BODY_OFFSET_FROM_TAIL NOTESKIN->GetMetricI(skin,name,"StopDrawingHoldBodyOffsetFromTail") +#define HOLD_NG_GRAY_PERCENT NOTESKIN->GetMetricF(skin,name,"HoldNGGrayPercent") +#define USE_LIGHTING NOTESKIN->GetMetricB(skin,name,"UseLighting") // cache struct NoteMetricCache_t { @@ -105,10 +105,10 @@ struct NoteMetricCache_t { float m_fHoldNGGrayPercent; bool m_bUseLighting; - void Load(PlayerNumber pn, const CString &name); + void Load(CString skin, const CString &name); } *NoteMetricCache; -void NoteMetricCache_t::Load(PlayerNumber pn, const CString &name) +void NoteMetricCache_t::Load(CString skin, const CString &name) { m_bDrawHoldHeadForTapsOnSameRow = DRAW_HOLD_HEAD_FOR_TAPS_ON_SAME_ROW; m_fTapNoteAnimationLengthInBeats = TAP_NOTE_ANIMATION_LENGTH_IN_BEATS; @@ -169,7 +169,7 @@ NoteDisplay::~NoteDisplay() delete cache; } -void NoteDisplay::Load( int iColNum, PlayerNumber pn, float fYReverseOffsetPixels ) +void NoteDisplay::Load( int iColNum, PlayerNumber pn, CString NoteSkin, float fYReverseOffsetPixels ) { m_PlayerNumber = pn; m_fYReverseOffsetPixels = fYReverseOffsetPixels; @@ -179,7 +179,7 @@ void NoteDisplay::Load( int iColNum, PlayerNumber pn, float fYReverseOffsetPixel if(Button == "") Button = NoteSkinManager::ColToButtonName(iColNum); - cache->Load( pn, Button ); + cache->Load( NoteSkin, Button ); // Look up note names once and store them here. CString sNoteType[ NOTE_COLOR_IMAGES ]; @@ -190,85 +190,85 @@ void NoteDisplay::Load( int iColNum, PlayerNumber pn, float fYReverseOffsetPixel if( cache->m_bTapNoteAnimationIsNoteColor ) { for( int i=0; iGetPathTo(pn, Button, "tap note "+sNoteType[i]) ); + m_pTapNote[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "tap note "+sNoteType[i]) ); } else { - m_pTapNote[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "tap note") ); + m_pTapNote[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "tap note") ); } - m_pTapAddition = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "tap addition") ); + m_pTapAddition = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "tap addition") ); - m_pTapMine = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "tap mine") ); + m_pTapMine = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "tap mine") ); if( cache->m_bHoldHeadAnimationIsNoteColor ) { for( int i=0; iGetPathTo(pn, Button, "hold head active "+sNoteType[i]) ); - m_pHoldHeadInactive[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "hold head inactive "+sNoteType[i]) ); + m_pHoldHeadActive[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold head active "+sNoteType[i]) ); + m_pHoldHeadInactive[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold head inactive "+sNoteType[i]) ); } } else { - m_pHoldHeadActive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "hold head active") ); - m_pHoldHeadInactive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "hold head inactive") ); + m_pHoldHeadActive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold head active") ); + m_pHoldHeadInactive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold head inactive") ); } if( cache->m_bHoldTopCapAnimationIsNoteColor ) { for( int i=0; iGetPathTo(pn, Button, "hold topcap active "+sNoteType[i]) ); - m_sprHoldTopCapInactive[i].Load( NOTESKIN->GetPathTo(pn, Button, "hold topcap inactive "+sNoteType[i]) ); + m_sprHoldTopCapActive[i].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold topcap active "+sNoteType[i]) ); + m_sprHoldTopCapInactive[i].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold topcap inactive "+sNoteType[i]) ); } } else { - m_sprHoldTopCapActive[0].Load( NOTESKIN->GetPathTo(pn, Button, "hold topcap active") ); - m_sprHoldTopCapInactive[0].Load( NOTESKIN->GetPathTo(pn, Button, "hold topcap inactive") ); + m_sprHoldTopCapActive[0].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold topcap active") ); + m_sprHoldTopCapInactive[0].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold topcap inactive") ); } if( cache->m_bHoldBodyAnimationIsNoteColor ) { for( int i=0; iGetPathTo(pn, Button, "hold body active "+sNoteType[i]) ); - m_sprHoldBodyInactive[i].Load( NOTESKIN->GetPathTo(pn, Button, "hold body inactive "+sNoteType[i]) ); + m_sprHoldBodyActive[i].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold body active "+sNoteType[i]) ); + m_sprHoldBodyInactive[i].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold body inactive "+sNoteType[i]) ); } } else { - m_sprHoldBodyActive[0].Load( NOTESKIN->GetPathTo(pn, Button, "hold body active") ); - m_sprHoldBodyInactive[0].Load( NOTESKIN->GetPathTo(pn, Button, "hold body inactive") ); + m_sprHoldBodyActive[0].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold body active") ); + m_sprHoldBodyInactive[0].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold body inactive") ); } if( cache->m_bHoldBottomCapAnimationIsNoteColor ) { for( int i=0; iGetPathTo(pn, Button, "hold bottomcap active "+sNoteType[i]) ); - m_sprHoldBottomCapInactive[i].Load( NOTESKIN->GetPathTo(pn, Button, "hold bottomcap inactive "+sNoteType[i]) ); + m_sprHoldBottomCapActive[i].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold bottomcap active "+sNoteType[i]) ); + m_sprHoldBottomCapInactive[i].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold bottomcap inactive "+sNoteType[i]) ); } } else { - m_sprHoldBottomCapActive[0].Load( NOTESKIN->GetPathTo(pn, Button, "hold bottomcap active") ); - m_sprHoldBottomCapInactive[0].Load( NOTESKIN->GetPathTo(pn, Button, "hold bottomcap inactive") ); + m_sprHoldBottomCapActive[0].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold bottomcap active") ); + m_sprHoldBottomCapInactive[0].Load( NOTESKIN->GetPathTo(NoteSkin, Button, "hold bottomcap inactive") ); } if( cache->m_bHoldTailAnimationIsNoteColor ) { for( int i=0; iGetPathTo(pn, Button, "hold tail active "+sNoteType[i]) ); - m_pHoldTailInactive[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "hold tail inactive "+sNoteType[i]) ); + m_pHoldTailActive[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold tail active "+sNoteType[i]) ); + m_pHoldTailInactive[i] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold tail inactive "+sNoteType[i]) ); } } else { - m_pHoldTailActive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "hold tail active") ); - m_pHoldTailInactive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(pn, Button, "hold tail inactive") ); + m_pHoldTailActive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold tail active") ); + m_pHoldTailInactive[0] = MakeModelOrSprite( NOTESKIN->GetPathTo(NoteSkin, Button, "hold tail inactive") ); } } diff --git a/stepmania/src/NoteDisplay.h b/stepmania/src/NoteDisplay.h index ddecf2b13d..ee8abf06ee 100644 --- a/stepmania/src/NoteDisplay.h +++ b/stepmania/src/NoteDisplay.h @@ -27,7 +27,7 @@ public: NoteDisplay(); ~NoteDisplay(); - void Load( int iColNum, PlayerNumber pn, float fYReverseOffsetPixels ); + void Load( int iColNum, PlayerNumber pn, CString NoteSkin, float fYReverseOffsetPixels ); void DrawTap( int iCol, float fBeat, bool bOnSameRowAsHoldStart, bool bIsAddition, bool bIsMine, float fPercentFadeToFail, float fLife, float fReverseOffsetPixels ); void DrawHold( const HoldNote& hn, bool bActive, float fLife, float fPercentFadeToFail, bool bDrawGlowOnly, float fReverseOffsetPixels ); diff --git a/stepmania/src/NoteField.cpp b/stepmania/src/NoteField.cpp index 43275f99c0..0118434e31 100644 --- a/stepmania/src/NoteField.cpp +++ b/stepmania/src/NoteField.cpp @@ -43,15 +43,42 @@ NoteField::NoteField() m_fPercentFadeToFail = -1; } +NoteField::~NoteField() +{ + Unload(); +} + +void NoteField::Unload() +{ + for( map::iterator it = m_NoteDisplays.begin(); + it != m_NoteDisplays.end(); ++it ) + delete it->second; + m_NoteDisplays.clear(); +} + +void NoteField::CacheNoteSkin( CString skin ) +{ + if( m_NoteDisplays.find(skin) != m_NoteDisplays.end() ) + return; + + LOG->Trace("NoteField::CacheNoteSkin: cache %s", skin.c_str() ); + NoteDisplayCols *nd = new NoteDisplayCols; + for( int c=0; cdisplay[c].Load( c, m_PlayerNumber, skin, m_fYReverseOffsetPixels ); + m_NoteDisplays[ skin ] = nd; +} void NoteField::Load( NoteData* pNoteData, PlayerNumber pn, int iFirstPixelToDraw, int iLastPixelToDraw, float fYReverseOffsetPixels ) { + Unload(); + m_PlayerNumber = pn; m_iStartDrawingPixel = iFirstPixelToDraw; m_iEndDrawingPixel = iLastPixelToDraw; m_fYReverseOffsetPixels = fYReverseOffsetPixels; m_fPercentFadeToFail = -1; + m_LastSeenBeatToNoteSkinRev = -1; NoteDataWithScoring::Init(); @@ -59,19 +86,46 @@ void NoteField::Load( NoteData* pNoteData, PlayerNumber pn, int iFirstPixelToDra m_bIsHoldingHoldNote.insert(m_bIsHoldingHoldNote.end(), pNoteData->GetNumTapNotes(), false); this->CopyAll( pNoteData ); - - // init note displays - for( int c=0; cGetCurrentStyleDef()->m_iColsPerPlayer ); + + /* Cache the default note skin. It's up to other code to cache any other skins + * that might be used, such as ones assigned as attacks in battle modes. */ + LOG->Trace("cache '%s'", GAMESTATE->m_PlayerOptions[pn].m_sNoteSkin.c_str()); + CacheNoteSkin( GAMESTATE->m_PlayerOptions[pn].m_sNoteSkin ); + + RefreshBeatToNoteSkin(); } -void NoteField::ReloadNoteSkin() + +void NoteField::RefreshBeatToNoteSkin() { - // init note displays - for( int c=0; cm_BeatToNoteSkinRev == m_LastSeenBeatToNoteSkinRev ) + return; + m_LastSeenBeatToNoteSkinRev = GAMESTATE->m_BeatToNoteSkinRev; + + /* Set by GameState::ResetNoteSkins(): */ + ASSERT( !GAMESTATE->m_BeatToNoteSkin[m_PlayerNumber].empty() ); + + m_BeatToNoteDisplays.clear(); + + /* GAMESTATE->m_BeatToNoteSkin[pn] maps from song beats to note skins. Maintain + * m_BeatToNoteDisplays, to map from song beats to NoteDisplay*s, so we don't + * have to do it while rendering. */ + map::iterator it; + for( it = GAMESTATE->m_BeatToNoteSkin[m_PlayerNumber].begin(); + it != GAMESTATE->m_BeatToNoteSkin[m_PlayerNumber].end(); ++it ) + { + const float Beat = it->first; + const CString &Skin = it->second; + + map::iterator display = m_NoteDisplays.find( Skin ); + + /* If this happens, we tried to use a note skin that wasn't pre-cached. */ + RAGE_ASSERT_M( display != m_NoteDisplays.end(), ssprintf("Couldn't find %s", Skin.c_str()) ); + + NoteDisplayCols *cols = display->second; + m_BeatToNoteDisplays[Beat] = cols; + } } void NoteField::Update( float fDeltaTime ) @@ -82,6 +136,8 @@ void NoteField::Update( float fDeltaTime ) if( m_fPercentFadeToFail >= 0 ) m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/1.5f, 1 ); // take 1.5 seconds to totally fade + + RefreshBeatToNoteSkin(); } float NoteField::GetWidth() @@ -215,11 +271,62 @@ void NoteField::DrawBGChangeText( const float fBeat, const CString sNewBGName ) m_textMeasureNumber.Draw(); } +/* cur is an iterator within m_NoteDisplays. next is ++cur. Advance cur to point to the + * block that contains beat. */ +void NoteField::SearchForBeat( NDMap::iterator &cur, NDMap::iterator &next, float Beat ) +{ + while( next != m_BeatToNoteDisplays.end() && next->first < Beat ) + { + cur = next; + ++next; + } +} + + +// CPU OPTIMIZATION OPPORTUNITY: +// change this probing to binary search +float FindFirstDisplayedBeat( PlayerNumber pn, int iFirstPixelToDraw ) +{ + float fFirstBeatToDraw = GAMESTATE->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes. + + while( fFirstBeatToDraw < GAMESTATE->m_fSongBeat ) + { + float fYOffset = ArrowGetYOffset(pn, 0, fFirstBeatToDraw); + float fYPosWOReverse = ArrowGetYPosWithoutReverse(pn, 0, fYOffset ); + if( fYPosWOReverse < iFirstPixelToDraw ) // off screen + fFirstBeatToDraw += 0.1f; // move toward fSongBeat + else // on screen + break; // stop probing + } + fFirstBeatToDraw -= 0.1f; // rewind if we intentionally overshot + return fFirstBeatToDraw; +} + +float FindLastDisplayedBeat( PlayerNumber pn, int iLastPixelToDraw ) +{ + // probe for last note to draw + float fLastBeatToDraw = GAMESTATE->m_fSongBeat+20; // worst case is 0.25x + boost. Adjust to balance of performance and showing enough notes. + while( fLastBeatToDraw > GAMESTATE->m_fSongBeat ) + { + float fYOffset = ArrowGetYOffset( pn, 0, fLastBeatToDraw ); + float fYPosWOReverse = ArrowGetYPosWithoutReverse( pn, 0, fYOffset ); + if( fYPosWOReverse > iLastPixelToDraw ) // off screen + fLastBeatToDraw -= 0.1f; // move toward fSongBeat + else // on screen + break; // stop probing + } + fLastBeatToDraw += 0.1f; // fast forward since we intentionally overshot + return fLastBeatToDraw; +} + +float g_fNoteFieldLastBeatToDraw = -1; + void NoteField::DrawPrimitives() { //LOG->Trace( "NoteField::DrawPrimitives()" ); - const float fSongBeat = GAMESTATE->m_fSongBeat; + /* This should be filled in on the first update. */ + ASSERT( !m_BeatToNoteDisplays.empty() ); // // Adjust draw range depending on some effects @@ -238,35 +345,12 @@ void NoteField::DrawPrimitives() iLastPixelToDraw = (int)(iLastPixelToDraw * fLastDrawScale * fDrawScale); - // CPU OPTIMIZATION OPPORTUNITY: - // change this probing to binary search - - // probe for first note on the screen - float fFirstBeatToDraw = fSongBeat-4; // Adjust to balance of performance and showing enough notes. - while( fFirstBeatToDrawfSongBeat ) - { - float fYOffset = ArrowGetYOffset(m_PlayerNumber, 0, fLastBeatToDraw); - float fYPosWOReverse = ArrowGetYPosWithoutReverse(m_PlayerNumber, 0, fYOffset ); - if( fYPosWOReverse > iLastPixelToDraw ) // off screen - fLastBeatToDraw -= 0.1f; // move toward fSongBeat - else // on screen - break; // stop probing - } - fLastBeatToDraw += 0.1f; // fast forward since we intentionally overshot + // probe for first and last notes on the screen + float fFirstBeatToDraw = FindFirstDisplayedBeat( m_PlayerNumber, iFirstPixelToDraw ); + float fLastBeatToDraw = FindLastDisplayedBeat( m_PlayerNumber, iLastPixelToDraw ); + /* Hack: */ + g_fNoteFieldLastBeatToDraw = fLastBeatToDraw; const int iFirstIndexToDraw = BeatToNoteRow(fFirstBeatToDraw); const int iLastIndexToDraw = BeatToNoteRow(fLastBeatToDraw); @@ -365,6 +449,10 @@ void NoteField::DrawPrimitives() // Draw all HoldNotes in this column (so that they appear under the tap notes) ///////////////////////////////// int i; + + NDMap::iterator CurDisplay = m_BeatToNoteDisplays.begin(); + ASSERT( CurDisplay != m_BeatToNoteDisplays.end() ); + NDMap::iterator NextDisplay = CurDisplay; ++NextDisplay; for( i=0; i < GetNumHoldNotes(); i++ ) { const HoldNote &hn = GetHoldNote(i); @@ -387,11 +475,15 @@ void NoteField::DrawPrimitives() continue; // skip } + SearchForBeat( CurDisplay, NextDisplay, hn.fStartBeat ); + bool bIsInSelectionRange = false; if( m_fBeginMarker!=-1 && m_fEndMarker!=-1 ) bIsInSelectionRange = m_fBeginMarker<=hn.fStartBeat && hn.fStartBeat<=m_fEndMarker && m_fBeginMarker<=hn.fEndBeat && hn.fEndBeat<=m_fEndMarker; - m_NoteDisplay[c].DrawHold( hn, bIsHoldingNote, fLife, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, false, m_fYReverseOffsetPixels ); + NoteDisplayCols *nd = CurDisplay->second; + + nd->display[c].DrawHold( hn, bIsHoldingNote, fLife, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, false, m_fYReverseOffsetPixels ); } @@ -409,6 +501,8 @@ void NoteField::DrawPrimitives() increment = -1; } */ + CurDisplay = m_BeatToNoteDisplays.begin(); + NextDisplay = CurDisplay; ++NextDisplay; for( i=first; i <= last; i+=increment ) // for each row { TapNote tn = GetTapNote(c, i); @@ -439,7 +533,10 @@ void NoteField::DrawPrimitives() bool bIsAddition = (tn == TAP_ADDITION); bool bIsMine = (tn == TAP_MINE); - m_NoteDisplay[c].DrawTap( c, NoteRowToBeat(i), bHoldNoteBeginsOnThisBeat, bIsAddition, bIsMine, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, 1, m_fYReverseOffsetPixels ); + SearchForBeat( CurDisplay, NextDisplay, NoteRowToBeat(i) ); + + NoteDisplayCols *nd = CurDisplay->second; + nd->display[c].DrawTap( c, NoteRowToBeat(i), bHoldNoteBeginsOnThisBeat, bIsAddition, bIsMine, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, 1, m_fYReverseOffsetPixels ); } g_NoteFieldMode[m_PlayerNumber].EndDrawTrack(c); diff --git a/stepmania/src/NoteField.h b/stepmania/src/NoteField.h index 818c8f10e9..682eff39e5 100644 --- a/stepmania/src/NoteField.h +++ b/stepmania/src/NoteField.h @@ -29,10 +29,12 @@ class NoteField : public NoteDataWithScoring, public ActorFrame { public: NoteField(); + ~NoteField(); virtual void Update( float fDeltaTime ); virtual void DrawPrimitives(); virtual void Load( NoteData* pNoteData, PlayerNumber pn, int iStartDrawingPixel, int iEndDrawingPixel, float fYReverseOffsetPixels ); + virtual void Unload(); void RemoveTapNoteRow( int iIndex ); vector m_bIsHoldingHoldNote; // hack: Need this to know when to "light up" the center of hold notes @@ -40,7 +42,7 @@ public: float m_fBeginMarker, m_fEndMarker; // only used with MODE_EDIT void FadeToFail(); - void ReloadNoteSkin(); + void CacheNoteSkin( CString skin ); protected: @@ -52,6 +54,8 @@ protected: void DrawBGChangeText( const float fBeat, const CString sNewBGName ); float GetWidth(); + void RefreshBeatToNoteSkin(); + float m_fPercentFadeToFail; // -1 of not fading to fail PlayerNumber m_PlayerNumber; @@ -60,8 +64,20 @@ protected: float m_fYReverseOffsetPixels; // color arrows - NoteDisplay m_NoteDisplay[MAX_NOTE_TRACKS]; - + struct NoteDisplayCols + { + NoteDisplay display[MAX_NOTE_TRACKS]; + }; + /* All loaded note displays, mapped by their name. */ + map m_NoteDisplays; + + int m_LastSeenBeatToNoteSkinRev; + + /* Map of beat->NoteDisplayCols. This is updated whenever GAMESTATE-> changes. */ + typedef map NDMap; + void SearchForBeat( NDMap::iterator &cur, NDMap::iterator &next, float Beat ); + NDMap m_BeatToNoteDisplays; + // used in MODE_EDIT Sprite m_sprBars; // 4 frames: Measure, 4th, 8th, 16th BitmapText m_textMeasureNumber; diff --git a/stepmania/src/Player.cpp b/stepmania/src/Player.cpp index 1e86e579c3..88896e7e95 100644 --- a/stepmania/src/Player.cpp +++ b/stepmania/src/Player.cpp @@ -90,6 +90,8 @@ PlayerMinus::~PlayerMinus() void PlayerMinus::Load( PlayerNumber pn, NoteData* pNoteData, LifeMeter* pLM, CombinedLifeMeter* pCombinedLM, ScoreDisplay* pScore, Inventory* pInventory, ScoreKeeper* pPrimaryScoreKeeper, ScoreKeeper* pSecondaryScoreKeeper, NoteFieldPlus* pNoteField ) { + GAMESTATE->ResetNoteSkins(); + //LOG->Trace( "PlayerMinus::Load()", ); m_PlayerNumber = pn; @@ -142,6 +144,25 @@ void PlayerMinus::Load( PlayerNumber pn, NoteData* pNoteData, LifeMeter* pLM, Co m_pNoteField->Load( (NoteData*)this, pn, iStartDrawingAtPixels, iStopDrawingAtPixels, GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD ); m_ArrowBackdrop.SetPlayer( pn ); + /* Cache note skins that are used as attacks. */ + switch( GAMESTATE->m_PlayMode ) + { + case PLAY_MODE_BATTLE: + case PLAY_MODE_RAVE: + for( int al=0; alm_pCurCharacters[m_PlayerNumber]; + CString* asAttacks = ch->m_sAttacks[al]; + for( int att = 0; att < NUM_ATTACKS_PER_LEVEL; ++att ) + { + PlayerOptions po; + po.FromString( asAttacks[att] ); + if( po.m_sNoteSkin != "" ) + m_pNoteField->CacheNoteSkin( po.m_sNoteSkin ); + } + } + } + const bool bReverse = GAMESTATE->m_PlayerOptions[pn].GetReversePercentForColumn(0) == 1; bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyleDef()->m_StyleType==StyleDef::ONE_PLAYER_TWO_CREDITS; m_Combo.SetX( COMBO_X(m_PlayerNumber,bPlayerUsingBothSides) ); @@ -166,7 +187,6 @@ void PlayerMinus::Load( PlayerNumber pn, NoteData* pNoteData, LifeMeter* pLM, Co // Need to set Y positions of all these elements in Update since // they change depending on PlayerOptions. - m_sLastSeenNoteSkin = GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_sNoteSkin; m_soundMineExplosion.Load( THEME->GetPathToS("Player explosion") ); } @@ -317,14 +337,6 @@ void PlayerMinus::Update( float fDeltaTime ) } - // reload noteskin if it has changed - if( m_sLastSeenNoteSkin != GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_sNoteSkin ) - { - m_pNoteField->ReloadNoteSkin(); - m_sLastSeenNoteSkin = GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_sNoteSkin; - } - - // process transforms that are waiting to be applied for( unsigned j=0; jm_TransformsToApply[m_PlayerNumber].size(); j++ ) { diff --git a/stepmania/src/Player.h b/stepmania/src/Player.h index eb2124f980..0eed8947f5 100644 --- a/stepmania/src/Player.h +++ b/stepmania/src/Player.h @@ -86,7 +86,6 @@ protected: ScoreKeeper* m_pSecondaryScoreKeeper; Inventory* m_pInventory; - CString m_sLastSeenNoteSkin; int m_iRowLastCrossed; RageSound m_soundMineExplosion; diff --git a/stepmania/src/ScoreKeeperRave.cpp b/stepmania/src/ScoreKeeperRave.cpp index a78c38cef5..2bef60175b 100644 --- a/stepmania/src/ScoreKeeperRave.cpp +++ b/stepmania/src/ScoreKeeperRave.cpp @@ -86,6 +86,7 @@ void ScoreKeeperRave::LaunchAttack( AttackLevel al ) sAttackToGive = asAttacks[ rand()%NUM_ATTACKS_PER_LEVEL ]; else { + /* If you add any note skins here, you need to make sure they're cached, too. */ CString DefaultAttacks[8] = { "1.5x", "2.0x", "0.5x", "reverse", "sudden", "boost", "brake", "wave" }; sAttackToGive = DefaultAttacks[ rand()%8 ]; } diff --git a/stepmania/src/ScreenEdit.cpp b/stepmania/src/ScreenEdit.cpp index 61d39ed3db..e802db1729 100644 --- a/stepmania/src/ScreenEdit.cpp +++ b/stepmania/src/ScreenEdit.cpp @@ -243,6 +243,7 @@ ScreenEdit::ScreenEdit() : Screen("ScreenEdit") /* Not all games have a noteskin named "note" ... */ if( NOTESKIN->DoesNoteSkinExist("note") ) GAMESTATE->m_PlayerOptions[PLAYER_1].m_sNoteSkin = "note"; // change noteskin before loading all of the edit Actors + GAMESTATE->ResetNoteSkins(); m_BGAnimation.LoadFromAniDir( THEME->GetPathToB("ScreenEdit background") ); @@ -274,6 +275,7 @@ ScreenEdit::ScreenEdit() : Screen("ScreenEdit") GAMESTATE->m_PlayerOptions[PLAYER_1].m_sNoteSkin = "default"; // change noteskin back to default before loading player + GAMESTATE->ResetNoteSkins(); m_Player.Load( PLAYER_1, ¬eData, NULL, NULL, NULL, NULL, NULL, NULL ); GAMESTATE->m_PlayerController[PLAYER_1] = PC_HUMAN; @@ -1571,6 +1573,9 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) m_EditMode = MODE_PLAYING; + /* Reset the note skin, in case preferences have changed. */ + GAMESTATE->ResetNoteSkins(); + /* Give a 1 measure lead-in. Set this before loading Player, so it knows * where we're starting. */ GAMESTATE->m_fSongBeat = m_NoteFieldEdit.m_fBeginMarker - 4; @@ -1597,6 +1602,9 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) m_EditMode = MODE_RECORDING; + /* Reset the note skin, in case preferences have changed. */ + GAMESTATE->ResetNoteSkins(); + // initialize m_NoteFieldRecord m_NoteFieldRecord.Load( &m_NoteFieldEdit, PLAYER_1, -150, 350, 350 ); m_NoteFieldRecord.SetNumTracks( m_NoteFieldEdit.GetNumTracks() );