Updated changelog for 5.0.7rc.
This commit is contained in:
@@ -4,8 +4,15 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes
|
|||||||
from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt.
|
from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt.
|
||||||
________________________________________________________________________________
|
________________________________________________________________________________
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
StepMania 5.0.7rc | 20150309
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
2015/03/09
|
2015/03/09
|
||||||
----------
|
----------
|
||||||
|
* [EditMode] Autosave every 5 minutes added. [kyzentun]
|
||||||
|
* [General] NotesLoaderSM, NotesLoaderSSC, IniFile::ReadFile rewritten,
|
||||||
|
load times substantially faster. [kyzentun]
|
||||||
* [Preference] HarshHotLifePenalty preference added, for toggling whether
|
* [Preference] HarshHotLifePenalty preference added, for toggling whether
|
||||||
LifeMeterBar takes away .1 life when it's full. [kyzentun]
|
LifeMeterBar takes away .1 life when it's full. [kyzentun]
|
||||||
|
|
||||||
|
|||||||
+33
-19
@@ -47,29 +47,43 @@ CourseDifficulty GetNextShownCourseDifficulty( CourseDifficulty cd )
|
|||||||
return Difficulty_Invalid;
|
return Difficulty_Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OldStyleStringToDifficultyMapHolder
|
||||||
|
{
|
||||||
|
std::map<RString, Difficulty> 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 )
|
Difficulty OldStyleStringToDifficulty( const RString& sDC )
|
||||||
{
|
{
|
||||||
RString s2 = sDC;
|
RString s2 = sDC;
|
||||||
s2.MakeLower();
|
s2.MakeLower();
|
||||||
if( s2 == "beginner" ) return Difficulty_Beginner;
|
std::map<RString, Difficulty>::iterator diff=
|
||||||
else if( s2 == "easy" ) return Difficulty_Easy;
|
OldStyleStringToDifficulty_converter.conversion_map.find(s2);
|
||||||
else if( s2 == "basic" ) return Difficulty_Easy;
|
if(diff != OldStyleStringToDifficulty_converter.conversion_map.end())
|
||||||
else if( s2 == "light" ) return Difficulty_Easy;
|
{
|
||||||
else if( s2 == "medium" ) return Difficulty_Medium;
|
return diff->second;
|
||||||
else if( s2 == "another" ) return Difficulty_Medium;
|
}
|
||||||
else if( s2 == "trick" ) return Difficulty_Medium;
|
return Difficulty_Invalid;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaFunction( OldStyleStringToDifficulty, OldStyleStringToDifficulty(SArg(1)) );
|
LuaFunction( OldStyleStringToDifficulty, OldStyleStringToDifficulty(SArg(1)) );
|
||||||
|
|||||||
@@ -201,30 +201,34 @@ static const char *TapNoteScoreNames[] = {
|
|||||||
"W1",
|
"W1",
|
||||||
"CheckpointHit",
|
"CheckpointHit",
|
||||||
};
|
};
|
||||||
|
struct tns_conversion_helper
|
||||||
|
{
|
||||||
|
std::map<RString, TapNoteScore> 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 );
|
XToString( TapNoteScore );
|
||||||
LuaXType( TapNoteScore );
|
LuaXType( TapNoteScore );
|
||||||
TapNoteScore StringToTapNoteScore( const RString &s )
|
TapNoteScore StringToTapNoteScore( const RString &s )
|
||||||
{
|
{
|
||||||
// new style
|
std::map<RString, TapNoteScore>::iterator tns=
|
||||||
if ( s == "None" ) return TNS_None;
|
tns_converter.conversion_map.find(s);
|
||||||
else if( s == "HitMine" ) return TNS_HitMine;
|
if(tns != tns_converter.conversion_map.end())
|
||||||
else if( s == "AvoidMine" ) return TNS_AvoidMine;
|
{
|
||||||
else if( s == "CheckpointHit" ) return TNS_CheckpointHit;
|
return tns->second;
|
||||||
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;
|
|
||||||
|
|
||||||
return TapNoteScore_Invalid;
|
return TapNoteScore_Invalid;
|
||||||
}
|
}
|
||||||
// This is necessary because the StringToX macro wasn't used, and Preference
|
// This is necessary because the StringToX macro wasn't used, and Preference
|
||||||
|
|||||||
+32
-17
@@ -34,6 +34,8 @@ bool IniFile::ReadFile( const RString &sPath )
|
|||||||
bool IniFile::ReadFile( RageFileBasic &f )
|
bool IniFile::ReadFile( RageFileBasic &f )
|
||||||
{
|
{
|
||||||
RString keyname;
|
RString keyname;
|
||||||
|
// keychild is used to cache the node that values are being added to. -Kyz
|
||||||
|
XNode* keychild= NULL;
|
||||||
while( 1 )
|
while( 1 )
|
||||||
{
|
{
|
||||||
RString line;
|
RString line;
|
||||||
@@ -61,35 +63,48 @@ bool IniFile::ReadFile( RageFileBasic &f )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if( line.size() == 0 )
|
if( line.empty() )
|
||||||
continue;
|
continue;
|
||||||
if( line[0] == ';' )
|
switch(line[0])
|
||||||
|
{
|
||||||
|
case ';':
|
||||||
|
case '#':
|
||||||
continue; // comment
|
continue; // comment
|
||||||
if( line[0] == '#' )
|
case '/':
|
||||||
continue; // comment
|
case '-':
|
||||||
if( line.size() > 1 && line[0] == '/' && line[1] == '/' )
|
if(line.size() > 1 && line[0] == line[1])
|
||||||
continue; // comment
|
{ continue; } // comment (Lua or C++ style)
|
||||||
if( line.size() > 1 && line[0] == '-' && line[1] == '-' )
|
goto keyvalue;
|
||||||
continue; // comment (Lua style)
|
case '[':
|
||||||
|
if(line[line.size()-1] == ']')
|
||||||
if( line[0] == '[' && line[line.size()-1] == ']' )
|
|
||||||
{
|
{
|
||||||
// New section.
|
// New section.
|
||||||
keyname = line.substr(1, line.size()-2);
|
keyname = line.substr(1, line.size()-2);
|
||||||
}
|
keychild= GetChild(keyname);
|
||||||
else
|
if(keychild == NULL)
|
||||||
{
|
{
|
||||||
|
keychild= AppendChild(keyname);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
keyvalue:
|
||||||
|
if(keychild == NULL)
|
||||||
|
{ break; }
|
||||||
// New value.
|
// New value.
|
||||||
size_t iEqualIndex = line.find("=");
|
size_t iEqualIndex = line.find("=");
|
||||||
if( iEqualIndex != string::npos )
|
if( iEqualIndex != string::npos )
|
||||||
{
|
{
|
||||||
RString valuename = line.Left( (int) iEqualIndex );
|
RString valuename = line.Left((int) iEqualIndex);
|
||||||
RString value = line.Right( line.size()-valuename.size()-1 );
|
RString value = line.Right(line.size()-valuename.size()-1);
|
||||||
Trim( valuename );
|
Trim(valuename);
|
||||||
if( keyname.size() && valuename.size() )
|
if(!valuename.empty())
|
||||||
SetValue( keyname, valuename, value );
|
{
|
||||||
|
SetKeyValue(keychild, valuename, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ public:
|
|||||||
pNode = AppendChild( sKey );
|
pNode = AppendChild( sKey );
|
||||||
pNode->AppendAttr<T>( sValueName, value );
|
pNode->AppendAttr<T>( sValueName, value );
|
||||||
}
|
}
|
||||||
|
template <typename T>
|
||||||
|
void SetKeyValue(XNode* keynode, const RString &sValueName, const T &value)
|
||||||
|
{
|
||||||
|
keynode->AppendAttr<T>(sValueName, value);
|
||||||
|
}
|
||||||
|
|
||||||
bool DeleteKey( const RString &keyname );
|
bool DeleteKey( const RString &keyname );
|
||||||
bool DeleteValue( const RString &keyname, const RString &valuename );
|
bool DeleteValue( const RString &keyname, const RString &valuename );
|
||||||
|
|||||||
+253
-186
@@ -13,6 +13,242 @@
|
|||||||
#include "Attack.h"
|
#include "Attack.h"
|
||||||
#include "PrefsManager.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<float, float> > 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<RString> 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<RString, song_tag_func_t> 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)
|
void SMLoader::SetSongTitle(const RString & title)
|
||||||
{
|
{
|
||||||
this->songTitle = title;
|
this->songTitle = title;
|
||||||
@@ -875,10 +1111,11 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector< pair<float, float> > vBPMChanges, vStops;
|
|
||||||
out.m_SongTiming.m_sFile = sPath;
|
out.m_SongTiming.m_sFile = sPath;
|
||||||
out.m_sSongFileName = sPath;
|
out.m_sSongFileName = sPath;
|
||||||
|
|
||||||
|
SMSongTagInfo reused_song_info(&*this, &out, sPath);
|
||||||
|
|
||||||
for( unsigned i=0; i<msd.GetNumValues(); i++ )
|
for( unsigned i=0; i<msd.GetNumValues(); i++ )
|
||||||
{
|
{
|
||||||
int iNumParams = msd.GetNumParams(i);
|
int iNumParams = msd.GetNumParams(i);
|
||||||
@@ -886,191 +1123,22 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache
|
|||||||
RString sValueName = sParams[0];
|
RString sValueName = sParams[0];
|
||||||
sValueName.MakeUpper();
|
sValueName.MakeUpper();
|
||||||
|
|
||||||
// handle the data
|
reused_song_info.params= &sParams;
|
||||||
|
song_handler_map_t::iterator handler=
|
||||||
|
sm_parser_helper.song_tag_handlers.find(sValueName);
|
||||||
|
if(handler != sm_parser_helper.song_tag_handlers.end())
|
||||||
|
{
|
||||||
/* Don't use GetMainAndSubTitlesFromFullTitle; that's only for heuristically
|
/* Don't use GetMainAndSubTitlesFromFullTitle; that's only for heuristically
|
||||||
* splitting other formats that *don't* natively support #SUBTITLE. */
|
* splitting other formats that *don't* natively support #SUBTITLE. */
|
||||||
if( sValueName=="TITLE" )
|
handler->second(reused_song_info);
|
||||||
{
|
|
||||||
out.m_sMainTitle = sParams[1];
|
|
||||||
this->SetSongTitle(sParams[1]);
|
|
||||||
}
|
}
|
||||||
|
else if(sValueName.Left(strlen("BGCHANGES")) == "BGCHANGES")
|
||||||
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" )
|
|
||||||
{
|
{
|
||||||
out.m_SongTiming.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
|
SMSetBGChanges(reused_song_info);
|
||||||
}
|
}
|
||||||
else if( sValueName=="BPMS" )
|
else if(sValueName == "NOTES" || sValueName == "NOTES2")
|
||||||
{
|
{
|
||||||
vBPMChanges.clear();
|
if(iNumParams < 7)
|
||||||
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<RString> aFGChangeExpressions;
|
|
||||||
split( sParams[1], ",", aFGChangeExpressions );
|
|
||||||
|
|
||||||
for( unsigned b=0; b<aFGChangeExpressions.size(); b++ )
|
|
||||||
{
|
|
||||||
BackgroundChange change;
|
|
||||||
if( LoadFromBGChangesString( change, aFGChangeExpressions[b] ) )
|
|
||||||
out.AddForegroundChange( change );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if( sValueName=="KEYSOUNDS" )
|
|
||||||
{
|
|
||||||
split( sParams[1], ",", out.m_vsKeysoundFile );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attacks loaded from file
|
|
||||||
else if( sValueName=="ATTACKS" )
|
|
||||||
{
|
|
||||||
ProcessAttackString(out.m_sAttackString, sParams);
|
|
||||||
ProcessAttacks(out.m_Attacks, sParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
|
|
||||||
{
|
|
||||||
if( iNumParams < 7 )
|
|
||||||
{
|
{
|
||||||
LOG->UserLog( "Song file", sPath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams );
|
LOG->UserLog( "Song file", sPath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams );
|
||||||
continue;
|
continue;
|
||||||
@@ -1084,20 +1152,19 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache
|
|||||||
sParams[4],
|
sParams[4],
|
||||||
sParams[5],
|
sParams[5],
|
||||||
sParams[6],
|
sParams[6],
|
||||||
*pNewNotes );
|
*pNewNotes);
|
||||||
|
|
||||||
pNewNotes->SetFilename(sPath);
|
pNewNotes->SetFilename(sPath);
|
||||||
out.AddSteps( pNewNotes );
|
out.AddSteps( pNewNotes );
|
||||||
}
|
}
|
||||||
// XXX: Does anyone know what LEADTRACK is for? -Wolfman2000
|
|
||||||
else if( sValueName=="LEADTRACK" )
|
|
||||||
;
|
|
||||||
else
|
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
|
// 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 );
|
TidyUpData( out, bFromCache );
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ protected:
|
|||||||
* @return the file extension. */
|
* @return the file extension. */
|
||||||
RString GetFileExtension() const { return fileExt; }
|
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.
|
* @brief Set the song title.
|
||||||
* @param t the song title. */
|
* @param t the song title. */
|
||||||
|
|||||||
+795
-762
File diff suppressed because it is too large
Load Diff
@@ -466,16 +466,17 @@ void PrefsManager::ReadGamePrefsFromIni( const RString &sIni )
|
|||||||
|
|
||||||
FOREACH_CONST_Child( &ini, section )
|
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;
|
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 ];
|
GamePrefs &gp = m_mapGameNameToGamePrefs[ sGame ];
|
||||||
|
|
||||||
// todo: read more prefs here? -aj
|
// todo: read more prefs here? -aj
|
||||||
ini.GetValue( section->GetName(), "Announcer", gp.m_sAnnouncer );
|
ini.GetValue(section_name, "Announcer", gp.m_sAnnouncer);
|
||||||
ini.GetValue( section->GetName(), "Theme", gp.m_sTheme );
|
ini.GetValue(section_name, "Theme", gp.m_sTheme);
|
||||||
ini.GetValue( section->GetName(), "DefaultModifiers", gp.m_sDefaultModifiers );
|
ini.GetValue(section_name, "DefaultModifiers", gp.m_sDefaultModifiers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ extern const RageTimer RageZeroTimer;
|
|||||||
|
|
||||||
// For profiling how long some chunk of code takes. -Kyz
|
// For profiling how long some chunk of code takes. -Kyz
|
||||||
#define START_TIME(name) float name##_start_time= RageTimer::GetTimeSinceStartFast();
|
#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
|
#endif
|
||||||
|
|
||||||
|
|||||||
+31
-10
@@ -46,6 +46,7 @@ void XNode::Free()
|
|||||||
FOREACH_Attr( this, pAttr )
|
FOREACH_Attr( this, pAttr )
|
||||||
delete pAttr->second;
|
delete pAttr->second;
|
||||||
m_childs.clear();
|
m_childs.clear();
|
||||||
|
m_children_by_name.clear();
|
||||||
m_attrs.clear();
|
m_attrs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,10 +99,11 @@ XNodeValue *XNode::GetAttr( const RString &attrname )
|
|||||||
|
|
||||||
XNode *XNode::GetChild( const RString &sName )
|
XNode *XNode::GetChild( const RString &sName )
|
||||||
{
|
{
|
||||||
FOREACH_Child( this, it )
|
multimap<RString, XNode*>::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 by_name->second;
|
||||||
return it;
|
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -120,10 +122,11 @@ bool XNode::PushChildValue( lua_State *L, const RString &sName ) const
|
|||||||
|
|
||||||
const XNode *XNode::GetChild( const RString &sName ) const
|
const XNode *XNode::GetChild( const RString &sName ) const
|
||||||
{
|
{
|
||||||
FOREACH_CONST_Child( this, it )
|
multimap<RString, XNode*>::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 by_name->second;
|
||||||
return it;
|
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -131,23 +134,41 @@ const XNode *XNode::GetChild( const RString &sName ) const
|
|||||||
XNode *XNode::AppendChild( XNode *node )
|
XNode *XNode::AppendChild( XNode *node )
|
||||||
{
|
{
|
||||||
DEBUG_ASSERT( node->m_sName.size() );
|
DEBUG_ASSERT( node->m_sName.size() );
|
||||||
|
m_children_by_name.insert(make_pair(node->m_sName, node));
|
||||||
m_childs.push_back( node );
|
m_childs.push_back( node );
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// detach node and delete object
|
// 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 );
|
XNodes::iterator it = find( m_childs.begin(), m_childs.end(), node );
|
||||||
if( it == m_childs.end() )
|
if( it == m_childs.end() )
|
||||||
return false;
|
return false;
|
||||||
|
RemoveChildFromByName(node);
|
||||||
delete node;
|
if(bDelete)
|
||||||
|
{ delete node; }
|
||||||
m_childs.erase( it );
|
m_childs.erase( it );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XNode::RemoveChildFromByName(XNode* node)
|
||||||
|
{
|
||||||
|
multimap<RString, XNode*>::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
|
// detach attribute
|
||||||
bool XNode::RemoveAttr( const RString &sName )
|
bool XNode::RemoveAttr( const RString &sName )
|
||||||
|
|||||||
+17
-7
@@ -68,23 +68,26 @@ typedef vector<XNode*> XNodes;
|
|||||||
/** @brief Loop through each child. */
|
/** @brief Loop through each child. */
|
||||||
#define FOREACH_Child( pNode, Var ) \
|
#define FOREACH_Child( pNode, Var ) \
|
||||||
XNode *Var = NULL; \
|
XNode *Var = NULL; \
|
||||||
for( XNodes::iterator Var##Iter = (pNode)->m_childs.begin(); \
|
for( XNodes::iterator Var##Iter = (pNode)->GetChildrenBegin(); \
|
||||||
Var = (Var##Iter != (pNode)->m_childs.end())? *Var##Iter:NULL, \
|
Var = (Var##Iter != (pNode)->GetChildrenEnd())? *Var##Iter:NULL, \
|
||||||
Var##Iter != (pNode)->m_childs.end(); \
|
Var##Iter != (pNode)->GetChildrenEnd(); \
|
||||||
++Var##Iter )
|
++Var##Iter )
|
||||||
/** @brief Loop through each child, using a constant iterator. */
|
/** @brief Loop through each child, using a constant iterator. */
|
||||||
#define FOREACH_CONST_Child( pNode, Var ) \
|
#define FOREACH_CONST_Child( pNode, Var ) \
|
||||||
const XNode *Var = NULL; \
|
const XNode *Var = NULL; \
|
||||||
for( XNodes::const_iterator Var##Iter = (pNode)->m_childs.begin(); \
|
for( XNodes::const_iterator Var##Iter = (pNode)->GetChildrenBegin(); \
|
||||||
Var = (Var##Iter != (pNode)->m_childs.end())? *Var##Iter:NULL, \
|
Var = (Var##Iter != (pNode)->GetChildrenEnd())? *Var##Iter:NULL, \
|
||||||
Var##Iter != (pNode)->m_childs.end(); \
|
Var##Iter != (pNode)->GetChildrenEnd(); \
|
||||||
++Var##Iter )
|
++Var##Iter )
|
||||||
|
|
||||||
class XNode
|
class XNode
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
XNodes m_childs; // child nodes
|
||||||
|
multimap<RString, XNode*> m_children_by_name;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RString m_sName;
|
RString m_sName;
|
||||||
XNodes m_childs; // child nodes
|
|
||||||
XAttrs m_attrs; // attributes
|
XAttrs m_attrs; // attributes
|
||||||
|
|
||||||
void SetName( const RString &sName ) { m_sName = sName; }
|
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 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;
|
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
|
// in one level child nodes
|
||||||
const XNode *GetChild( const RString &sName ) const;
|
const XNode *GetChild( const RString &sName ) const;
|
||||||
XNode *GetChild( const RString &sName );
|
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( const RString &sName ) { XNode *p=new XNode(sName); return AppendChild(p); }
|
||||||
XNode *AppendChild( XNode *node );
|
XNode *AppendChild( XNode *node );
|
||||||
bool RemoveChild( XNode *node, bool bDelete = true );
|
bool RemoveChild( XNode *node, bool bDelete = true );
|
||||||
|
void RemoveChildFromByName(XNode *node);
|
||||||
|
|
||||||
XNodeValue *AppendAttrFrom( const RString &sName, XNodeValue *pValue, bool bOverwrite = true );
|
XNodeValue *AppendAttrFrom( const RString &sName, XNodeValue *pValue, bool bOverwrite = true );
|
||||||
XNodeValue *AppendAttr( const RString &sName );
|
XNodeValue *AppendAttr( const RString &sName );
|
||||||
|
|||||||
+9
-10
@@ -321,8 +321,7 @@ RString::size_type LoadInternal( XNode *pNode, const RString &xml, RString &sErr
|
|||||||
|
|
||||||
if( !node->GetName().empty() )
|
if( !node->GetName().empty() )
|
||||||
{
|
{
|
||||||
DEBUG_ASSERT( node->GetName().size() );
|
pNode->AppendChild(node);
|
||||||
pNode->m_childs.push_back( node );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -421,7 +420,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int
|
|||||||
WRITE( "'" );
|
WRITE( "'" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( pNode->m_childs.empty() && pNode->GetAttr(XNode::TEXT_ATTRIBUTE) == NULL )
|
if( pNode->ChildrenEmpty() && pNode->GetAttr(XNode::TEXT_ATTRIBUTE) == NULL )
|
||||||
{
|
{
|
||||||
// <TAG Attr1="Val1"/> alone tag
|
// <TAG Attr1="Val1"/> alone tag
|
||||||
WRITE( "/>" );
|
WRITE( "/>" );
|
||||||
@@ -431,7 +430,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int
|
|||||||
// <TAG Attr1="Val1"> and get child
|
// <TAG Attr1="Val1"> and get child
|
||||||
WRITE( ">" );
|
WRITE( ">" );
|
||||||
|
|
||||||
if( !pNode->m_childs.empty() )
|
if( !pNode->ChildrenEmpty() )
|
||||||
iTabBase++;
|
iTabBase++;
|
||||||
|
|
||||||
FOREACH_CONST_Child( pNode, p )
|
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 );
|
const XNodeValue *pText = pNode->GetAttr( XNode::TEXT_ATTRIBUTE );
|
||||||
if( pText != NULL )
|
if( pText != NULL )
|
||||||
{
|
{
|
||||||
if( !pNode->m_childs.empty() )
|
if( !pNode->ChildrenEmpty() )
|
||||||
{
|
{
|
||||||
WRITE( "\r\n" );
|
WRITE( "\r\n" );
|
||||||
if( bWriteTabs )
|
if( bWriteTabs )
|
||||||
@@ -456,7 +455,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
// </TAG> CloseTag
|
// </TAG> CloseTag
|
||||||
if( !pNode->m_childs.empty() )
|
if( !pNode->ChildrenEmpty() )
|
||||||
{
|
{
|
||||||
WRITE( "\r\n" );
|
WRITE( "\r\n" );
|
||||||
if( bWriteTabs )
|
if( bWriteTabs )
|
||||||
@@ -467,7 +466,7 @@ bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int
|
|||||||
WRITE( pNode->GetName() );
|
WRITE( pNode->GetName() );
|
||||||
WRITE( ">" );
|
WRITE( ">" );
|
||||||
|
|
||||||
if( !pNode->m_childs.empty() )
|
if( !pNode->ChildrenEmpty() )
|
||||||
iTabBase--;
|
iTabBase--;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -781,8 +780,8 @@ void XmlFileUtil::MergeIniUnder( XNode *pFrom, XNode *pTo )
|
|||||||
vector<XNodes::iterator> aToMove;
|
vector<XNodes::iterator> aToMove;
|
||||||
|
|
||||||
// Iterate over each section in pFrom.
|
// Iterate over each section in pFrom.
|
||||||
XNodes::iterator it = pFrom->m_childs.begin();
|
XNodes::iterator it = pFrom->GetChildrenBegin();
|
||||||
while( it != pFrom->m_childs.end() )
|
while( it != pFrom->GetChildrenEnd() )
|
||||||
{
|
{
|
||||||
XNodes::iterator next = it;
|
XNodes::iterator next = it;
|
||||||
++next;
|
++next;
|
||||||
@@ -811,7 +810,7 @@ void XmlFileUtil::MergeIniUnder( XNode *pFrom, XNode *pTo )
|
|||||||
for( int i = aToMove.size()-1; i >= 0; --i )
|
for( int i = aToMove.size()-1; i >= 0; --i )
|
||||||
{
|
{
|
||||||
XNode *pNode = *aToMove[i];
|
XNode *pNode = *aToMove[i];
|
||||||
pFrom->m_childs.erase( aToMove[i] );
|
pFrom->RemoveChild(pNode, false);
|
||||||
pTo->AppendChild( pNode );
|
pTo->AppendChild( pNode );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user