From d6511c103444b479e0e97331dfe3e97483d7c29c Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Thu, 10 Feb 2011 23:35:34 -0500 Subject: [PATCH 01/19] Bring from sandbox to new branch. More work and testing needs to be done, but it shouldn't be too bad. --- Xcode/stepmania.xcodeproj/project.pbxproj | 16 + src/NotesLoaderSSC.cpp | 1154 +++++++++++++++++++++ src/NotesLoaderSSC.h | 59 ++ src/NotesWriterSSC.cpp | 507 +++++++++ src/NotesWriterSSC.h | 41 + 5 files changed, 1777 insertions(+) create mode 100644 src/NotesLoaderSSC.cpp create mode 100644 src/NotesLoaderSSC.h create mode 100644 src/NotesWriterSSC.cpp create mode 100644 src/NotesWriterSSC.h diff --git a/Xcode/stepmania.xcodeproj/project.pbxproj b/Xcode/stepmania.xcodeproj/project.pbxproj index 6a3406d7cf..dedd8ea04a 100644 --- a/Xcode/stepmania.xcodeproj/project.pbxproj +++ b/Xcode/stepmania.xcodeproj/project.pbxproj @@ -23,6 +23,10 @@ /* Begin PBXBuildFile section */ 1215D7A208C6559A00B87D8B /* ScreenGameplayNormal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1215D7A008C6559A00B87D8B /* ScreenGameplayNormal.cpp */; }; + 2BA7925E1304F20D002BD8E1 /* NotesLoaderSSC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BA7925A1304F20D002BD8E1 /* NotesLoaderSSC.cpp */; }; + 2BA7925F1304F20D002BD8E1 /* NotesLoaderSSC.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BA7925B1304F20D002BD8E1 /* NotesLoaderSSC.h */; }; + 2BA792601304F20D002BD8E1 /* NotesWriterSSC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BA7925C1304F20D002BD8E1 /* NotesWriterSSC.cpp */; }; + 2BA792611304F20D002BD8E1 /* NotesWriterSSC.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BA7925D1304F20D002BD8E1 /* NotesWriterSSC.h */; }; 3659B56F0F902143002B6B68 /* CommandLineActions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3659B56D0F902143002B6B68 /* CommandLineActions.cpp */; }; 3659B5700F902143002B6B68 /* CommandLineActions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3659B56E0F902143002B6B68 /* CommandLineActions.h */; }; 367D575B0EDE56F200B46814 /* ScreenHighScores.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 367D57590EDE56F200B46814 /* ScreenHighScores.cpp */; }; @@ -1768,6 +1772,10 @@ 1215D7A108C6559A00B87D8B /* ScreenGameplayNormal.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; name = ScreenGameplayNormal.h; path = ../src/ScreenGameplayNormal.h; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 2BA7925A1304F20D002BD8E1 /* NotesLoaderSSC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NotesLoaderSSC.cpp; path = ../src/NotesLoaderSSC.cpp; sourceTree = SOURCE_ROOT; }; + 2BA7925B1304F20D002BD8E1 /* NotesLoaderSSC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NotesLoaderSSC.h; path = ../src/NotesLoaderSSC.h; sourceTree = SOURCE_ROOT; }; + 2BA7925C1304F20D002BD8E1 /* NotesWriterSSC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NotesWriterSSC.cpp; path = ../src/NotesWriterSSC.cpp; sourceTree = SOURCE_ROOT; }; + 2BA7925D1304F20D002BD8E1 /* NotesWriterSSC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NotesWriterSSC.h; path = ../src/NotesWriterSSC.h; sourceTree = SOURCE_ROOT; }; 3659B56D0F902143002B6B68 /* CommandLineActions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommandLineActions.cpp; sourceTree = ""; }; 3659B56E0F902143002B6B68 /* CommandLineActions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommandLineActions.h; sourceTree = ""; }; 367D57590EDE56F200B46814 /* ScreenHighScores.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScreenHighScores.cpp; path = ../src/ScreenHighScores.cpp; sourceTree = SOURCE_ROOT; }; @@ -3779,10 +3787,14 @@ EE2153A810A75BEE0074B01B /* NotesLoaderPMS.h */, AA6EFDF504770C79005F316C /* NotesLoaderSM.cpp */, AA6EFDF604770C79005F316C /* NotesLoaderSM.h */, + 2BA7925A1304F20D002BD8E1 /* NotesLoaderSSC.cpp */, + 2BA7925B1304F20D002BD8E1 /* NotesLoaderSSC.h */, AA6EFDF704770C79005F316C /* NotesWriterDWI.cpp */, AA6EFDF804770C79005F316C /* NotesWriterDWI.h */, AA6EFDF904770C79005F316C /* NotesWriterSM.cpp */, AA6EFDFA04770C79005F316C /* NotesWriterSM.h */, + 2BA7925C1304F20D002BD8E1 /* NotesWriterSSC.cpp */, + 2BA7925D1304F20D002BD8E1 /* NotesWriterSSC.h */, AA6EFDFB04770C79005F316C /* NoteTypes.cpp */, AA6EFDFC04770C79005F316C /* NoteTypes.h */, AA80C75707F20D6600C99272 /* OptionRowHandler.cpp */, @@ -4914,6 +4926,8 @@ EE906F7712BF3A6900146C96 /* json_batchallocator.h in Headers */, EE906F8312BF3AF800146C96 /* JsonUtil.h in Headers */, EE906F8B12BF3C7C00146C96 /* FileDownload.h in Headers */, + 2BA7925F1304F20D002BD8E1 /* NotesLoaderSSC.h in Headers */, + 2BA792611304F20D002BD8E1 /* NotesWriterSSC.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6156,6 +6170,8 @@ EE906F7D12BF3A6900146C96 /* json_writer.cpp in Sources */, EE906F8212BF3AF800146C96 /* JsonUtil.cpp in Sources */, EE906F8A12BF3C7C00146C96 /* FileDownload.cpp in Sources */, + 2BA7925E1304F20D002BD8E1 /* NotesLoaderSSC.cpp in Sources */, + 2BA792601304F20D002BD8E1 /* NotesWriterSSC.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp new file mode 100644 index 0000000000..b36143fd8c --- /dev/null +++ b/src/NotesLoaderSSC.cpp @@ -0,0 +1,1154 @@ +#include "global.h" +#include "NotesLoaderSSC.h" +#include "BackgroundUtil.h" +#include "GameManager.h" +#include "MsdFile.h" // No JSON here. +#include "NoteTypes.h" +#include "NotesLoaderSM.h" // edit file holdover. +#include "RageFileManager.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "Song.h" +#include "SongManager.h" +#include "Steps.h" +#include "PrefsManager.h" + +const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60 KB + +// Holdover from .sm format, mainly to make edits behave. +static void LoadFromSMTokens( + RString sStepsType, + RString sDescription, + RString sDifficulty, + RString sMeter, + RString sRadarValues, + RString sNoteData, + Steps &out + ) +{ + // we're loading from disk, so this is by definition already saved: + out.SetSavedToDisk( true ); + + Trim( sStepsType ); + Trim( sDescription ); + Trim( sDifficulty ); + Trim( sNoteData ); + + // LOG->Trace( "Steps::LoadFromSMTokens()" ); + + // insert stepstype hacks from GameManager.cpp here? -aj + out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType ); + out.SetDescription( sDescription ); + out.SetDifficulty( DwiCompatibleStringToDifficulty(sDifficulty) ); + + // Handle hacks that originated back when StepMania didn't have + // Difficulty_Challenge. (At least v1.64, possibly v3.0 final...) + if( out.GetDifficulty() == Difficulty_Hard ) + { + // HACK: SMANIAC used to be Difficulty_Hard with a special description. + if( sDescription.CompareNoCase("smaniac") == 0 ) + out.SetDifficulty( Difficulty_Challenge ); + + // HACK: CHALLENGE used to be Difficulty_Hard with a special description. + if( sDescription.CompareNoCase("challenge") == 0 ) + out.SetDifficulty( Difficulty_Challenge ); + } + + out.SetMeter( atoi(sMeter) ); + vector saValues; + split( sRadarValues, ",", saValues, true ); + if( saValues.size() == NUM_RadarCategory * NUM_PLAYERS ) + { + RadarValues v[NUM_PLAYERS]; + FOREACH_PlayerNumber( pn ) + FOREACH_ENUM( RadarCategory, rc ) + v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] ); + out.SetCachedRadarValues( v ); + } + + out.SetSMNoteData( sNoteData ); + + out.TidyUpData(); +} + +bool LoadFromBGSSCChangesString( BackgroundChange &change, const RString &sBGChangeExpression ) +{ + vector aBGChangeValues; + split( sBGChangeExpression, "=", aBGChangeValues, false ); + + aBGChangeValues.resize( min((int)aBGChangeValues.size(),11) ); + + switch( aBGChangeValues.size() ) + { + case 11: + change.m_def.m_sColor2 = aBGChangeValues[10]; + change.m_def.m_sColor2.Replace( '^', ',' ); + change.m_def.m_sColor2 = RageColor::NormalizeColorString( change.m_def.m_sColor2 ); + // fall through + case 10: + change.m_def.m_sColor1 = aBGChangeValues[9]; + change.m_def.m_sColor1.Replace( '^', ',' ); + change.m_def.m_sColor1 = RageColor::NormalizeColorString( change.m_def.m_sColor1 ); + // fall through + case 9: + change.m_sTransition = aBGChangeValues[8]; + // fall through + case 8: + change.m_def.m_sFile2 = aBGChangeValues[7]; + // fall through + case 7: + change.m_def.m_sEffect = aBGChangeValues[6]; + // fall through + case 6: + // param 7 overrides this. + // Backward compatibility: + if( change.m_def.m_sEffect.empty() ) + { + bool bLoop = atoi( aBGChangeValues[5] ) != 0; + if( !bLoop ) + change.m_def.m_sEffect = SBE_StretchNoLoop; + } + // fall through + case 5: + // param 7 overrides this. + // Backward compatibility: + if( change.m_def.m_sEffect.empty() ) + { + bool bRewindMovie = atoi( aBGChangeValues[4] ) != 0; + if( bRewindMovie ) + change.m_def.m_sEffect = SBE_StretchRewind; + } + // fall through + case 4: + // param 9 overrides this. + // Backward compatibility: + if( change.m_sTransition.empty() ) + change.m_sTransition = (atoi( aBGChangeValues[3] ) != 0) ? "CrossFade" : ""; + // fall through + case 3: + change.m_fRate = StringToFloat( aBGChangeValues[2] ); + // fall through + case 2: + change.m_def.m_sFile1 = aBGChangeValues[1]; + // fall through + case 1: + change.m_fStartBeat = StringToFloat( aBGChangeValues[0] ); + // fall through + } + + return aBGChangeValues.size() >= 2; +} + +bool SSCLoader::LoadFromDir( const RString &sPath, Song &out ) +{ + vector aFileNames; + GetApplicableFiles( sPath, aFileNames ); + + if( aFileNames.size() > 1 ) + { + LOG->UserLog( "Song", sPath, "has more than one SSC file. Only one SSC file is allowed per song." ); + return false; + } + + ASSERT( aFileNames.size() == 1 ); // Ensure one was found entirely. + + return LoadFromSSCFile( sPath + aFileNames[0], out ); +} + +bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCache ) +{ + LOG->Trace( "Song::LoadFromSSCFile(%s)", sPath.c_str() ); + + MsdFile msd; + if( !msd.ReadFile( sPath, true ) ) + { + LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); + return false; + } + + out.m_Timing.m_sFile = sPath; // songs still have their fallback timing. + + int state = GETTING_SONG_INFO; + const unsigned values = msd.GetNumValues(); + Steps* pNewNotes; + + for( unsigned i = 0; i < values; i++ ) + { + const MsdFile::value_t &sParams = msd.GetValue(i); + RString sValueName = sParams[0]; + sValueName.MakeUpper(); + + switch (state) + { + case GETTING_SONG_INFO: + { + if( sValueName=="TITLE" ) + { + out.m_sMainTitle = sParams[1]; + } + + else if( sValueName=="SUBTITLE" ) + { + out.m_sSubTitle = sParams[1]; + } + + else if( sValueName=="ARTIST" ) + { + out.m_sArtist = sParams[1]; + } + + else if( sValueName=="TITLETRANSLIT" ) + { + out.m_sMainTitleTranslit = sParams[1]; + } + + else if( sValueName=="SUBTITLETRANSLIT" ) + { + out.m_sSubTitleTranslit = sParams[1]; + } + + else if( sValueName=="ARTISTTRANSLIT" ) + { + out.m_sArtistTranslit = sParams[1]; + } + + else if( sValueName=="GENRE" ) + { + out.m_sGenre = sParams[1]; + } + + else if( sValueName=="CREDIT" ) + { + out.m_sCredit = sParams[1]; + } + + else if( sValueName=="BANNER" ) + { + out.m_sBannerFile = sParams[1]; + } + + else if( sValueName=="BACKGROUND" ) + { + out.m_sBackgroundFile = sParams[1]; + } + + else if( sValueName=="LYRICSPATH" ) + { + out.m_sLyricsFile = sParams[1]; + } + + else if( sValueName=="CDTITLE" ) + { + out.m_sCDTitleFile = sParams[1]; + } + + else if( sValueName=="MUSIC" ) + { + out.m_sMusicFile = sParams[1]; + } + + else if( sValueName=="INSTRUMENTTRACK" ) + { + vector vs1; + split( sParams[1], ",", vs1 ); + FOREACH_CONST( RString, vs1, s ) + { + vector vs2; + split( *s, "=", vs2 ); + if( vs2.size() >= 2 ) + { + InstrumentTrack it = StringToInstrumentTrack( vs2[0] ); + if( it != InstrumentTrack_Invalid ) + out.m_sInstrumentTrackFile[it] = vs2[1]; + } + } + } + + else if( sValueName=="MUSICLENGTH" ) + { + if( !bFromCache ) + continue; + out.m_fMusicLengthSeconds = StringToFloat( sParams[1] ); + } + + else if( sValueName=="LASTBEATHINT" ) + { + out.m_fSpecifiedLastBeat = StringToFloat( sParams[1] ); + } + + else if( sValueName=="MUSICBYTES" ) + { + ; /* ignore */ + } + + else if( sValueName=="SAMPLESTART" ) + { + out.m_fMusicSampleStartSeconds = HHMMSSToSeconds( sParams[1] ); + } + + else if( sValueName=="SAMPLELENGTH" ) + { + out.m_fMusicSampleLengthSeconds = HHMMSSToSeconds( sParams[1] ); + } + + else if( sValueName=="DISPLAYBPM" ) + { + // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; + if( sParams[1] == "*" ) + out.m_DisplayBPMType = Song::DISPLAY_RANDOM; + else + { + out.m_DisplayBPMType = Song::DISPLAY_SPECIFIED; + out.m_fSpecifiedBPMMin = StringToFloat( sParams[1] ); + if( sParams[2].empty() ) + out.m_fSpecifiedBPMMax = out.m_fSpecifiedBPMMin; + else + out.m_fSpecifiedBPMMax = StringToFloat( sParams[2] ); + } + } + + else if( sValueName=="SELECTABLE" ) + { + if(!stricmp(sParams[1],"YES")) + out.m_SelectionDisplay = out.SHOW_ALWAYS; + else if(!stricmp(sParams[1],"NO")) + out.m_SelectionDisplay = out.SHOW_NEVER; + // ROULETTE from 3.9 is no longer in use. + else if(!stricmp(sParams[1],"ROULETTE")) + out.m_SelectionDisplay = out.SHOW_ALWAYS; + /* The following two cases are just fixes to make sure simfiles that + * used 3.9+ features are not excluded here */ + else if(!stricmp(sParams[1],"ES") || !stricmp(sParams[1],"OMES")) + out.m_SelectionDisplay = out.SHOW_ALWAYS; + else if( atoi(sParams[1]) > 0 ) + out.m_SelectionDisplay = out.SHOW_ALWAYS; + else + LOG->UserLog( "Song file", sPath, "has an unknown #SELECTABLE value, \"%s\"; ignored.", sParams[1].c_str() ); + } + + else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" ) + { + BackgroundLayer iLayer = BACKGROUND_LAYER_1; + if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue(&iLayer)) == 1 ) + enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2 + + bool bValid = iLayer>=0 && iLayerUserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() ); + } + else + { + vector aBGChangeExpressions; + split( sParams[1], ",", aBGChangeExpressions ); + + for( unsigned b=0; b aFGChangeExpressions; + split( sParams[1], ",", aFGChangeExpressions ); + + for( unsigned b=0; b sBits; + split( sParams[j], "=", sBits, false ); + + // Need an identifer and a value for this to work + if( sBits.size() < 2 ) + continue; + + TrimLeft( sBits[0] ); + TrimRight( sBits[0] ); + + if( !sBits[0].CompareNoCase("TIME") ) + attack.fStartSecond = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("LEN") ) + attack.fSecsRemaining = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("END") ) + end = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("MODS") ) + { + attack.sModifiers = sBits[1]; + + if( end != -9999 ) + { + attack.fSecsRemaining = end - attack.fStartSecond; + end = -9999; + } + + if( attack.fSecsRemaining < 0.0f ) + attack.fSecsRemaining = 0.0f; + + out.m_Attacks.push_back( attack ); + } + } + } + + else if( sValueName=="OFFSET" ) + { + out.m_Timing.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] ); + } + /* + * Below are the song based timings that + * should only be used if the steps do + * not have their own timing. + */ + else if( sValueName=="STOPS" ) + { + vector arrayFreezeExpressions; + split( sParams[1], ",", arrayFreezeExpressions ); + + for( unsigned f=0; f arrayFreezeValues; + split( arrayFreezeExpressions[f], "=", arrayFreezeValues ); + if( arrayFreezeValues.size() != 2 ) + { + // XXX: Hard to tell which file caused this. + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayFreezeExpressions[f].c_str() ); + continue; + } + + const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] ); + const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ); + StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds ); + + if(fFreezeSeconds > 0.0f) + { + // LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + out.m_Timing.AddStopSegment( new_seg ); + } + else + { + // negative stops (hi JS!) -aj + if( PREFSMAN->m_bQuirksMode ) + { + // LOG->Trace( "Adding a negative freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + out.m_Timing.AddStopSegment( new_seg ); + } + else + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid stop at beat %f, length %f.", fFreezeBeat, fFreezeSeconds ); + } + } + } + else if( sValueName=="DELAYS" ) + { + vector arrayDelayExpressions; + split( sParams[1], ",", arrayDelayExpressions ); + + for( unsigned f=0; f arrayDelayValues; + split( arrayDelayExpressions[f], "=", arrayDelayValues ); + if( arrayDelayValues.size() != 2 ) + { + // XXX: Hard to tell which file caused this. + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayDelayExpressions[f].c_str() ); + continue; + } + + const float fFreezeBeat = StringToFloat( arrayDelayValues[0] ); + const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] ); + + StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true ); + + // LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + + if(fFreezeSeconds > 0.0f) + out.m_Timing.AddStopSegment( new_seg ); + else + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds ); + } + } + + else if( sValueName=="BPMS" ) + { + vector arrayBPMChangeExpressions; + split( sParams[1], ",", arrayBPMChangeExpressions ); + + for( unsigned b=0; b arrayBPMChangeValues; + split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues ); + // XXX: Hard to tell which file caused this. + if( arrayBPMChangeValues.size() != 2 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() ); + continue; + } + + const float fBeat = StringToFloat( arrayBPMChangeValues[0] ); + const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] ); + + + + if(fNewBPM > 0.0f) + out.m_Timing.AddBPMSegment( BPMSegment(BeatToNoteRow(fBeat), fNewBPM) ); + else + { + out.m_Timing.m_bHasNegativeBpms = true; + // only add Negative BPMs in quirks mode -aj + if( PREFSMAN->m_bQuirksMode ) + out.m_Timing.AddBPMSegment( BPMSegment(BeatToNoteRow(fBeat), fNewBPM) ); + else + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM ); + } + } + } + + else if( sValueName=="TIMESIGNATURES" ) + { + vector vs1; + split( sParams[1], ",", vs1 ); + + FOREACH_CONST( RString, vs1, s1 ) + { + vector vs2; + split( *s1, "=", vs2 ); + + if( vs2.size() < 3 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() ); + continue; + } + + const float fBeat = StringToFloat( vs2[0] ); + + TimeSignatureSegment seg; + seg.m_iStartRow = BeatToNoteRow(fBeat); + seg.m_iNumerator = atoi( vs2[1] ); + seg.m_iDenominator = atoi( vs2[2] ); + + if( fBeat < 0 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat ); + continue; + } + + if( seg.m_iNumerator < 1 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator ); + continue; + } + + if( seg.m_iDenominator < 1 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator ); + continue; + } + + out.m_Timing.AddTimeSignatureSegment( seg ); + } + } + + else if( sValueName=="TICKCOUNTS" ) + { + vector arrayTickcountExpressions; + split( sParams[1], ",", arrayTickcountExpressions ); + + for( unsigned f=0; f arrayTickcountValues; + split( arrayTickcountExpressions[f], "=", arrayTickcountValues ); + if( arrayTickcountValues.size() != 2 ) + { + // XXX: Hard to tell which file caused this. + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayTickcountExpressions[f].c_str() ); + continue; + } + + const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] ); + const int iTicks = atoi( arrayTickcountValues[1] ); + TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks ); + + if(iTicks >= 1 && iTicks <= ROWS_PER_BEAT ) // Constants + { + // LOG->Trace( "Adding a tickcount segment: beat: %f, ticks = %d", fTickcountBeat, iTicks ); + out.m_Timing.AddTickcountSegment( new_seg ); + } + else + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid tickcount at beat %f, ticks %d.", fTickcountBeat, iTicks ); + } + } + } + + else if( sValueName=="COMBOS" ) + {/* + vector arrayComboExpressions; + split( sParams[1], ",", arrayComboExpressions ); + + for( unsigned f=0; f arrayComboValues; + split( arrayComboExpressions[f], "=", arrayComboValues ); + if( arrayComboValues.size() != 2 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayComboExpressions[f].c_str() ); + continue; + } + const float fComboBeat = StringToFloat( arrayComboValues[0] ); + const int iCombos = atoi( arrayComboValues[1] ); + ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos ); + out.m_Timing.AddComboSegment( new_seg ); + } + */ + } + + /* + * The following are cache tags. + * Never fill their values directly: + * only from the cached version. + */ + else if( sValueName=="FIRSTBEAT" ) + { + if( bFromCache ) + out.m_fFirstBeat = StringToFloat( sParams[1] ); + } + + else if( sValueName=="LASTBEAT" ) + { + if( bFromCache ) + out.m_fLastBeat = StringToFloat( sParams[1] ); + } + + else if( sValueName=="SONGFILENAME" ) + { + if( bFromCache ) + out.m_sSongFileName = sParams[1]; + } + + else if( sValueName=="HASMUSIC" ) + { + if( bFromCache ) + out.m_bHasMusic = atoi( sParams[1] ) != 0; + } + + else if( sValueName=="HASBANNER" ) + { + if( bFromCache ) + out.m_bHasBanner = atoi( sParams[1] ) != 0; + } + + // This tag will get us to the next section. + else if( sValueName=="NOTEDATA" ) + { + state = GETTING_STEP_INFO; + pNewNotes = new Steps; + } + break; + } + case GETTING_STEP_INFO: + { + if( sValueName=="STEPSTYPE" ) + { + pNewNotes->m_StepsType = GAMEMAN->StringToStepsType( sParams[1] ); + } + + else if( sValueName=="DESCRIPTION" ) + { + pNewNotes->SetDescription( sParams[1] ); + } + + else if( sValueName=="DIFFICULTY" ) + { + pNewNotes->SetDifficulty( DwiCompatibleStringToDifficulty( sParams[1] ) ); + } + + else if( sValueName=="METER" ) + { + pNewNotes->SetMeter( atoi( sParams[1] ) ); + } + + else if( sValueName=="RADARVALUES" ) + { + vector saValues; + split( sParams[1], ",", saValues, true ); + if( saValues.size() == NUM_RadarCategory * NUM_PLAYERS ) + { + RadarValues v[NUM_PLAYERS]; + FOREACH_PlayerNumber( pn ) + FOREACH_ENUM( RadarCategory, rc ) + v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] ); + pNewNotes->SetCachedRadarValues( v ); + } + } + + else if( sValueName=="CREDIT" ) + { + ; // Not implemented yet, but should be. + } + + else if( sValueName=="NOTES" ) + { + state = GETTING_SONG_INFO; + //pNewNotes->m_Timing = out.m_Timing; + pNewNotes->SetSMNoteData( sParams[1] ); + pNewNotes->TidyUpData(); + out.AddSteps( pNewNotes ); + } + + else if( sValueName=="BPMS" ) + { + /* + state = GETTING_STEP_TIMING_INFO; + vector arrayBPMChangeExpressions; + split( sParams[1], ",", arrayBPMChangeExpressions ); + + for( unsigned b=0; b arrayBPMChangeValues; + split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues ); + // XXX: Hard to tell which file caused this. + if( arrayBPMChangeValues.size() != 2 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() ); + continue; + } + + const float fBeat = StringToFloat( arrayBPMChangeValues[0] ); + const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] ); + + + + if(fNewBPM > 0.0f) + pNewNotes->m_Timing.AddBPMSegment( BPMSegment(BeatToNoteRow(fBeat), fNewBPM) ); + else + { + pNewNotes->m_Timing.m_bHasNegativeBpms = true; + // only add Negative BPMs in quirks mode -aj + if( PREFSMAN->m_bQuirksMode ) + pNewNotes->m_Timing.AddBPMSegment( BPMSegment(BeatToNoteRow(fBeat), fNewBPM) ); + else + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM ); + } + } + */ + } + break; + } + case GETTING_STEP_TIMING_INFO: + { + if( sValueName=="STOPS" ) + { + /* + vector arrayFreezeExpressions; + split( sParams[1], ",", arrayFreezeExpressions ); + + for( unsigned f=0; f arrayFreezeValues; + split( arrayFreezeExpressions[f], "=", arrayFreezeValues ); + if( arrayFreezeValues.size() != 2 ) + { + // XXX: Hard to tell which file caused this. + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayFreezeExpressions[f].c_str() ); + continue; + } + + const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] ); + const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ); + StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds ); + + if(fFreezeSeconds > 0.0f) + { + // LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + pNewNotes->m_Timing.AddStopSegment( new_seg ); + } + else + { + // negative stops (hi JS!) -aj + if( PREFSMAN->m_bQuirksMode ) + { + // LOG->Trace( "Adding a negative freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + pNewNotes->m_Timing.AddStopSegment( new_seg ); + } + else + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid stop at beat %f, length %f.", fFreezeBeat, fFreezeSeconds ); + } + } + */ + } + else if( sValueName=="DELAYS" ) + { + /* + vector arrayDelayExpressions; + split( sParams[1], ",", arrayDelayExpressions ); + + for( unsigned f=0; f arrayDelayValues; + split( arrayDelayExpressions[f], "=", arrayDelayValues ); + if( arrayDelayValues.size() != 2 ) + { + // XXX: Hard to tell which file caused this. + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayDelayExpressions[f].c_str() ); + continue; + } + + const float fFreezeBeat = StringToFloat( arrayDelayValues[0] ); + const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] ); + + StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true ); + + // LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + + if(fFreezeSeconds > 0.0f) + pNewNotes->m_Timing.AddStopSegment( new_seg ); + else + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds ); + } + */ + } + else if( sValueName=="TIMESIGNATURES" ) + { + /* + vector vs1; + split( sParams[1], ",", vs1 ); + + FOREACH_CONST( RString, vs1, s1 ) + { + vector vs2; + split( *s1, "=", vs2 ); + + if( vs2.size() < 3 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() ); + continue; + } + + const float fBeat = StringToFloat( vs2[0] ); + + TimeSignatureSegment seg; + seg.m_iStartRow = BeatToNoteRow(fBeat); + seg.m_iNumerator = atoi( vs2[1] ); + seg.m_iDenominator = atoi( vs2[2] ); + + if( fBeat < 0 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat ); + continue; + } + + if( seg.m_iNumerator < 1 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator ); + continue; + } + + if( seg.m_iDenominator < 1 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator ); + continue; + } + + pNewNotes->m_Timing.AddTimeSignatureSegment( seg ); + } + */ + } + + else if( sValueName=="TICKCOUNTS" ) + { + /* + vector arrayTickcountExpressions; + split( sParams[1], ",", arrayTickcountExpressions ); + + for( unsigned f=0; f arrayTickcountValues; + split( arrayTickcountExpressions[f], "=", arrayTickcountValues ); + if( arrayTickcountValues.size() != 2 ) + { + // XXX: Hard to tell which file caused this. + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayTickcountExpressions[f].c_str() ); + continue; + } + + const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] ); + const int iTicks = atoi( arrayTickcountValues[1] ); + TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks ); + + if(iTicks >= 1 && iTicks <= ROWS_PER_BEAT ) // Constants + { + // LOG->Trace( "Adding a tickcount segment: beat: %f, ticks = %d", fTickcountBeat, iTicks ); + pNewNotes->m_Timing.AddTickcountSegment( new_seg ); + } + else + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid tickcount at beat %f, ticks %d.", fTickcountBeat, iTicks ); + } + } + */ + } + + else if( sValueName=="COMBOS" ) + { + /* + vector arrayComboExpressions; + split( sParams[1], ",", arrayComboExpressions ); + + for( unsigned f=0; f arrayComboValues; + split( arrayComboExpressions[f], "=", arrayComboValues ); + if( arrayComboValues.size() != 2 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayComboExpressions[f].c_str() ); + continue; + } + const float fComboBeat = StringToFloat( arrayComboValues[0] ); + const int iCombos = atoi( arrayComboValues[1] ); + ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos ); + pNewNotes->m_Timing.AddComboSegment( new_seg ); + } + */ + } + else if( sValueName=="OFFSET" ) + {/* + pNewNotes->m_Timing.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] ); + */ + } + + else if( sValueName=="NOTES" ) + { + + state = GETTING_SONG_INFO; + // pNewNotes->m_Timing.m_fBeat0OffsetInSeconds = out.m_Timing.m_fBeat0OffsetInSeconds; + pNewNotes->SetSMNoteData( sParams[1] ); + pNewNotes->TidyUpData(); + out.AddSteps( pNewNotes ); + } + break; + } + } + } + return true; +} + +void SSCLoader::GetApplicableFiles( const RString &sPath, vector &out ) +{ + GetDirListing( sPath + RString("*.ssc"), out ); +} + +bool SSCLoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ) +{ + LOG->Trace( "SSCLoader::LoadEditFromFile(%s)", sEditFilePath.c_str() ); + + int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath ); + if( iBytes > MAX_EDIT_STEPS_SIZE_BYTES ) + { + LOG->UserLog( "Edit file", sEditFilePath, "is unreasonably large. It won't be loaded." ); + return false; + } + + MsdFile msd; + if( !msd.ReadFile( sEditFilePath, true ) ) // unescape + { + LOG->UserLog( "Edit file", sEditFilePath, "couldn't be opened: %s", msd.GetError().c_str() ); + return false; + } + + return LoadEditFromMsd( msd, sEditFilePath, slot, bAddStepsToSong ); +} + +bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ) +{ + Song* pSong = NULL; + + for( unsigned i=0; iUserLog( "Edit file", sEditFilePath, "has more than one #SONG tag." ); + return false; + } + + RString sSongFullTitle = sParams[1]; + sSongFullTitle.Replace( '\\', '/' ); + + pSong = SONGMAN->FindSong( sSongFullTitle ); + if( pSong == NULL ) + { + LOG->UserLog( "Edit file", sEditFilePath, "requires a song \"%s\" that isn't present.", sSongFullTitle.c_str() ); + return false; + } + + if( pSong->GetNumStepsLoadedFromProfile(slot) >= MAX_EDITS_PER_SONG_PER_PROFILE ) + { + LOG->UserLog( "Song file", sSongFullTitle, "already has the maximum number of edits allowed for ProfileSlotP%d.", slot+1 ); + return false; + } + } + + else if( sValueName=="NOTES" ) + { + // TODO: deal with transferring the timing data if required. + if( pSong == NULL ) + { + LOG->UserLog( "Edit file", sEditFilePath, "doesn't have a #SONG tag preceeding the first #NOTES tag." ); + return false; + } + + if( iNumParams < 7 ) + { + LOG->UserLog( "Edit file", sEditFilePath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams ); + continue; + } + + if( !bAddStepsToSong ) + return true; + + Steps* pNewNotes = new Steps; + LoadFromSMTokens( + sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], + *pNewNotes); + + pNewNotes->SetLoadedFromProfile( slot ); + pNewNotes->SetDifficulty( Difficulty_Edit ); + pNewNotes->SetFilename( sEditFilePath ); + + if( pSong->IsEditAlreadyLoaded(pNewNotes) ) + { + LOG->UserLog( "Edit file", sEditFilePath, "is a duplicate of another edit that was already loaded." ); + SAFE_DELETE( pNewNotes ); + return false; + } + + pSong->AddSteps( pNewNotes ); + return true; // Only allow one Steps per edit file! + } + else + { + LOG->UserLog( "Edit file", sEditFilePath, "has an unexpected value \"%s\".", sValueName.c_str() ); + } + } + + return true; +} + +void SSCLoader::TidyUpData( Song &song, bool bFromCache ) +{ + /* + * Hack: if the song has any changes at all (so it won't use a random BGA) + * and doesn't end with "-nosongbg-", add a song background BGC. Remove + * "-nosongbg-" if it exists. + * + * This way, songs that were created earlier, when we added the song BG + * at the end by default, will still behave as expected; all new songs will + * have to add an explicit song BG tag if they want it. This is really a + * formatting hack only; nothing outside of SMLoader ever sees "-nosongbg-". + */ + vector &bg = song.GetBackgroundChanges(BACKGROUND_LAYER_1); + if( !bg.empty() ) + { + /* BGChanges have been sorted. On the odd chance that a BGChange exists + * with a very high beat, search the whole list. */ + bool bHasNoSongBgTag = false; + + for( unsigned i = 0; !bHasNoSongBgTag && i < bg.size(); ++i ) + { + if( !bg[i].m_def.m_sFile1.CompareNoCase(NO_SONG_BG_FILE) ) + { + bg.erase( bg.begin()+i ); + bHasNoSongBgTag = true; + } + } + + // If there's no -nosongbg- tag, add the song BG. + if( !bHasNoSongBgTag ) do + { + /* If we're loading cache, -nosongbg- should always be in there. We + * must not call IsAFile(song.GetBackgroundPath()) when loading cache. */ + if( bFromCache ) + break; + + /* If BGChanges already exist after the last beat, don't add the + * background in the middle. */ + if( !bg.empty() && bg.back().m_fStartBeat-0.0001f >= song.m_fLastBeat ) + break; + + // If the last BGA is already the song BGA, don't add a duplicate. + if( !bg.empty() && !bg.back().m_def.m_sFile1.CompareNoCase(song.m_sBackgroundFile) ) + break; + + if( !IsAFile( song.GetBackgroundPath() ) ) + break; + + bg.push_back( BackgroundChange(song.m_fLastBeat,song.m_sBackgroundFile) ); + } while(0); + } +} + +/* + * (c) 2011 spinal shark collective + * 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. + */ diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h new file mode 100644 index 0000000000..02cbf2fdc6 --- /dev/null +++ b/src/NotesLoaderSSC.h @@ -0,0 +1,59 @@ +/* SSCLoader - Reads a Song and its Steps from a .SSC file. */ + +#ifndef NotesLoaderSSC_H +#define NotesLoaderSSC_H + +#include "GameConstantsAndTypes.h" + +class MsdFile; +class Song; +class Steps; +class TimingData; + +enum SSCLoadingStates +{ + GETTING_SONG_INFO, + GETTING_STEP_INFO, + GETTING_STEP_TIMING_INFO, + GETTING_NOTE_INFO, + NUM_SSCLoadingStates +}; + +namespace SSCLoader +{ + bool LoadFromDir( const RString &sPath, Song &out ); + + bool LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCache = false ); + void GetApplicableFiles( const RString &sPath, vector &out ); + bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ); + bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ); + void TidyUpData( Song &song, bool bFromCache ); + +} + +#endif + +/* + * (c) 2011 spinal shark collective + * 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. + */ diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp new file mode 100644 index 0000000000..914718e5fa --- /dev/null +++ b/src/NotesWriterSSC.cpp @@ -0,0 +1,507 @@ +#include "global.h" +#include +#include +#include "NotesWriterSSC.h" +#include "BackgroundUtil.h" +#include "Foreach.h" +#include "GameManager.h" +#include "LocalizedString.h" +#include "NoteTypes.h" +#include "Profile.h" +#include "ProfileManager.h" +#include "RageFile.h" +#include "RageFileManager.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "Song.h" +#include "Steps.h" + +static RString BackgroundChangeToString( const BackgroundChange &bgc ) +{ + // TODO: Technically we need to double-escape the filename (because it might contain '=') and then + // unescape the value returned by the MsdFile. + RString s = ssprintf( + "%.3f=%s=%.3f=%d=%d=%d=%s=%s=%s=%s=%s", + bgc.m_fStartBeat, + SmEscape(bgc.m_def.m_sFile1).c_str(), + bgc.m_fRate, + bgc.m_sTransition == SBT_CrossFade, // backward compat + bgc.m_def.m_sEffect == SBE_StretchRewind, // backward compat + bgc.m_def.m_sEffect != SBE_StretchNoLoop, // backward compat + bgc.m_def.m_sEffect.c_str(), + bgc.m_def.m_sFile2.c_str(), + bgc.m_sTransition.c_str(), + SmEscape(RageColor::NormalizeColorString(bgc.m_def.m_sColor1)).c_str(), + SmEscape(RageColor::NormalizeColorString(bgc.m_def.m_sColor2)).c_str() + ); + return s; +} + +static void WriteGlobalTags( RageFile &f, const Song &out ) +{ + //f.PutLine( ssprintf( "#SMVERSION:;", SmEscape(RString("ssc-v1.0")).c_str() ) ); + f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) ); + f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) ); + f.PutLine( ssprintf( "#ARTIST:%s;", SmEscape(out.m_sArtist).c_str() ) ); + f.PutLine( ssprintf( "#TITLETRANSLIT:%s;", SmEscape(out.m_sMainTitleTranslit).c_str() ) ); + f.PutLine( ssprintf( "#SUBTITLETRANSLIT:%s;", SmEscape(out.m_sSubTitleTranslit).c_str() ) ); + f.PutLine( ssprintf( "#ARTISTTRANSLIT:%s;", SmEscape(out.m_sArtistTranslit).c_str() ) ); + f.PutLine( ssprintf( "#GENRE:%s;", SmEscape(out.m_sGenre).c_str() ) ); + f.PutLine( ssprintf( "#CREDIT:%s;", SmEscape(out.m_sCredit).c_str() ) ); + f.PutLine( ssprintf( "#BANNER:%s;", SmEscape(out.m_sBannerFile).c_str() ) ); + f.PutLine( ssprintf( "#BACKGROUND:%s;", SmEscape(out.m_sBackgroundFile).c_str() ) ); + f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) ); + f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) ); + f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) ); + + { + vector vs; + FOREACH_ENUM( InstrumentTrack, it ) + if( out.HasInstrumentTrack(it) ) + vs.push_back( InstrumentTrackToString(it) + "=" + out.m_sInstrumentTrackFile[it] ); + if( !vs.empty() ) + { + RString s = join( ",", vs ); + f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" ); + } + } + f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_Timing.m_fBeat0OffsetInSeconds ) ); + f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) ); + f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) ); + if( out.m_fSpecifiedLastBeat > 0 ) + f.PutLine( ssprintf("#LASTBEATHINT:%.6f;", out.m_fSpecifiedLastBeat) ); + + f.Write( "#SELECTABLE:" ); + switch(out.m_SelectionDisplay) + { + default: ASSERT(0); // fall through + case Song::SHOW_ALWAYS: f.Write( "YES" ); break; + //case Song::SHOW_NONSTOP: f.Write( "NONSTOP" ); break; + case Song::SHOW_NEVER: f.Write( "NO" ); break; + } + f.PutLine( ";" ); + + switch( out.m_DisplayBPMType ) + { + case Song::DISPLAY_ACTUAL: + // write nothing + break; + case Song::DISPLAY_SPECIFIED: + if( out.m_fSpecifiedBPMMin == out.m_fSpecifiedBPMMax ) + f.PutLine( ssprintf( "#DISPLAYBPM:%.6f;", out.m_fSpecifiedBPMMin ) ); + else + f.PutLine( ssprintf( "#DISPLAYBPM:%.6f:%.6f;", out.m_fSpecifiedBPMMin, out.m_fSpecifiedBPMMax ) ); + break; + case Song::DISPLAY_RANDOM: + f.PutLine( ssprintf( "#DISPLAYBPM:*;" ) ); + break; + } + + + f.Write( "#BPMS:" ); + for( unsigned i=0; im_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) ); + vector::const_iterator iter2 = iter; + iter2++; + if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() ) + f.Write( "," ); + } + f.PutLine( ";" ); + + ASSERT( !out.m_Timing.m_TickcountSegments.empty() ); + f.Write( "#TICKCOUNTS:" ); + for( unsigned i=0; i &lines ) +{ + for( unsigned i = 0; i < lines.size(); ++i ) + TrimRight( lines[i] ); + + /* Skip leading blanks. */ + unsigned j = 0; + while( j < lines.size() && lines.size() == 0 ) + ++j; + + return join( "\r\n", lines.begin()+j, lines.end() ); +} + +static RString GetSSCNotesTag( const Song &song, const Steps &in, bool bSavingCache ) +{ + vector lines; + + lines.push_back( "" ); + // Escape to prevent some clown from making a comment of "\r\n;" + lines.push_back( ssprintf("//---------------%s - %s----------------", + GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) ); + lines.push_back( "#NOTEDATA:;" ); // our new separator. + lines.push_back( ssprintf( "#STEPSTYPE:%s;", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) ); + lines.push_back( ssprintf( "#DESCRIPTION:%s;", SmEscape(in.GetDescription()).c_str() ) ); + lines.push_back( ssprintf( "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty()).c_str() ) ); + lines.push_back( ssprintf( "#METER:%d;", in.GetMeter() ) ); + + vector asRadarValues; + FOREACH_PlayerNumber( pn ) + { + const RadarValues &rv = in.GetRadarValues( pn ); + FOREACH_ENUM( RadarCategory, rc ) + asRadarValues.push_back( ssprintf("%.3f", rv[rc]) ); + } + lines.push_back( ssprintf( "#RADARVALUES:%s:", join(",",asRadarValues).c_str() ) ); + + lines.push_back( "#CREDIT:;" ); // will be done later. + + // TODO: Remove this block, uncommon below block for Split Timing. -Wolfman2000 + lines.push_back( "#BPMS:;" ); + lines.push_back( "#STOPS:;" ); + lines.push_back( "DELAYS:;" ); + lines.push_back( "TIMESIGNATURES:;" ); + lines.push_back( "TICKCOUNTS:;" ); + // lines.push_back( "COMBOS:;" ); + + /* + vector asBPMValues; + for( unsigned i=0; i asStopValues; + for( unsigned i=0; i asDelayValues; + for( unsigned i=0; i asTimeSigValues; + FOREACH_CONST( TimeSignatureSegment, in.m_Timing.m_vTimeSignatureSegments, iter ) + { + asTimeSigValues.push_back( ssprintf( "%.6f=%d=%d", NoteRowToBeat(iter->m_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) ); + vector::const_iterator iter2 = iter; + iter2++; + } + lines.push_back( ssprintf( "#TIMESIGNATURES:%s;", join("\n,", asTimeSigValues).c_str() ) ); + + ASSERT( !in.m_Timing.m_TickcountSegments.empty() ); + vector asTickValues; + for( unsigned i=0; i asComboValues; + for( unsigned i=0; i& vpStepsToSave, bool bSavingCache ) +{ + int flags = RageFile::WRITE; + + /* If we're not saving cache, we're saving real data, so enable SLOW_FLUSH + * to prevent data loss. If we're saving cache, this will slow things down + * too much. */ + if( !bSavingCache ) + flags |= RageFile::SLOW_FLUSH; + + RageFile f; + if( !f.Open( sPath, flags ) ) + { + LOG->UserLog( "Song file", sPath, "couldn't be opened for writing: %s", f.GetError().c_str() ); + return false; + } + + WriteGlobalTags( f, out ); + if( bSavingCache ) + { + f.PutLine( ssprintf( "// cache tags:" ) ); + f.PutLine( ssprintf( "#FIRSTBEAT:%.3f;", out.m_fFirstBeat ) ); + f.PutLine( ssprintf( "#LASTBEAT:%.3f;", out.m_fLastBeat ) ); + f.PutLine( ssprintf( "#SONGFILENAME:%s;", out.m_sSongFileName.c_str() ) ); + f.PutLine( ssprintf( "#HASMUSIC:%i;", out.m_bHasMusic ) ); + f.PutLine( ssprintf( "#HASBANNER:%i;", out.m_bHasBanner ) ); + f.PutLine( ssprintf( "#MUSICLENGTH:%.3f;", out.m_fMusicLengthSeconds ) ); + f.PutLine( ssprintf( "// end cache tags" ) ); + } + + // Save specified Steps to this file + FOREACH_CONST( Steps*, vpStepsToSave, s ) + { + const Steps* pSteps = *s; + RString sTag = GetSSCNotesTag( out, *pSteps, bSavingCache ); + f.PutLine( sTag ); + } + if( f.Flush() == -1 ) + return false; + + return true; +} + +void NotesWriterSSC::GetEditFileContents( const Song *pSong, const Steps *pSteps, RString &sOut ) +{ + sOut = ""; + RString sDir = pSong->GetSongDir(); + + // "Songs/foo/bar"; strip off "Songs/". + vector asParts; + split( sDir, "/", asParts ); + if( asParts.size() ) + sDir = join( "/", asParts.begin()+1, asParts.end() ); + sOut += ssprintf( "#SONG:%s;\r\n", sDir.c_str() ); + sOut += GetSSCNotesTag( *pSong, *pSteps, false ); +} + +RString NotesWriterSSC::GetEditFileName( const Song *pSong, const Steps *pSteps ) +{ + /* Try to make a unique name. This isn't guaranteed. Edit descriptions are + * case-sensitive, filenames on disk are usually not, and we decimate certain + * characters for FAT filesystems. */ + RString sFile = pSong->GetTranslitFullTitle() + " - " + pSteps->GetDescription(); + + // HACK: + if( pSteps->m_StepsType == StepsType_dance_double ) + sFile += " (doubles)"; + + sFile += ".edit"; + + MakeValidFilename( sFile ); + return sFile; +} + +static LocalizedString DESTINATION_ALREADY_EXISTS ("NotesWriterSSC", "Error renaming file. Destination file '%s' already exists."); +static LocalizedString ERROR_WRITING_FILE ("NotesWriterSSC", "Error writing file '%s'."); +bool NotesWriterSSC::WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ) +{ + RString sDir = PROFILEMAN->GetProfileDir( ProfileSlot_Machine ) + EDIT_STEPS_SUBDIR; + + RString sPath = sDir + GetEditFileName(pSong,pSteps); + + // Check to make sure that we're not clobering an existing file before opening. + bool bFileNameChanging = + pSteps->GetSavedToDisk() && + pSteps->GetFilename() != sPath; + if( bFileNameChanging && DoesFileExist(sPath) ) + { + sErrorOut = ssprintf( DESTINATION_ALREADY_EXISTS.GetValue(), sPath.c_str() ); + return false; + } + + RageFile f; + if( !f.Open(sPath, RageFile::WRITE | RageFile::SLOW_FLUSH) ) + { + sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); + return false; + } + + RString sTag; + GetEditFileContents( pSong, pSteps, sTag ); + if( f.PutLine(sTag) == -1 || f.Flush() == -1 ) + { + sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); + return false; + } + + /* If the file name of the edit has changed since the last save, then delete the old + * file after saving the new one. If we delete it first, then we'll lose data on error. */ + + if( bFileNameChanging ) + FILEMAN->Remove( pSteps->GetFilename() ); + pSteps->SetFilename( sPath ); + + return true; +} + +/* + * (c) 2011 spinal shark collective + * 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. + */ diff --git a/src/NotesWriterSSC.h b/src/NotesWriterSSC.h new file mode 100644 index 0000000000..e8d544d43e --- /dev/null +++ b/src/NotesWriterSSC.h @@ -0,0 +1,41 @@ +/* NotesWriterSM - Writes a Song to an .SSC file. */ + +#ifndef NOTES_WRITER_SSC_H +#define NOTES_WRITER_SSC_H + +class Song; +class Steps; +namespace NotesWriterSSC +{ + bool Write( RString sPath, const Song &out, const vector& vpStepsToSave, bool bSavingCache ); + void GetEditFileContents( const Song *pSong, const Steps *pSteps, RString &sOut ); + RString GetEditFileName( const Song *pSong, const Steps *pSteps ); + bool WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ); +} + +#endif + +/* + * (c) 2011 spinal shark collective + * 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. + */ From e61745cdd60e1a1f6ecc8d114db00e0d000f043b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Thu, 10 Feb 2011 23:45:25 -0500 Subject: [PATCH 02/19] Updating other OSes. Please test this. --- src/Makefile.am | 7 +++++-- src/StepMania-net2003.vcproj | 12 ++++++++++++ src/StepMania-net2005.vcproj | 18 ++++++++++++++++++ src/StepMania-net2008.vcproj | 18 ++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 9ff2b3bf43..28eb71d84d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -144,8 +144,11 @@ LyricsLoader.cpp LyricsLoader.h \ ModsGroup.cpp ModsGroup.h \ NoteData.cpp NoteData.h NoteDataUtil.cpp NoteDataUtil.h NoteDataWithScoring.cpp NoteDataWithScoring.h \ NoteTypes.cpp NoteTypes.h NotesLoader.cpp NotesLoader.h \ -NotesLoaderBMS.cpp NotesLoaderBMS.h NotesLoaderDWI.cpp NotesLoaderDWI.h NotesLoaderKSF.cpp NotesLoaderKSF.h NotesLoaderMidi.cpp NotesLoaderMidi.h \ -NotesLoaderPMS.cpp NotesLoaderPMS.h NotesLoaderSM.cpp NotesLoaderSM.h NotesWriterDWI.cpp NotesWriterDWI.h NotesWriterSM.cpp NotesWriterSM.h \ +NotesLoaderBMS.cpp NotesLoaderBMS.h NotesLoaderDWI.cpp NotesLoaderDWI.h \ +NotesLoaderKSF.cpp NotesLoaderKSF.h NotesLoaderMidi.cpp NotesLoaderMidi.h \ +NotesLoaderPMS.cpp NotesLoaderPMS.h NotesLoaderSM.cpp NotesLoaderSM.h \ +NotesLoaderSSC.cpp NotesLoaderSSC.h NotesWriterDWI.cpp NotesWriterDWI.h \ +NotesWriterSM.cpp NotesWriterSM.h NotesWriterSSC.cpp NotesWriterSSC.h \ OptionRowHandler.cpp OptionRowHandler.h OptionsList.cpp OptionsList.h \ PlayerAI.cpp PlayerAI.h PlayerNumber.cpp PlayerNumber.h PlayerOptions.cpp PlayerOptions.h \ PlayerStageStats.cpp PlayerStageStats.h \ diff --git a/src/StepMania-net2003.vcproj b/src/StepMania-net2003.vcproj index 735fdfbfae..2a0cbf18f1 100644 --- a/src/StepMania-net2003.vcproj +++ b/src/StepMania-net2003.vcproj @@ -986,6 +986,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\ + + + + @@ -998,6 +1004,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\ + + + + diff --git a/src/StepMania-net2005.vcproj b/src/StepMania-net2005.vcproj index 3e5361521b..4b6dd086bc 100644 --- a/src/StepMania-net2005.vcproj +++ b/src/StepMania-net2005.vcproj @@ -1314,6 +1314,15 @@ RelativePath="NotesLoaderSM.h" > + + + + + @@ -1330,6 +1339,15 @@ RelativePath="NotesWriterSM.h" > + + + + + diff --git a/src/StepMania-net2008.vcproj b/src/StepMania-net2008.vcproj index 9acd60d62a..1fc2052b40 100644 --- a/src/StepMania-net2008.vcproj +++ b/src/StepMania-net2008.vcproj @@ -1456,6 +1456,15 @@ RelativePath="NotesLoaderSM.h" > + + + + + @@ -1472,6 +1481,15 @@ RelativePath="NotesWriterSM.h" > + + + + + From dbcfcd0cfcf5810abdbc4139d3d6d5f951d7cfc7 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:00:40 -0500 Subject: [PATCH 03/19] Disable caching for the SM format. --- src/NotesWriterSM.cpp | 26 ++++++-------------------- src/NotesWriterSM.h | 2 +- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/NotesWriterSM.cpp b/src/NotesWriterSM.cpp index b23a2ceb2a..285fa2311b 100644 --- a/src/NotesWriterSM.cpp +++ b/src/NotesWriterSM.cpp @@ -239,7 +239,7 @@ static RString JoinLineList( vector &lines ) return join( "\r\n", lines.begin()+j, lines.end() ); } -static RString GetSMNotesTag( const Song &song, const Steps &in, bool bSavingCache ) +static RString GetSMNotesTag( const Song &song, const Steps &in ) { vector lines; @@ -271,15 +271,11 @@ static RString GetSMNotesTag( const Song &song, const Steps &in, bool bSavingCac return JoinLineList( lines ); } -bool NotesWriterSM::Write( RString sPath, const Song &out, const vector& vpStepsToSave, bool bSavingCache ) +bool NotesWriterSM::Write( RString sPath, const Song &out ) { int flags = RageFile::WRITE; - /* If we're not saving cache, we're saving real data, so enable SLOW_FLUSH - * to prevent data loss. If we're saving cache, this will slow things down - * too much. */ - if( !bSavingCache ) - flags |= RageFile::SLOW_FLUSH; + flags |= RageFile::SLOW_FLUSH; RageFile f; if( !f.Open( sPath, flags ) ) @@ -289,23 +285,13 @@ bool NotesWriterSM::Write( RString sPath, const Song &out, const vector& } WriteGlobalTags( f, out ); - if( bSavingCache ) - { - f.PutLine( ssprintf( "// cache tags:" ) ); - f.PutLine( ssprintf( "#FIRSTBEAT:%.3f;", out.m_fFirstBeat ) ); - f.PutLine( ssprintf( "#LASTBEAT:%.3f;", out.m_fLastBeat ) ); - f.PutLine( ssprintf( "#SONGFILENAME:%s;", out.m_sSongFileName.c_str() ) ); - f.PutLine( ssprintf( "#HASMUSIC:%i;", out.m_bHasMusic ) ); - f.PutLine( ssprintf( "#HASBANNER:%i;", out.m_bHasBanner ) ); - f.PutLine( ssprintf( "#MUSICLENGTH:%.3f;", out.m_fMusicLengthSeconds ) ); - f.PutLine( ssprintf( "// end cache tags" ) ); - } // Save specified Steps to this file + const vector& vpStepsToSave = out.GetAllSteps(); FOREACH_CONST( Steps*, vpStepsToSave, s ) { const Steps* pSteps = *s; - RString sTag = GetSMNotesTag( out, *pSteps, bSavingCache ); + RString sTag = GetSMNotesTag( out, *pSteps ); f.PutLine( sTag ); } if( f.Flush() == -1 ) @@ -325,7 +311,7 @@ void NotesWriterSM::GetEditFileContents( const Song *pSong, const Steps *pSteps, if( asParts.size() ) sDir = join( "/", asParts.begin()+1, asParts.end() ); sOut += ssprintf( "#SONG:%s;\r\n", sDir.c_str() ); - sOut += GetSMNotesTag( *pSong, *pSteps, false ); + sOut += GetSMNotesTag( *pSong, *pSteps ); } RString NotesWriterSM::GetEditFileName( const Song *pSong, const Steps *pSteps ) diff --git a/src/NotesWriterSM.h b/src/NotesWriterSM.h index 046bedc9b5..7427248425 100644 --- a/src/NotesWriterSM.h +++ b/src/NotesWriterSM.h @@ -7,7 +7,7 @@ class Song; class Steps; namespace NotesWriterSM { - bool Write( RString sPath, const Song &out, const vector& vpStepsToSave, bool bSavingCache ); + bool Write( RString sPath, const Song &out ); void GetEditFileContents( const Song *pSong, const Steps *pSteps, RString &sOut ); RString GetEditFileName( const Song *pSong, const Steps *pSteps ); bool WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ); From 565b9f930665648561ed121f36108aaaffe3a7c6 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:10:57 -0500 Subject: [PATCH 04/19] Get SSCLoader used when appropriate. --- src/GameSoundManager.cpp | 4 ++-- src/NotesLoader.cpp | 10 ++++++--- src/NotesWriterSSC.h | 2 +- src/ScreenGameplaySyncMachine.cpp | 4 ++-- src/ScreenHowToPlay.cpp | 4 ++-- src/ScreenServiceAction.cpp | 4 ++-- src/Song.cpp | 34 ++++++++++++++++++++++--------- src/Song.h | 14 +++++++------ src/SongManager.cpp | 4 ++-- src/Steps.cpp | 4 ++-- 10 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/GameSoundManager.cpp b/src/GameSoundManager.cpp index 0e36042116..9034b86a34 100644 --- a/src/GameSoundManager.cpp +++ b/src/GameSoundManager.cpp @@ -6,7 +6,7 @@ #include "RageUtil.h" #include "GameState.h" #include "TimingData.h" -#include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include "PrefsManager.h" #include "RageDisplay.h" #include "AnnouncerManager.h" @@ -141,7 +141,7 @@ static void StartMusic( MusicToPlay &ToPlay ) { LOG->Trace( "Found '%s'", ToPlay.m_sTimingFile.c_str() ); Song song; - if( SMLoader::LoadFromSMFile(ToPlay.m_sTimingFile, song) ) + if( SSCLoader::LoadFromSSCFile(ToPlay.m_sTimingFile, song) ) { ToPlay.HasTiming = true; ToPlay.m_TimingData = song.m_Timing; diff --git a/src/NotesLoader.cpp b/src/NotesLoader.cpp index 6d12e2cb75..0d257c3c81 100644 --- a/src/NotesLoader.cpp +++ b/src/NotesLoader.cpp @@ -1,6 +1,7 @@ #include "global.h" #include "NotesLoader.h" #include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include "NotesLoaderDWI.h" #include "NotesLoaderBMS.h" #include "NotesLoaderPMS.h" @@ -30,14 +31,17 @@ bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set &Bl vector list; BlacklistedImages.clear(); - SMLoader::GetApplicableFiles( sPath, list ); + SSCLoader::GetApplicableFiles( sPath, list ); if( !list.empty() ) { - if( !SMLoader::LoadFromDir(sPath, out) ) + if( !SSCLoader::LoadFromDir( sPath, out ) ) return false; - SMLoader::TidyUpData( out, false ); + SSCLoader::TidyUpData( out, false ); return true; } + SMLoader::GetApplicableFiles( sPath, list ); + if (!list.empty() ) + return SMLoader::LoadFromDir( sPath, out ); DWILoader::GetApplicableFiles( sPath, list ); if( !list.empty() ) return DWILoader::LoadFromDir( sPath, out, BlacklistedImages ); diff --git a/src/NotesWriterSSC.h b/src/NotesWriterSSC.h index e8d544d43e..118a37987a 100644 --- a/src/NotesWriterSSC.h +++ b/src/NotesWriterSSC.h @@ -1,4 +1,4 @@ -/* NotesWriterSM - Writes a Song to an .SSC file. */ +/* NotesWriterSSC - Writes a Song to an .SSC file. */ #ifndef NOTES_WRITER_SSC_H #define NOTES_WRITER_SSC_H diff --git a/src/ScreenGameplaySyncMachine.cpp b/src/ScreenGameplaySyncMachine.cpp index ed6bee0814..30d7dad981 100644 --- a/src/ScreenGameplaySyncMachine.cpp +++ b/src/ScreenGameplaySyncMachine.cpp @@ -1,6 +1,6 @@ #include "global.h" #include "ScreenGameplaySyncMachine.h" -#include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include "GameState.h" #include "GameManager.h" #include "PrefsManager.h" @@ -19,7 +19,7 @@ void ScreenGameplaySyncMachine::Init() AdjustSync::ResetOriginalSyncData(); RString sFile = THEME->GetPathO("ScreenGameplaySyncMachine","music.sm"); - SMLoader::LoadFromSMFile( sFile, m_Song ); + SSCLoader::LoadFromSSCFile( sFile, m_Song ); m_Song.SetSongDir( Dirname(sFile) ); m_Song.TidyUpData(); diff --git a/src/ScreenHowToPlay.cpp b/src/ScreenHowToPlay.cpp index 552eee8783..d5627f66d9 100644 --- a/src/ScreenHowToPlay.cpp +++ b/src/ScreenHowToPlay.cpp @@ -4,7 +4,7 @@ #include "GameState.h" #include "Steps.h" #include "GameManager.h" -#include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include "GameSoundManager.h" #include "Model.h" #include "ThemeMetric.h" @@ -132,7 +132,7 @@ void ScreenHowToPlay::Init() ActorUtil::LoadAllCommandsAndSetXY( m_pLifeMeterBar, m_sName ); m_pLifeMeterBar->FillForHowToPlay( NUM_W2S, NUM_MISSES ); - SMLoader::LoadFromSMFile( THEME->GetPathO(m_sName, "steps"), m_Song, false ); + SSCLoader::LoadFromSSCFile( THEME->GetPathO(m_sName, "steps"), m_Song, false ); m_Song.AddAutoGenNotes(); const Style* pStyle = GAMESTATE->GetCurrentStyle(); diff --git a/src/ScreenServiceAction.cpp b/src/ScreenServiceAction.cpp index 638d8bd4b1..d223c52299 100644 --- a/src/ScreenServiceAction.cpp +++ b/src/ScreenServiceAction.cpp @@ -15,7 +15,7 @@ #include "PlayerState.h" #include "LocalizedString.h" #include "StepMania.h" -#include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" static LocalizedString BOOKKEEPING_DATA_CLEARED( "ScreenServiceAction", "Bookkeeping data cleared." ); static RString ClearBookkeepingData() @@ -197,7 +197,7 @@ static void CopyEdits( const RString &sFromProfileDir, const RString &sToProfile iNumErrored++; // Test whether the song we need for this edit is present and ignore this edit if not present. - if( !SMLoader::LoadEditFromFile( sFromDir+*i, ProfileSlot_Machine, false ) ) + if( !SSCLoader::LoadEditFromFile( sFromDir+*i, ProfileSlot_Machine, false ) ) { iNumIgnored++; continue; diff --git a/src/Song.cpp b/src/Song.cpp index 9baf705190..55d0545c8b 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -25,8 +25,10 @@ #include "SpecialFiles.h" #include "NotesLoader.h" #include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include "NotesWriterDWI.h" #include "NotesWriterSM.h" +#include "NotesWriterSSC.h" #include "UnlockManager.h" #include "LyricsLoader.h" @@ -34,7 +36,7 @@ #include #include -const int FILE_CACHE_VERSION = 162; // increment this to invalidate cache +const int FILE_CACHE_VERSION = 163; // increment this to invalidate cache const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; @@ -214,8 +216,8 @@ bool Song::LoadFromSongDir( RString sDir ) if( bUseCache ) { // LOG->Trace( "Loading '%s' from cache file '%s'.", m_sSongDir.c_str(), GetCacheFilePath().c_str() ); - SMLoader::LoadFromSMFile( sCacheFilePath, *this, true ); - SMLoader::TidyUpData( *this, true ); + SSCLoader::LoadFromSSCFile( sCacheFilePath, *this, true ); + SSCLoader::TidyUpData( *this, true ); } else { @@ -844,9 +846,10 @@ void Song::Save() TranslateTitles(); // Save the new files. These calls make backups on their own. - if( !SaveToSMFile(GetSongFilePath(), false) ) + if( !SaveToSSCFile(GetSongFilePath(), false) ) return; - SaveToDWIFile(); + SaveToSMFile(); + //SaveToDWIFile(); SaveToCacheFile(); /* We've safely written our files and created backups. Rename non-SM and @@ -871,10 +874,21 @@ void Song::Save() } } - -bool Song::SaveToSMFile( RString sPath, bool bSavingCache ) +bool Song::SaveToSMFile() { - LOG->Trace( "Song::SaveToSMFile('%s')", sPath.c_str() ); + const RString sPath = SetExtension( GetSongFilePath(), "sm" ); + LOG->Trace( "Song::SaveToSMFile(%s)", sPath.c_str() ); + + // If the file exists, make a backup. + if( IsAFile(sPath) ) + FileCopy( sPath, sPath + ".old" ); + return NotesWriterSM::Write( sPath, *this ); + +} + +bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) +{ + LOG->Trace( "Song::SaveToSSCFile('%s')", sPath.c_str() ); // If the file exists, make a backup. if( !bSavingCache && IsAFile(sPath) ) @@ -894,7 +908,7 @@ bool Song::SaveToSMFile( RString sPath, bool bSavingCache ) vpStepsToSave.push_back( pSteps ); } - if( !NotesWriterSM::Write(sPath, *this, vpStepsToSave, bSavingCache) ) + if( !NotesWriterSSC::Write(sPath, *this, vpStepsToSave, bSavingCache) ) return false; if( !bSavingCache && g_BackUpAllSongSaves.Get() ) @@ -932,7 +946,7 @@ bool Song::SaveToCacheFile() { SONGINDEX->AddCacheIndex(m_sSongDir, GetHashForDirectory(m_sSongDir)); const RString sPath = GetCacheFilePath(); - if( !SaveToSMFile(sPath, true) ) + if( !SaveToSSCFile(sPath, true) ) return false; FOREACH( Steps*, m_vpSteps, pSteps ) diff --git a/src/Song.h b/src/Song.h index cb439ef08c..3aa36f025b 100644 --- a/src/Song.h +++ b/src/Song.h @@ -61,7 +61,7 @@ public: enum SelectionDisplay { SHOW_ALWAYS, // always - SHOW_NEVER // never (unless song hiding is turned off) + SHOW_NEVER // never (unless song hiding is turned off) } m_SelectionDisplay; Song(); @@ -78,9 +78,10 @@ public: void ReCalculateRadarValuesAndLastBeat(); // called by TidyUpData, and after saving void TranslateTitles(); // called by TidyUpData - bool SaveToSMFile( RString sPath, bool bSavingCache ); - void Save(); // saves SM and DWI + bool SaveToSSCFile( RString sPath, bool bSavingCache ); + void Save(); // saves SSC and SM guaranteed. bool SaveToCacheFile(); + bool SaveToSMFile(); bool SaveToDWIFile(); const RString &GetSongFilePath() const; @@ -93,9 +94,10 @@ public: // Directory this song data came from: const RString &GetSongDir() const { return m_sSongDir; } - /* Filename associated with this file. This will always have an .SM extension. - * If we loaded an .SM, this will point to it, but if we loaded any other - * type, this will point to a generated .SM filename. */ + /* Filename associated with this file. This will always have a + * .SSC extension. If we loaded a .SSC, this will point to it, + * but if we loaded any other type, this will point to a + * generated .SSC filename. */ RString m_sSongFileName; RString m_sGroupName; diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 5679bb821c..2d9bb1f737 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -15,7 +15,7 @@ #include "MsdFile.h" #include "NoteSkinManager.h" #include "NotesLoaderDWI.h" -#include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include "PrefsManager.h" #include "Profile.h" #include "ProfileManager.h" @@ -1653,7 +1653,7 @@ void SongManager::LoadStepEditsFromProfileDir( const RString &sProfileDir, Profi { RString fn = vsFiles[i]; - SMLoader::LoadEditFromFile( fn, slot, true ); + SSCLoader::LoadEditFromFile( fn, slot, true ); } } } diff --git a/src/Steps.cpp b/src/Steps.cpp index e4349e0aea..70815cf0fe 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -20,7 +20,7 @@ #include "NoteData.h" #include "GameManager.h" #include "NoteDataUtil.h" -#include "NotesLoaderSM.h" +#include "NotesLoaderSSC.h" #include @@ -231,7 +231,7 @@ void Steps::Decompress() const { /* We have data on disk and not in memory. Load it. */ Song s; - if( !SMLoader::LoadFromSMFile(m_sFilename, s, true) ) + if( !SSCLoader::LoadFromSSCFile(m_sFilename, s, true) ) { LOG->Warn( "Couldn't load \"%s\"", m_sFilename.c_str() ); return; From dd28dc7332e729426c3b294cdac5d3399c9c0edd Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:25:51 -0500 Subject: [PATCH 05/19] Use .ssc files now. --- ...sm => ScreenGameplaySyncMachine music.ssc} | 32 ++++----- ...lay steps.sm => ScreenHowToPlay steps.ssc} | 69 ++++--------------- 2 files changed, 26 insertions(+), 75 deletions(-) rename Themes/_fallback/Other/{ScreenGameplaySyncMachine music.sm => ScreenGameplaySyncMachine music.ssc} (70%) rename Themes/_fallback/Other/{ScreenHowToPlay steps.sm => ScreenHowToPlay steps.ssc} (62%) diff --git a/Themes/_fallback/Other/ScreenGameplaySyncMachine music.sm b/Themes/_fallback/Other/ScreenGameplaySyncMachine music.ssc similarity index 70% rename from Themes/_fallback/Other/ScreenGameplaySyncMachine music.sm rename to Themes/_fallback/Other/ScreenGameplaySyncMachine music.ssc index 4e31da0de3..a190bf0d9d 100644 --- a/Themes/_fallback/Other/ScreenGameplaySyncMachine music.sm +++ b/Themes/_fallback/Other/ScreenGameplaySyncMachine music.ssc @@ -1,4 +1,4 @@ -#TITLE:Sync; +#TITLE:Sync; #SUBTITLE:; #ARTIST:Unknown artist; #TITLETRANSLIT:; @@ -20,18 +20,15 @@ #BGCHANGES:; #KEYSOUNDS:; -//---------------dance-single - blank---------------- +#NOTEDATA:; +#STEPSTYPE:dance-single:; +#DESCRIPTION:blank; +#DIFFICULTY:Beginner; +#METER:1; +#RADARVALUES:0.302,0.205,0.000,0.000,0.000; #NOTES: - dance-single: - blank: - Beginner: - 1: - 0.302,0.205,0.000,0.000,0.000: // measure 1 0000 -0000 -0000 -0000 , // measure 2 1000 0001 @@ -97,18 +94,15 @@ 0001 ; -//---------------dance-single - blank---------------- +#NOTEDATA:; +#STEPSTYPE:pump-single; +#DESCRIPTION:blank; +#DIFFICULTY:Beginner; +#METER:1; +#RADARVALUES:0.302,0.205,0.000,0.000,0.000; #NOTES: - pump-single: - blank: - Beginner: - 1: - 0.302,0.205,0.000,0.000,0.000: // measure 1 00000 -00000 -00000 -00000 , // measure 2 10000 00001 diff --git a/Themes/_fallback/Other/ScreenHowToPlay steps.sm b/Themes/_fallback/Other/ScreenHowToPlay steps.ssc similarity index 62% rename from Themes/_fallback/Other/ScreenHowToPlay steps.sm rename to Themes/_fallback/Other/ScreenHowToPlay steps.ssc index 5cf0a1c9af..1b43422f65 100644 --- a/Themes/_fallback/Other/ScreenHowToPlay steps.sm +++ b/Themes/_fallback/Other/ScreenHowToPlay steps.ssc @@ -6,44 +6,28 @@ // a song file here. #TITLE:ScreenHowToPlay steps; #ARTIST:foo; -#BPMS:0.000=100.000 +#BPMS:0.000=100.000; #STOPS:16=2,18=2,20=2,22=2; - +#NOTEDATA:; +#STEPSTYPE:dance-single; +#DESCRIPTION:BASIC; +#DIFFICULTY:easy; +#METER:1; +#RADARVALUES:0,0,0,0,0; #NOTES: - dance-single: - BASIC: - easy: - 1: - 0,0,0,0,0: -0000 -0000 -0000 0000 , 0000 -0000 -0000 -0000 , 0000 -0000 -0000 -0000 , 0000 -0000 -0000 -0000 , 0100 -0000 0010 -0000 , 1000 -0000 1001 -0000 , 1000 0100 @@ -79,51 +63,30 @@ 0000 , 0000 -0000 -0000 -0000 , 0000 -0000 -0000 -0000 ,; +#NOTEDATA:; +#STEPSTYPE:pump-single; +#DESCRIPTION:NORMAL; +#DIFFICULTY:easy; +#METER:1; +#RADARVALUES:0,0,0,0,0; #NOTES: - pump-single: - NORMAL: - easy: - 1: - 0,0,0,0,0: -00000 -00000 -00000 00000 , 00000 -00000 -00000 -00000 , 00000 -00000 -00000 -00000 , 00000 -00000 -00000 -00000 , 01000 -00000 00100 -00000 , 10000 -00000 10010 -00000 , 10000 01000 @@ -159,12 +122,6 @@ 00000 , 00000 -00000 -00000 -00000 , 00000 -00000 -00000 -00000 ,; From 106a32fa845be531009f6d6ca13e5d8ee2c8352a Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:29:18 -0500 Subject: [PATCH 06/19] Language. --- Themes/_fallback/Languages/en.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 3da0275e45..e0c819f4f3 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1373,6 +1373,10 @@ Error deleting the file '%s'.=Error deleting the file "%s". Error renaming file. Destination file '%s' already exists.=Error renaming file. Destination file '%s' already exists. Error writing file '%s'.=Error writing file '%s'. +[NotesWriterSSC] +Error renaming file. Destination file '%s' already exists.=Error renaming file. Destination file '%s' already exists. +Error writing file '%s'.=Error writing file '%s'. + [ScreenOptionsManageProfiles] Are you sure you want to clear all data in the profile '%s'?=Are you sure you want to clear all data in the profile &oq;%s&cq;? Are you sure you want to delete the profile '%s'?=Are you sure you want to delete the profile &oq;%s&cq;? From 295e494e9ea4b6180bedc4a4d70215a7767ad67c Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:31:22 -0500 Subject: [PATCH 07/19] Typo. --- src/NotesWriterSSC.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 914718e5fa..3a41dde97c 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -280,10 +280,10 @@ static RString GetSSCNotesTag( const Song &song, const Steps &in, bool bSavingCa // TODO: Remove this block, uncommon below block for Split Timing. -Wolfman2000 lines.push_back( "#BPMS:;" ); lines.push_back( "#STOPS:;" ); - lines.push_back( "DELAYS:;" ); - lines.push_back( "TIMESIGNATURES:;" ); - lines.push_back( "TICKCOUNTS:;" ); - // lines.push_back( "COMBOS:;" ); + lines.push_back( "#DELAYS:;" ); + lines.push_back( "#TIMESIGNATURES:;" ); + lines.push_back( "#TICKCOUNTS:;" ); + // lines.push_back( "#COMBOS:;" ); /* vector asBPMValues; From 16d6138a6292dfa14899f7af1ea39642d5b01987 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:44:32 -0500 Subject: [PATCH 08/19] Remove hardcoded extension. ScreenHowToPlay follows a similar pattern. --- src/ScreenGameplaySyncMachine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScreenGameplaySyncMachine.cpp b/src/ScreenGameplaySyncMachine.cpp index 30d7dad981..62ca1d3d29 100644 --- a/src/ScreenGameplaySyncMachine.cpp +++ b/src/ScreenGameplaySyncMachine.cpp @@ -18,7 +18,7 @@ void ScreenGameplaySyncMachine::Init() GAMESTATE->SetCurrentStyle( GAMEMAN->GetHowToPlayStyleForGame(GAMESTATE->m_pCurGame) ); AdjustSync::ResetOriginalSyncData(); - RString sFile = THEME->GetPathO("ScreenGameplaySyncMachine","music.sm"); + RString sFile = THEME->GetPathO("ScreenGameplaySyncMachine","music"); SSCLoader::LoadFromSSCFile( sFile, m_Song ); m_Song.SetSongDir( Dirname(sFile) ); m_Song.TidyUpData(); From 35748ed9a79a9352dc9f758bed13d9baea29b07a Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 00:45:00 -0500 Subject: [PATCH 09/19] Update comments, tidy. --- src/MsdFile.h | 2 +- src/RageFileManager.cpp | 4 ++-- src/Song.cpp | 33 +++++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/MsdFile.h b/src/MsdFile.h index b18ed60342..7086b44c2c 100644 --- a/src/MsdFile.h +++ b/src/MsdFile.h @@ -1,4 +1,4 @@ -/* MsdFile - Read .SM, .DWI, and .MSD files. */ +/* MsdFile - Read .SSC, .SM, .DWI, and .MSD files. */ #ifndef MSDFILE_H #define MSDFILE_H diff --git a/src/RageFileManager.cpp b/src/RageFileManager.cpp index 22304916b1..deaa84bc2c 100644 --- a/src/RageFileManager.cpp +++ b/src/RageFileManager.cpp @@ -838,8 +838,8 @@ RageFileBasic *RageFileManager::OpenForWriting( const RString &sPath, int mode, * * The purpose of this is to create files in the expected place. For example, if we * have both C:/games/StepMania and C:/games/DWI loaded, and we're writing - * "Songs/Music/Waltz/waltz.sm", and the song was loaded out of - * "C:/games/DWI/Songs/Music/Waltz/waltz.dwi", we want to write the new SM into the + * "Songs/Music/Waltz/waltz.ssc", and the song was loaded out of + * "C:/games/DWI/Songs/Music/Waltz/waltz.dwi", we want to write the new SSC into the * same directory (if possible). Don't split up files in the same directory any * more than we have to. * diff --git a/src/Song.cpp b/src/Song.cpp index 55d0545c8b..4f44ac884a 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -709,7 +709,7 @@ void Song::TidyUpData() { /* Generated filename; this doesn't always point to a loadable file, * but instead points to the file we should write changed files to, - * and will always be an .SM. + * and will always be a .SSC. * * This is a little tricky. We can't always use the song title directly, * since it might contain characters we can't store in filenames. Two @@ -723,23 +723,32 @@ void Song::TidyUpData() * but not KSFs and BMSs. * * So, let's do this (by priority): - * 1. If there's an .SM file, use that filename. No reason to use anything + * 1. If there's a .SSC file, use that filename. No reason to use anything * else; it's the filename in use. - * 2. If there's a .DWI, use it with a changed extension. - * 3. Otherwise, use the name of the directory, since it's definitely a valid + * 2. If there's an .SM, use it with a changed extension. + * 3. If there's a .DWI, use it with a changed extension. + * 4. Otherwise, use the name of the directory, since it's definitely a valid * filename, and should always be the title of the song (unlike KSFs). */ m_sSongFileName = m_sSongDir; vector asFileNames; - GetDirListing( m_sSongDir+"*.sm", asFileNames ); + GetDirListing( m_sSongDir+"*.ssc", asFileNames ); if( !asFileNames.empty() ) + { m_sSongFileName += asFileNames[0]; - else { - GetDirListing( m_sSongDir+"*.dwi", asFileNames ); - if( !asFileNames.empty() ) { - m_sSongFileName += SetExtension( asFileNames[0], "sm" ); - } else { - m_sSongFileName += Basename(m_sSongDir); - m_sSongFileName += ".sm"; + } + else + { + GetDirListing( m_sSongDir+"*.sm", asFileNames ); + if( !asFileNames.empty() ) + m_sSongFileName += SetExtension( asFileNames[0], "ssc" ); + else { + GetDirListing( m_sSongDir+"*.dwi", asFileNames ); + if( !asFileNames.empty() ) { + m_sSongFileName += SetExtension( asFileNames[0], "ssc" ); + } else { + m_sSongFileName += Basename(m_sSongDir); + m_sSongFileName += ".ssc"; + } } } } From abd9a446e455deb9e80b2a6179330f4ee2efb553 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:05:46 -0500 Subject: [PATCH 10/19] Add requested VERSION tag. --- src/NotesLoaderSSC.cpp | 5 +++++ src/NotesWriterSSC.cpp | 1 + src/Song.cpp | 3 +++ src/Song.h | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index b36143fd8c..2a8500c807 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -212,6 +212,11 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach out.m_sArtistTranslit = sParams[1]; } + else if( sValueName=="VERSION" ) + { + out.m_fVersion = StringToFloat( sParams[1] ); + } + else if( sValueName=="GENRE" ) { out.m_sGenre = sParams[1]; diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 3a41dde97c..365a490493 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -46,6 +46,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) f.PutLine( ssprintf( "#TITLETRANSLIT:%s;", SmEscape(out.m_sMainTitleTranslit).c_str() ) ); f.PutLine( ssprintf( "#SUBTITLETRANSLIT:%s;", SmEscape(out.m_sSubTitleTranslit).c_str() ) ); f.PutLine( ssprintf( "#ARTISTTRANSLIT:%s;", SmEscape(out.m_sArtistTranslit).c_str() ) ); + f.PutLine( ssprintf( "#VERSION:%f;", out.m_fVersion ) ); f.PutLine( ssprintf( "#GENRE:%s;", SmEscape(out.m_sGenre).c_str() ) ); f.PutLine( ssprintf( "#CREDIT:%s;", SmEscape(out.m_sCredit).c_str() ) ); f.PutLine( ssprintf( "#BANNER:%s;", SmEscape(out.m_sBannerFile).c_str() ) ); diff --git a/src/Song.cpp b/src/Song.cpp index 4f44ac884a..a4b62e0bab 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -52,6 +52,8 @@ static const char *InstrumentTrackNames[] = { XToString( InstrumentTrack ); StringToX( InstrumentTrack ); +const static float VERSION_NUMBER = 0.5f; + Song::Song() { FOREACH_BackgroundLayer( i ) @@ -59,6 +61,7 @@ Song::Song() m_ForegroundChanges = AutoPtrCopyOnWrite(new VBackgroundChange); m_LoadedFromProfile = ProfileSlot_Invalid; + m_fVersion = VERSION_NUMBER; m_fMusicSampleStartSeconds = -1; m_fMusicSampleLengthSeconds = DEFAULT_MUSIC_SAMPLE_LENGTH; m_fMusicLengthSeconds = 0; diff --git a/src/Song.h b/src/Song.h index 3aa36f025b..4fb810754b 100644 --- a/src/Song.h +++ b/src/Song.h @@ -123,6 +123,9 @@ public: // "title subtitle" RString GetDisplayFullTitle() const; RString GetTranslitFullTitle() const; + + // allow versioning with the song. + float m_fVersion; RString m_sGenre; From 985a62e504deaae61fdcc101a0863b0b803cfa2d Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:17:24 -0500 Subject: [PATCH 11/19] Activate Step's #CREDIT tag. --- src/NotesLoaderSSC.cpp | 2 +- src/NotesWriterSSC.cpp | 2 +- src/Steps.cpp | 8 +++++++- src/Steps.h | 5 ++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 2a8500c807..8f5d0dc1c3 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -717,7 +717,7 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach else if( sValueName=="CREDIT" ) { - ; // Not implemented yet, but should be. + pNewNotes->SetCredit( sParams[1] ); } else if( sValueName=="NOTES" ) diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 365a490493..97c3458d7b 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -276,7 +276,7 @@ static RString GetSSCNotesTag( const Song &song, const Steps &in, bool bSavingCa } lines.push_back( ssprintf( "#RADARVALUES:%s:", join(",",asRadarValues).c_str() ) ); - lines.push_back( "#CREDIT:;" ); // will be done later. + lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) ); // TODO: Remove this block, uncommon below block for Split Timing. -Wolfman2000 lines.push_back( "#BPMS:;" ); diff --git a/src/Steps.cpp b/src/Steps.cpp index 70815cf0fe..4fad419d41 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -322,7 +322,7 @@ void Steps::DeAutogen( bool bCopyNoteData ) m_Difficulty = Real()->m_Difficulty; m_iMeter = Real()->m_iMeter; copy( Real()->m_CachedRadarValues, Real()->m_CachedRadarValues + NUM_PLAYERS, m_CachedRadarValues ); - + m_sCredit = Real()->m_sCredit; parent = NULL; if( bCopyNoteData ) @@ -366,6 +366,12 @@ void Steps::SetDifficultyAndDescription( Difficulty dc, RString sDescription ) MakeValidEditDescription( m_sDescription ); } +void Steps::SetCredit( RString sCredit ) +{ + DeAutogen(); + m_sCredit = sCredit; +} + bool Steps::MakeValidEditDescription( RString &sPreferredDescription ) { if( int(sPreferredDescription.size()) > MAX_EDIT_STEPS_DESCRIPTION_LENGTH ) diff --git a/src/Steps.h b/src/Steps.h index 77d0fa3359..36c7f382a5 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -40,6 +40,7 @@ public: Difficulty GetDifficulty() const { return Real()->m_Difficulty; } int GetMeter() const { return Real()->m_iMeter; } const RadarValues& GetRadarValues( PlayerNumber pn ) const { return Real()->m_CachedRadarValues[pn]; } + RString GetCredit() const { return Real()->m_sCredit; } void SetFilename( RString fn ) { m_sFilename = fn; } RString GetFilename() const { return m_sFilename; } @@ -48,6 +49,7 @@ public: void SetDifficulty( Difficulty dc ) { SetDifficultyAndDescription( dc, GetDescription() ); } void SetDescription( RString sDescription ) { SetDifficultyAndDescription( this->GetDifficulty(), sDescription ); } void SetDifficultyAndDescription( Difficulty dc, RString sDescription ); + void SetCredit( RString sCredit ); static bool MakeValidEditDescription( RString &sPreferredDescription ); // return true if was modified void SetLoadedFromProfile( ProfileSlot slot ) { m_LoadedFromProfile = slot; } @@ -92,10 +94,11 @@ private: /* These values are pulled from the autogen source first, if there is one. */ mutable unsigned m_iHash; // only used if m_Difficulty == Difficulty_Edit - RString m_sDescription; // Step author, edit name, or something meaningful + RString m_sDescription; // Edit name, or something meaningful Difficulty m_Difficulty; // difficulty classification int m_iMeter; // difficulty rating from MIN_METER to MAX_METER RadarValues m_CachedRadarValues[NUM_PLAYERS]; + RString m_sCredit; // Step author }; #endif From f8694fe463ec1fd50f9c902186047bf153883854 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:50:23 -0500 Subject: [PATCH 12/19] Add utility methods for step's #CREDIT tag. --- src/SongUtil.cpp | 45 +++++++++++++++++++++++++++++++++++++++++---- src/SongUtil.h | 10 +++++++--- src/StepsUtil.cpp | 4 ++-- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index b206cf1fd2..11b102ddcd 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -97,7 +97,8 @@ void SongUtil::GetSteps( Difficulty dc, int iMeterLow, int iMeterHigh, - const RString &sDescription, + const RString &sDescription, + const RString &sCredit, bool bIncludeAutoGen, unsigned uHash, int iMaxToGet @@ -119,6 +120,8 @@ void SongUtil::GetSteps( continue; if( sDescription.size() && sDescription != pSteps->GetDescription() ) continue; + if( sCredit.size() && sCredit != pSteps->GetCredit() ) + continue; if( uHash != 0 && uHash != pSteps->GetHash() ) continue; if( !bIncludeAutoGen && pSteps->IsAutogen() ) @@ -141,13 +144,14 @@ Steps* SongUtil::GetOneSteps( Difficulty dc, int iMeterLow, int iMeterHigh, - const RString &sDescription, + const RString &sDescription, + const RString &sCredit, unsigned uHash, bool bIncludeAutoGen ) { vector vpSteps; - GetSteps( pSong, vpSteps, st, dc, iMeterLow, iMeterHigh, sDescription, bIncludeAutoGen, uHash, 1 ); // get max 1 + GetSteps( pSong, vpSteps, st, dc, iMeterLow, iMeterHigh, sDescription, sCredit, bIncludeAutoGen, uHash, 1 ); // get max 1 if( vpSteps.empty() ) return NULL; else @@ -193,13 +197,23 @@ Steps* SongUtil::GetStepsByMeter( const Song *pSong, StepsType st, int iMeterLow Steps* SongUtil::GetStepsByDescription( const Song *pSong, StepsType st, RString sDescription ) { vector vNotes; - GetSteps( pSong, vNotes, st, Difficulty_Invalid, -1, -1, sDescription ); + GetSteps( pSong, vNotes, st, Difficulty_Invalid, -1, -1, sDescription, "" ); if( vNotes.size() == 0 ) return NULL; else return vNotes[0]; } +Steps* SongUtil::GetStepsByCredit( const Song *pSong, StepsType st, RString sCredit ) +{ + vector vNotes; + GetSteps(pSong, vNotes, st, Difficulty_Invalid, -1, -1, "", sCredit ); + if( vNotes.size() == 0 ) + return NULL; + else + return vNotes[0]; +} + Steps* SongUtil::GetClosestNotes( const Song *pSong, StepsType st, Difficulty dc, bool bIgnoreLocked ) { @@ -803,6 +817,29 @@ bool SongUtil::ValidateCurrentStepsDescription( const RString &sAnswer, RString return true; } +static LocalizedString AUTHOR_NAME_CANNOT_CONTAIN( "SongUtil", "The step author's name cannot contain any of the following characters: %s" ); + +bool SongUtil::ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ) +{ + if( sAnswer.empty() ) + return true; + + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + // If unchanged: + if( pSteps->GetCredit() == sAnswer ) + return true; + + // Borrow from EditDescription testing. Perhaps this should be abstracted? -Wolfman2000 + static const RString sInvalidChars = "\\/:*?\"<>|"; + if( strpbrk(sAnswer, sInvalidChars) != NULL ) + { + sErrorOut = ssprintf( AUTHOR_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); + return false; + } + + return true; +} + void SongUtil::GetAllSongGenres( vector &vsOut ) { set genres; diff --git a/src/SongUtil.h b/src/SongUtil.h index 7dabb69e88..be42f8a7c9 100644 --- a/src/SongUtil.h +++ b/src/SongUtil.h @@ -70,7 +70,8 @@ namespace SongUtil Difficulty dc = Difficulty_Invalid, int iMeterLow = -1, int iMeterHigh = -1, - const RString &sDescription = "", + const RString &sDescription = "", + const RString &sCredit = "", bool bIncludeAutoGen = true, unsigned uHash = 0, int iMaxToGet = -1 @@ -81,15 +82,17 @@ namespace SongUtil Difficulty dc = Difficulty_Invalid, int iMeterLow = -1, int iMeterHigh = -1, - const RString &sDescription = "", + const RString &sDescription = "", + const RString &sCredit = "", unsigned uHash = 0, bool bIncludeAutoGen = true ); Steps* GetStepsByDifficulty( const Song *pSong, StepsType st, Difficulty dc, bool bIncludeAutoGen = true ); Steps* GetStepsByMeter( const Song *pSong, StepsType st, int iMeterLow, int iMeterHigh ); Steps* GetStepsByDescription( const Song *pSong, StepsType st, RString sDescription ); + Steps* GetStepsByCredit( const Song *pSong, StepsType st, RString sCredit ); Steps* GetClosestNotes( const Song *pSong, StepsType st, Difficulty dc, bool bIgnoreLocked=false ); - + void AdjustDuplicateSteps( Song *pSong ); // part of TidyUpData void DeleteDuplicateSteps( Song *pSong, vector &vSteps ); @@ -115,6 +118,7 @@ namespace SongUtil RString MakeUniqueEditDescription( const Song* pSong, StepsType st, const RString &sPreferredDescription ); bool ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ); bool ValidateCurrentStepsDescription( const RString &sAnswer, RString &sErrorOut ); + bool ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ); void GetAllSongGenres( vector &vsOut ); void FilterSongs( const SongCriteria &sc, const vector &in, vector &out ); diff --git a/src/StepsUtil.cpp b/src/StepsUtil.cpp index 30dee5b56c..f2f4afd9f5 100644 --- a/src/StepsUtil.cpp +++ b/src/StepsUtil.cpp @@ -258,11 +258,11 @@ Steps *StepsID::ToSteps( const Song *p, bool bAllowNull ) const Steps *pRet = NULL; if( dc == Difficulty_Edit ) { - pRet = SongUtil::GetOneSteps( p, st, dc, -1, -1, sDescription, uHash, true ); + pRet = SongUtil::GetOneSteps( p, st, dc, -1, -1, sDescription, "", uHash, true ); } else { - pRet = SongUtil::GetOneSteps( p, st, dc, -1, -1, "", 0, true ); + pRet = SongUtil::GetOneSteps( p, st, dc, -1, -1, "", "", 0, true ); } if( !bAllowNull && pRet == NULL ) From ba55653468f1154698801a4499046da07a48b6e1 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:51:01 -0500 Subject: [PATCH 13/19] Allow modifying the step's #CREDIT tag. --- Themes/_fallback/Languages/en.ini | 3 +++ src/ScreenEdit.cpp | 21 +++++++++++++++++++++ src/ScreenEdit.h | 1 + 3 files changed, 25 insertions(+) diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index e0c819f4f3..032631c80e 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -989,6 +989,7 @@ SoundResampleQuality=Resampling Quality SoundVolume=Sound Volume SoundVolumeAttract=Attract Volume Speed=Speed +Step Author=Step Author Steps=Steps Stream=Stream Style=Style @@ -1204,6 +1205,7 @@ Enter a new artist.=Enter a new artist. Enter a new genre.=Enter a new genre. Enter a new credit.=Enter a new credit. Enter a new description.=Enter a new description. +Enter the author who made this step pattern.=Enter the author who made this step pattern. Enter a new last beat hint.=Enter a new last beat hint. Enter a new main title transliteration.=Enter a new main title transliteration. Enter a new main title.=Enter a new main title. @@ -1608,6 +1610,7 @@ The folder "%s" appears to be a song folder. All song folders must reside in a You must supply a name for your new edit.=You must supply a name for your new edit. The name you chose conflicts with another edit. Please use a different name.=The name you chose conflicts with another edit. Please use a different name. The edit name cannot contain any of the following characters: %s=The edit name can not contain any of the following characters: %s +The step author's name cannot contain any of the following characters: %s=The step author's name cannot contain any of the following characters: %s [SongSort] FewestPlays=Fewest Plays diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index ca664c2762..5b196d8bcc 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -542,6 +542,7 @@ static MenuDef g_StepsInformation( // xxx: this giant list of numbers SUUUUUUUUUUCKS -aj MenuRowDef( ScreenEdit::meter, "Meter", true, EditMode_Practice, true, false, 0, "1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25" ), MenuRowDef( ScreenEdit::description, "Description", true, EditMode_Practice, true, true, 0, NULL ), + MenuRowDef( ScreenEdit::step_credit, "Step Author", true, EditMode_Practice, true, true, 0, NULL ), MenuRowDef( ScreenEdit::predict_meter, "Predicted Meter", false, EditMode_Full, true, true, 0, NULL ), MenuRowDef( ScreenEdit::tap_notes, "Tap Steps", false, EditMode_Full, true, true, 0, NULL ), MenuRowDef( ScreenEdit::jumps, "Jumps", false, EditMode_Full, true, true, 0, NULL ), @@ -2862,6 +2863,12 @@ static void ChangeDescription( const RString &sNew ) pSteps->SetDescription(sNew); } +static void ChangeStepCredit( const RString &sNew ) +{ + Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + pSteps->SetCredit(sNew); +} + static void ChangeMainTitle( const RString &sNew ) { Song* pSong = GAMESTATE->m_pCurSong; @@ -3003,6 +3010,8 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns g_StepsInformation.rows[predict_meter].SetOneUnthemedChoice( ssprintf("%.2f",pSteps->PredictMeter()) ); g_StepsInformation.rows[description].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); g_StepsInformation.rows[description].SetOneUnthemedChoice( pSteps->GetDescription() ); + g_StepsInformation.rows[step_credit].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); + g_StepsInformation.rows[step_credit].SetOneUnthemedChoice( pSteps->GetCredit() ); g_StepsInformation.rows[tap_notes].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumTapNotes()) ); g_StepsInformation.rows[jumps].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumJumps()) ); g_StepsInformation.rows[hands].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumHands()) ); @@ -3466,6 +3475,7 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns } static LocalizedString ENTER_NEW_DESCRIPTION( "ScreenEdit", "Enter a new description." ); +static LocalizedString ENTER_NEW_STEP_AUTHOR( "ScreenEdit", "Enter the author who made this step pattern." ); void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const vector &iAnswers ) { Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; @@ -3488,6 +3498,17 @@ void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const v NULL ); break; + case step_credit: + ScreenTextEntry::TextEntry( + SM_None, + ENTER_NEW_STEP_AUTHOR, + m_pSteps->GetCredit(), + 255, + SongUtil::ValidateCurrentStepsCredit, + ChangeStepCredit, + NULL + ); + break; } } diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index dd34e3d4e7..4d7999b277 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -407,6 +407,7 @@ public: difficulty, meter, description, + step_credit, predict_meter, tap_notes, jumps, From 3f1bed592596347d943cca1de137fcdf2bd8cc23 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:55:44 -0500 Subject: [PATCH 14/19] Duplicate step check with #CREDIT. --- src/SongUtil.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index 11b102ddcd..e9e59fedb0 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -293,7 +293,7 @@ static RString RemoveInitialWhitespace( RString s ) void SongUtil::DeleteDuplicateSteps( Song *pSong, vector &vSteps ) { /* vSteps have the same StepsType and Difficulty. Delete them if they have the - * same m_sDescription, m_iMeter and SMNoteData. */ + * same m_sDescription, m_sCredit, m_iMeter and SMNoteData. */ CHECKPOINT; for( unsigned i=0; i &vSteps ) if( s1->GetDescription() != s2->GetDescription() ) continue; + if( s1->GetCredit() != s2->GetCredit() ) + continue; if( s1->GetMeter() != s2->GetMeter() ) continue; /* Compare, ignoring whitespace. */ @@ -317,8 +319,8 @@ void SongUtil::DeleteDuplicateSteps( Song *pSong, vector &vSteps ) if( RemoveInitialWhitespace(sSMNoteData1) != RemoveInitialWhitespace(sSMNoteData2) ) continue; - LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\" and meter \"%i\"", - s2, pSong->GetSongDir().c_str(), s1->GetDescription().c_str(), s1->GetMeter() ); + LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\", step author \"%s\", and meter \"%i\"", + s2, pSong->GetSongDir().c_str(), s1->GetDescription().c_str(), s1->GetCredit().c_str(), s1->GetMeter() ); pSong->DeleteSteps( s2, false ); From c73057fc290d76ff83633742235bdffb2295f280 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:56:17 -0500 Subject: [PATCH 15/19] Allow getting the author's credit via lua. --- src/Steps.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Steps.cpp b/src/Steps.cpp index 4fad419d41..02d46b5dda 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -404,6 +404,7 @@ public: DEFINE_METHOD( GetStepsType, m_StepsType ) DEFINE_METHOD( GetDifficulty, GetDifficulty() ) DEFINE_METHOD( GetDescription, GetDescription() ) + DEFINE_METHOD( GetAuthorCredit, GetCredit() ) DEFINE_METHOD( GetMeter, GetMeter() ) DEFINE_METHOD( GetFilename, GetFilename() ) DEFINE_METHOD( IsAutogen, IsAutogen() ) @@ -434,6 +435,7 @@ public: ADD_METHOD( GetStepsType ); ADD_METHOD( GetDifficulty ); ADD_METHOD( GetDescription ); + ADD_METHOD( GetAuthorCredit ); ADD_METHOD( GetMeter ); ADD_METHOD( GetFilename ); ADD_METHOD( GetRadarValues ); From b3be2466ff6a77b43ca56b4c518c1d1472eb226b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 01:59:39 -0500 Subject: [PATCH 16/19] Add new lua function to the docs. --- Docs/Luadoc/Lua.xml | 1 + Docs/Luadoc/LuaDocumentation.xml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 2459a8b16c..85cd618dd9 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1237,6 +1237,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index f029c1d133..173a5495d0 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3033,6 +3033,9 @@ Returns the Steps description. + + Returns the author that made that particular Steps pattern. + Returns the Steps difficulty. From 0f98072274343c20e98cbe9a19c7989c4156bfcb Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 11:39:19 -0500 Subject: [PATCH 17/19] Allow .edit files to use .ssc format. --- src/NotesLoaderSSC.cpp | 97 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 8f5d0dc1c3..0d2a6d4409 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -1000,6 +1000,8 @@ bool SSCLoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ) { Song* pSong = NULL; + Steps* pNewNotes; + bool bSSCFormat = false; for( unsigned i=0; im_StepsType = GAMEMAN->StringToStepsType( sParams[1] ); + bSSCFormat = true; + } + + else if( sValueName=="DESCRIPTION" ) + { + pNewNotes->SetDescription( sParams[1] ); + bSSCFormat = true; + } + + else if( sValueName=="DIFFICULTY" ) + { + pNewNotes->SetDifficulty( DwiCompatibleStringToDifficulty( sParams[1] ) ); + bSSCFormat = true; + } + + else if( sValueName=="METER" ) + { + pNewNotes->SetMeter( atoi( sParams[1] ) ); + bSSCFormat = true; + } + + else if( sValueName=="RADARVALUES" ) + { + vector saValues; + split( sParams[1], ",", saValues, true ); + if( saValues.size() == NUM_RadarCategory * NUM_PLAYERS ) + { + RadarValues v[NUM_PLAYERS]; + FOREACH_PlayerNumber( pn ) + FOREACH_ENUM( RadarCategory, rc ) + v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] ); + pNewNotes->SetCachedRadarValues( v ); + } + bSSCFormat = true; + } + + else if( sValueName=="CREDIT" ) + { + pNewNotes->SetCredit( sParams[1] ); + bSSCFormat = true; + } + + // TimingData for Steps isn't set yet, but still prepare for it. + else if( sValueName=="BPMS" ) + { + bSSCFormat = true; + } + else if( sValueName=="STOPS" ) + { + bSSCFormat = true; + } + else if( sValueName=="DELAYS" ) + { + bSSCFormat = true; + } + else if( sValueName=="TIMESIGNATURES" ) + { + bSSCFormat = true; + } + else if( sValueName=="TICKCOUNTS" ) + { + bSSCFormat = true; + } + else if( sValueName=="COMBOS" ) + { + bSSCFormat = true; + } else if( sValueName=="NOTES" ) { - // TODO: deal with transferring the timing data if required. if( pSong == NULL ) { LOG->UserLog( "Edit file", sEditFilePath, "doesn't have a #SONG tag preceeding the first #NOTES tag." ); return false; } - if( iNumParams < 7 ) + if ( !bSSCFormat && iNumParams < 7 ) { LOG->UserLog( "Edit file", sEditFilePath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams ); continue; @@ -1052,10 +1128,19 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat if( !bAddStepsToSong ) return true; - Steps* pNewNotes = new Steps; - LoadFromSMTokens( - sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], - *pNewNotes); + if( bSSCFormat ) + { + pNewNotes->SetSMNoteData( sParams[1] ); + pNewNotes->TidyUpData(); + pSong->AddSteps( pNewNotes ); + } + else + { + pNewNotes = new Steps; + LoadFromSMTokens( + sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], + *pNewNotes); + } pNewNotes->SetLoadedFromProfile( slot ); pNewNotes->SetDifficulty( Difficulty_Edit ); From aa6a7471e5959dd1921a028ac7e35fe9f9e9c34e Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 12:28:23 -0500 Subject: [PATCH 18/19] Make LoadFromSMTokens a member. Allows for code reuse rather than duplication. --- src/NotesLoaderSM.cpp | 2 +- src/NotesLoaderSM.h | 3 +++ src/NotesLoaderSSC.cpp | 60 ++---------------------------------------- 3 files changed, 6 insertions(+), 59 deletions(-) diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index 9aa75f6e19..7307258842 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -14,7 +14,7 @@ const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB -static void LoadFromSMTokens( +void SMLoader::LoadFromSMTokens( RString sStepsType, RString sDescription, RString sDifficulty, diff --git a/src/NotesLoaderSM.h b/src/NotesLoaderSM.h index 433c0023e7..f8a7e78352 100644 --- a/src/NotesLoaderSM.h +++ b/src/NotesLoaderSM.h @@ -12,6 +12,9 @@ class TimingData; namespace SMLoader { + void LoadFromSMTokens( RString sStepsType, RString sDescription, RString sDifficulty, + RString sMeter, RString sRadarValues, RString sNoteData, Steps &out ); + bool LoadFromDir( const RString &sPath, Song &out ); void TidyUpData( Song &song, bool bFromCache ); diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 0d2a6d4409..a19033bb1a 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -4,7 +4,7 @@ #include "GameManager.h" #include "MsdFile.h" // No JSON here. #include "NoteTypes.h" -#include "NotesLoaderSM.h" // edit file holdover. +#include "NotesLoaderSM.h" // For loading SM style edits. #include "RageFileManager.h" #include "RageLog.h" #include "RageUtil.h" @@ -15,62 +15,6 @@ const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60 KB -// Holdover from .sm format, mainly to make edits behave. -static void LoadFromSMTokens( - RString sStepsType, - RString sDescription, - RString sDifficulty, - RString sMeter, - RString sRadarValues, - RString sNoteData, - Steps &out - ) -{ - // we're loading from disk, so this is by definition already saved: - out.SetSavedToDisk( true ); - - Trim( sStepsType ); - Trim( sDescription ); - Trim( sDifficulty ); - Trim( sNoteData ); - - // LOG->Trace( "Steps::LoadFromSMTokens()" ); - - // insert stepstype hacks from GameManager.cpp here? -aj - out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType ); - out.SetDescription( sDescription ); - out.SetDifficulty( DwiCompatibleStringToDifficulty(sDifficulty) ); - - // Handle hacks that originated back when StepMania didn't have - // Difficulty_Challenge. (At least v1.64, possibly v3.0 final...) - if( out.GetDifficulty() == Difficulty_Hard ) - { - // HACK: SMANIAC used to be Difficulty_Hard with a special description. - if( sDescription.CompareNoCase("smaniac") == 0 ) - out.SetDifficulty( Difficulty_Challenge ); - - // HACK: CHALLENGE used to be Difficulty_Hard with a special description. - if( sDescription.CompareNoCase("challenge") == 0 ) - out.SetDifficulty( Difficulty_Challenge ); - } - - out.SetMeter( atoi(sMeter) ); - vector saValues; - split( sRadarValues, ",", saValues, true ); - if( saValues.size() == NUM_RadarCategory * NUM_PLAYERS ) - { - RadarValues v[NUM_PLAYERS]; - FOREACH_PlayerNumber( pn ) - FOREACH_ENUM( RadarCategory, rc ) - v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] ); - out.SetCachedRadarValues( v ); - } - - out.SetSMNoteData( sNoteData ); - - out.TidyUpData(); -} - bool LoadFromBGSSCChangesString( BackgroundChange &change, const RString &sBGChangeExpression ) { vector aBGChangeValues; @@ -1137,7 +1081,7 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat else { pNewNotes = new Steps; - LoadFromSMTokens( + SMLoader::LoadFromSMTokens( sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], *pNewNotes); } From c88c698cd21dd3a1097a741651f979c91e6de8eb Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Fri, 11 Feb 2011 14:30:32 -0500 Subject: [PATCH 19/19] Changed copyright at the request of freem. --- src/NotesLoaderSSC.cpp | 2 +- src/NotesLoaderSSC.h | 2 +- src/NotesWriterSSC.cpp | 2 +- src/NotesWriterSSC.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index a19033bb1a..11191ea901 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -1163,7 +1163,7 @@ void SSCLoader::TidyUpData( Song &song, bool bFromCache ) } /* - * (c) 2011 spinal shark collective + * (c) 2011 Jason Felds * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h index 02cbf2fdc6..96d38fdfbe 100644 --- a/src/NotesLoaderSSC.h +++ b/src/NotesLoaderSSC.h @@ -34,7 +34,7 @@ namespace SSCLoader #endif /* - * (c) 2011 spinal shark collective + * (c) 2011 Jason Felds * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 97c3458d7b..2e1eaa1507 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -483,7 +483,7 @@ bool NotesWriterSSC::WriteEditFileToMachine( const Song *pSong, Steps *pSteps, R } /* - * (c) 2011 spinal shark collective + * (c) 2011 Jason Felds * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/src/NotesWriterSSC.h b/src/NotesWriterSSC.h index 118a37987a..4819dc60d9 100644 --- a/src/NotesWriterSSC.h +++ b/src/NotesWriterSSC.h @@ -16,7 +16,7 @@ namespace NotesWriterSSC #endif /* - * (c) 2011 spinal shark collective + * (c) 2011 Jason Felds * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a