diff --git a/src/Difficulty.cpp b/src/Difficulty.cpp index 1d49ae4615..0201139495 100644 --- a/src/Difficulty.cpp +++ b/src/Difficulty.cpp @@ -47,29 +47,43 @@ CourseDifficulty GetNextShownCourseDifficulty( CourseDifficulty cd ) return Difficulty_Invalid; } +struct OldStyleStringToDifficultyMapHolder +{ + std::map conversion_map; + OldStyleStringToDifficultyMapHolder() + { + conversion_map["beginner"]= Difficulty_Beginner; + conversion_map["easy"]= Difficulty_Easy; + conversion_map["basic"]= Difficulty_Easy; + conversion_map["light"]= Difficulty_Easy; + conversion_map["medium"]= Difficulty_Medium; + conversion_map["another"]= Difficulty_Medium; + conversion_map["trick"]= Difficulty_Medium; + conversion_map["standard"]= Difficulty_Medium; + conversion_map["difficult"]= Difficulty_Medium; + conversion_map["hard"]= Difficulty_Hard; + conversion_map["ssr"]= Difficulty_Hard; + conversion_map["maniac"]= Difficulty_Hard; + conversion_map["heavy"]= Difficulty_Hard; + conversion_map["smaniac"]= Difficulty_Challenge; + conversion_map["challenge"]= Difficulty_Challenge; + conversion_map["expert"]= Difficulty_Challenge; + conversion_map["oni"]= Difficulty_Challenge; + conversion_map["edit"]= Difficulty_Edit; + } +}; +OldStyleStringToDifficultyMapHolder OldStyleStringToDifficulty_converter; Difficulty OldStyleStringToDifficulty( const RString& sDC ) { RString s2 = sDC; s2.MakeLower(); - if( s2 == "beginner" ) return Difficulty_Beginner; - else if( s2 == "easy" ) return Difficulty_Easy; - else if( s2 == "basic" ) return Difficulty_Easy; - else if( s2 == "light" ) return Difficulty_Easy; - else if( s2 == "medium" ) return Difficulty_Medium; - else if( s2 == "another" ) return Difficulty_Medium; - else if( s2 == "trick" ) return Difficulty_Medium; - else if( s2 == "standard" ) return Difficulty_Medium; - else if( s2 == "difficult") return Difficulty_Medium; - else if( s2 == "hard" ) return Difficulty_Hard; - else if( s2 == "ssr" ) return Difficulty_Hard; - else if( s2 == "maniac" ) return Difficulty_Hard; - else if( s2 == "heavy" ) return Difficulty_Hard; - else if( s2 == "smaniac" ) return Difficulty_Challenge; - else if( s2 == "challenge" ) return Difficulty_Challenge; - else if( s2 == "expert" ) return Difficulty_Challenge; - else if( s2 == "oni" ) return Difficulty_Challenge; - else if( s2 == "edit" ) return Difficulty_Edit; - else return Difficulty_Invalid; + std::map::iterator diff= + OldStyleStringToDifficulty_converter.conversion_map.find(s2); + if(diff != OldStyleStringToDifficulty_converter.conversion_map.end()) + { + return diff->second; + } + return Difficulty_Invalid; } LuaFunction( OldStyleStringToDifficulty, OldStyleStringToDifficulty(SArg(1)) ); diff --git a/src/GameConstantsAndTypes.cpp b/src/GameConstantsAndTypes.cpp index 8c406f82c9..8bfec7c0b9 100644 --- a/src/GameConstantsAndTypes.cpp +++ b/src/GameConstantsAndTypes.cpp @@ -201,30 +201,34 @@ static const char *TapNoteScoreNames[] = { "W1", "CheckpointHit", }; +struct tns_conversion_helper +{ + std::map conversion_map; + tns_conversion_helper() + { + FOREACH_ENUM(TapNoteScore, tns) + { + conversion_map[TapNoteScoreNames[tns]]= tns; + } + // for backward compatibility + conversion_map["Boo"]= TNS_W5; + conversion_map["Good"]= TNS_W4; + conversion_map["Great"]= TNS_W3; + conversion_map["Perfect"]= TNS_W2; + conversion_map["Marvelous"]= TNS_W1; + } +}; +tns_conversion_helper tns_converter; XToString( TapNoteScore ); LuaXType( TapNoteScore ); TapNoteScore StringToTapNoteScore( const RString &s ) { - // new style - if ( s == "None" ) return TNS_None; - else if( s == "HitMine" ) return TNS_HitMine; - else if( s == "AvoidMine" ) return TNS_AvoidMine; - else if( s == "CheckpointHit" ) return TNS_CheckpointHit; - else if( s == "CheckpointMiss" )return TNS_CheckpointMiss; - else if( s == "Miss" ) return TNS_Miss; - else if( s == "W5" ) return TNS_W5; - else if( s == "W4" ) return TNS_W4; - else if( s == "W3" ) return TNS_W3; - else if( s == "W2" ) return TNS_W2; - else if( s == "W1" ) return TNS_W1; - - // for backward compatibility - else if( s == "Boo" ) return TNS_W5; - else if( s == "Good" ) return TNS_W4; - else if( s == "Great" ) return TNS_W3; - else if( s == "Perfect" ) return TNS_W2; - else if( s == "Marvelous" ) return TNS_W1; - + std::map::iterator tns= + tns_converter.conversion_map.find(s); + if(tns != tns_converter.conversion_map.end()) + { + return tns->second; + } return TapNoteScore_Invalid; } // This is necessary because the StringToX macro wasn't used, and Preference diff --git a/src/IniFile.cpp b/src/IniFile.cpp index 95a08d63d4..a4766f509d 100644 --- a/src/IniFile.cpp +++ b/src/IniFile.cpp @@ -34,6 +34,8 @@ bool IniFile::ReadFile( const RString &sPath ) bool IniFile::ReadFile( RageFileBasic &f ) { RString keyname; + // keychild is used to cache the node that values are being added to. -Kyz + XNode* keychild= NULL; while( 1 ) { RString line; @@ -61,34 +63,47 @@ bool IniFile::ReadFile( RageFileBasic &f ) } - if( line.size() == 0 ) + if( line.empty() ) continue; - if( line[0] == ';' ) - continue; // comment - if( line[0] == '#' ) - continue; // comment - if( line.size() > 1 && line[0] == '/' && line[1] == '/' ) - continue; // comment - if( line.size() > 1 && line[0] == '-' && line[1] == '-' ) - continue; // comment (Lua style) - - if( line[0] == '[' && line[line.size()-1] == ']' ) + switch(line[0]) { - // New section. - keyname = line.substr(1, line.size()-2); - } - else - { - // New value. - size_t iEqualIndex = line.find("="); - if( iEqualIndex != string::npos ) - { - RString valuename = line.Left( (int) iEqualIndex ); - RString value = line.Right( line.size()-valuename.size()-1 ); - Trim( valuename ); - if( keyname.size() && valuename.size() ) - SetValue( keyname, valuename, value ); - } + case ';': + case '#': + continue; // comment + case '/': + case '-': + if(line.size() > 1 && line[0] == line[1]) + { continue; } // comment (Lua or C++ style) + goto keyvalue; + case '[': + if(line[line.size()-1] == ']') + { + // New section. + keyname = line.substr(1, line.size()-2); + keychild= GetChild(keyname); + if(keychild == NULL) + { + keychild= AppendChild(keyname); + } + break; + } + default: + keyvalue: + if(keychild == NULL) + { break; } + // New value. + size_t iEqualIndex = line.find("="); + if( iEqualIndex != string::npos ) + { + RString valuename = line.Left((int) iEqualIndex); + RString value = line.Right(line.size()-valuename.size()-1); + Trim(valuename); + if(!valuename.empty()) + { + SetKeyValue(keychild, valuename, value); + } + } + break; } } } diff --git a/src/IniFile.h b/src/IniFile.h index d2eb04ac20..010dc3a214 100644 --- a/src/IniFile.h +++ b/src/IniFile.h @@ -44,6 +44,11 @@ public: pNode = AppendChild( sKey ); pNode->AppendAttr( sValueName, value ); } + template + void SetKeyValue(XNode* keynode, const RString &sValueName, const T &value) + { + keynode->AppendAttr(sValueName, value); + } bool DeleteKey( const RString &keyname ); bool DeleteValue( const RString &keyname, const RString &valuename ); diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index 11d91d5226..f6bdff43cd 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -13,6 +13,242 @@ #include "Attack.h" #include "PrefsManager.h" +// Everything from this line to the creation of sm_parser_helper exists to +// speed up parsing by allowing the use of std::map. All these functions +// are put into a map of function pointers which is used when loading. +// -Kyz +/****************************************************************/ +struct SMSongTagInfo +{ + SMLoader* loader; + Song* song; + const MsdFile::value_t* params; + const RString& path; + vector< pair > BPMChanges, Stops; + SMSongTagInfo(SMLoader* l, Song* s, const RString& p) + :loader(l), song(s), path(p) + {} +}; + +typedef void (*song_tag_func_t)(SMSongTagInfo& info); + +// Functions for song tags go below this line. -Kyz +/****************************************************************/ +void SMSetTitle(SMSongTagInfo& info) +{ + info.song->m_sMainTitle = (*info.params)[1]; + info.loader->SetSongTitle((*info.params)[1]); +} +void SMSetSubtitle(SMSongTagInfo& info) +{ + info.song->m_sSubTitle = (*info.params)[1]; +} +void SMSetArtist(SMSongTagInfo& info) +{ + info.song->m_sArtist = (*info.params)[1]; +} +void SMSetTitleTranslit(SMSongTagInfo& info) +{ + info.song->m_sMainTitleTranslit = (*info.params)[1]; +} +void SMSetSubtitleTranslit(SMSongTagInfo& info) +{ + info.song->m_sSubTitleTranslit = (*info.params)[1]; +} +void SMSetArtistTranslit(SMSongTagInfo& info) +{ + info.song->m_sArtistTranslit = (*info.params)[1]; +} +void SMSetGenre(SMSongTagInfo& info) +{ + info.song->m_sGenre = (*info.params)[1]; +} +void SMSetCredit(SMSongTagInfo& info) +{ + info.song->m_sCredit = (*info.params)[1]; +} +void SMSetBanner(SMSongTagInfo& info) +{ + info.song->m_sBannerFile = (*info.params)[1]; +} +void SMSetBackground(SMSongTagInfo& info) +{ + info.song->m_sBackgroundFile = (*info.params)[1]; +} +void SMSetLyricsPath(SMSongTagInfo& info) +{ + info.song->m_sLyricsFile = (*info.params)[1]; +} +void SMSetCDTitle(SMSongTagInfo& info) +{ + info.song->m_sCDTitleFile = (*info.params)[1]; +} +void SMSetMusic(SMSongTagInfo& info) +{ + info.song->m_sMusicFile = (*info.params)[1]; +} +void SMSetOffset(SMSongTagInfo& info) +{ + info.song->m_SongTiming.m_fBeat0OffsetInSeconds = StringToFloat((*info.params)[1]); +} +void SMSetBPMs(SMSongTagInfo& info) +{ + info.BPMChanges.clear(); + info.loader->ParseBPMs(info.BPMChanges, (*info.params)[1]); +} +void SMSetStops(SMSongTagInfo& info) +{ + info.Stops.clear(); + info.loader->ParseStops(info.Stops, (*info.params)[1]); +} +void SMSetDelays(SMSongTagInfo& info) +{ + info.loader->ProcessDelays(info.song->m_SongTiming, (*info.params)[1]); +} +void SMSetTimeSignatures(SMSongTagInfo& info) +{ + info.loader->ProcessTimeSignatures(info.song->m_SongTiming, (*info.params)[1]); +} +void SMSetTickCounts(SMSongTagInfo& info) +{ + info.loader->ProcessTickcounts(info.song->m_SongTiming, (*info.params)[1]); +} +void SMSetInstrumentTrack(SMSongTagInfo& info) +{ + info.loader->ProcessInstrumentTracks(*info.song, (*info.params)[1]); +} +void SMSetSampleStart(SMSongTagInfo& info) +{ + info.song->m_fMusicSampleStartSeconds = HHMMSSToSeconds((*info.params)[1]); +} +void SMSetSampleLength(SMSongTagInfo& info) +{ + info.song->m_fMusicSampleLengthSeconds = HHMMSSToSeconds((*info.params)[1]); +} +void SMSetDisplayBPM(SMSongTagInfo& info) +{ + // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; + if((*info.params)[1] == "*") + { info.song->m_DisplayBPMType = DISPLAY_BPM_RANDOM; } + else + { + info.song->m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; + info.song->m_fSpecifiedBPMMin = StringToFloat((*info.params)[1]); + if((*info.params)[2].empty()) + { info.song->m_fSpecifiedBPMMax = info.song->m_fSpecifiedBPMMin; } + else + { info.song->m_fSpecifiedBPMMax = StringToFloat((*info.params)[2]); } + } +} +void SMSetSelectable(SMSongTagInfo& info) +{ + if((*info.params)[1].EqualsNoCase("YES")) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + else if((*info.params)[1].EqualsNoCase("NO")) + { info.song->m_SelectionDisplay = info.song->SHOW_NEVER; } + // ROULETTE from 3.9. It was removed since UnlockManager can serve + // the same purpose somehow. This, of course, assumes you're using + // unlocks. -aj + else if((*info.params)[1].EqualsNoCase("ROULETTE")) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + /* The following two cases are just fixes to make sure simfiles that + * used 3.9+ features are not excluded here */ + else if((*info.params)[1].EqualsNoCase("ES") || (*info.params)[1].EqualsNoCase("OMES")) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + else if(StringToInt((*info.params)[1]) > 0) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + else + { LOG->UserLog("Song file", info.path, "has an unknown #SELECTABLE value, \"%s\"; ignored.", (*info.params)[1].c_str()); } +} +void SMSetBGChanges(SMSongTagInfo& info) +{ + info.loader->ProcessBGChanges(*info.song, (*info.params)[0], info.path, (*info.params)[1]); +} +void SMSetFGChanges(SMSongTagInfo& info) +{ + vector aFGChangeExpressions; + split((*info.params)[1], ",", aFGChangeExpressions); + + for(unsigned int b = 0; b < aFGChangeExpressions.size(); ++b) + { + BackgroundChange change; + if(info.loader->LoadFromBGChangesString(change, aFGChangeExpressions[b])) + info.song->AddForegroundChange(change); + } +} +void SMSetKeysounds(SMSongTagInfo& info) +{ + split((*info.params)[1], ",", info.song->m_vsKeysoundFile); +} +void SMSetAttacks(SMSongTagInfo& info) +{ + info.loader->ProcessAttackString(info.song->m_sAttackString, (*info.params)); + info.loader->ProcessAttacks(info.song->m_Attacks, (*info.params)); +} + +typedef std::map song_handler_map_t; + +struct sm_parser_helper_t +{ + song_handler_map_t song_tag_handlers; + // Unless signed, the comments in this tag list are not by me. They were + // moved here when converting from the else if chain. -Kyz + sm_parser_helper_t() + { + song_tag_handlers["TITLE"]= &SMSetTitle; + song_tag_handlers["SUBTITLE"]= &SMSetSubtitle; + song_tag_handlers["ARTIST"]= &SMSetArtist; + song_tag_handlers["TITLETRANSLIT"]= &SMSetTitleTranslit; + song_tag_handlers["SUBTITLETRANSLIT"]= &SMSetSubtitleTranslit; + song_tag_handlers["ARTISTTRANSLIT"]= &SMSetArtistTranslit; + song_tag_handlers["GENRE"]= &SMSetGenre; + song_tag_handlers["CREDIT"]= &SMSetCredit; + song_tag_handlers["BANNER"]= &SMSetBanner; + song_tag_handlers["BACKGROUND"]= &SMSetBackground; + // Save "#LYRICS" for later, so we can add an internal lyrics tag. + song_tag_handlers["LYRICSPATH"]= &SMSetLyricsPath; + song_tag_handlers["CDTITLE"]= &SMSetCDTitle; + song_tag_handlers["MUSIC"]= &SMSetMusic; + song_tag_handlers["OFFSET"]= &SMSetOffset; + song_tag_handlers["BPMS"]= &SMSetBPMs; + song_tag_handlers["STOPS"]= &SMSetStops; + song_tag_handlers["FREEZES"]= &SMSetStops; + song_tag_handlers["DELAYS"]= &SMSetDelays; + song_tag_handlers["TIMESIGNATURES"]= &SMSetTimeSignatures; + song_tag_handlers["TICKCOUNTS"]= &SMSetTickCounts; + song_tag_handlers["INSTRUMENTTRACK"]= &SMSetInstrumentTrack; + song_tag_handlers["SAMPLESTART"]= &SMSetSampleStart; + song_tag_handlers["SAMPLELENGTH"]= &SMSetSampleLength; + song_tag_handlers["DISPLAYBPM"]= &SMSetDisplayBPM; + song_tag_handlers["SELECTABLE"]= &SMSetSelectable; + // It's a bit odd to have the tag that exists for backwards compatibility + // in this list and not the replacement, but the BGCHANGES tag has a + // number on the end, allowing up to NUM_BackgroundLayer tags, so it + // can't fit in the map. -Kyz + song_tag_handlers["ANIMATIONS"]= &SMSetBGChanges; + song_tag_handlers["FGCHANGES"]= &SMSetFGChanges; + song_tag_handlers["KEYSOUNDS"]= &SMSetKeysounds; + // Attacks loaded from file + song_tag_handlers["ATTACKS"]= &SMSetAttacks; + /* Tags that no longer exist, listed for posterity. May their names + * never be forgotten for their service to Stepmania. -Kyz + * LASTBEATHINT: // unable to identify at this point: ignore + * MUSICBYTES: // ignore + * FIRSTBEAT: // cache tags from older SM files: ignore. + * LASTBEAT: // cache tags from older SM files: ignore. + * SONGFILENAME: // cache tags from older SM files: ignore. + * HASMUSIC: // cache tags from older SM files: ignore. + * HASBANNER: // cache tags from older SM files: ignore. + * SAMPLEPATH: // SamplePath was used when the song has a separate preview clip. -aj + * LEADTRACK: // XXX: Does anyone know what LEADTRACK is for? -Wolfman2000 + * MUSICLENGTH: // Loaded from the cache now. -Kyz + */ + } +}; +sm_parser_helper_t sm_parser_helper; +// End sm_parser_helper related functions. -Kyz +/****************************************************************/ + void SMLoader::SetSongTitle(const RString & title) { this->songTitle = title; @@ -885,10 +1121,11 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache return false; } - vector< pair > vBPMChanges, vStops; out.m_SongTiming.m_sFile = sPath; out.m_sSongFileName = sPath; + SMSongTagInfo reused_song_info(&*this, &out, sPath); + for( unsigned i=0; iSetSongTitle(sParams[1]); + handler->second(reused_song_info); } - - 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]; - - // Save "#LYRICS" for later, so we can add an internal lyrics tag. - 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=="OFFSET" ) + else if(sValueName.Left(strlen("BGCHANGES")) == "BGCHANGES") { - out.m_SongTiming.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] ); + SMSetBGChanges(reused_song_info); } - else if( sValueName=="BPMS" ) + else if(sValueName == "NOTES" || sValueName == "NOTES2") { - vBPMChanges.clear(); - ParseBPMs(vBPMChanges, sParams[1]); - } - - else if( sValueName=="STOPS" || sValueName=="FREEZES" ) - { - vStops.clear(); - ParseStops(vStops, sParams[1]); - } - - else if( sValueName=="DELAYS" ) - { - ProcessDelays(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="TIMESIGNATURES" ) - { - ProcessTimeSignatures(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="TICKCOUNTS" ) - { - ProcessTickcounts(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="INSTRUMENTTRACK" ) - { - ProcessInstrumentTracks( out, sParams[1] ); - } - - else if( sValueName=="MUSICLENGTH" ) - { - if( !bFromCache ) - continue; - out.m_fMusicLengthSeconds = StringToFloat( sParams[1] ); - } - - else if( sValueName=="LASTBEATHINT" ) - { - // unable to identify at this point: ignore - } - - else if( sValueName=="MUSICBYTES" ) - ; /* ignore */ - - // cache tags from older SM files: ignore. - else if(sValueName=="FIRSTBEAT" || sValueName=="LASTBEAT" || - sValueName=="SONGFILENAME" || sValueName=="HASMUSIC" || - sValueName=="HASBANNER") - { - ; - } - - else if( sValueName=="SAMPLESTART" ) - out.m_fMusicSampleStartSeconds = HHMMSSToSeconds( sParams[1] ); - - else if( sValueName=="SAMPLELENGTH" ) - out.m_fMusicSampleLengthSeconds = HHMMSSToSeconds( sParams[1] ); - - // SamplePath is used when the song has a separate preview clip. -aj - //else if( sValueName=="SAMPLEPATH" ) - //out.m_sMusicSamplePath = sParams[1]; - - else if( sValueName=="DISPLAYBPM" ) - { - // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; - if( sParams[1] == "*" ) - out.m_DisplayBPMType = DISPLAY_BPM_RANDOM; - else - { - out.m_DisplayBPMType = DISPLAY_BPM_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(sParams[1].EqualsNoCase("YES")) - out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if(sParams[1].EqualsNoCase("NO")) - out.m_SelectionDisplay = out.SHOW_NEVER; - // ROULETTE from 3.9. It was removed since UnlockManager can serve - // the same purpose somehow. This, of course, assumes you're using - // unlocks. -aj - else if(sParams[1].EqualsNoCase("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(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES")) - out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if( StringToInt(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" ) - { - ProcessBGChanges( out, sValueName, sPath, sParams[1]); - } - - else if( sValueName=="FGCHANGES" ) - { - vector aFGChangeExpressions; - split( sParams[1], ",", aFGChangeExpressions ); - - for( unsigned b=0; bUserLog( "Song file", sPath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams ); continue; @@ -1094,20 +1162,19 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache sParams[4], sParams[5], sParams[6], - *pNewNotes ); + *pNewNotes); pNewNotes->SetFilename(sPath); out.AddSteps( pNewNotes ); } - // XXX: Does anyone know what LEADTRACK is for? -Wolfman2000 - else if( sValueName=="LEADTRACK" ) - ; else - LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() ); + { + LOG->UserLog("Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str()); + } } // Turn negative time changes into warps - ProcessBPMsAndStops(out.m_SongTiming, vBPMChanges, vStops); + ProcessBPMsAndStops(out.m_SongTiming, reused_song_info.BPMChanges, reused_song_info.Stops); TidyUpData( out, bFromCache ); return true; diff --git a/src/NotesLoaderSM.h b/src/NotesLoaderSM.h index 358515ec51..70f1841510 100644 --- a/src/NotesLoaderSM.h +++ b/src/NotesLoaderSM.h @@ -198,7 +198,10 @@ protected: * @brief Retrieve the file extension associated with this loader. * @return the file extension. */ RString GetFileExtension() const { return fileExt; } - + +public: + // SetSongTitle and GetSongTitle changed to public to allow the functions + // used by the parser helper to access them. -Kyz /** * @brief Set the song title. * @param t the song title. */ diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index e5ad98574e..5f091db8df 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -14,6 +14,632 @@ #include "Attack.h" #include "PrefsManager.h" +// Everything from this line to the creation of parser_helper exists to +// speed up parsing by allowing the use of std::map. All these functions +// are put into a map of function pointers which is used when loading. +// -Kyz +/****************************************************************/ +struct StepsTagInfo +{ + SSCLoader* loader; + Song* song; + Steps* steps; + TimingData* timing; + const MsdFile::value_t* params; + const RString& path; + bool has_own_timing; + bool ssc_format; + bool from_cache; + bool for_load_edit; + StepsTagInfo(SSCLoader* l, Song* s, const RString& p, bool fc) + :loader(l), song(s), path(p), has_own_timing(false), ssc_format(false), + from_cache(fc), for_load_edit(false) + {} +}; +struct SongTagInfo +{ + SSCLoader* loader; + Song* song; + const MsdFile::value_t* params; + const RString& path; + bool from_cache; + SongTagInfo(SSCLoader* l, Song* s, const RString& p, bool fc) + :loader(l), song(s), path(p), from_cache(fc) + {} +}; +// LoadNoteDataFromSimfile uses LoadNoteDataTagIDs because its parts operate +// on state variables internal to the function. +enum LoadNoteDataTagIDs +{ + LNDID_version, + LNDID_stepstype, + LNDID_chartname, + LNDID_description, + LNDID_difficulty, + LNDID_meter, + LNDID_credit, + LNDID_notes, + LNDID_notes2, + LNDID_notedata +}; + +typedef void (*steps_tag_func_t)(StepsTagInfo& info); +typedef void (*song_tag_func_t)(SongTagInfo& info); + +// Functions for song tags go below this line. -Kyz +/****************************************************************/ +void SetVersion(SongTagInfo& info) +{ + info.song->m_fVersion = StringToFloat((*info.params)[1]); +} +void SetTitle(SongTagInfo& info) +{ + info.song->m_sMainTitle = (*info.params)[1]; + info.loader->SetSongTitle((*info.params)[1]); +} +void SetSubtitle(SongTagInfo& info) +{ + info.song->m_sSubTitle = (*info.params)[1]; +} +void SetArtist(SongTagInfo& info) +{ + info.song->m_sArtist = (*info.params)[1]; +} +void SetMainTitleTranslit(SongTagInfo& info) +{ + info.song->m_sMainTitleTranslit = (*info.params)[1]; +} +void SetSubtitleTranslit(SongTagInfo& info) +{ + info.song->m_sSubTitleTranslit = (*info.params)[1]; +} +void SetArtistTranslit(SongTagInfo& info) +{ + info.song->m_sArtistTranslit = (*info.params)[1]; +} +void SetGenre(SongTagInfo& info) +{ + info.song->m_sGenre = (*info.params)[1]; +} +void SetOrigin(SongTagInfo& info) +{ + info.song->m_sOrigin = (*info.params)[1]; +} +void SetCredit(SongTagInfo& info) +{ + info.song->m_sCredit = (*info.params)[1]; +} +void SetBanner(SongTagInfo& info) +{ + info.song->m_sBannerFile = (*info.params)[1]; +} +void SetBackground(SongTagInfo& info) +{ + info.song->m_sBackgroundFile = (*info.params)[1]; +} +void SetPreviewVid(SongTagInfo& info) +{ + info.song->m_sPreviewVidFile = (*info.params)[1]; +} +void SetJacket(SongTagInfo& info) +{ + info.song->m_sJacketFile = (*info.params)[1]; +} +void SetCDImage(SongTagInfo& info) +{ + info.song->m_sCDFile = (*info.params)[1]; +} +void SetDiscImage(SongTagInfo& info) +{ + info.song->m_sDiscFile = (*info.params)[1]; +} +void SetLyricsPath(SongTagInfo& info) +{ + info.song->m_sLyricsFile = (*info.params)[1]; +} +void SetCDTitle(SongTagInfo& info) +{ + info.song->m_sCDTitleFile = (*info.params)[1]; +} +void SetMusic(SongTagInfo& info) +{ + info.song->m_sMusicFile = (*info.params)[1]; +} +void SetPreview(SongTagInfo& info) +{ + info.song->m_PreviewFile= (*info.params)[1]; +} +void SetInstrumentTrack(SongTagInfo& info) +{ + info.loader->ProcessInstrumentTracks(*info.song, (*info.params)[1]); +} +void SetMusicLength(SongTagInfo& info) +{ + if(info.from_cache) + info.song->m_fMusicLengthSeconds = StringToFloat((*info.params)[1]); +} +void SetLastSecondHint(SongTagInfo& info) +{ + info.song->SetSpecifiedLastSecond(StringToFloat((*info.params)[1])); +} +void SetSampleStart(SongTagInfo& info) +{ + info.song->m_fMusicSampleStartSeconds = HHMMSSToSeconds((*info.params)[1]); +} +void SetSampleLength(SongTagInfo& info) +{ + info.song->m_fMusicSampleLengthSeconds = HHMMSSToSeconds((*info.params)[1]); +} +void SetDisplayBPM(SongTagInfo& info) +{ + // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; + if((*info.params)[1] == "*") + { info.song->m_DisplayBPMType = DISPLAY_BPM_RANDOM; } + else + { + info.song->m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; + info.song->m_fSpecifiedBPMMin = StringToFloat((*info.params)[1]); + if((*info.params)[2].empty()) + { info.song->m_fSpecifiedBPMMax = info.song->m_fSpecifiedBPMMin; } + else + { info.song->m_fSpecifiedBPMMax = StringToFloat((*info.params)[2]); } + } +} +void SetSelectable(SongTagInfo& info) +{ + if((*info.params)[1].EqualsNoCase("YES")) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + else if((*info.params)[1].EqualsNoCase("NO")) + { info.song->m_SelectionDisplay = info.song->SHOW_NEVER; } + // ROULETTE from 3.9 is no longer in use. + else if((*info.params)[1].EqualsNoCase("ROULETTE")) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + /* The following two cases are just fixes to make sure simfiles that + * used 3.9+ features are not excluded here */ + else if((*info.params)[1].EqualsNoCase("ES") || (*info.params)[1].EqualsNoCase("OMES")) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + else if(StringToInt((*info.params)[1]) > 0) + { info.song->m_SelectionDisplay = info.song->SHOW_ALWAYS; } + else + { LOG->UserLog("Song file", info.path, "has an unknown #SELECTABLE value, \"%s\"; ignored.", (*info.params)[1].c_str()); } +} +void SetBGChanges(SongTagInfo& info) +{ + info.loader->ProcessBGChanges(*info.song, (*info.params)[0], info.path, (*info.params)[1]); +} +void SetFGChanges(SongTagInfo& info) +{ + vector aFGChangeExpressions; + split((*info.params)[1], ",", aFGChangeExpressions); + + for(size_t b = 0; b < aFGChangeExpressions.size(); ++b) + { + BackgroundChange change; + if(info.loader->LoadFromBGChangesString(change, aFGChangeExpressions[b])) + { info.song->AddForegroundChange(change); } + } +} +void SetKeysounds(SongTagInfo& info) +{ + RString keysounds = (*info.params)[1]; + if(keysounds.length() >= 2 && keysounds.substr(0, 2) == "\\#") + { keysounds = keysounds.substr(1); } + split(keysounds, ",", info.song->m_vsKeysoundFile); +} +void SetAttacks(SongTagInfo& info) +{ + info.loader->ProcessAttackString(info.song->m_sAttackString, (*info.params)); + info.loader->ProcessAttacks(info.song->m_Attacks, (*info.params)); +} +void SetOffset(SongTagInfo& info) +{ + info.song->m_SongTiming.m_fBeat0OffsetInSeconds = StringToFloat((*info.params)[1]); +} +void SetSongStops(SongTagInfo& info) +{ + info.loader->ProcessStops(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongDelays(SongTagInfo& info) +{ + info.loader->ProcessDelays(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongBPMs(SongTagInfo& info) +{ + info.loader->ProcessBPMs(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongWarps(SongTagInfo& info) +{ + info.loader->ProcessWarps( info.song->m_SongTiming, (*info.params)[1], info.song->m_fVersion ); +} +void SetSongLabels(SongTagInfo& info) +{ + info.loader->ProcessLabels( info.song->m_SongTiming, (*info.params)[1] ); +} +void SetSongTimeSignatures(SongTagInfo& info) +{ + info.loader->ProcessTimeSignatures(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongTickCounts(SongTagInfo& info) +{ + info.loader->ProcessTickcounts(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongCombos(SongTagInfo& info) +{ + info.loader->ProcessCombos( info.song->m_SongTiming, (*info.params)[1] ); +} +void SetSongSpeeds(SongTagInfo& info) +{ + info.loader->ProcessSpeeds(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongScrolls(SongTagInfo& info) +{ + info.loader->ProcessScrolls(info.song->m_SongTiming, (*info.params)[1]); +} +void SetSongFakes(SongTagInfo& info) +{ + info.loader->ProcessFakes(info.song->m_SongTiming, (*info.params)[1]); +} +void SetFirstSecond(SongTagInfo& info) +{ + if(info.from_cache) + { info.song->SetFirstSecond(StringToFloat((*info.params)[1])); } +} +void SetLastSecond(SongTagInfo& info) +{ + if(info.from_cache) + { info.song->SetLastSecond(StringToFloat((*info.params)[1])); } +} +void SetSongFilename(SongTagInfo& info) +{ + if(info.from_cache) + { info.song->m_sSongFileName = (*info.params)[1]; } +} +void SetHasMusic(SongTagInfo& info) +{ + if(info.from_cache) + { info.song->m_bHasMusic = StringToInt((*info.params)[1]) != 0; } +} +void SetHasBanner(SongTagInfo& info) +{ + if(info.from_cache) + { info.song->m_bHasBanner = StringToInt((*info.params)[1]) != 0; } +} + +// Functions for steps tags go below this line. -Kyz +/****************************************************************/ +void SetStepsVersion(StepsTagInfo& info) +{ + info.song->m_fVersion = StringToFloat((*info.params)[1]); +} +void SetChartName(StepsTagInfo& info) +{ + info.steps->SetChartName((*info.params)[1]); +} +void SetStepsType(StepsTagInfo& info) +{ + info.steps->m_StepsType = GAMEMAN->StringToStepsType((*info.params)[1]); + info.steps->m_StepsTypeStr= (*info.params)[1]; + info.ssc_format= true; +} +void SetChartStyle(StepsTagInfo& info) +{ + info.steps->SetChartStyle((*info.params)[1]); + info.ssc_format= true; +} +void SetDescription(StepsTagInfo& info) +{ + if(info.song->m_fVersion < VERSION_CHART_NAME_TAG && !info.for_load_edit) + { + info.steps->SetChartName((*info.params)[1]); + } + else + { + info.steps->SetDescription((*info.params)[1]); + } + info.ssc_format= true; +} +void SetDifficulty(StepsTagInfo& info) +{ + info.steps->SetDifficulty(StringToDifficulty((*info.params)[1])); + info.ssc_format= true; +} +void SetMeter(StepsTagInfo& info) +{ + info.steps->SetMeter(StringToInt((*info.params)[1])); + info.ssc_format= true; +} +void SetRadarValues(StepsTagInfo& info) +{ + if(info.from_cache || info.for_load_edit) + { + vector saValues; + split((*info.params)[1], ",", saValues, true); + int categories = NUM_RadarCategory; + if(info.song->m_fVersion < VERSION_RADAR_FAKE && !info.for_load_edit) + { categories -= 1; } + if(saValues.size() == (unsigned int)categories * NUM_PLAYERS) + { + RadarValues v[NUM_PLAYERS]; + FOREACH_PlayerNumber(pn) + { + // Can't use the foreach anymore due to flexible radar lines. + for(RadarCategory rc = (RadarCategory)0; rc < categories; + enum_add(rc, +1)) + { + v[pn][rc] = StringToFloat(saValues[pn*categories + rc]); + } + } + info.steps->SetCachedRadarValues(v); + } + } + else + { + // just recalc at time. + } + info.ssc_format= true; +} +void SetCredit(StepsTagInfo& info) +{ + info.steps->SetCredit((*info.params)[1]); + info.ssc_format= true; +} +void SetStepsMusic(StepsTagInfo& info) +{ + pNewNotes->SetMusicFile(sParams[1]); +} +void SetStepsBPMs(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessBPMs(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsStops(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessStops(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsDelays(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessDelays(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsTimeSignatures(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessTimeSignatures(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsTickCounts(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessTickcounts(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsCombos(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessCombos(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsWarps(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessWarps(*info.timing, (*info.params)[1], info.song->m_fVersion); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsSpeeds(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessSpeeds(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsScrolls(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessScrolls(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsFakes(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessFakes(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsLabels(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessLabels(*info.timing, (*info.params)[1]); + info.has_own_timing = true; + } + info.ssc_format= true; +} +void SetStepsAttacks(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.loader->ProcessAttackString(info.steps->m_sAttackString, *info.params); + info.loader->ProcessAttacks(info.steps->m_Attacks, *info.params); + } +} +void SetStepsOffset(StepsTagInfo& info) +{ + if(info.song->m_fVersion >= VERSION_SPLIT_TIMING || info.for_load_edit) + { + info.timing->m_fBeat0OffsetInSeconds = StringToFloat((*info.params)[1]); + info.has_own_timing = true; + } +} +void SetStepsDisplayBPM(StepsTagInfo& info) +{ + // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; + if((*info.params)[1] == "*") + { info.steps->SetDisplayBPM(DISPLAY_BPM_RANDOM); } + else + { + info.steps->SetDisplayBPM(DISPLAY_BPM_SPECIFIED); + float min = StringToFloat((*info.params)[1]); + info.steps->SetMinBPM(min); + if((*info.params)[2].empty()) + { info.steps->SetMaxBPM(min); } + else + { info.steps->SetMaxBPM(StringToFloat((*info.params)[2])); } + } +} + + +typedef std::map steps_handler_map_t; +typedef std::map song_handler_map_t; +typedef std::map load_note_data_handler_map_t; + +struct ssc_parser_helper_t +{ + steps_handler_map_t steps_tag_handlers; + song_handler_map_t song_tag_handlers; + load_note_data_handler_map_t load_note_data_handlers; + // Unless signed, the comments in this tag list are not by me. They were + // moved here when converting from the else if chain. -Kyz + ssc_parser_helper_t() + { + song_tag_handlers["VERSION"]= &SetVersion; + song_tag_handlers["TITLE"]= &SetTitle; + song_tag_handlers["SUBTITLE"]= &SetSubtitle; + song_tag_handlers["ARTIST"]= &SetArtist; + song_tag_handlers["TITLETRANSLIT"]= &SetMainTitleTranslit; + song_tag_handlers["SUBTITLETRANSLIT"]= &SetSubtitleTranslit; + song_tag_handlers["ARTISTTRANSLIT"]= &SetArtistTranslit; + song_tag_handlers["GENRE"]= &SetGenre; + song_tag_handlers["ORIGIN"]= &SetOrigin; + song_tag_handlers["CREDIT"]= &SetCredit; + song_tag_handlers["BANNER"]= &SetBanner; + song_tag_handlers["BACKGROUND"]= &SetBackground; + song_tag_handlers["PREVIEWVID"]= &SetPreviewVid; + song_tag_handlers["JACKET"]= &SetJacket; + song_tag_handlers["CDIMAGE"]= &SetCDImage; + song_tag_handlers["DISCIMAGE"]= &SetDiscImage; + song_tag_handlers["LYRICSPATH"]= &SetLyricsPath; + song_tag_handlers["CDTITLE"]= &SetCDTitle; + song_tag_handlers["MUSIC"]= &SetMusic; + song_tag_handlers["PREVIEW"]= &SetPreview; + song_tag_handlers["INSTRUMENTTRACK"]= &SetInstrumentTrack; + song_tag_handlers["MUSICLENGTH"]= &SetMusicLength; + song_tag_handlers["LASTSECONDHINT"]= &SetLastSecondHint; + song_tag_handlers["SAMPLESTART"]= &SetSampleStart; + song_tag_handlers["SAMPLELENGTH"]= &SetSampleLength; + song_tag_handlers["DISPLAYBPM"]= &SetDisplayBPM; + song_tag_handlers["SELECTABLE"]= &SetSelectable; + // It's a bit odd to have the tag that exists for backwards compatibility + // in this list and not the replacement, but the BGCHANGES tag has a + // number on the end, allowing up to NUM_BackgroundLayer tags, so it + // can't fit in the map. -Kyz + song_tag_handlers["ANIMATIONS"]= &SetBGChanges; + song_tag_handlers["FGCHANGES"]= &SetFGChanges; + song_tag_handlers["KEYSOUNDS"]= &SetKeysounds; + song_tag_handlers["ATTACKS"]= &SetAttacks; + song_tag_handlers["OFFSET"]= &SetOffset; + /* Below are the song based timings that should only be used + * if the steps do not have their own timing. */ + song_tag_handlers["STOPS"]= &SetSongStops; + song_tag_handlers["DELAYS"]= &SetSongDelays; + song_tag_handlers["BPMS"]= &SetSongBPMs; + song_tag_handlers["WARPS"]= &SetSongWarps; + song_tag_handlers["LABELS"]= &SetSongLabels; + song_tag_handlers["TIMESIGNATURES"]= &SetSongTimeSignatures; + song_tag_handlers["TICKCOUNTS"]= &SetSongTickCounts; + song_tag_handlers["COMBOS"]= &SetSongCombos; + song_tag_handlers["SPEEDS"]= &SetSongSpeeds; + song_tag_handlers["SCROLLS"]= &SetSongScrolls; + song_tag_handlers["FAKES"]= &SetSongFakes; + /* The following are cache tags. Never fill their values + * directly: only from the cached version. */ + song_tag_handlers["FIRSTSECOND"]= &SetFirstSecond; + song_tag_handlers["LASTSECOND"]= &SetLastSecond; + song_tag_handlers["SONGFILENAME"]= &SetSongFilename; + song_tag_handlers["HASMUSIC"]= &SetHasMusic; + song_tag_handlers["HASBANNER"]= &SetHasBanner; + /* Tags that no longer exist, listed for posterity. May their names + * never be forgotten for their service to Stepmania. -Kyz + * LASTBEATHINT: // unable to parse due to tag position. Ignore. + * MUSICBYTES: // ignore + * FIRSTBEAT: // no longer used. + * LASTBEAT: // no longer used. + */ + + steps_tag_handlers["VERSION"]= &SetStepsVersion; + steps_tag_handlers["CHARTNAME"]= &SetChartName; + steps_tag_handlers["STEPSTYPE"]= &SetStepsType; + steps_tag_handlers["CHARTSTYLE"]= &SetChartStyle; + steps_tag_handlers["DESCRIPTION"]= &SetDescription; + steps_tag_handlers["DIFFICULTY"]= &SetDifficulty; + steps_tag_handlers["METER"]= &SetMeter; + steps_tag_handlers["RADARVALUES"]= &SetRadarValues; + steps_tag_handlers["CREDIT"]= &SetCredit; + steps_tag_handlers["MUSIC"]= &SetStepsMusic; + steps_tag_handlers["BPMS"]= &SetStepsBPMs; + steps_tag_handlers["STOPS"]= &SetStepsStops; + steps_tag_handlers["DELAYS"]= &SetStepsDelays; + steps_tag_handlers["TIMESIGNATURES"]= &SetStepsTimeSignatures; + steps_tag_handlers["TICKCOUNTS"]= &SetStepsTickCounts; + steps_tag_handlers["COMBOS"]= &SetStepsCombos; + steps_tag_handlers["WARPS"]= &SetStepsWarps; + steps_tag_handlers["SPEEDS"]= &SetStepsSpeeds; + steps_tag_handlers["SCROLLS"]= &SetStepsScrolls; + steps_tag_handlers["FAKES"]= &SetStepsFakes; + steps_tag_handlers["LABELS"]= &SetStepsLabels; + /* If this is called, the chart does not use the same attacks + * as the Song's timing. No other changes are required. */ + steps_tag_handlers["ATTACKS"]= &SetStepsAttacks; + steps_tag_handlers["OFFSET"]= &SetStepsOffset; + steps_tag_handlers["DISPLAYBPM"]= &SetStepsDisplayBPM; + + load_note_data_handlers["VERSION"]= LNDID_version; + load_note_data_handlers["STEPSTYPE"]= LNDID_stepstype; + load_note_data_handlers["CHARTNAME"]= LNDID_chartname; + load_note_data_handlers["DESCRIPTION"]= LNDID_description; + load_note_data_handlers["DIFFICULTY"]= LNDID_difficulty; + load_note_data_handlers["METER"]= LNDID_meter; + load_note_data_handlers["CREDIT"]= LNDID_credit; + load_note_data_handlers["NOTES"]= LNDID_notes; + load_note_data_handlers["NOTES2"]= LNDID_notes2; + load_note_data_handlers["NOTEDATA"]= LNDID_notedata; + } +}; +ssc_parser_helper_t parser_helper; +// End parser_helper related functions. -Kyz +/****************************************************************/ + void SSCLoader::ProcessBPMs( TimingData &out, const RString sParam ) { vector arrayBPMExpressions; @@ -236,65 +862,76 @@ bool SSCLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) valueName.MakeUpper(); RString matcher = params[1]; // mainly for debugging. Trim(matcher); - - if (valueName=="VERSION") + + load_note_data_handler_map_t::iterator handler= + parser_helper.load_note_data_handlers.find(valueName); + if(handler != parser_helper.load_note_data_handlers.end()) { - storedVersion = StringToFloat(matcher); - } - if (tryingSteps) - { - if( valueName=="STEPSTYPE" ) + if(tryingSteps) { - if (out.m_StepsType != GAMEMAN->StringToStepsType(matcher)) - tryingSteps = false; - } - else if( valueName=="CHARTNAME") - { - if (storedVersion >= VERSION_CHART_NAME_TAG && out.GetChartName() != matcher) - tryingSteps = false; - } - else if( valueName=="DESCRIPTION" ) - { - if (storedVersion < VERSION_CHART_NAME_TAG) + switch(handler->second) { - if (out.GetChartName() != matcher) - tryingSteps = false; + case LNDID_version: + // Note that version is in both switches. Formerly, it was + // checked before the tryingSteps condition. -Kyz + storedVersion = StringToFloat(matcher); + break; + case LNDID_stepstype: + if(out.m_StepsType != GAMEMAN->StringToStepsType(matcher)) + { tryingSteps = false; } + break; + case LNDID_chartname: + if(storedVersion >= VERSION_CHART_NAME_TAG && + out.GetChartName() != matcher) + { tryingSteps = false; } + break; + case LNDID_description: + if(storedVersion < VERSION_CHART_NAME_TAG) + { + if(out.GetChartName() != matcher) + { tryingSteps = false; } + } + else if(out.GetDescription() != matcher) + { tryingSteps = false; } + break; + case LNDID_difficulty: + if(out.GetDifficulty() != StringToDifficulty(matcher)) + { tryingSteps = false; } + break; + case LNDID_meter: + if(out.GetMeter() != StringToInt(matcher)) + { tryingSteps = false; } + break; + case LNDID_credit: + if(out.GetCredit() != matcher) + { tryingSteps = false; } + break; + case LNDID_notes: + case LNDID_notes2: + out.SetSMNoteData(matcher); + out.TidyUpData(); + return true; + break; } - else if (out.GetDescription() != matcher) - tryingSteps = false; } - - else if( valueName=="DIFFICULTY" ) + else { - if (out.GetDifficulty() != StringToDifficulty(matcher)) - tryingSteps = false; - } - - else if( valueName=="METER" ) - { - if (out.GetMeter() != StringToInt(matcher)) - tryingSteps = false; - } - - else if( valueName=="CREDIT" ) - { - if (out.GetCredit() != matcher) - tryingSteps = false; - } - - else if( valueName=="NOTES" || valueName=="NOTES2" ) - { - out.SetSMNoteData(matcher); - out.TidyUpData(); - return true; + switch(handler->second) + { + case LNDID_version: + // Note that version is in both switches. Formerly, it was + // checked before the tryingSteps condition. -Kyz + storedVersion = StringToFloat(matcher); + break; + case LNDID_notedata: + tryingSteps = true; + break; + } } } else { - if(valueName == "NOTEDATA") - { - tryingSteps = true; - } + // Silently ignore unrecognized tags, as was done before. -Kyz } } return false; @@ -318,7 +955,9 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach const unsigned values = msd.GetNumValues(); Steps* pNewNotes = NULL; TimingData stepsTiming; - bool bHasOwnTiming = false; + + SongTagInfo reused_song_info(&*this, &out, sPath, bFromCache); + StepsTagInfo reused_steps_info(&*this, &out, sPath, bFromCache); for( unsigned i = 0; i < values; i++ ) { @@ -330,551 +969,65 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach { case GETTING_SONG_INFO: { - if( sValueName=="VERSION" ) + reused_song_info.params= &sParams; + song_handler_map_t::iterator handler= + parser_helper.song_tag_handlers.find(sValueName); + if(handler != parser_helper.song_tag_handlers.end()) { - out.m_fVersion = StringToFloat( sParams[1] ); + handler->second(reused_song_info); } - - else if( sValueName=="TITLE" ) + else if(sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES") { - out.m_sMainTitle = sParams[1]; - this->SetSongTitle(sParams[1]); + SetBGChanges(reused_song_info); } - - 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=="ORIGIN" ) - { - out.m_sOrigin = 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=="PREVIEWVID" ) - { - out.m_sPreviewVidFile = sParams[1]; - } - - else if( sValueName=="JACKET" ) - { - out.m_sJacketFile = sParams[1]; - } - - else if( sValueName=="CDIMAGE" ) - { - out.m_sCDFile = sParams[1]; - } - - else if( sValueName=="DISCIMAGE" ) - { - out.m_sDiscFile = 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 == "PREVIEW") - { - out.m_PreviewFile= sParams[1]; - } - - else if( sValueName=="INSTRUMENTTRACK" ) - { - SMLoader::ProcessInstrumentTracks( out, sParams[1] ); - } - - else if( sValueName=="MUSICLENGTH" ) - { - if( !bFromCache ) - continue; - out.m_fMusicLengthSeconds = StringToFloat( sParams[1] ); - } - - else if( sValueName=="LASTBEATHINT" ) - { - // unable to parse due to tag position. Ignore. - } - - else if (sValueName == "LASTSECONDHINT") - { - out.SetSpecifiedLastSecond(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 = DISPLAY_BPM_RANDOM; - else - { - out.m_DisplayBPMType = DISPLAY_BPM_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(sParams[1].EqualsNoCase("YES")) - out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if(sParams[1].EqualsNoCase("NO")) - out.m_SelectionDisplay = out.SHOW_NEVER; - // ROULETTE from 3.9 is no longer in use. - else if(sParams[1].EqualsNoCase("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(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES")) - out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if( StringToInt(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" ) - { - SMLoader::ProcessBGChanges( out, sValueName, sPath, sParams[1]); - } - - else if( sValueName=="FGCHANGES" ) - { - vector aFGChangeExpressions; - split( sParams[1], ",", aFGChangeExpressions ); - - for( unsigned b=0; b= 2 && keysounds.substr(0, 2) == "\\#" ) - keysounds = keysounds.substr(1); - split( keysounds, ",", out.m_vsKeysoundFile ); - } - - // Attacks loaded from file - else if( sValueName=="ATTACKS" ) - { - ProcessAttackString(out.m_sAttackString, sParams); - ProcessAttacks(out.m_Attacks, sParams); - } - - else if( sValueName=="OFFSET" ) - { - out.m_SongTiming.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" ) - { - ProcessStops(out.m_SongTiming, sParams[1]); - } - else if( sValueName=="DELAYS" ) - { - SMLoader::ProcessDelays(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="BPMS" ) - { - ProcessBPMs(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="WARPS" ) // Older versions allowed em here. - { - ProcessWarps( out.m_SongTiming, sParams[1], out.m_fVersion ); - } - - else if( sValueName=="LABELS" ) - { - ProcessLabels( out.m_SongTiming, sParams[1] ); - } - - else if( sValueName=="TIMESIGNATURES" ) - { - SMLoader::ProcessTimeSignatures(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="TICKCOUNTS" ) - { - SMLoader::ProcessTickcounts(out.m_SongTiming, sParams[1]); - } - - else if( sValueName=="COMBOS" ) - { - ProcessCombos( out.m_SongTiming, sParams[1] ); - } - else if (sValueName=="SPEEDS") - { - ProcessSpeeds(out.m_SongTiming, sParams[1]); - } - else if (sValueName=="SCROLLS") - { - ProcessScrolls(out.m_SongTiming, sParams[1]); - } - else if (sValueName=="FAKES") - { - ProcessFakes(out.m_SongTiming, sParams[1]); - } - - /* The following are cache tags. Never fill their values - * directly: only from the cached version. */ - else if( sValueName=="FIRSTBEAT" || sValueName=="LASTBEAT" ) - { - // no longer used. - } - else if (sValueName=="FIRSTSECOND") - { - if( bFromCache ) - out.SetFirstSecond(StringToFloat(sParams[1])); - } - - else if( sValueName=="LASTSECOND" ) - { - if( bFromCache ) - out.SetLastSecond(StringToFloat(sParams[1])); - } - - else if( sValueName=="SONGFILENAME" ) - { - if( bFromCache ) - out.m_sSongFileName = sParams[1]; - } - - else if( sValueName=="HASMUSIC" ) - { - if( bFromCache ) - out.m_bHasMusic = StringToInt( sParams[1] ) != 0; - } - - else if( sValueName=="HASBANNER" ) - { - if( bFromCache ) - out.m_bHasBanner = StringToInt( sParams[1] ) != 0; - } - // This tag will get us to the next section. - else if( sValueName=="NOTEDATA" ) + else if(sValueName == "NOTEDATA") { state = GETTING_STEP_INFO; pNewNotes = out.CreateSteps(); - stepsTiming = TimingData( out.m_SongTiming.m_fBeat0OffsetInSeconds ); - bHasOwnTiming = false; + stepsTiming = TimingData(out.m_SongTiming.m_fBeat0OffsetInSeconds); + reused_steps_info.has_own_timing = false; + reused_steps_info.steps= pNewNotes; + reused_steps_info.timing= &stepsTiming; + } + else + { + // Silently ignore unrecognized tags, as was done before. -Kyz } break; } case GETTING_STEP_INFO: { - if (sValueName == "CHARTNAME") + reused_steps_info.params= &sParams; + steps_handler_map_t::iterator handler= + parser_helper.steps_tag_handlers.find(sValueName); + if(handler != parser_helper.steps_tag_handlers.end()) { - pNewNotes->SetChartName(sParams[1]); + handler->second(reused_steps_info); } - if( sValueName=="STEPSTYPE" ) - { - pNewNotes->m_StepsType = GAMEMAN->StringToStepsType( sParams[1] ); - pNewNotes->m_StepsTypeStr= sParams[1]; - } - - else if( sValueName=="CHARTSTYLE" ) - { - pNewNotes->SetChartStyle( sParams[1] ); - } - - else if( sValueName=="DESCRIPTION" ) - { - if (out.m_fVersion < VERSION_CHART_NAME_TAG) - { - pNewNotes->SetChartName(sParams[1]); - } - else - { - pNewNotes->SetDescription(sParams[1]); - } - } - - else if( sValueName=="DIFFICULTY" ) - { - pNewNotes->SetDifficulty( StringToDifficulty( sParams[1] ) ); - } - - else if( sValueName=="METER" ) - { - pNewNotes->SetMeter( StringToInt( sParams[1] ) ); - } - - else if( sValueName=="RADARVALUES" ) - { - if (bFromCache) - { - vector saValues; - split( sParams[1], ",", saValues, true ); - - int categories = NUM_RadarCategory; - if( out.m_fVersion < VERSION_RADAR_FAKE ) - categories -= 1; - - if( saValues.size() == (unsigned)categories * NUM_PLAYERS ) - { - RadarValues v[NUM_PLAYERS]; - FOREACH_PlayerNumber( pn ) - { - // Can't use the foreach anymore due to flexible radar lines. - for( RadarCategory rc = (RadarCategory)0; rc < categories; - enum_add( rc, +1 ) ) - { - v[pn][rc] = StringToFloat( saValues[pn*categories + rc] ); - } - } - pNewNotes->SetCachedRadarValues( v ); - } - } - else - { - // just recalc at time. - } - - } - - else if( sValueName=="CREDIT" ) - { - pNewNotes->SetCredit( sParams[1] ); - } - - else if( sValueName=="NOTES" || sValueName=="NOTES2" ) + else if(sValueName=="NOTES" || sValueName=="NOTES2") { state = GETTING_SONG_INFO; - if( bHasOwnTiming ) - pNewNotes->m_Timing = stepsTiming; - bHasOwnTiming = false; - pNewNotes->SetSMNoteData( sParams[1] ); + if(reused_steps_info.has_own_timing) + { pNewNotes->m_Timing = stepsTiming; } + reused_steps_info.has_own_timing = false; + pNewNotes->SetSMNoteData(sParams[1]); pNewNotes->TidyUpData(); pNewNotes->SetFilename(sPath); - out.AddSteps( pNewNotes ); + out.AddSteps(pNewNotes); } - - else if(sValueName == "MUSIC") - { - pNewNotes->SetMusicFile(sParams[1]); - } - - else if( sValueName=="BPMS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessBPMs(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - - else if( sValueName=="STOPS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessStops(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - - else if( sValueName=="DELAYS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - SMLoader::ProcessDelays(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - - else if( sValueName=="TIMESIGNATURES" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - SMLoader::ProcessTimeSignatures(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - - else if( sValueName=="TICKCOUNTS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - SMLoader::ProcessTickcounts(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - - else if( sValueName=="COMBOS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessCombos(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - - else if( sValueName=="WARPS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessWarps(stepsTiming, sParams[1], out.m_fVersion); - bHasOwnTiming = true; - } - } - - else if( sValueName=="SPEEDS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessSpeeds( stepsTiming, sParams[1] ); - bHasOwnTiming = true; - } - } - - else if( sValueName=="SCROLLS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessScrolls( stepsTiming, sParams[1] ); - bHasOwnTiming = true; - } - } - - else if( sValueName=="FAKES" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessFakes( stepsTiming, sParams[1] ); - bHasOwnTiming = true; - } - } - - else if( sValueName=="LABELS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessLabels(stepsTiming, sParams[1]); - bHasOwnTiming = true; - } - } - /* If this is called, the chart does not use the same attacks - * as the Song's timing. No other changes are required. */ - else if( sValueName=="ATTACKS" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - ProcessAttackString(pNewNotes->m_sAttackString, sParams); - ProcessAttacks(pNewNotes->m_Attacks, sParams); - } - } - - else if( sValueName=="OFFSET" ) - { - if (out.m_fVersion >= VERSION_SPLIT_TIMING) - { - stepsTiming.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] ); - bHasOwnTiming = true; - } - } - - else if( sValueName=="DISPLAYBPM" ) - { - // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; - if( sParams[1] == "*" ) - pNewNotes->SetDisplayBPM(DISPLAY_BPM_RANDOM); - else - { - pNewNotes->SetDisplayBPM(DISPLAY_BPM_SPECIFIED); - float min = StringToFloat(sParams[1]); - pNewNotes->SetMinBPM(min); - if(sParams[2].empty()) - pNewNotes->SetMaxBPM(min); - else - pNewNotes->SetMaxBPM(StringToFloat(sParams[2])); - } - } - else if( sValueName=="STEPFILENAME" ) + else if(sValueName=="STEPFILENAME") { state = GETTING_SONG_INFO; - if( bHasOwnTiming ) - pNewNotes->m_Timing = stepsTiming; - bHasOwnTiming = false; + if(reused_steps_info.has_own_timing) + { pNewNotes->m_Timing = stepsTiming; } + reused_steps_info.has_own_timing = false; pNewNotes->SetFilename(sParams[1]); - out.AddSteps( pNewNotes ); + out.AddSteps(pNewNotes); + } + else + { + // Silently ignore unrecognized tags, as was done before. -Kyz } break; } @@ -918,249 +1071,133 @@ bool SSCLoader::LoadEditFromMsd(const MsdFile &msd, { Song* pSong = givenSong; Steps* pNewNotes = NULL; - bool bSSCFormat = false; - bool bHasOwnTiming = false; TimingData stepsTiming; - for( unsigned i=0; im_fVersion = StringToFloat( sParams[1] ); - } - - else if( sValueName=="SONG" ) - { - if( pSong ) + reused_steps_info.params= &sParams; + steps_handler_map_t::iterator handler= + parser_helper.steps_tag_handlers.find(sValueName); + if(handler != parser_helper.steps_tag_handlers.end()) { - /* LOG->UserLog( "Edit file", sEditFilePath, "has more than one #SONG tag." ); - return false; */ - continue; + handler->second(reused_steps_info); } - - RString sSongFullTitle = sParams[1]; - this->SetSongTitle(sParams[1]); - sSongFullTitle.Replace( '\\', '/' ); - - pSong = SONGMAN->FindSong( sSongFullTitle ); - if( pSong == NULL ) + else if(sValueName=="NOTEDATA") { - LOG->UserLog("Edit file", - sEditFilePath, - "requires a song \"%s\" that isn't present.", - sSongFullTitle.c_str() ); - return false; + pNewNotes = pSong->CreateSteps(); + reused_steps_info.steps= pNewNotes; + reused_steps_info.ssc_format= true; } - - if( pSong->GetNumStepsLoadedFromProfile(slot) >= MAX_EDITS_PER_SONG_PER_PROFILE ) + else if(sValueName=="NOTES") { - LOG->UserLog("Song file", - sSongFullTitle, - "already has the maximum number of edits allowed for ProfileSlotP%d.", - slot+1 ); - return false; - } - } + if(pSong == NULL) + { + LOG->UserLog("Edit file", sEditFilePath, + "doesn't have a #SONG tag preceeding the first #NOTES tag," + " and is not in a valid song-specific folder."); + return false; + } - else if( sValueName=="NOTEDATA" ) - { - pNewNotes = pSong->CreateSteps(); - bSSCFormat = true; - } - if( sValueName=="STEPSTYPE" ) - { - pNewNotes->m_StepsType = GAMEMAN->StringToStepsType( sParams[1] ); - pNewNotes->m_StepsTypeStr= sParams[1]; - bSSCFormat = true; - } + if(!reused_steps_info.ssc_format && iNumParams < 7) + { + LOG->UserLog("Edit file", sEditFilePath, + "has %d fields in a #NOTES tag, but should have at least 7.", + iNumParams); + continue; + } - else if( sValueName=="CHARTSTYLE" ) - { - pNewNotes->SetChartStyle( sParams[1] ); - bSSCFormat = true; - } + // I have no idea what the purpose of this bAddStepsToSong flag is. + // It looks like it causes LoadEditFromMsd to just throw away + // whatever steps data was loaded, possibly leaking the memory used + // by pNewNotes. It was here before I rewrote LoadEditFromMsd to + // use a map instead of an else if chain, so I preserved it. -Kyz + if(!bAddStepsToSong) + { return true; } - else if( sValueName=="DESCRIPTION" ) - { - pNewNotes->SetDescription( sParams[1] ); - bSSCFormat = true; - } - - else if( sValueName=="DIFFICULTY" ) - { - pNewNotes->SetDifficulty( StringToDifficulty( sParams[1] ) ); - bSSCFormat = true; - } - - else if( sValueName=="METER" ) - { - pNewNotes->SetMeter( StringToInt( 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; - } - - else if( sValueName=="BPMS" ) - { - ProcessBPMs(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="STOPS" ) - { - ProcessStops(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="DELAYS" ) - { - SMLoader::ProcessDelays(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="TIMESIGNATURES" ) - { - SMLoader::ProcessTimeSignatures(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="TICKCOUNTS" ) - { - SMLoader::ProcessTickcounts(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="COMBOS" ) - { - ProcessCombos(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="WARPS" ) - { - ProcessWarps(stepsTiming, sParams[1], pSong->m_fVersion); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="SPEEDS" ) - { - ProcessSpeeds( stepsTiming, sParams[1] ); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="FAKES" ) - { - ProcessFakes( stepsTiming, sParams[1] ); - bSSCFormat = true; - bHasOwnTiming = true; - } - - else if( sValueName=="LABELS" ) - { - ProcessLabels(stepsTiming, sParams[1]); - bSSCFormat = true; - bHasOwnTiming = true; - } - else if( sValueName=="NOTES" ) - { - if( pSong == NULL ) - { - LOG->UserLog("Edit file", - sEditFilePath, - "doesn't have a #SONG tag preceeding the first #NOTES tag, and is not in a valid song-specific folder." ); - return false; - } - - if ( !bSSCFormat && 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; - - if( bSSCFormat ) - { - if ( bHasOwnTiming ) + if(reused_steps_info.ssc_format) + { + if(reused_steps_info.has_own_timing) pNewNotes->m_Timing = stepsTiming; - pNewNotes->SetSMNoteData( sParams[1] ); - pNewNotes->TidyUpData(); - pSong->AddSteps( pNewNotes ); + pNewNotes->SetSMNoteData(sParams[1]); + pNewNotes->TidyUpData(); + pSong->AddSteps(pNewNotes); + } + else + { + pNewNotes = pSong->CreateSteps(); + LoadFromTokens(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 { - pNewNotes = pSong->CreateSteps(); - LoadFromTokens(sParams[1], - sParams[2], - sParams[3], - sParams[4], - sParams[5], - sParams[6], - *pNewNotes); + LOG->UserLog("Edit file", sEditFilePath, + "has an unexpected value \"%s\".", sValueName.c_str()); } - - 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() ); + if(sValueName == "SONG") + { + if(pSong) + { + /* LOG->UserLog("Edit file", sEditFilePath, "has more than one #SONG tag."); + return false; */ + continue; + } + + RString sSongFullTitle = sParams[1]; + this->SetSongTitle(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; + } + reused_steps_info.song= pSong; + } } } - // Edit had no valid #NOTES sections return false; } diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index e18e3ef944..17629dbc96 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -466,16 +466,17 @@ void PrefsManager::ReadGamePrefsFromIni( const RString &sIni ) FOREACH_CONST_Child( &ini, section ) { - if( !BeginsWith(section->GetName(), GAME_SECTION_PREFIX) ) + RString section_name= section->GetName(); + if( !BeginsWith(section_name, GAME_SECTION_PREFIX) ) continue; - RString sGame = section->GetName().Right( section->GetName().length() - GAME_SECTION_PREFIX.length() ); + RString sGame = section_name.Right( section_name.length() - GAME_SECTION_PREFIX.length() ); GamePrefs &gp = m_mapGameNameToGamePrefs[ sGame ]; // todo: read more prefs here? -aj - ini.GetValue( section->GetName(), "Announcer", gp.m_sAnnouncer ); - ini.GetValue( section->GetName(), "Theme", gp.m_sTheme ); - ini.GetValue( section->GetName(), "DefaultModifiers", gp.m_sDefaultModifiers ); + ini.GetValue(section_name, "Announcer", gp.m_sAnnouncer); + ini.GetValue(section_name, "Theme", gp.m_sTheme); + ini.GetValue(section_name, "DefaultModifiers", gp.m_sDefaultModifiers); } } diff --git a/src/RageTimer.h b/src/RageTimer.h index da863c11d5..fda5ab3a9a 100644 --- a/src/RageTimer.h +++ b/src/RageTimer.h @@ -53,7 +53,7 @@ extern const RageTimer RageZeroTimer; // For profiling how long some chunk of code takes. -Kyz #define START_TIME(name) float name##_start_time= RageTimer::GetTimeSinceStartFast(); -#define END_TIME(name) float name##_end_time= RageTimer::GetTimeSinceStartFast(); LOG->Warn(#name " time: %f", name##_end_time - name##_start_time); +#define END_TIME(name) float name##_end_time= RageTimer::GetTimeSinceStartFast(); LOG->Warn(#name " time: %f to %f = %f", name##_start_time, name##_end_time, name##_end_time - name##_start_time); #endif diff --git a/src/XmlFile.cpp b/src/XmlFile.cpp index 1cb67627d8..1b302065b1 100644 --- a/src/XmlFile.cpp +++ b/src/XmlFile.cpp @@ -46,6 +46,7 @@ void XNode::Free() FOREACH_Attr( this, pAttr ) delete pAttr->second; m_childs.clear(); + m_children_by_name.clear(); m_attrs.clear(); } @@ -98,10 +99,11 @@ XNodeValue *XNode::GetAttr( const RString &attrname ) XNode *XNode::GetChild( const RString &sName ) { - FOREACH_Child( this, it ) + multimap::iterator by_name= m_children_by_name.lower_bound(sName); + if(by_name != m_children_by_name.end() && + sName == by_name->second->GetName()) { - if( it->GetName() == sName ) - return it; + return by_name->second; } return NULL; } @@ -120,10 +122,11 @@ bool XNode::PushChildValue( lua_State *L, const RString &sName ) const const XNode *XNode::GetChild( const RString &sName ) const { - FOREACH_CONST_Child( this, it ) + multimap::const_iterator by_name= m_children_by_name.lower_bound(sName); + if(by_name != m_children_by_name.end() && + sName == by_name->second->GetName()) { - if( it->GetName() == sName ) - return it; + return by_name->second; } return NULL; } @@ -131,23 +134,41 @@ const XNode *XNode::GetChild( const RString &sName ) const XNode *XNode::AppendChild( XNode *node ) { DEBUG_ASSERT( node->m_sName.size() ); - + m_children_by_name.insert(make_pair(node->m_sName, node)); m_childs.push_back( node ); return node; } // detach node and delete object -bool XNode::RemoveChild( XNode *node, bool /* bDelete */ ) +bool XNode::RemoveChild(XNode *node, bool bDelete) { XNodes::iterator it = find( m_childs.begin(), m_childs.end(), node ); if( it == m_childs.end() ) return false; - - delete node; + RemoveChildFromByName(node); + if(bDelete) + { delete node; } m_childs.erase( it ); return true; } +void XNode::RemoveChildFromByName(XNode* node) +{ + multimap::iterator by_name= m_children_by_name.lower_bound(node->m_sName); + if(by_name != m_children_by_name.end() && + node->GetName() == by_name->second->GetName()) + { + for(; by_name != m_children_by_name.end(); ++by_name) + { + if(by_name->second == node) + { + m_children_by_name.erase(by_name); + break; + } + } + } +} + // detach attribute bool XNode::RemoveAttr( const RString &sName ) diff --git a/src/XmlFile.h b/src/XmlFile.h index 725b27d26e..58b0014391 100644 --- a/src/XmlFile.h +++ b/src/XmlFile.h @@ -68,23 +68,26 @@ typedef vector XNodes; /** @brief Loop through each child. */ #define FOREACH_Child( pNode, Var ) \ XNode *Var = NULL; \ - for( XNodes::iterator Var##Iter = (pNode)->m_childs.begin(); \ - Var = (Var##Iter != (pNode)->m_childs.end())? *Var##Iter:NULL, \ - Var##Iter != (pNode)->m_childs.end(); \ + for( XNodes::iterator Var##Iter = (pNode)->GetChildrenBegin(); \ + Var = (Var##Iter != (pNode)->GetChildrenEnd())? *Var##Iter:NULL, \ + Var##Iter != (pNode)->GetChildrenEnd(); \ ++Var##Iter ) /** @brief Loop through each child, using a constant iterator. */ #define FOREACH_CONST_Child( pNode, Var ) \ const XNode *Var = NULL; \ - for( XNodes::const_iterator Var##Iter = (pNode)->m_childs.begin(); \ - Var = (Var##Iter != (pNode)->m_childs.end())? *Var##Iter:NULL, \ - Var##Iter != (pNode)->m_childs.end(); \ + for( XNodes::const_iterator Var##Iter = (pNode)->GetChildrenBegin(); \ + Var = (Var##Iter != (pNode)->GetChildrenEnd())? *Var##Iter:NULL, \ + Var##Iter != (pNode)->GetChildrenEnd(); \ ++Var##Iter ) class XNode { +private: + XNodes m_childs; // child nodes + multimap m_children_by_name; + public: RString m_sName; - XNodes m_childs; // child nodes XAttrs m_attrs; // attributes void SetName( const RString &sName ) { m_sName = sName; } @@ -101,6 +104,12 @@ public: bool GetAttrValue( const RString &sName, T &out ) const { const XNodeValue *pAttr=GetAttr(sName); if(pAttr==NULL) return false; pAttr->GetValue(out); return true; } bool PushAttrValue( lua_State *L, const RString &sName ) const; + XNodes::iterator GetChildrenBegin() { return m_childs.begin(); } + XNodes::const_iterator GetChildrenBegin() const { return m_childs.begin(); } + XNodes::iterator GetChildrenEnd() { return m_childs.end(); } + XNodes::const_iterator GetChildrenEnd() const { return m_childs.end(); } + bool ChildrenEmpty() const { return m_childs.empty(); } + // in one level child nodes const XNode *GetChild( const RString &sName ) const; XNode *GetChild( const RString &sName ); @@ -114,6 +123,7 @@ public: XNode *AppendChild( const RString &sName ) { XNode *p=new XNode(sName); return AppendChild(p); } XNode *AppendChild( XNode *node ); bool RemoveChild( XNode *node, bool bDelete = true ); + void RemoveChildFromByName(XNode *node); XNodeValue *AppendAttrFrom( const RString &sName, XNodeValue *pValue, bool bOverwrite = true ); XNodeValue *AppendAttr( const RString &sName ); diff --git a/src/XmlFileUtil.cpp b/src/XmlFileUtil.cpp index f7d5f1664e..d3d1308ee8 100644 --- a/src/XmlFileUtil.cpp +++ b/src/XmlFileUtil.cpp @@ -321,8 +321,7 @@ RString::size_type LoadInternal( XNode *pNode, const RString &xml, RString &sErr if( !node->GetName().empty() ) { - DEBUG_ASSERT( node->GetName().size() ); - pNode->m_childs.push_back( node ); + pNode->AppendChild(node); } else { @@ -421,7 +420,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int WRITE( "'" ); } - if( pNode->m_childs.empty() && pNode->GetAttr(XNode::TEXT_ATTRIBUTE) == NULL ) + if( pNode->ChildrenEmpty() && pNode->GetAttr(XNode::TEXT_ATTRIBUTE) == NULL ) { // alone tag WRITE( "/>" ); @@ -431,7 +430,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int // and get child WRITE( ">" ); - if( !pNode->m_childs.empty() ) + if( !pNode->ChildrenEmpty() ) iTabBase++; FOREACH_CONST_Child( pNode, p ) @@ -442,7 +441,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int const XNodeValue *pText = pNode->GetAttr( XNode::TEXT_ATTRIBUTE ); if( pText != NULL ) { - if( !pNode->m_childs.empty() ) + if( !pNode->ChildrenEmpty() ) { WRITE( "\r\n" ); if( bWriteTabs ) @@ -456,7 +455,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int } // CloseTag - if( !pNode->m_childs.empty() ) + if( !pNode->ChildrenEmpty() ) { WRITE( "\r\n" ); if( bWriteTabs ) @@ -467,7 +466,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int WRITE( pNode->GetName() ); WRITE( ">" ); - if( !pNode->m_childs.empty() ) + if( !pNode->ChildrenEmpty() ) iTabBase--; } return true; @@ -781,8 +780,8 @@ void XmlFileUtil::MergeIniUnder( XNode *pFrom, XNode *pTo ) vector aToMove; // Iterate over each section in pFrom. - XNodes::iterator it = pFrom->m_childs.begin(); - while( it != pFrom->m_childs.end() ) + XNodes::iterator it = pFrom->GetChildrenBegin(); + while( it != pFrom->GetChildrenEnd() ) { XNodes::iterator next = it; ++next; @@ -811,7 +810,7 @@ void XmlFileUtil::MergeIniUnder( XNode *pFrom, XNode *pTo ) for( int i = aToMove.size()-1; i >= 0; --i ) { XNode *pNode = *aToMove[i]; - pFrom->m_childs.erase( aToMove[i] ); + pFrom->RemoveChild(pNode, false); pTo->AppendChild( pNode ); } }