diff --git a/src/ArrowEffects.cpp b/src/ArrowEffects.cpp index e5938722f4..c7fae1de4c 100644 --- a/src/ArrowEffects.cpp +++ b/src/ArrowEffects.cpp @@ -488,7 +488,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float if( curr_options->m_fTimeSpacing != 0.0f ) { - float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible; + float fSongSeconds = pPlayerState->m_Position.m_fMusicSecondsVisible; float fNoteSeconds = pCurSteps->GetTimingData()->GetElapsedTimeFromBeat(fNoteBeat); float fSecondsUntilStep = fNoteSeconds - fSongSeconds; float fBPM = curr_options->m_fScrollBPM; diff --git a/src/GameState.cpp b/src/GameState.cpp index 51852c9a51..148e8e0562 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -1271,7 +1271,8 @@ void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &ti { if( m_pCurSteps[pn] ) { - m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, *m_pCurSteps[pn]->GetTimingData(), timestamp ); + float fAdditionalVisualDelay = m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_fVisualDelay; + m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, *m_pCurSteps[pn]->GetTimingData(), timestamp, fAdditionalVisualDelay ); Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset ); } } diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index 193dab8abd..1cc9a27456 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -95,6 +95,7 @@ void PlayerOptions::Init() m_bZBuffer = false; m_bCosecant = false; m_sNoteSkin = ""; + m_fVisualDelay = 0.0f; ZERO( m_fMovesX ); ONE( m_SpeedfMovesX ); ZERO( m_fMovesY ); ONE( m_SpeedfMovesY ); ZERO( m_fMovesZ ); ONE( m_SpeedfMovesZ ); @@ -185,6 +186,7 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds ) DO_COPY( m_FailType ); DO_COPY( m_MinTNSToHideNotes ); DO_COPY( m_sNoteSkin ); + DO_COPY( m_fVisualDelay ); #undef APPROACH #undef DO_COPY } @@ -555,6 +557,13 @@ void PlayerOptions::GetMods( vector &AddTo, bool bForceNoteSkin ) const Capitalize( s ); AddTo.push_back( s ); } + + if ( fabsf(m_fVisualDelay) > 0.0001f ) + { + // Format the string to be something like "10ms VisualDelay". + // Note that we don't process sub-millisecond visual delay. + AddTo.push_back( ssprintf("%.0fms VisualDelay", m_fVisualDelay * 1000.0f) ); + } } /* Options are added to the current settings; call Init() beforehand if @@ -601,9 +610,15 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut } else if( isdigit(s[0]) || s[0] == '-' ) { + if ( EndsWith(s, "ms") ) + { + // Strip off the "ms" before parsing and convert to seconds. + RString ms_value = s.substr(0, s.size()-2 ); + level = StringToFloat( ms_value ) / 1000.0f; + } /* If the last character is a *, they probably said "123*" when * they meant "*123". */ - if( s.Right(1) == "*" ) + else if( s.Right(1) == "*" ) { // XXX: We know what they want, is there any reason not to handle it? // Yes. We should be strict in handling the format. -Chris @@ -1126,6 +1141,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut } else if( sBit == "zbuffer" ) m_bZBuffer = on; else if( sBit == "cosecant" ) m_bCosecant = on; + else if( sBit == "visualdelay" ) m_fVisualDelay = level; // deprecated mods/left in for compatibility else if( sBit == "converge" ) SET_FLOAT( fScrolls[SCROLL_CENTERED] ) // end of the list @@ -1378,6 +1394,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const { return false; } + COMPARE(m_fVisualDelay); for( int i = 0; i < PlayerOptions::NUM_ACCELS; ++i ) COMPARE(m_fAccels[i]); for( int i = 0; i < PlayerOptions::NUM_EFFECTS; ++i ) @@ -1443,6 +1460,7 @@ PlayerOptions& PlayerOptions::operator=(PlayerOptions const& other) CPY(m_bDizzyHolds); CPY(m_bZBuffer); CPY(m_bCosecant); + CPY(m_fVisualDelay); CPY_SPEED(fDark); CPY_SPEED(fBlind); CPY_SPEED(fCover); @@ -1674,6 +1692,7 @@ RString PlayerOptions::GetSavedPrefsString() const SAVE( m_bTransforms[TRANSFORM_NOFAKES] ); SAVE( m_bMuteOnError ); SAVE( m_sNoteSkin ); + SAVE( m_fVisualDelay ); #undef SAVE return po_prefs.GetString(); } @@ -1721,6 +1740,7 @@ void PlayerOptions::ResetPrefs( ResetPrefsType type ) CPY( m_bTransforms[TRANSFORM_NOFAKES] ); // Don't clear this. // CPY( m_sNoteSkin ); + CPY(m_fVisualDelay); #undef CPY } @@ -1969,6 +1989,8 @@ public: ENUM_INTERFACE(FailSetting, FailType, FailType); ENUM_INTERFACE(MinTNSToHideNotes, MinTNSToHideNotes, TapNoteScore); + FLOAT_NO_SPEED_INTERFACE(VisualDelay, VisualDelay, true); + // NoteSkins static int NoteSkin(T* p, lua_State* L) { diff --git a/src/PlayerOptions.h b/src/PlayerOptions.h index 78c89da400..b2ea2931bf 100644 --- a/src/PlayerOptions.h +++ b/src/PlayerOptions.h @@ -87,6 +87,7 @@ public: m_MinTNSToHideNotes(PREFSMAN->m_MinTNSToHideNotes) { m_sNoteSkin = ""; + m_fVisualDelay = 0.0f; ZERO( m_fAccels ); ONE( m_SpeedfAccels ); ZERO( m_fEffects ); ONE( m_SpeedfEffects ); ZERO( m_fAppearances ); ONE( m_SpeedfAppearances ); @@ -394,6 +395,9 @@ public: * If an empty string, it means to not change from the default. */ RString m_sNoteSkin; + /** @brief The Visual Delay additionally applied on a per-player basis in ms. */ + float m_fVisualDelay; + void NextAccel(); void NextEffect(); void NextAppearance(); diff --git a/src/SongPosition.cpp b/src/SongPosition.cpp index 2668d0043a..4804bb29da 100644 --- a/src/SongPosition.cpp +++ b/src/SongPosition.cpp @@ -3,7 +3,7 @@ static Preference g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f ); -void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp ) +void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp, float fAdditionalVisualDelay ) { if( !timestamp.IsZero() ) @@ -31,7 +31,7 @@ void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds ); - m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get(); + m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get() - fAdditionalVisualDelay; beat_info.elapsed_time= m_fMusicSecondsVisible; timing.GetBeatAndBPSFromElapsedTime(beat_info); m_fSongBeatVisible= beat_info.beat; diff --git a/src/SongPosition.h b/src/SongPosition.h index 314629b702..fa8f39d57b 100644 --- a/src/SongPosition.h +++ b/src/SongPosition.h @@ -34,7 +34,7 @@ public: float m_fSongBeatVisible; void Reset(); - void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp = RageZeroTimer ); + void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp = RageZeroTimer, float fAdditionalVisualDelay = 0.0f ); // Lua void PushSelf( lua_State *L );