Files
itgmania212121/src/NotesLoaderSSC.cpp
T
2015-03-15 16:54:04 -06:00

1242 lines
37 KiB
C++

#include "global.h"
#include "NotesLoaderSSC.h"
#include "BackgroundUtil.h"
#include "GameManager.h"
#include "MsdFile.h" // No JSON here.
#include "NoteTypes.h"
#include "NotesLoaderSM.h" // For programming shortcuts.
#include "RageFileManager.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "Song.h"
#include "SongManager.h"
#include "Steps.h"
#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<RString> 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<RString> 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<RadarCategory>(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)
{
info.steps->SetMusicFile((*info.params)[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<RString, steps_tag_func_t> steps_handler_map_t;
typedef std::map<RString, song_tag_func_t> song_handler_map_t;
typedef std::map<RString, LoadNoteDataTagIDs> 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<RString> arrayBPMExpressions;
split( sParam, ",", arrayBPMExpressions );
for( unsigned b=0; b<arrayBPMExpressions.size(); b++ )
{
vector<RString> arrayBPMValues;
split( arrayBPMExpressions[b], "=", arrayBPMValues );
if( arrayBPMValues.size() != 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #BPMS value \"%s\" (must have exactly one '='), ignored.",
arrayBPMExpressions[b].c_str() );
continue;
}
const float fBeat = StringToFloat( arrayBPMValues[0] );
const float fNewBPM = StringToFloat( arrayBPMValues[1] );
if( fBeat >= 0 && fNewBPM > 0 )
{
out.AddSegment( BPMSegment(BeatToNoteRow(fBeat), fNewBPM) );
}
else
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid BPM at beat %f, BPM %f.",
fBeat, fNewBPM );
}
}
}
void SSCLoader::ProcessStops( TimingData &out, const RString sParam )
{
vector<RString> arrayStopExpressions;
split( sParam, ",", arrayStopExpressions );
for( unsigned b=0; b<arrayStopExpressions.size(); b++ )
{
vector<RString> arrayStopValues;
split( arrayStopExpressions[b], "=", arrayStopValues );
if( arrayStopValues.size() != 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayStopExpressions[b].c_str() );
continue;
}
const float fBeat = StringToFloat( arrayStopValues[0] );
const float fNewStop = StringToFloat( arrayStopValues[1] );
if( fBeat >= 0 && fNewStop > 0 )
out.AddSegment( StopSegment(BeatToNoteRow(fBeat), fNewStop) );
else
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid Stop at beat %f, length %f.",
fBeat, fNewStop );
}
}
}
void SSCLoader::ProcessWarps( TimingData &out, const RString sParam, const float fVersion )
{
vector<RString> arrayWarpExpressions;
split( sParam, ",", arrayWarpExpressions );
for( unsigned b=0; b<arrayWarpExpressions.size(); b++ )
{
vector<RString> arrayWarpValues;
split( arrayWarpExpressions[b], "=", arrayWarpValues );
if( arrayWarpValues.size() != 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #WARPS value \"%s\" (must have exactly one '='), ignored.",
arrayWarpExpressions[b].c_str() );
continue;
}
const float fBeat = StringToFloat( arrayWarpValues[0] );
const float fNewBeat = StringToFloat( arrayWarpValues[1] );
// Early versions were absolute in beats. They should be relative.
if( ( fVersion < VERSION_SPLIT_TIMING && fNewBeat > fBeat ) )
{
out.AddSegment( WarpSegment(BeatToNoteRow(fBeat), fNewBeat - fBeat) );
}
else if( fNewBeat > 0 )
out.AddSegment( WarpSegment(BeatToNoteRow(fBeat), fNewBeat) );
else
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid Warp at beat %f, BPM %f.",
fBeat, fNewBeat );
}
}
}
void SSCLoader::ProcessLabels( TimingData &out, const RString sParam )
{
vector<RString> arrayLabelExpressions;
split( sParam, ",", arrayLabelExpressions );
for( unsigned b=0; b<arrayLabelExpressions.size(); b++ )
{
vector<RString> arrayLabelValues;
split( arrayLabelExpressions[b], "=", arrayLabelValues );
if( arrayLabelValues.size() != 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #LABELS value \"%s\" (must have exactly one '='), ignored.",
arrayLabelExpressions[b].c_str() );
continue;
}
const float fBeat = StringToFloat( arrayLabelValues[0] );
RString sLabel = arrayLabelValues[1];
TrimRight(sLabel);
if( fBeat >= 0.0f )
out.AddSegment( LabelSegment(BeatToNoteRow(fBeat), sLabel) );
else
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid Label at beat %f called %s.",
fBeat, sLabel.c_str() );
}
}
}
void SSCLoader::ProcessCombos( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayComboExpressions;
split( line, ",", arrayComboExpressions );
for( unsigned f=0; f<arrayComboExpressions.size(); f++ )
{
vector<RString> arrayComboValues;
split( arrayComboExpressions[f], "=", arrayComboValues );
unsigned size = arrayComboValues.size();
if( size < 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #COMBOS value \"%s\" (must have at least one '='), ignored.",
arrayComboExpressions[f].c_str() );
continue;
}
const float fComboBeat = StringToFloat( arrayComboValues[0] );
const int iCombos = StringToInt( arrayComboValues[1] );
const int iMisses = (size == 2 ? iCombos : StringToInt(arrayComboValues[2]));
out.AddSegment( ComboSegment( BeatToNoteRow(fComboBeat), iCombos, iMisses ) );
}
}
void SSCLoader::ProcessScrolls( TimingData &out, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an scroll change with %i values.",
static_cast<int>(vs2.size()) );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
const float fRatio = StringToFloat( vs2[1] );
if( fBeat < 0 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an scroll change with beat %f.",
fBeat );
continue;
}
out.AddSegment( ScrollSegment(BeatToNoteRow(fBeat), fRatio) );
}
}
bool SSCLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out )
{
LOG->Trace( "Loading notes from %s", cachePath.c_str() );
MsdFile msd;
if (!msd.ReadFile(cachePath, true))
{
LOG->UserLog("Unable to load any notes from",
cachePath,
"for this reason: %s",
msd.GetError().c_str());
return false;
}
bool tryingSteps = false;
float storedVersion = 0;
const unsigned values = msd.GetNumValues();
for (unsigned i = 0; i < values; i++)
{
const MsdFile::value_t &params = msd.GetValue(i);
RString valueName = params[0];
valueName.MakeUpper();
RString matcher = params[1]; // mainly for debugging.
Trim(matcher);
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())
{
if(tryingSteps)
{
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_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:
// Accept any difficulty if it's an edit because LoadEditFromMsd
// forces edits onto Edit difficulty even if they have a difficulty
// tag. -Kyz
if(out.GetDifficulty() != StringToDifficulty(matcher) &&
!(out.GetDifficulty() == Difficulty_Edit &&
GetExtension(cachePath).MakeLower() == "edit"))
{ 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
{
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
{
// Silently ignore unrecognized tags, as was done before. -Kyz
}
}
return false;
}
bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache )
{
LOG->Trace( "Song::LoadFromSSCFile(%s)", sPath.c_str() );
MsdFile msd;
if( !msd.ReadFile( sPath, true ) )
{
LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() );
return false;
}
out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing.
out.m_sSongFileName = sPath;
int state = GETTING_SONG_INFO;
const unsigned values = msd.GetNumValues();
Steps* pNewNotes = NULL;
TimingData stepsTiming;
SongTagInfo reused_song_info(&*this, &out, sPath, bFromCache);
StepsTagInfo reused_steps_info(&*this, &out, sPath, bFromCache);
for( unsigned i = 0; i < values; i++ )
{
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
switch (state)
{
case GETTING_SONG_INFO:
{
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())
{
handler->second(reused_song_info);
}
else if(sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES")
{
SetBGChanges(reused_song_info);
}
// This tag will get us to the next section.
else if(sValueName == "NOTEDATA")
{
state = GETTING_STEP_INFO;
pNewNotes = out.CreateSteps();
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:
{
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())
{
handler->second(reused_steps_info);
}
else if(sValueName=="NOTES" || sValueName=="NOTES2")
{
state = GETTING_SONG_INFO;
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);
}
else if(sValueName=="STEPFILENAME")
{
state = GETTING_SONG_INFO;
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);
}
else
{
// Silently ignore unrecognized tags, as was done before. -Kyz
}
break;
}
}
}
out.m_fVersion = STEPFILE_VERSION_NUMBER;
TidyUpData(out, bFromCache);
return true;
}
bool SSCLoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong, Song *givenSong /* =NULL */ )
{
LOG->Trace( "SSCLoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath );
if( iBytes > MAX_EDIT_STEPS_SIZE_BYTES )
{
LOG->UserLog("Edit file",
sEditFilePath,
"is unreasonably large. It won't be loaded." );
return false;
}
MsdFile msd;
if( !msd.ReadFile( sEditFilePath, true ) ) // unescape
{
LOG->UserLog("Edit file",
sEditFilePath,
"couldn't be opened: %s", msd.GetError().c_str() );
return false;
}
return LoadEditFromMsd( msd, sEditFilePath, slot, bAddStepsToSong, givenSong );
}
bool SSCLoader::LoadEditFromMsd(const MsdFile &msd,
const RString &sEditFilePath,
ProfileSlot slot,
bool bAddStepsToSong,
Song *givenSong /* =NULL */ )
{
Song* pSong = givenSong;
Steps* pNewNotes = NULL;
TimingData stepsTiming;
StepsTagInfo reused_steps_info(&*this, pSong, sEditFilePath, false);
reused_steps_info.for_load_edit= true;
reused_steps_info.timing= &stepsTiming;
for(unsigned int i = 0; i < msd.GetNumValues(); ++i)
{
int iNumParams = msd.GetNumParams(i);
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
if(pSong != NULL)
{
reused_steps_info.params= &sParams;
steps_handler_map_t::iterator handler=
parser_helper.steps_tag_handlers.find(sValueName);
if(pNewNotes != NULL && handler != parser_helper.steps_tag_handlers.end())
{
handler->second(reused_steps_info);
}
else if(sValueName=="NOTEDATA")
{
pNewNotes = pSong->CreateSteps();
reused_steps_info.steps= pNewNotes;
reused_steps_info.ssc_format= 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(!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;
}
// 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; }
// Force the difficulty to edit in case the edit set its own
// difficulty because IsEditAlreadyLoaded has an assert and edits
// shouldn't be able to add charts of other difficulties. -Kyz
if(pNewNotes != NULL)
{
pNewNotes->SetDifficulty(Difficulty_Edit);
if(pSong->IsEditAlreadyLoaded(pNewNotes))
{
LOG->UserLog("Edit file", sEditFilePath,
"is a duplicate of another edit that was already loaded.");
SAFE_DELETE(pNewNotes);
return false;
}
}
if(reused_steps_info.ssc_format)
{
if(reused_steps_info.has_own_timing)
{ pNewNotes->m_Timing = stepsTiming; }
pNewNotes->SetSMNoteData(sParams[1]);
pNewNotes->TidyUpData();
}
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);
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());
}
}
else
{
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);
reused_steps_info.song= pSong;
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;
}
/*
* (c) 2011 Jason Felds
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/