diff --git a/Themes/_fallback/Scripts/03 CustomSpeedMods.lua b/Themes/_fallback/Scripts/03 CustomSpeedMods.lua index 48e23bd5ed..5d9ed9cabc 100644 --- a/Themes/_fallback/Scripts/03 CustomSpeedMods.lua +++ b/Themes/_fallback/Scripts/03 CustomSpeedMods.lua @@ -68,7 +68,7 @@ local function ParseSpeedModFile(path) return mods else -- error; write a fallback mod file and return it - local fallbackString = "0.5x,1x,1.5x,2x,3x,4x,8x,C200,C400" + local fallbackString = "0.5x,1x,1.5x,2x,3x,4x,8x,C200,C400,m550" Trace("[CustomSpeedMods]: Could not read SpeedMods; writing fallback to "..path) file:Open(path, 2) file:Write(fallbackString) @@ -148,6 +148,7 @@ end local function SpeedModSort(tab) local xMods = {} + local mMods = {} local cMods = {} --local mMods = {} @@ -159,10 +160,9 @@ local function SpeedModSort(tab) if string.find(tab[i],"C%d") then typ = cMods val = string.gsub(tab[i], "C", "") - elseif string.find(tab[i],"M%d") then - Trace("[CustomSpeedMods] OpenITG's M-Mods are not supported yet in sm-ssc.") - --typ = mMods - --val = string.gsub(tab[i], "M", "") + elseif string.find(tab[i],"m%d") then + typ = mMods + val = string.gsub(tab[i], "m", "") else typ = xMods val = string.gsub(tab[i], "x", "") @@ -175,7 +175,7 @@ local function SpeedModSort(tab) -- sort cMods cMods = AnonSort(cMods) -- sort mMods - --mMods = AnonSort(mMods) + mMods = AnonSort(mMods) local fin = {} -- convert it back to a string since that's what it expects for i=1,#xMods do @@ -184,6 +184,9 @@ local function SpeedModSort(tab) for i=1,#cMods do table.insert(fin, "C"..cMods[i]) end + for i=1,#mMods do + table.insert(fin, "m"..mMods[i]) + end --for i=1,#mMods do table.insert(fin, "M"..mMods[i]); end; return fin end diff --git a/src/Player.cpp b/src/Player.cpp index 98c19b8d30..3d58835515 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -400,7 +400,63 @@ void Player::Init( m_soundAttackEnding.Load( THEME->GetPathS(sType,"course attack ending"), true, &SoundParams ); break; } +// XXX: Fix errors for m-mod support! +/* + // calculate M-mod speed here, so we can adjust properly on a per-song basis. + // XXX: can we find a better location for this? + if( m_pPlayerState->m_PlayerOptions.m_fMaxScrollBPM != 0 ) + { + DisplayBpms bpms; + if( GAMESTATE->IsCourseMode() ) + { + ASSERT( GAMESTATE->m_pCurTrail[pn] ); + GAMESTATE->m_pCurTrail[pn]->GetDisplayBpms( bpms ); + } + else + { + ASSERT( GAMESTATE->m_pCurSong ); + GAMESTATE->m_pCurSong->GetDisplayBpms( bpms ); + } + + float fMaxBPM = 0; + + // all BPMs are listed and available, so try them first. + // get the maximum listed value for the song or course. + // if the BPMs are < 0, reset and get the actual values. + if( !bpms.IsSecret() ) + { + fMaxBPM = bpms.GetMax(); + fMaxBPM = max( 0, fMaxBPM ); + } + + // we can't rely on the displayed BPMs, so manually calculate. + if( fMaxBPM == 0 ) + { + float fThrowAway = 0; + + if( GAMESTATE->IsCourseMode() ) + { + FOREACH_CONST( TrailEntry, GAMESTATE->m_pCurTrail[pn]->m_vEntries, e ) + { + float fMaxForEntry; + e->pSong->m_Timing.GetActualBPM( fThrowAway, fMaxForEntry ); + fMaxBPM = max( fMaxForEntry, fMaxBPM ); + } + } + else + { + GAMESTATE->m_pCurSong->m_Timing.GetActualBPM( fThrowAway, fMaxBPM ); + } + } + + ASSERT( fMaxBPM > 0 ); + + // set an X-mod equal to Mnum / fMaxBPM (e.g. M600 with 150 becomes 4x) + m_pPlayerState->m_StoredPlayerOptions.m_fScrollSpeed = + ( m_pPlayerState->m_StoredPlayerOptions.m_fMaxScrollBPM / fMaxBPM ); + } +*/ float fBalance = GameSoundManager::GetPlayerBalance( pn ); m_soundMine.SetProperty( "Pan", fBalance ); diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index 5e4aa5666d..3f577a17d3 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -26,6 +26,7 @@ ThemeMetric RANDOM_SUDDEN_CHANCE ( "PlayerOptions", "RandomSuddenChance" void PlayerOptions::Init() { m_bSetScrollSpeed = false; + m_fMaxScrollBPM = 0; m_SpeedfMaxScrollBPM = 1.0f; m_fTimeSpacing = 0; m_SpeedfTimeSpacing = 1.0f; m_fScrollSpeed = 1.0f; m_SpeedfScrollSpeed = 1.0f; m_fScrollBPM = 200; m_SpeedfScrollBPM = 1.0f; @@ -60,6 +61,7 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds ) APPROACH( fTimeSpacing ); APPROACH( fScrollSpeed ); + APPROACH( fMaxScrollBPM ); fapproach( m_fScrollBPM, other.m_fScrollBPM, fDeltaSeconds * other.m_SpeedfScrollBPM*150 ); for( int i=0; i &AddTo, bool bForceNoteSkin ) const if( !m_fTimeSpacing ) { + if( m_fMaxScrollBPM ) + { + RString s = ssprintf( "m%.0f", m_fMaxScrollBPM ); + AddTo.push_back( s ); + } if( m_bSetScrollSpeed || m_fScrollSpeed != 1 ) { /* -> 1.00 */ @@ -332,6 +339,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut SET_FLOAT( fScrollSpeed ) SET_FLOAT( fTimeSpacing ) m_fTimeSpacing = 0; + m_fMaxScrollBPM = 0; } else if( sscanf( sBit, "c%f", &level ) == 1 ) { @@ -340,17 +348,18 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut SET_FLOAT( fScrollBPM ) SET_FLOAT( fTimeSpacing ) m_fTimeSpacing = 1; + m_fMaxScrollBPM = 0; } - /* Port M-Mods from OpenITG, starting from r537 */ - // Midiman + // oITG's m-mods + // XXX: will not properly tween, I don't think. else if( sscanf( sBit, "m%f", &level ) == 1 ) { if( !isfinite(level) || level <= 0.0f ) - level = 200.0f; // Just pick some value. - SET_FLOAT( fScrollBPM ) - SET_FLOAT( fTimeSpacing ) - m_fTimeSpacing = 1; + level = 200.0f; + SET_FLOAT( fMaxScrollBPM ) + m_fTimeSpacing = 0; } + else if( sBit == "clearall" ) Init(); else if( sBit == "boost" ) SET_FLOAT( fAccels[ACCEL_BOOST] ) else if( sBit == "brake" || sBit == "land" ) SET_FLOAT( fAccels[ACCEL_BRAKE] ) @@ -650,6 +659,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const COMPARE(m_fTimeSpacing); COMPARE(m_fScrollSpeed); COMPARE(m_fScrollBPM); + COMPARE(m_fMaxScrollBPM); COMPARE(m_fRandomSpeed); COMPARE(m_FailType); COMPARE(m_bMuteOnError); @@ -717,6 +727,31 @@ bool PlayerOptions::IsEasierForSongAndSteps( Song* pSong, Steps* pSteps, PlayerN return true; if( m_fCover ) return true; + + // M-mods make songs with indefinite BPMs easier because + // they ensure that the song has a scrollable speed. + if( m_fMaxScrollBPM != 0 ) + { + // BPM display is obfuscated +// if( pSong->m_DisplayBPMType == DISPLAY_BPM_RANDOM ) +// return true; + + DisplayBpms bpms; + if( GAMESTATE->IsCourseMode() ) + { + Trail *pTrail = GAMESTATE->m_pCurCourse->GetTrail( GAMESTATE->GetCurrentStyle()->m_StepsType ); + pTrail->GetDisplayBpms( bpms ); + } + else + { + GAMESTATE->m_pCurSong->GetDisplayBpms( bpms ); + } + pSong->GetDisplayBpms( bpms ); + + // maximum BPM is obfuscated, so M-mods will set a playable speed. + if( bpms.GetMax() <= 0 ) + return true; + } if( m_fPlayerAutoPlay ) return true; return false; } @@ -788,6 +823,7 @@ RString PlayerOptions::GetSavedPrefsString() const SAVE( m_fTimeSpacing ); SAVE( m_fScrollSpeed ); SAVE( m_fScrollBPM ); + SAVE( m_fMaxScrollBPM ); SAVE( m_fScrolls[SCROLL_REVERSE] ); SAVE( m_fPerspectiveTilt ); SAVE( m_bTransforms[TRANSFORM_NOHOLDS] ); @@ -816,6 +852,7 @@ void PlayerOptions::ResetPrefs( ResetPrefsType type ) CPY( m_fTimeSpacing ); CPY( m_fScrollSpeed ); CPY( m_fScrollBPM ); + CPY( m_fMaxScrollBPM ); break; case saved_prefs_invalid_for_course: break; diff --git a/src/PlayerOptions.h b/src/PlayerOptions.h index 3b5caacc0f..0166af9c4e 100644 --- a/src/PlayerOptions.h +++ b/src/PlayerOptions.h @@ -157,6 +157,7 @@ public: * PlayerOptions::Approach approaches. */ bool m_bSetScrollSpeed; // true if the scroll speed was set by FromString float m_fTimeSpacing, m_SpeedfTimeSpacing; // instead of Beat spacing (CMods, mMods) + float m_fMaxScrollBPM, m_SpeedfMaxScrollBPM; float m_fScrollSpeed, m_SpeedfScrollSpeed; // used if !m_bTimeSpacing (xMods) float m_fScrollBPM, m_SpeedfScrollBPM; // used if m_bTimeSpacing (CMod) float m_fAccels[NUM_ACCELS], m_SpeedfAccels[NUM_ACCELS];